@kal-elsam/kairo-runtime 0.1.3 → 0.1.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kal-elsam/kairo-runtime",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Kairo Runtime — local agent operating system for Codex, Cursor, Claude, Gemini, Copilot, Engram, and Graphify.",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/Kal-elSam/harness#readme",
@@ -184,7 +184,7 @@ require_cmd npm
184
184
  NODE_VERSION="$(node --version 2>/dev/null || true)"
185
185
  NPM_VERSION="$(npm --version 2>/dev/null || true)"
186
186
  GLOBAL_SPEC="$(resolve_global_spec)"
187
- GLOBAL_INSTALL_CMD="npm install -g ${GLOBAL_SPEC}"
187
+ GLOBAL_INSTALL_CMD="npm install -g --force ${GLOBAL_SPEC}"
188
188
  SETUP_CMD="${PREFERRED_CLI} setup ${SETUP_MODE}${SETUP_EXTRA:+ ${SETUP_EXTRA}}"
189
189
 
190
190
  printf '%s\n' \
@@ -224,7 +224,7 @@ fi
224
224
 
225
225
  printf '%s\n' "Installing global CLI..." ""
226
226
  # shellcheck disable=SC2086
227
- npm install -g ${GLOBAL_SPEC}
227
+ npm install -g --force ${GLOBAL_SPEC}
228
228
 
229
229
  KAIRO_BIN="$(resolve_kairo_bin || true)"
230
230
  if [ -z "$KAIRO_BIN" ]; then
@@ -2,6 +2,31 @@ import { fileURLToPath } from "node:url";
2
2
  import path from "node:path";
3
3
 
4
4
  const DEFAULT_REPO = "Kal-elSam/harness";
5
+ const LEGACY_HARNESS_MINOR_CUTOFF = 29;
6
+
7
+ function parseSemver(version) {
8
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-.+)?$/.exec(version);
9
+
10
+ if (!match) {
11
+ return null;
12
+ }
13
+
14
+ return {
15
+ major: Number(match[1]),
16
+ minor: Number(match[2]),
17
+ patch: Number(match[3])
18
+ };
19
+ }
20
+
21
+ export function usesLegacyHarnessTag(version) {
22
+ const semver = parseSemver(version);
23
+
24
+ if (!semver) {
25
+ return false;
26
+ }
27
+
28
+ return semver.major === 0 && semver.minor >= LEGACY_HARNESS_MINOR_CUTOFF;
29
+ }
5
30
 
6
31
  export function resolveInstallScriptRef({ version, tag = null }) {
7
32
  if (version === "latest") {
@@ -12,7 +37,11 @@ export function resolveInstallScriptRef({ version, tag = null }) {
12
37
  return tag;
13
38
  }
14
39
 
15
- return `v${version}`;
40
+ if (usesLegacyHarnessTag(version)) {
41
+ return `v${version}`;
42
+ }
43
+
44
+ return `kairo-runtime-v${version}`;
16
45
  }
17
46
 
18
47
  export function resolveInstallScriptUrl({
package/src/cli.js CHANGED
@@ -30,6 +30,7 @@ import {
30
30
  import { applyPolicyToOptions, loadPolicyFile } from "./global/policy.js";
31
31
  import { resolveHomeDir } from "./global/paths.js";
32
32
  import { runWorkspaceDetect, runWorkspaceDoctor, runWorkspaceInit, runWorkspaceUpdate } from "./workspace-cli.js";
33
+ import { runOrchestratorDiagnostics, runOrchestratorShell } from "./global/orchestrator.js";
33
34
  import {
34
35
  LEGACY_PACKAGE_NAME,
35
36
  PACKAGE_NAME,
@@ -66,6 +67,24 @@ export async function runCli(argv) {
66
67
  const invoke = resolveSuggestedInvocation(packageManifest.name);
67
68
 
68
69
  switch (command) {
70
+ case "shell":
71
+ await runOrchestratorShell({
72
+ packageRoot,
73
+ packageManifest,
74
+ workspaceRoot: optionsWithPolicy.cwd,
75
+ interactive: optionsWithPolicy.interactive
76
+ });
77
+ return;
78
+ case "orchestrator":
79
+ await runOrchestratorDiagnostics({
80
+ homeDir: resolveHomeDir(),
81
+ workspaceRoot: optionsWithPolicy.cwd,
82
+ packageName: packageManifest.name,
83
+ packageRoot,
84
+ cliVersion: packageManifest.version,
85
+ json: optionsWithPolicy.json
86
+ });
87
+ return;
69
88
  case "setup":
70
89
  await runGlobalSetup(optionsWithPolicy, packageManifest, packageRoot);
71
90
  return;
@@ -219,7 +238,37 @@ function resolveImplicitCommand(args) {
219
238
  if (argsWantsWorkspaceScope(args)) {
220
239
  return "init";
221
240
  }
222
- return "setup";
241
+ if (hasImplicitSetupFlags(args)) {
242
+ return "setup";
243
+ }
244
+ return "shell";
245
+ }
246
+
247
+ function hasImplicitSetupFlags(args) {
248
+ const setupFlags = new Set([
249
+ "--dry-run",
250
+ "--yes",
251
+ "-y",
252
+ "--confirm",
253
+ "--simple",
254
+ "--no-preflight",
255
+ "--all-adapters",
256
+ "--no-default-components",
257
+ "--detect"
258
+ ]);
259
+
260
+ for (let index = 0; index < args.length; index += 1) {
261
+ const arg = args[index];
262
+ if (setupFlags.has(arg)) return true;
263
+ if (arg.startsWith("--mode=")) return true;
264
+ if (arg.startsWith("--adapters=") || arg.startsWith("--agents=")) return true;
265
+ if (arg.startsWith("--components=")) return true;
266
+ if (arg === "--mode" || arg === "--adapters" || arg === "--agents" || arg === "--components") {
267
+ return true;
268
+ }
269
+ }
270
+
271
+ return false;
223
272
  }
224
273
 
225
274
  function argsWantsWorkspaceScope(args) {
@@ -461,6 +510,8 @@ function normalizeCommand(command) {
461
510
  if (!command) return "install";
462
511
 
463
512
  if (command === "install" || command === "i") return "install";
513
+ if (command === "shell") return "shell";
514
+ if (command === "orchestrator") return "orchestrator";
464
515
  if (command === "setup") return "setup";
465
516
  if (command === "status") return "status";
466
517
  if (command === "sync") return "sync";
@@ -517,8 +568,11 @@ sections, components, backups, and drift repair under ~/.harness.
517
568
  Bootstrap: see README.md (curl install.sh or npx ${PACKAGE_NAME}).
518
569
 
519
570
  Usage:
520
- ${cli} [--dry-run] [--yes] [--confirm] [--agents <list|all>] [--components <list>]
571
+ ${cli} Interactive orchestrator shell (TTY)
572
+ ${cli} --dry-run Setup dry-run (scriptable)
521
573
  ${cli} --version
574
+ ${cli} shell Interactive orchestrator shell (TTY)
575
+ ${cli} orchestrator [--json] Read-only agent capability diagnostics
522
576
  ${cli} setup [--dry-run] [--yes] [--confirm] [--simple] [--no-preflight] [--agents <list|all>] [--components <list>]
523
577
  ${cli} status [--json]
524
578
  ${cli} sync [--dry-run] [--yes] [--confirm] [--json] [--no-preflight]
@@ -552,7 +606,9 @@ Scopes:
552
606
  Explicit --scope=workspace only.
553
607
 
554
608
  Commands:
555
- setup Interactive Ink UI (TTY). Use --simple for Clack prompts.
609
+ shell Interactive Ink orchestrator (TTY). Bare ${cli} opens this in TTY sessions.
610
+ orchestrator Read-only capability registry diagnostics (--json supported).
611
+ setup Managed ecosystem setup. Interactive Ink UI (TTY). Use --simple for Clack prompts.
556
612
  status Control panel: agents, components, drift, backups, next action.
557
613
  sync Converge managed content (repair drift), then show status.
558
614
  upgrade Preview or apply ecosystem updates (apply requires --yes).
@@ -0,0 +1,178 @@
1
+ import { CAPABILITY_STATES } from "./capability-states.js";
2
+ import { inspectAllCapabilities } from "./capability-registry.js";
3
+ import { resolveProfile, resolveProfileAgents } from "./profile.js";
4
+ import { formatCliCommand } from "./brand/cli.js";
5
+
6
+ export const PLAN_ACTIONS = {
7
+ DIAGNOSE: "diagnose",
8
+ SETUP: "setup",
9
+ SYNC: "sync",
10
+ INSTALL: "install",
11
+ STATUS: "status"
12
+ };
13
+
14
+ export async function buildReadOnlyDiagnostics({
15
+ homeDir,
16
+ workspaceRoot,
17
+ packageName,
18
+ packageRoot,
19
+ cliVersion
20
+ }) {
21
+ const [{ profile, sources }, capabilities] = await Promise.all([
22
+ resolveProfile({ homeDir, workspaceRoot }),
23
+ inspectAllCapabilities({ homeDir, workspaceRoot, packageName })
24
+ ]);
25
+
26
+ const detectedIds = capabilities.filter((entry) => entry.detected).map((entry) => entry.id);
27
+ const profileAgents = resolveProfileAgents(profile, detectedIds);
28
+ const diagnostics = summarizeDiagnostics(capabilities);
29
+
30
+ return {
31
+ readOnly: true,
32
+ cliVersion,
33
+ profile: { ...profile, sources },
34
+ capabilities,
35
+ profileAgents,
36
+ diagnostics,
37
+ recommendations: buildDiagnosticRecommendations({ capabilities, profile, diagnostics })
38
+ };
39
+ }
40
+
41
+ export async function buildActionPlan({
42
+ action,
43
+ homeDir,
44
+ workspaceRoot,
45
+ packageName,
46
+ options = {}
47
+ }) {
48
+ const diagnostics = await buildReadOnlyDiagnostics({
49
+ homeDir,
50
+ workspaceRoot,
51
+ packageName,
52
+ packageRoot: options.packageRoot ?? null,
53
+ cliVersion: options.cliVersion ?? null
54
+ });
55
+
56
+ const steps = [];
57
+ const warnings = [];
58
+
59
+ switch (action) {
60
+ case PLAN_ACTIONS.DIAGNOSE:
61
+ case PLAN_ACTIONS.STATUS:
62
+ return {
63
+ action,
64
+ readOnly: true,
65
+ requiresConfirmation: false,
66
+ steps: ["Show ecosystem diagnostics (read-only)."],
67
+ diagnostics,
68
+ warnings
69
+ };
70
+ case PLAN_ACTIONS.SETUP:
71
+ case PLAN_ACTIONS.INSTALL:
72
+ steps.push(`Target agents: ${diagnostics.profileAgents.join(", ")}`);
73
+ steps.push("Preview managed section changes under ~/.harness and agent config files.");
74
+ steps.push("Create backups before writing managed content.");
75
+ if (options.dryRun) {
76
+ steps.push("Dry run: no files will be written.");
77
+ }
78
+ return {
79
+ action,
80
+ readOnly: Boolean(options.dryRun),
81
+ requiresConfirmation: !options.dryRun,
82
+ steps,
83
+ diagnostics,
84
+ warnings: collectCapabilityWarnings(diagnostics.capabilities)
85
+ };
86
+ case PLAN_ACTIONS.SYNC:
87
+ steps.push("Compare managed content against bundled assets.");
88
+ steps.push("Repair drift in agent config files when differences are found.");
89
+ if (options.dryRun) {
90
+ steps.push("Dry run: show planned repairs only.");
91
+ }
92
+ return {
93
+ action,
94
+ readOnly: Boolean(options.dryRun),
95
+ requiresConfirmation: !options.dryRun,
96
+ steps,
97
+ diagnostics,
98
+ warnings: collectCapabilityWarnings(diagnostics.capabilities)
99
+ };
100
+ default: {
101
+ const _exhaustive = action;
102
+ throw new Error(`Unknown plan action "${_exhaustive}".`);
103
+ }
104
+ }
105
+ }
106
+
107
+ export function shouldExecutePlan(plan, { confirmed = false } = {}) {
108
+ if (plan.readOnly) return true;
109
+ return confirmed;
110
+ }
111
+
112
+ export function formatActionPlan(plan) {
113
+ const lines = [];
114
+ lines.push(`Action: ${plan.action}`);
115
+ lines.push(`Mode: ${plan.readOnly ? "read-only" : "write"}`);
116
+ lines.push(`Confirmation required: ${plan.requiresConfirmation ? "yes" : "no"}`);
117
+ lines.push("");
118
+ lines.push("Steps:");
119
+ for (const step of plan.steps) {
120
+ lines.push(` - ${step}`);
121
+ }
122
+
123
+ if (plan.warnings.length > 0) {
124
+ lines.push("");
125
+ lines.push("Warnings:");
126
+ for (const warning of plan.warnings) {
127
+ lines.push(` - ${warning}`);
128
+ }
129
+ }
130
+
131
+ if (plan.diagnostics.recommendations.length > 0) {
132
+ lines.push("");
133
+ lines.push("Recommendations:");
134
+ for (const recommendation of plan.diagnostics.recommendations) {
135
+ lines.push(` - ${recommendation}`);
136
+ }
137
+ }
138
+
139
+ return lines.join("\n");
140
+ }
141
+
142
+ function summarizeDiagnostics(capabilities) {
143
+ return {
144
+ detected: capabilities.filter((entry) => entry.detected).length,
145
+ available: capabilities.filter((entry) => entry.state === CAPABILITY_STATES.AVAILABLE).length,
146
+ unknown: capabilities.filter((entry) => entry.state === CAPABILITY_STATES.UNKNOWN).length,
147
+ errors: capabilities.filter((entry) => entry.state === CAPABILITY_STATES.ERROR).length
148
+ };
149
+ }
150
+
151
+ function buildDiagnosticRecommendations({ capabilities, profile, diagnostics }) {
152
+ const recommendations = [];
153
+
154
+ if (diagnostics.detected === 0) {
155
+ recommendations.push(`No agents detected. Run ${formatCliCommand("detect")} or install a supported agent CLI.`);
156
+ }
157
+
158
+ if (profile.coordinator) {
159
+ const coordinator = capabilities.find((entry) => entry.id === profile.coordinator);
160
+ if (!coordinator?.detected && coordinator?.state === CAPABILITY_STATES.UNKNOWN) {
161
+ recommendations.push(`Profile coordinator "${profile.coordinator}" is not detected on this machine.`);
162
+ }
163
+ }
164
+
165
+ for (const capability of capabilities) {
166
+ if (capability.recommendation) {
167
+ recommendations.push(`${capability.label}: ${capability.recommendation}`);
168
+ }
169
+ }
170
+
171
+ return [...new Set(recommendations)];
172
+ }
173
+
174
+ function collectCapabilityWarnings(capabilities) {
175
+ return capabilities
176
+ .filter((entry) => entry.state === CAPABILITY_STATES.ERROR || entry.state === CAPABILITY_STATES.UNKNOWN)
177
+ .map((entry) => `${entry.label} is ${entry.state}${entry.error ? ` (${entry.error})` : ""}.`);
178
+ }
@@ -0,0 +1,224 @@
1
+ import { CAPABILITY_STATES } from "../capability-states.js";
2
+ import {
3
+ isExecutableAvailable,
4
+ parseVersionFromOutput,
5
+ probeCommand,
6
+ resolveProbeState
7
+ } from "../cli-probe.js";
8
+
9
+ export function createAgentCapabilityAdapter({
10
+ id,
11
+ label,
12
+ managedAdapter,
13
+ executable = null,
14
+ versionArgs = ["--version"],
15
+ authArgs = null,
16
+ modelsArgs = null,
17
+ opaqueAuth = false,
18
+ runExecutable = null
19
+ }) {
20
+ const cliName = runExecutable ?? executable;
21
+
22
+ return {
23
+ id,
24
+ label,
25
+ managedAdapter,
26
+
27
+ detect(context) {
28
+ return managedAdapter.detect(context);
29
+ },
30
+
31
+ inspect(context, { probeImpl = defaultProbe } = {}) {
32
+ const detected = managedAdapter.detect(context);
33
+ const cliAvailable = executable ? isExecutableAvailable(executable) : false;
34
+
35
+ if (!detected && !cliAvailable) {
36
+ return buildInspection({
37
+ id,
38
+ label,
39
+ state: CAPABILITY_STATES.UNKNOWN,
40
+ detected: false,
41
+ cliAvailable: false,
42
+ version: null,
43
+ authenticated: null,
44
+ recommendation: `Install ${label} or run ${formatCliCommand("setup")} to configure managed sections.`
45
+ });
46
+ }
47
+
48
+ if (opaqueAuth) {
49
+ return buildInspection({
50
+ id,
51
+ label,
52
+ state: detected || cliAvailable ? CAPABILITY_STATES.DETECTED : CAPABILITY_STATES.UNKNOWN,
53
+ detected,
54
+ cliAvailable,
55
+ version: null,
56
+ authenticated: null,
57
+ recommendation: detected
58
+ ? `${label} config detected. Authentication status is provider-managed.`
59
+ : `Install ${label} to enable managed configuration.`
60
+ });
61
+ }
62
+
63
+ return probeImpl({
64
+ id,
65
+ label,
66
+ detected,
67
+ cliAvailable,
68
+ executable,
69
+ versionArgs,
70
+ authArgs
71
+ });
72
+ },
73
+
74
+ listModels(context, { probeImpl = defaultProbeModels } = {}) {
75
+ if (!modelsArgs || !executable) return null;
76
+ if (!isExecutableAvailable(executable)) return null;
77
+ return probeImpl({ executable, modelsArgs, context });
78
+ },
79
+
80
+ run(context, { args = [], cwd = context.workspaceRoot ?? process.cwd(), spawnImpl = probeCommand } = {}) {
81
+ if (!cliName) {
82
+ return {
83
+ ok: false,
84
+ state: CAPABILITY_STATES.ERROR,
85
+ message: `${label} does not expose a delegatable CLI through Kairo.`
86
+ };
87
+ }
88
+
89
+ if (!isExecutableAvailable(cliName)) {
90
+ return {
91
+ ok: false,
92
+ state: CAPABILITY_STATES.ERROR,
93
+ message: `${label} CLI "${cliName}" is not on PATH. Install the agent or add it to PATH.`
94
+ };
95
+ }
96
+
97
+ const result = spawnImpl(cliName, args, { cwd, env: process.env });
98
+
99
+ if (!result.ok) {
100
+ const detail = result.stderr || result.stdout || result.error || "unknown error";
101
+ return {
102
+ ok: false,
103
+ state: CAPABILITY_STATES.ERROR,
104
+ message: `${label} CLI failed: ${detail}`
105
+ };
106
+ }
107
+
108
+ return {
109
+ ok: true,
110
+ state: CAPABILITY_STATES.AVAILABLE,
111
+ stdout: result.stdout,
112
+ stderr: result.stderr
113
+ };
114
+ }
115
+ };
116
+ }
117
+
118
+ function defaultProbe({ id, label, detected, cliAvailable, executable, versionArgs, authArgs }) {
119
+ let version = null;
120
+ let authenticated = null;
121
+ let probeError = null;
122
+ let authReady = false;
123
+
124
+ if (cliAvailable && executable) {
125
+ const versionResult = probeCommand(executable, versionArgs);
126
+ if (versionResult.timedOut || versionResult.error) {
127
+ probeError = versionResult.error ?? "probe timed out";
128
+ } else if (versionResult.ok) {
129
+ version = parseVersionFromOutput(versionResult.stdout) ?? versionResult.stdout.split("\n")[0] ?? null;
130
+ }
131
+
132
+ if (authArgs) {
133
+ const authResult = probeCommand(executable, authArgs);
134
+ if (authResult.timedOut || authResult.error) {
135
+ probeError = probeError ?? authResult.error ?? "auth probe timed out";
136
+ } else {
137
+ authenticated = authResult.ok;
138
+ authReady = authResult.ok;
139
+ }
140
+ } else if (version) {
141
+ authenticated = null;
142
+ authReady = true;
143
+ }
144
+ }
145
+
146
+ const state = resolveProbeState({
147
+ detected,
148
+ cliAvailable,
149
+ authReady,
150
+ probeError,
151
+ opaque: false
152
+ });
153
+
154
+ return buildInspection({
155
+ id,
156
+ label,
157
+ state,
158
+ detected,
159
+ cliAvailable,
160
+ version,
161
+ authenticated,
162
+ error: probeError,
163
+ recommendation: buildRecommendation({ label, state, detected, cliAvailable, authenticated })
164
+ });
165
+ }
166
+
167
+ function defaultProbeModels({ executable, modelsArgs }) {
168
+ const result = probeCommand(executable, modelsArgs);
169
+ if (!result.ok) return null;
170
+
171
+ const lines = result.stdout
172
+ .split("\n")
173
+ .map((line) => line.trim())
174
+ .filter(Boolean);
175
+
176
+ return lines.length > 0 ? lines : null;
177
+ }
178
+
179
+ function buildInspection(fields) {
180
+ return {
181
+ id: fields.id,
182
+ label: fields.label,
183
+ state: fields.state,
184
+ detected: fields.detected,
185
+ cliAvailable: fields.cliAvailable,
186
+ version: fields.version ?? null,
187
+ authenticated: fields.authenticated ?? null,
188
+ models: fields.models ?? null,
189
+ error: fields.error ?? null,
190
+ recommendation: fields.recommendation ?? null
191
+ };
192
+ }
193
+
194
+ function buildRecommendation({ label, state, detected, cliAvailable, authenticated }) {
195
+ if (state === CAPABILITY_STATES.ERROR) {
196
+ return `Re-run detection or check ${label} CLI logs for details.`;
197
+ }
198
+
199
+ if (state === CAPABILITY_STATES.UNKNOWN) {
200
+ return `Install ${label} or run ${formatCliCommand("detect")} after setup.`;
201
+ }
202
+
203
+ if (state === CAPABILITY_STATES.AVAILABLE) {
204
+ return `${label} is ready. Delegate tasks through its CLI.`;
205
+ }
206
+
207
+ if (authenticated === false) {
208
+ return `Authenticate with ${label} (provider login) before delegating work.`;
209
+ }
210
+
211
+ if (detected && !cliAvailable) {
212
+ return `${label} config detected but CLI not on PATH.`;
213
+ }
214
+
215
+ if (cliAvailable && !detected) {
216
+ return `${label} CLI found. Run ${formatCliCommand("setup")} to add managed sections.`;
217
+ }
218
+
219
+ return `${label} detected. Run ${formatCliCommand("status")} for ecosystem health.`;
220
+ }
221
+
222
+ function formatCliCommand(command) {
223
+ return `kairo ${command}`;
224
+ }
@@ -0,0 +1,54 @@
1
+ import cursorManaged from "../adapters/cursor.js";
2
+ import codexManaged from "../adapters/codex.js";
3
+ import opencodeManaged from "../adapters/opencode.js";
4
+ import claudeManaged from "../adapters/claude.js";
5
+ import { createAgentCapabilityAdapter } from "./create-capability-adapter.js";
6
+
7
+ const cursor = createAgentCapabilityAdapter({
8
+ id: "cursor",
9
+ label: "Cursor",
10
+ managedAdapter: cursorManaged,
11
+ executable: "cursor",
12
+ opaqueAuth: true
13
+ });
14
+
15
+ const codex = createAgentCapabilityAdapter({
16
+ id: "codex",
17
+ label: "Codex",
18
+ managedAdapter: codexManaged,
19
+ executable: "codex",
20
+ runExecutable: "codex"
21
+ });
22
+
23
+ const opencode = createAgentCapabilityAdapter({
24
+ id: "opencode",
25
+ label: "OpenCode",
26
+ managedAdapter: opencodeManaged,
27
+ executable: "opencode",
28
+ runExecutable: "opencode"
29
+ });
30
+
31
+ const claude = createAgentCapabilityAdapter({
32
+ id: "claude",
33
+ label: "Claude Code",
34
+ managedAdapter: claudeManaged,
35
+ executable: "claude",
36
+ authArgs: ["auth", "status"],
37
+ runExecutable: "claude"
38
+ });
39
+
40
+ const CAPABILITY_ADAPTERS = [cursor, codex, opencode, claude];
41
+
42
+ export const AGENT_CAPABILITY_IDS = CAPABILITY_ADAPTERS.map((adapter) => adapter.id);
43
+
44
+ export function listCapabilityAdapters() {
45
+ return [...CAPABILITY_ADAPTERS];
46
+ }
47
+
48
+ export function resolveCapabilityAdapter(id) {
49
+ const adapter = CAPABILITY_ADAPTERS.find((candidate) => candidate.id === id);
50
+ if (!adapter) {
51
+ throw new Error(`Unknown agent capability "${id}". Use ${AGENT_CAPABILITY_IDS.join(", ")}.`);
52
+ }
53
+ return adapter;
54
+ }
@@ -51,9 +51,9 @@ export function formatCliCommand(subcommand, cliName = PREFERRED_CLI) {
51
51
  return trimmed ? `${cliName} ${trimmed}` : cliName;
52
52
  }
53
53
 
54
- function detectInvocationPackageManager() {
55
- const execPath = process.env.npm_execpath ?? "";
56
- const userAgent = process.env.npm_config_user_agent ?? "";
54
+ function detectInvocationPackageManager(env = process.env) {
55
+ const execPath = env.npm_execpath ?? "";
56
+ const userAgent = env.npm_config_user_agent ?? "";
57
57
 
58
58
  if (execPath.includes("pnpm") || userAgent.startsWith("pnpm/")) return "pnpm";
59
59
  if (execPath.includes("yarn") || userAgent.startsWith("yarn/")) return "yarn";
@@ -61,7 +61,11 @@ function detectInvocationPackageManager() {
61
61
  return "npm";
62
62
  }
63
63
 
64
- export function resolveSuggestedInvocation(packageName = PACKAGE_NAME, argv = process.argv) {
64
+ export function resolveSuggestedInvocation(
65
+ packageName = PACKAGE_NAME,
66
+ argv = process.argv,
67
+ { env = process.env } = {}
68
+ ) {
65
69
  const invokedPath = argv[1] ?? PREFERRED_CLI;
66
70
  const invokedBase = basename(invokedPath);
67
71
 
@@ -69,7 +73,7 @@ export function resolveSuggestedInvocation(packageName = PACKAGE_NAME, argv = pr
69
73
  return invokedBase;
70
74
  }
71
75
 
72
- const packageManager = detectInvocationPackageManager();
76
+ const packageManager = detectInvocationPackageManager(env);
73
77
 
74
78
  switch (packageManager) {
75
79
  case "pnpm":
@@ -85,9 +89,9 @@ export function resolveSuggestedInvocation(packageName = PACKAGE_NAME, argv = pr
85
89
 
86
90
  export function formatSuggestedCliCommand(
87
91
  subcommand,
88
- { packageName = PACKAGE_NAME, argv = process.argv, suggestedInvocation } = {}
92
+ { packageName = PACKAGE_NAME, argv = process.argv, suggestedInvocation, env = process.env } = {}
89
93
  ) {
90
- const invoke = suggestedInvocation ?? resolveSuggestedInvocation(packageName, argv);
94
+ const invoke = suggestedInvocation ?? resolveSuggestedInvocation(packageName, argv, { env });
91
95
  const trimmed = subcommand.trim();
92
96
  return trimmed ? `${invoke} ${trimmed}` : invoke;
93
97
  }