|
1 | 1 | // hosts (install-if-missing) — cheap: file read + `which`, no network. |
2 | 2 | // An enabled host that is entirely absent is installable by sync; an external |
3 | | -// install (mise/native/brew) is reported but never touched. |
4 | | -import { HOSTS, hostInstallState, hostAuthState } from '../../../lib/providers.mjs'; |
| 3 | +// install (mise/native/brew) is reported but never touched. An npm install is |
| 4 | +// also launched once (`--version`): its package.json can outlive the binary. |
| 5 | +import { HOSTS, hostInstallState, hostAuthState, hostExecutable } from '../../../lib/providers.mjs'; |
5 | 6 | import { row } from '../row.mjs'; |
6 | 7 |
|
| 8 | +const DEFAULT_DEPS = { installState: hostInstallState, executable: hostExecutable, authState: hostAuthState }; |
| 9 | + |
| 10 | +// Install row + auth row for a host that is on disk. |
| 11 | +async function installedHostRows(h, st, primary, deps) { |
| 12 | + const label = `${h.id} ${st.version ?? ''} (${st.method}${st.method === 'external' ? ' — self-managed' : ''})`; |
| 13 | + const launch = st.method === 'npm' ? await deps.executable(h) : { ok: true, detail: null }; |
| 14 | + const install = launch.ok ? row('hosts', 'ok', label) |
| 15 | + : row('hosts', primary ? 'fail' : 'warn', `${label} installed but not executable: ${launch.detail}`, |
| 16 | + `sync reinstalls ${h.pkg}`); |
| 17 | + // auth mode (billing axis): oauth/subscription ($0) vs metered api-key. |
| 18 | + // A distinct row so `ak status --json` (and the dashboard) can badge it. |
| 19 | + const auth = deps.authState(h.id, { present: true }); |
| 20 | + const billing = auth.billing === 'subscription' ? 'subscription, $0' |
| 21 | + : auth.billing === 'metered' ? 'metered' : auth.billing; |
| 22 | + return [install, row('hosts', auth.mode === 'none' ? 'warn' : 'ok', |
| 23 | + `${h.id} auth: ${auth.mode} (${billing})${auth.source ? ` · ${auth.source}` : ''}${auth.note ? ` — ${auth.note}` : ''}`, |
| 24 | + auth.mode === 'none' ? `${h.id} login` : null)]; |
| 25 | +} |
| 26 | + |
7 | 27 | export default { |
8 | 28 | id: 'hosts', |
9 | | - async collect({ cfg, integrationFacts }) { |
| 29 | + /** @param {{ cfg: any, integrationFacts: any, hostDeps?: Partial<typeof DEFAULT_DEPS> }} ctx */ |
| 30 | + async collect({ cfg, integrationFacts, hostDeps = {} }) { |
| 31 | + const deps = { ...DEFAULT_DEPS, ...hostDeps }; |
10 | 32 | const rows = []; |
11 | 33 | try { |
12 | 34 | // primary host absent = fail (nothing can drive); alternate absent = warn. |
13 | 35 | const primaryHost = cfg.routing?.primaryHost ?? 'claude'; |
14 | 36 | for (const h of HOSTS) { |
15 | 37 | if (!cfg.integrations.hosts[h.id]) continue; |
16 | | - const detected = integrationFacts.hosts[h.id]; |
17 | | - if (detected?.present === false) { |
18 | | - rows.push(row('hosts', h.id === primaryHost ? 'fail' : 'warn', |
19 | | - `${h.id} enabled but not installed${h.id === primaryHost ? ' (primary)' : ''}`, `sync installs ${h.pkg}`)); |
20 | | - continue; |
21 | | - } |
22 | | - const st = await hostInstallState(h); |
| 38 | + const primary = h.id === primaryHost; |
| 39 | + const st = integrationFacts.hosts[h.id]?.present === false |
| 40 | + ? { method: 'absent', version: null } : await deps.installState(h); |
23 | 41 | if (st.method === 'absent') { |
24 | | - rows.push(row('hosts', h.id === primaryHost ? 'fail' : 'warn', |
25 | | - `${h.id} enabled but not installed${h.id === primaryHost ? ' (primary)' : ''}`, `sync installs ${h.pkg}`)); |
| 42 | + rows.push(row('hosts', primary ? 'fail' : 'warn', |
| 43 | + `${h.id} enabled but not installed${primary ? ' (primary)' : ''}`, `sync installs ${h.pkg}`)); |
26 | 44 | } else { |
27 | | - rows.push(row('hosts', 'ok', `${h.id} ${st.version ?? ''} (${st.method}${st.method === 'external' ? ' — self-managed' : ''})`)); |
28 | | - // auth mode (billing axis): oauth/subscription ($0) vs metered api-key. |
29 | | - // A distinct row so `ak status --json` (and the dashboard) can badge it. |
30 | | - const auth = hostAuthState(h.id, { present: true }); |
31 | | - const billing = auth.billing === 'subscription' ? 'subscription, $0' |
32 | | - : auth.billing === 'metered' ? 'metered' : auth.billing; |
33 | | - rows.push(row('hosts', auth.mode === 'none' ? 'warn' : 'ok', |
34 | | - `${h.id} auth: ${auth.mode} (${billing})${auth.source ? ` · ${auth.source}` : ''}${auth.note ? ` — ${auth.note}` : ''}`, |
35 | | - auth.mode === 'none' ? `${h.id} login` : null)); |
| 45 | + rows.push(...await installedHostRows(h, st, primary, deps)); |
36 | 46 | } |
37 | 47 | } |
38 | 48 | } catch (e) { |
|
0 commit comments