mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-07-23 07:54:27 +00:00
Merge pull request #410 from ozymandiashh/codex/antigravity-hook-stale-path
Fix Antigravity hook stale CLI paths: install the statusLine hook through a persistent codeburn binary resolved from PATH and repair stale CodeBurn-owned hooks on re-install.
This commit is contained in:
commit
b246e822b7
4 changed files with 133 additions and 13 deletions
|
|
@ -1,5 +1,14 @@
|
|||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Fixed (CLI)
|
||||
- **Antigravity hook stale path repair.** `codeburn antigravity-hook install`
|
||||
now installs the statusLine command through a persistent `codeburn` binary
|
||||
from PATH and repairs older CodeBurn-owned hooks that pointed at stale local
|
||||
build artifacts, preventing `agy` from auto-disabling capture after
|
||||
`MODULE_NOT_FOUND` failures.
|
||||
|
||||
## 0.9.11 - 2026-05-27
|
||||
|
||||
### Added (CLI)
|
||||
|
|
|
|||
|
|
@ -27,9 +27,12 @@ For Antigravity CLI (`agy`), CodeBurn can also install an opt-in status line
|
|||
hook with `codeburn antigravity-hook install`. The hook records the CLI's
|
||||
sanitized `context_window.current_usage` payload while `agy` is still alive,
|
||||
without prompts or local working-directory paths. It also attempts a best-effort
|
||||
RPC snapshot for full response metadata. Remove it with
|
||||
`codeburn antigravity-hook uninstall`; if `--force` replaced an existing
|
||||
statusLine command, uninstall restores that previous command.
|
||||
RPC snapshot for full response metadata. The installed command points at a
|
||||
persistent `codeburn` binary from PATH rather than a local build artifact, and
|
||||
running `codeburn antigravity-hook install` again repairs older CodeBurn-owned
|
||||
statusLine commands that used stale absolute paths. Remove it with `codeburn
|
||||
antigravity-hook uninstall`; if `--force` replaced an existing statusLine
|
||||
command, uninstall restores that previous command.
|
||||
|
||||
## Storage format
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { mkdir, open, readFile, rename, unlink } from 'fs/promises'
|
||||
import { constants } from 'fs'
|
||||
import { access, mkdir, open, readFile, rename, unlink } from 'fs/promises'
|
||||
import { randomBytes } from 'crypto'
|
||||
import { dirname, join } from 'path'
|
||||
import { delimiter, dirname, join } from 'path'
|
||||
import { homedir } from 'os'
|
||||
|
||||
import {
|
||||
|
|
@ -18,6 +19,12 @@ type Settings = Record<string, unknown> & {
|
|||
|
||||
type StatusLineSettings = NonNullable<Settings['statusLine']>
|
||||
|
||||
const PERSISTENT_CLI_REQUIRED_MESSAGE =
|
||||
'The Antigravity hook needs a persistent codeburn command. Install CodeBurn globally first: npm install -g codeburn'
|
||||
const DEFAULT_CLI_LOOKUP_PATHS = process.platform === 'win32'
|
||||
? []
|
||||
: ['/opt/homebrew/bin', '/usr/local/bin', '/usr/bin', '/bin']
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
|
@ -31,10 +38,49 @@ function shellQuote(value: string): string {
|
|||
return `'${value.replace(/'/g, `'\\''`)}'`
|
||||
}
|
||||
|
||||
function hookCommand(): string {
|
||||
const script = process.argv[1] || 'codeburn'
|
||||
if (script === 'codeburn') return 'codeburn agy-statusline-hook'
|
||||
return `${shellQuote(process.execPath)} ${shellQuote(script)} agy-statusline-hook`
|
||||
function isTransientNpxPath(path: string): boolean {
|
||||
return path.includes('/_npx/') || path.includes('/.npm/_npx/') || path.includes('\\_npx\\')
|
||||
}
|
||||
|
||||
function codeburnExecutableNames(): string[] {
|
||||
if (process.platform !== 'win32') return ['codeburn']
|
||||
return ['codeburn.cmd', 'codeburn.exe', 'codeburn.bat', 'codeburn']
|
||||
}
|
||||
|
||||
export function buildAntigravityHookLookupPath(existingPath = process.env.PATH ?? ''): string {
|
||||
const parts = existingPath.split(delimiter).filter(Boolean)
|
||||
for (const fallback of DEFAULT_CLI_LOOKUP_PATHS) {
|
||||
if (!parts.includes(fallback)) parts.push(fallback)
|
||||
}
|
||||
return parts.join(delimiter)
|
||||
}
|
||||
|
||||
async function executableExists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await access(path, process.platform === 'win32' ? constants.F_OK : constants.F_OK | constants.X_OK)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolvePersistentCodeburnPathFromPath(lookupPath: string): Promise<string> {
|
||||
const seen = new Set<string>()
|
||||
for (const dir of lookupPath.split(delimiter).filter(Boolean)) {
|
||||
for (const executable of codeburnExecutableNames()) {
|
||||
const candidate = join(dir, executable)
|
||||
if (seen.has(candidate)) continue
|
||||
seen.add(candidate)
|
||||
if (isTransientNpxPath(candidate)) continue
|
||||
if (await executableExists(candidate)) return candidate
|
||||
}
|
||||
}
|
||||
throw new Error(PERSISTENT_CLI_REQUIRED_MESSAGE)
|
||||
}
|
||||
|
||||
async function hookCommand(): Promise<string> {
|
||||
const codeburnPath = await resolvePersistentCodeburnPathFromPath(buildAntigravityHookLookupPath())
|
||||
return `${shellQuote(codeburnPath)} agy-statusline-hook`
|
||||
}
|
||||
|
||||
function settingsPath(): string {
|
||||
|
|
@ -115,12 +161,15 @@ export async function installAntigravityStatusLineHook(force = false): Promise<'
|
|||
)
|
||||
}
|
||||
|
||||
if (isCodeBurnHook(existing?.command)) return 'already-installed'
|
||||
const command = await hookCommand()
|
||||
if (isCodeBurnHook(existing?.command) && existing?.command === command && existing.type === 'command' && existing.padding === 0) {
|
||||
return 'already-installed'
|
||||
}
|
||||
if (existing && !isCodeBurnHook(existing.command)) await savePreviousStatusLine(existing)
|
||||
|
||||
settings.statusLine = {
|
||||
type: 'command',
|
||||
command: hookCommand(),
|
||||
command,
|
||||
padding: 0,
|
||||
}
|
||||
await writeSettings(settings)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { mkdtemp, readFile, rm, writeFile } from 'fs/promises'
|
||||
import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from 'fs/promises'
|
||||
import { tmpdir } from 'os'
|
||||
import { join } from 'path'
|
||||
import { delimiter, join } from 'path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
buildAntigravityHookLookupPath,
|
||||
installAntigravityStatusLineHook,
|
||||
resolvePersistentCodeburnPathFromPath,
|
||||
uninstallAntigravityStatusLineHook,
|
||||
} from '../src/antigravity-statusline.js'
|
||||
|
||||
|
|
@ -12,10 +14,17 @@ describe('Antigravity CLI statusLine hook installer', () => {
|
|||
async function withTempSettings(run: (dir: string, settingsPath: string) => Promise<void>) {
|
||||
const dir = await mkdtemp(join(tmpdir(), 'codeburn-agy-hook-'))
|
||||
const settingsPath = join(dir, 'settings.json')
|
||||
const binDir = join(dir, 'bin')
|
||||
const codeburnPath = join(binDir, process.platform === 'win32' ? 'codeburn.cmd' : 'codeburn')
|
||||
const oldSettingsPath = process.env['CODEBURN_ANTIGRAVITY_SETTINGS_PATH']
|
||||
const oldCacheDir = process.env['CODEBURN_CACHE_DIR']
|
||||
const oldPath = process.env.PATH
|
||||
await mkdir(binDir, { recursive: true })
|
||||
await writeFile(codeburnPath, process.platform === 'win32' ? '@echo off\r\n' : '#!/bin/sh\n')
|
||||
await chmod(codeburnPath, 0o755)
|
||||
process.env['CODEBURN_ANTIGRAVITY_SETTINGS_PATH'] = settingsPath
|
||||
process.env['CODEBURN_CACHE_DIR'] = join(dir, 'cache')
|
||||
process.env.PATH = binDir
|
||||
|
||||
try {
|
||||
await run(dir, settingsPath)
|
||||
|
|
@ -24,10 +33,38 @@ describe('Antigravity CLI statusLine hook installer', () => {
|
|||
else process.env['CODEBURN_ANTIGRAVITY_SETTINGS_PATH'] = oldSettingsPath
|
||||
if (oldCacheDir === undefined) delete process.env['CODEBURN_CACHE_DIR']
|
||||
else process.env['CODEBURN_CACHE_DIR'] = oldCacheDir
|
||||
if (oldPath === undefined) delete process.env.PATH
|
||||
else process.env.PATH = oldPath
|
||||
await rm(dir, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
|
||||
it('builds a lookup PATH with user paths before fallbacks', () => {
|
||||
const lookupPath = buildAntigravityHookLookupPath(['/Users/me/.nvm/versions/node/v22.13.0/bin', '/usr/bin'].join(delimiter))
|
||||
|
||||
expect(lookupPath.split(delimiter)).toContain('/Users/me/.nvm/versions/node/v22.13.0/bin')
|
||||
if (process.platform !== 'win32') expect(lookupPath.split(delimiter)).toContain('/opt/homebrew/bin')
|
||||
})
|
||||
|
||||
it('skips transient npx codeburn shims when resolving the hook command', async () => {
|
||||
await withTempSettings(async (dir) => {
|
||||
const npxBin = join(dir, '.npm', '_npx', 'abcd', 'node_modules', '.bin')
|
||||
const persistentBin = join(dir, 'persistent-bin')
|
||||
const npxCodeburn = join(npxBin, process.platform === 'win32' ? 'codeburn.cmd' : 'codeburn')
|
||||
const persistentCodeburn = join(persistentBin, process.platform === 'win32' ? 'codeburn.cmd' : 'codeburn')
|
||||
await mkdir(npxBin, { recursive: true })
|
||||
await mkdir(persistentBin, { recursive: true })
|
||||
await writeFile(npxCodeburn, process.platform === 'win32' ? '@echo off\r\n' : '#!/bin/sh\n')
|
||||
await writeFile(persistentCodeburn, process.platform === 'win32' ? '@echo off\r\n' : '#!/bin/sh\n')
|
||||
await chmod(npxCodeburn, 0o755)
|
||||
await chmod(persistentCodeburn, 0o755)
|
||||
|
||||
const resolved = await resolvePersistentCodeburnPathFromPath([npxBin, persistentBin].join(delimiter))
|
||||
|
||||
expect(resolved).toBe(persistentCodeburn)
|
||||
})
|
||||
})
|
||||
|
||||
it('backs up and restores an existing custom statusLine when forced', async () => {
|
||||
await withTempSettings(async (dir, settingsPath) => {
|
||||
const customStatusLine = {
|
||||
|
|
@ -42,6 +79,7 @@ describe('Antigravity CLI statusLine hook installer', () => {
|
|||
|
||||
const installed = JSON.parse(await readFile(settingsPath, 'utf-8'))
|
||||
expect(installed.statusLine.command).toContain('agy-statusline-hook')
|
||||
expect(installed.statusLine.command).not.toContain('custom-statusline')
|
||||
|
||||
const backupPath = join(dir, 'cache', 'antigravity-statusline-previous.json')
|
||||
const backup = JSON.parse(await readFile(backupPath, 'utf-8'))
|
||||
|
|
@ -64,6 +102,27 @@ describe('Antigravity CLI statusLine hook installer', () => {
|
|||
padding: 0,
|
||||
})
|
||||
expect(settings.statusLine.command).toContain('agy-statusline-hook')
|
||||
expect(settings.statusLine.command).toContain(join(_dir, 'bin'))
|
||||
expect(settings.statusLine.command).not.toContain('dist/cli.js')
|
||||
})
|
||||
})
|
||||
|
||||
it('repairs an existing stale CodeBurn statusLine command without force', async () => {
|
||||
await withTempSettings(async (dir, settingsPath) => {
|
||||
await writeFile(settingsPath, JSON.stringify({
|
||||
statusLine: {
|
||||
type: 'command',
|
||||
command: "'/usr/local/bin/node' '/Users/me/codeburn-agy-statusline/dist/cli.js' agy-statusline-hook",
|
||||
padding: 0,
|
||||
},
|
||||
}))
|
||||
|
||||
expect(await installAntigravityStatusLineHook(false)).toBe('installed')
|
||||
|
||||
const settings = JSON.parse(await readFile(settingsPath, 'utf-8'))
|
||||
expect(settings.statusLine.command).toContain(join(dir, 'bin'))
|
||||
expect(settings.statusLine.command).toContain('agy-statusline-hook')
|
||||
expect(settings.statusLine.command).not.toContain('codeburn-agy-statusline/dist/cli.js')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue