@kal-elsam/kairo-runtime 0.1.4 → 0.2.0

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/README.md CHANGED
@@ -161,6 +161,14 @@ kairo install --agents all
161
161
  kairo install --agents cursor,codex --components orchestrator,sdd-core
162
162
  kairo doctor
163
163
  kairo doctor --json
164
+ kairo orchestrator --json
165
+ kairo intelligence status
166
+ kairo intelligence models
167
+ kairo intelligence context --json
168
+ kairo intelligence route --task "explain architecture"
169
+ kairo intelligence ask --prompt "Summarize project risks" --json
170
+ # Cloud (OpenRouter/free) only after explicit consent + confirm:
171
+ # OPENROUTER_API_KEY=... kairo intelligence ask --prompt "..." --cloud-consent --yes
164
172
  kairo update # technical alias; prefer sync
165
173
  kairo detect
166
174
  kairo components
@@ -180,6 +188,21 @@ kairo uninstall
180
188
  kairo install --scope=workspace # opt-in / legacy
181
189
  ```
182
190
 
191
+ ### Intelligence layer (0.2.0)
192
+
193
+ Kairo owns **Harness Engineering** governance: compile relevant project context, route to a backend, and require human confirmation for cloud transmission. It does not store credentials.
194
+
195
+ | Backend | Detection | Invoke |
196
+ |---|---|---|
197
+ | Ollama | `GET $OLLAMA_HOST/api/tags` (default `http://127.0.0.1:11434`) | Local chat |
198
+ | OpenRouter | `OPENROUTER_API_KEY` in env | `openrouter/free` after `--cloud-consent` + `--yes` |
199
+ | Custom HTTP | Profile `customProviders` (`baseUrl`, `modelId`, optional local-only `apiKeyEnv`) | OpenAI-compatible `/chat/completions` |
200
+
201
+ Routing order: user override → Ollama → OpenRouter free (consent) → diagnostics mode.
202
+
203
+ Private paths (`.env`, secrets, keys) are excluded from context packs unless `--include-private`.
204
+ Remote custom providers require explicit cloud consent and cannot receive an `apiKeyEnv` credential in 0.2.0; use a built-in provider or a local custom endpoint for env-backed authentication.
205
+
183
206
  Legacy CLI aliases (backward compatible): `harness`, `agentic-harness`, `sgs-harness`, `harness-sgs`
184
207
 
185
208
  `kairo help` lists commands and JSON support; longer examples live in this README.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kal-elsam/kairo-runtime",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
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",
package/src/cli.js CHANGED
@@ -30,6 +30,8 @@ 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";
34
+ import { runIntelligenceCli } from "./global/intelligence-cli.js";
33
35
  import {
34
36
  LEGACY_PACKAGE_NAME,
35
37
  PACKAGE_NAME,
@@ -66,6 +68,27 @@ export async function runCli(argv) {
66
68
  const invoke = resolveSuggestedInvocation(packageManifest.name);
67
69
 
68
70
  switch (command) {
71
+ case "shell":
72
+ await runOrchestratorShell({
73
+ packageRoot,
74
+ packageManifest,
75
+ workspaceRoot: optionsWithPolicy.cwd,
76
+ interactive: optionsWithPolicy.interactive
77
+ });
78
+ return;
79
+ case "orchestrator":
80
+ await runOrchestratorDiagnostics({
81
+ homeDir: resolveHomeDir(),
82
+ workspaceRoot: optionsWithPolicy.cwd,
83
+ packageName: packageManifest.name,
84
+ packageRoot,
85
+ cliVersion: packageManifest.version,
86
+ json: optionsWithPolicy.json
87
+ });
88
+ return;
89
+ case "intelligence":
90
+ await runIntelligenceCli(optionsWithPolicy, packageManifest);
91
+ return;
69
92
  case "setup":
70
93
  await runGlobalSetup(optionsWithPolicy, packageManifest, packageRoot);
71
94
  return;
@@ -219,7 +242,37 @@ function resolveImplicitCommand(args) {
219
242
  if (argsWantsWorkspaceScope(args)) {
220
243
  return "init";
221
244
  }
222
- return "setup";
245
+ if (hasImplicitSetupFlags(args)) {
246
+ return "setup";
247
+ }
248
+ return "shell";
249
+ }
250
+
251
+ function hasImplicitSetupFlags(args) {
252
+ const setupFlags = new Set([
253
+ "--dry-run",
254
+ "--yes",
255
+ "-y",
256
+ "--confirm",
257
+ "--simple",
258
+ "--no-preflight",
259
+ "--all-adapters",
260
+ "--no-default-components",
261
+ "--detect"
262
+ ]);
263
+
264
+ for (let index = 0; index < args.length; index += 1) {
265
+ const arg = args[index];
266
+ if (setupFlags.has(arg)) return true;
267
+ if (arg.startsWith("--mode=")) return true;
268
+ if (arg.startsWith("--adapters=") || arg.startsWith("--agents=")) return true;
269
+ if (arg.startsWith("--components=")) return true;
270
+ if (arg === "--mode" || arg === "--adapters" || arg === "--agents" || arg === "--components") {
271
+ return true;
272
+ }
273
+ }
274
+
275
+ return false;
223
276
  }
224
277
 
225
278
  function argsWantsWorkspaceScope(args) {
@@ -275,7 +328,13 @@ export function parseArgs(argv) {
275
328
  simple: false,
276
329
  help: false,
277
330
  version: false,
278
- interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY)
331
+ interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
332
+ intelligenceAction: null,
333
+ intelligenceTask: null,
334
+ intelligencePrompt: null,
335
+ intelligencePaths: [],
336
+ includePrivate: false,
337
+ cloudConsent: false
279
338
  };
280
339
 
281
340
  if (command === "components") {
@@ -290,6 +349,10 @@ export function parseArgs(argv) {
290
349
  parseHistoryAction(args, options);
291
350
  }
292
351
 
352
+ if (command === "intelligence") {
353
+ parseIntelligenceAction(args, options);
354
+ }
355
+
293
356
  for (let index = 0; index < args.length; index += 1) {
294
357
  const arg = args[index];
295
358
 
@@ -351,6 +414,14 @@ export function parseArgs(argv) {
351
414
  else if (arg === "--out") options.outPath = resolve(args[++index]);
352
415
  else if (arg.startsWith("--out=")) options.outPath = resolve(arg.slice("--out=".length));
353
416
  else if (arg === "--simple") options.simple = true;
417
+ else if (arg === "--task") options.intelligenceTask = args[++index];
418
+ else if (arg.startsWith("--task=")) options.intelligenceTask = arg.slice("--task=".length);
419
+ else if (arg === "--prompt") options.intelligencePrompt = args[++index];
420
+ else if (arg.startsWith("--prompt=")) options.intelligencePrompt = arg.slice("--prompt=".length);
421
+ else if (arg === "--paths") options.intelligencePaths = parsePathList(args[++index]);
422
+ else if (arg.startsWith("--paths=")) options.intelligencePaths = parsePathList(arg.slice("--paths=".length));
423
+ else if (arg === "--include-private") options.includePrivate = true;
424
+ else if (arg === "--cloud-consent") options.cloudConsent = true;
354
425
  else if (arg === "--help" || arg === "-h") options.help = true;
355
426
  else if (arg === "--version" || arg === "-v") options.version = true;
356
427
  else throw new Error(`Unknown option "${arg}".`);
@@ -441,6 +512,31 @@ function parseHistoryAction(args, options) {
441
512
  throw new Error(`Unknown history action "${action}". Use last or omit for the full log.`);
442
513
  }
443
514
 
515
+ function parseIntelligenceAction(args, options) {
516
+ const action = args[0];
517
+ if (!action || action.startsWith("-")) {
518
+ options.intelligenceAction = "status";
519
+ return;
520
+ }
521
+
522
+ args.shift();
523
+ const allowed = new Set(["status", "models", "context", "route", "ask"]);
524
+ if (!allowed.has(action)) {
525
+ throw new Error(`Unknown intelligence action "${action}". Use status, models, context, route, or ask.`);
526
+ }
527
+ options.intelligenceAction = action;
528
+ }
529
+
530
+ function parsePathList(value) {
531
+ if (!value) return [];
532
+ return [...new Set(
533
+ value
534
+ .split(",")
535
+ .map((item) => item.trim())
536
+ .filter(Boolean)
537
+ )];
538
+ }
539
+
444
540
  function parsePositiveInt(value, label) {
445
541
  const parsed = Number.parseInt(value, 10);
446
542
  if (!Number.isFinite(parsed) || parsed < 1) {
@@ -461,6 +557,9 @@ function normalizeCommand(command) {
461
557
  if (!command) return "install";
462
558
 
463
559
  if (command === "install" || command === "i") return "install";
560
+ if (command === "shell") return "shell";
561
+ if (command === "orchestrator") return "orchestrator";
562
+ if (command === "intelligence" || command === "intel") return "intelligence";
464
563
  if (command === "setup") return "setup";
465
564
  if (command === "status") return "status";
466
565
  if (command === "sync") return "sync";
@@ -517,8 +616,13 @@ sections, components, backups, and drift repair under ~/.harness.
517
616
  Bootstrap: see README.md (curl install.sh or npx ${PACKAGE_NAME}).
518
617
 
519
618
  Usage:
520
- ${cli} [--dry-run] [--yes] [--confirm] [--agents <list|all>] [--components <list>]
619
+ ${cli} Interactive orchestrator shell (TTY)
620
+ ${cli} --dry-run Setup dry-run (scriptable)
521
621
  ${cli} --version
622
+ ${cli} shell Interactive orchestrator shell (TTY)
623
+ ${cli} orchestrator [--json] Read-only agent capability diagnostics
624
+ ${cli} intelligence [status|models|context|route|ask] [--json]
625
+ ${cli} intelligence ask --prompt "..." [--cloud-consent] [--yes] [--paths a,b]
522
626
  ${cli} setup [--dry-run] [--yes] [--confirm] [--simple] [--no-preflight] [--agents <list|all>] [--components <list>]
523
627
  ${cli} status [--json]
524
628
  ${cli} sync [--dry-run] [--yes] [--confirm] [--json] [--no-preflight]
@@ -552,7 +656,12 @@ Scopes:
552
656
  Explicit --scope=workspace only.
553
657
 
554
658
  Commands:
555
- setup Interactive Ink UI (TTY). Use --simple for Clack prompts.
659
+ shell Interactive Ink orchestrator (TTY). Bare ${cli} opens this in TTY sessions.
660
+ orchestrator Read-only capability registry diagnostics (--json supported).
661
+ intelligence Harness Engineering layer: backends, context packs, routing, budgets.
662
+ Local-first (Ollama). Cloud (OpenRouter/free) only with --cloud-consent.
663
+ Credentials via env only (OPENROUTER_API_KEY, OLLAMA_HOST). Never stored.
664
+ setup Managed ecosystem setup. Interactive Ink UI (TTY). Use --simple for Clack prompts.
556
665
  status Control panel: agents, components, drift, backups, next action.
557
666
  sync Converge managed content (repair drift), then show status.
558
667
  upgrade Preview or apply ecosystem updates (apply requires --yes).
@@ -0,0 +1,230 @@
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
+ import {
6
+ inspectIntelligenceBackends,
7
+ summarizeIntelligenceBackends,
8
+ resolveRoutingDecision
9
+ } from "./intelligence/index.js";
10
+
11
+ export const PLAN_ACTIONS = {
12
+ DIAGNOSE: "diagnose",
13
+ SETUP: "setup",
14
+ SYNC: "sync",
15
+ INSTALL: "install",
16
+ STATUS: "status"
17
+ };
18
+
19
+ export async function buildReadOnlyDiagnostics({
20
+ homeDir,
21
+ workspaceRoot,
22
+ packageName,
23
+ packageRoot,
24
+ cliVersion,
25
+ env = process.env,
26
+ fetchImpl = globalThis.fetch
27
+ }) {
28
+ const [{ profile, sources }, capabilities] = await Promise.all([
29
+ resolveProfile({ homeDir, workspaceRoot }),
30
+ inspectAllCapabilities({ homeDir, workspaceRoot, packageName })
31
+ ]);
32
+
33
+ const intelligence = await inspectIntelligenceBackends({
34
+ env,
35
+ fetchImpl,
36
+ customProviders: profile.customProviders ?? []
37
+ });
38
+
39
+ const detectedIds = capabilities.filter((entry) => entry.detected).map((entry) => entry.id);
40
+ const profileAgents = resolveProfileAgents(profile, detectedIds);
41
+ const diagnostics = summarizeDiagnostics(capabilities);
42
+ const intelligenceSummary = summarizeIntelligenceBackends(intelligence);
43
+ const routingPreview = resolveRoutingDecision({
44
+ backends: intelligence,
45
+ profile,
46
+ // Session consent only — profile.cloudConsent is a preference, never live authorization.
47
+ cloudConsent: false,
48
+ tokenBudget: profile.tokenBudget
49
+ });
50
+
51
+ return {
52
+ readOnly: true,
53
+ cliVersion,
54
+ profile: { ...profile, sources },
55
+ capabilities,
56
+ profileAgents,
57
+ diagnostics,
58
+ intelligence: {
59
+ backends: intelligence,
60
+ summary: intelligenceSummary,
61
+ routingPreview
62
+ },
63
+ recommendations: buildDiagnosticRecommendations({
64
+ capabilities,
65
+ profile,
66
+ diagnostics,
67
+ intelligenceSummary,
68
+ routingPreview
69
+ })
70
+ };
71
+ }
72
+
73
+ export async function buildActionPlan({
74
+ action,
75
+ homeDir,
76
+ workspaceRoot,
77
+ packageName,
78
+ options = {}
79
+ }) {
80
+ const diagnostics = await buildReadOnlyDiagnostics({
81
+ homeDir,
82
+ workspaceRoot,
83
+ packageName,
84
+ packageRoot: options.packageRoot ?? null,
85
+ cliVersion: options.cliVersion ?? null
86
+ });
87
+
88
+ const steps = [];
89
+ const warnings = [];
90
+
91
+ switch (action) {
92
+ case PLAN_ACTIONS.DIAGNOSE:
93
+ case PLAN_ACTIONS.STATUS:
94
+ return {
95
+ action,
96
+ readOnly: true,
97
+ requiresConfirmation: false,
98
+ steps: ["Show ecosystem diagnostics (read-only)."],
99
+ diagnostics,
100
+ warnings
101
+ };
102
+ case PLAN_ACTIONS.SETUP:
103
+ case PLAN_ACTIONS.INSTALL:
104
+ steps.push(`Target agents: ${diagnostics.profileAgents.join(", ")}`);
105
+ steps.push("Preview managed section changes under ~/.harness and agent config files.");
106
+ steps.push("Create backups before writing managed content.");
107
+ if (options.dryRun) {
108
+ steps.push("Dry run: no files will be written.");
109
+ }
110
+ return {
111
+ action,
112
+ readOnly: Boolean(options.dryRun),
113
+ requiresConfirmation: !options.dryRun,
114
+ steps,
115
+ diagnostics,
116
+ warnings: collectCapabilityWarnings(diagnostics.capabilities)
117
+ };
118
+ case PLAN_ACTIONS.SYNC:
119
+ steps.push("Compare managed content against bundled assets.");
120
+ steps.push("Repair drift in agent config files when differences are found.");
121
+ if (options.dryRun) {
122
+ steps.push("Dry run: show planned repairs only.");
123
+ }
124
+ return {
125
+ action,
126
+ readOnly: Boolean(options.dryRun),
127
+ requiresConfirmation: !options.dryRun,
128
+ steps,
129
+ diagnostics,
130
+ warnings: collectCapabilityWarnings(diagnostics.capabilities)
131
+ };
132
+ default: {
133
+ const _exhaustive = action;
134
+ throw new Error(`Unknown plan action "${_exhaustive}".`);
135
+ }
136
+ }
137
+ }
138
+
139
+ export function shouldExecutePlan(plan, { confirmed = false } = {}) {
140
+ if (plan.readOnly) return true;
141
+ return confirmed;
142
+ }
143
+
144
+ export function formatActionPlan(plan) {
145
+ const lines = [];
146
+ lines.push(`Action: ${plan.action}`);
147
+ lines.push(`Mode: ${plan.readOnly ? "read-only" : "write"}`);
148
+ lines.push(`Confirmation required: ${plan.requiresConfirmation ? "yes" : "no"}`);
149
+ lines.push("");
150
+ lines.push("Steps:");
151
+ for (const step of plan.steps) {
152
+ lines.push(` - ${step}`);
153
+ }
154
+
155
+ if (plan.warnings.length > 0) {
156
+ lines.push("");
157
+ lines.push("Warnings:");
158
+ for (const warning of plan.warnings) {
159
+ lines.push(` - ${warning}`);
160
+ }
161
+ }
162
+
163
+ if (plan.diagnostics.recommendations.length > 0) {
164
+ lines.push("");
165
+ lines.push("Recommendations:");
166
+ for (const recommendation of plan.diagnostics.recommendations) {
167
+ lines.push(` - ${recommendation}`);
168
+ }
169
+ }
170
+
171
+ return lines.join("\n");
172
+ }
173
+
174
+ function summarizeDiagnostics(capabilities) {
175
+ return {
176
+ detected: capabilities.filter((entry) => entry.detected).length,
177
+ available: capabilities.filter((entry) => entry.state === CAPABILITY_STATES.AVAILABLE).length,
178
+ unknown: capabilities.filter((entry) => entry.state === CAPABILITY_STATES.UNKNOWN).length,
179
+ errors: capabilities.filter((entry) => entry.state === CAPABILITY_STATES.ERROR).length
180
+ };
181
+ }
182
+
183
+ function buildDiagnosticRecommendations({
184
+ capabilities,
185
+ profile,
186
+ diagnostics,
187
+ intelligenceSummary,
188
+ routingPreview
189
+ }) {
190
+ const recommendations = [];
191
+
192
+ if (diagnostics.detected === 0) {
193
+ recommendations.push(`No agents detected. Run ${formatCliCommand("detect")} or install a supported agent CLI.`);
194
+ }
195
+
196
+ if (profile.coordinator) {
197
+ const coordinator = capabilities.find((entry) => entry.id === profile.coordinator);
198
+ if (!coordinator?.detected && coordinator?.state === CAPABILITY_STATES.UNKNOWN) {
199
+ recommendations.push(`Profile coordinator "${profile.coordinator}" is not detected on this machine.`);
200
+ }
201
+ }
202
+
203
+ for (const capability of capabilities) {
204
+ if (capability.recommendation) {
205
+ recommendations.push(`${capability.label}: ${capability.recommendation}`);
206
+ }
207
+ }
208
+
209
+ if (!intelligenceSummary?.localAvailable && !intelligenceSummary?.cloudAuthenticated) {
210
+ recommendations.push(
211
+ "No intelligence backend available. Start Ollama or set OPENROUTER_API_KEY (env only). Diagnostics mode remains available."
212
+ );
213
+ } else if (!intelligenceSummary?.localAvailable && intelligenceSummary?.cloudAuthenticated) {
214
+ recommendations.push(
215
+ `OpenRouter is authenticated. Cloud invoke still requires session flags: ${formatCliCommand("intelligence ask --cloud-consent --yes")}.`
216
+ );
217
+ }
218
+
219
+ if (routingPreview && !routingPreview.canInvoke) {
220
+ recommendations.push(`Routing: ${routingPreview.reason}`);
221
+ }
222
+
223
+ return [...new Set(recommendations)];
224
+ }
225
+
226
+ function collectCapabilityWarnings(capabilities) {
227
+ return capabilities
228
+ .filter((entry) => entry.state === CAPABILITY_STATES.ERROR || entry.state === CAPABILITY_STATES.UNKNOWN)
229
+ .map((entry) => `${entry.label} is ${entry.state}${entry.error ? ` (${entry.error})` : ""}.`);
230
+ }
@@ -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
+ }