@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 +1 -1
- package/scripts/install.sh +2 -2
- package/scripts/lib/install-script-url.mjs +30 -1
- package/src/cli.js +59 -3
- package/src/global/action-planner.js +178 -0
- package/src/global/agent-capabilities/create-capability-adapter.js +224 -0
- package/src/global/agent-capabilities/index.js +54 -0
- package/src/global/brand/cli.js +11 -7
- package/src/global/capability-registry.js +80 -0
- package/src/global/capability-states.js +30 -0
- package/src/global/cli-probe.js +47 -0
- package/src/global/ink/orchestrator-app.js +209 -0
- package/src/global/ink/orchestrator-state.js +78 -0
- package/src/global/ink/run-orchestrator-ink.js +29 -0
- package/src/global/orchestrator.js +139 -0
- package/src/global/paths.js +1 -0
- package/src/global/profile.js +146 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kal-elsam/kairo-runtime",
|
|
3
|
-
"version": "0.1.
|
|
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",
|
package/scripts/install.sh
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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}
|
|
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
|
-
|
|
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
|
+
}
|
package/src/global/brand/cli.js
CHANGED
|
@@ -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 =
|
|
56
|
-
const userAgent =
|
|
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(
|
|
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
|
}
|