@agfpd/iapeer 0.2.17 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli/cli.test.ts +3 -0
- package/src/cli/index.ts +14 -4
- package/src/onboard/index.ts +29 -12
package/package.json
CHANGED
package/src/cli/cli.test.ts
CHANGED
|
@@ -116,6 +116,9 @@ describe('remove (registry record via the locked writer)', () => {
|
|
|
116
116
|
const o = await removePeerCli('zombie', { env: e })
|
|
117
117
|
expect(o.action).toBe('removed')
|
|
118
118
|
expect(findPeer(readPeersIndex({ env: e }), 'zombie')).toBeNull()
|
|
119
|
+
// the folder is deliberately KEPT; the outcome carries the cwd so the verb can
|
|
120
|
+
// say so instead of leaving silent orphans (boris 10.06)
|
|
121
|
+
expect(o.cwd).toBe('/tmp/zombie')
|
|
119
122
|
})
|
|
120
123
|
test('removing an absent peer is an idempotent no-op (not an error)', async () => {
|
|
121
124
|
const o = await removePeerCli('never-existed', { env: env() })
|
package/src/cli/index.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// sentinel-marked always-on plist) are stop/start-able.
|
|
12
12
|
|
|
13
13
|
import { spawnSync } from 'child_process'
|
|
14
|
-
import { readFileSync } from 'fs'
|
|
14
|
+
import { existsSync, readFileSync } from 'fs'
|
|
15
15
|
import { fileURLToPath } from 'url'
|
|
16
16
|
import {
|
|
17
17
|
isInfraRuntime,
|
|
@@ -255,6 +255,10 @@ export interface RemoveOutcome {
|
|
|
255
255
|
personality: string
|
|
256
256
|
action: 'removed' | 'absent' | 'refused-live'
|
|
257
257
|
reason?: string
|
|
258
|
+
/** The removed peer's cwd (registry fact, captured BEFORE the removal). remove
|
|
259
|
+
* deliberately keeps the folder — user data is never deleted by a registry reap
|
|
260
|
+
* (boris's finding 10.06: say so in the output instead of leaving silent orphans). */
|
|
261
|
+
cwd?: string
|
|
258
262
|
}
|
|
259
263
|
|
|
260
264
|
/**
|
|
@@ -285,7 +289,7 @@ export async function removePeerCli(
|
|
|
285
289
|
}
|
|
286
290
|
}
|
|
287
291
|
await removePeer(personality, { env })
|
|
288
|
-
return { personality, action: 'removed' }
|
|
292
|
+
return { personality, action: 'removed', cwd: peer.cwd }
|
|
289
293
|
}
|
|
290
294
|
|
|
291
295
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -657,8 +661,14 @@ export async function runCli(argv: string[], env: NodeJS.ProcessEnv = process.en
|
|
|
657
661
|
// peer unless --force (orphaning a running session from routing is the risk).
|
|
658
662
|
if (!positionals[0]) return usage(errOut)
|
|
659
663
|
const o = await removePeerCli(positionals[0], { force: flags.force === true, env })
|
|
660
|
-
if (o.action === 'removed')
|
|
661
|
-
|
|
664
|
+
if (o.action === 'removed') {
|
|
665
|
+
out(`removed "${o.personality}" from the registry\n`)
|
|
666
|
+
// Deliberate: the registry reap never deletes user data — but SAY so, or
|
|
667
|
+
// the default-location peers leave silent orphan folders (boris 10.06).
|
|
668
|
+
if (o.cwd && existsSync(o.cwd)) {
|
|
669
|
+
out(`folder kept: ${o.cwd} (remove never deletes peer data — \`rm -rf\` it yourself if it was a throwaway)\n`)
|
|
670
|
+
}
|
|
671
|
+
} else if (o.action === 'absent') out(`"${o.personality}" not registered — no-op\n`)
|
|
662
672
|
else errOut(`remove: ${o.reason}\n`)
|
|
663
673
|
return o.action === 'refused-live' ? 1 : 0
|
|
664
674
|
}
|
package/src/onboard/index.ts
CHANGED
|
@@ -55,7 +55,7 @@ function runtimeBin(runtime: OnboardRuntime, env: NodeJS.ProcessEnv): string {
|
|
|
55
55
|
return env.IAPEER_CODEX_BIN?.trim() || 'codex'
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function isExecutable(binOrName: string): boolean {
|
|
58
|
+
function isExecutable(binOrName: string, env: NodeJS.ProcessEnv = process.env): boolean {
|
|
59
59
|
if (binOrName.includes('/')) {
|
|
60
60
|
try {
|
|
61
61
|
accessSync(binOrName, FS.X_OK)
|
|
@@ -64,13 +64,22 @@ function isExecutable(binOrName: string): boolean {
|
|
|
64
64
|
return false
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
// bare name →
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
// bare name → PRESENCE probe over PATH (`command -v` semantics), NO spawn.
|
|
68
|
+
// History (both live finds 10.06): the original `--version` ANSWER probe HANGS
|
|
69
|
+
// FOREVER for codex in a non-tty (three stray probes sat 25+ min); the 10 s
|
|
70
|
+
// timeout that replaced it then DEGRADED a LIVE codex to 'runtime-missing' —
|
|
71
|
+
// masking a working runtime (boris's catch). The skip-decision only asks "is
|
|
72
|
+
// the runtime installed", and presence answers that without executing anything.
|
|
73
|
+
for (const dir of (env.PATH ?? '').split(':')) {
|
|
74
|
+
if (!dir) continue
|
|
75
|
+
try {
|
|
76
|
+
accessSync(join(dir, binOrName), FS.X_OK)
|
|
77
|
+
return true
|
|
78
|
+
} catch {
|
|
79
|
+
/* not in this PATH segment */
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return false
|
|
74
83
|
}
|
|
75
84
|
|
|
76
85
|
/**
|
|
@@ -81,7 +90,11 @@ function isExecutable(binOrName: string): boolean {
|
|
|
81
90
|
*/
|
|
82
91
|
export function isMarketplaceRegistered(runtime: OnboardRuntime, env: NodeJS.ProcessEnv = process.env): boolean {
|
|
83
92
|
const bin = runtimeBin(runtime, env)
|
|
84
|
-
|
|
93
|
+
// HARD TIMEOUT — the codex CLI hangs FOREVER in a non-tty on ANY subcommand
|
|
94
|
+
// (live 10.06: first `--version`, then `plugin marketplace list` after the
|
|
95
|
+
// presence-probe fix let a live codex through). Timeout → status null →
|
|
96
|
+
// "not registered" → the add (also time-bounded) decides; never a wedge.
|
|
97
|
+
const r = spawnSync(bin, ['plugin', 'marketplace', 'list'], { encoding: 'utf8', timeout: 60_000 })
|
|
85
98
|
if (r.status !== 0) return false
|
|
86
99
|
return isAgfpdInList(`${r.stdout ?? ''}`)
|
|
87
100
|
}
|
|
@@ -102,8 +115,12 @@ export function isAgfpdInList(listOutput: string): boolean {
|
|
|
102
115
|
/** Register OUR marketplace for this runtime (`<runtime> plugin marketplace add <ref>`). */
|
|
103
116
|
function registerMarketplace(runtime: OnboardRuntime, env: NodeJS.ProcessEnv): { ok: boolean; detail?: string } {
|
|
104
117
|
const bin = runtimeBin(runtime, env)
|
|
105
|
-
|
|
106
|
-
|
|
118
|
+
// Same hard timeout as the list probe (codex non-tty hang class) — a wedged add
|
|
119
|
+
// degrades to a loud 'failed' line instead of freezing the host phase.
|
|
120
|
+
const r = spawnSync(bin, ['plugin', 'marketplace', 'add', MARKETPLACE_REF], { encoding: 'utf8', timeout: 120_000 })
|
|
121
|
+
return r.status === 0
|
|
122
|
+
? { ok: true }
|
|
123
|
+
: { ok: false, detail: (r.stderr ?? '').trim() || (r.status === null ? 'timed out (non-tty hang?)' : `exit ${r.status}`) }
|
|
107
124
|
}
|
|
108
125
|
|
|
109
126
|
/**
|
|
@@ -119,7 +136,7 @@ export function onboardHost(opts: OnboardOptions = {}): OnboardResult {
|
|
|
119
136
|
const runtimes = opts.runtimes ?? (['claude', 'codex'] as OnboardRuntime[])
|
|
120
137
|
const marketplaces: OnboardRuntimeResult[] = []
|
|
121
138
|
for (const runtime of runtimes) {
|
|
122
|
-
if (!isExecutable(runtimeBin(runtime, env))) {
|
|
139
|
+
if (!isExecutable(runtimeBin(runtime, env), env)) {
|
|
123
140
|
marketplaces.push({ runtime, state: 'runtime-missing' })
|
|
124
141
|
continue
|
|
125
142
|
}
|