@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.
@@ -0,0 +1,80 @@
1
+ import { buildAdapterContext } from "./adapter-context.js";
2
+ import { CAPABILITY_STATES } from "./capability-states.js";
3
+ import {
4
+ AGENT_CAPABILITY_IDS,
5
+ listCapabilityAdapters,
6
+ resolveCapabilityAdapter
7
+ } from "./agent-capabilities/index.js";
8
+
9
+ export async function inspectAllCapabilities({
10
+ homeDir,
11
+ workspaceRoot = process.cwd(),
12
+ packageName = "@kal-elsam/kairo-runtime",
13
+ probeOverrides = null
14
+ } = {}) {
15
+ const context = buildAdapterContext({ homeDir, workspaceRoot, packageName });
16
+ const adapters = listCapabilityAdapters();
17
+
18
+ return Promise.all(adapters.map(async (adapter) => {
19
+ const probeOptions = probeOverrides?.[adapter.id] ?? {};
20
+ const inspection = adapter.inspect(context, probeOptions);
21
+ const models = adapter.listModels?.(context, probeOptions) ?? null;
22
+
23
+ return {
24
+ ...inspection,
25
+ models
26
+ };
27
+ }));
28
+ }
29
+
30
+ export async function inspectCapability(agentId, context, probeOptions = {}) {
31
+ const adapter = resolveCapabilityAdapter(agentId);
32
+ const inspection = adapter.inspect(context, probeOptions);
33
+ const models = adapter.listModels?.(context, probeOptions) ?? null;
34
+ return { ...inspection, models };
35
+ }
36
+
37
+ export function summarizeCapabilityRegistry(capabilities) {
38
+ const byState = {};
39
+ for (const state of Object.values(CAPABILITY_STATES)) {
40
+ byState[state] = 0;
41
+ }
42
+
43
+ for (const capability of capabilities) {
44
+ if (byState[capability.state] != null) {
45
+ byState[capability.state] += 1;
46
+ }
47
+ }
48
+
49
+ return {
50
+ total: capabilities.length,
51
+ supported: AGENT_CAPABILITY_IDS.length,
52
+ detected: capabilities.filter((entry) => entry.detected).length,
53
+ available: capabilities.filter((entry) => entry.state === CAPABILITY_STATES.AVAILABLE).length,
54
+ byState
55
+ };
56
+ }
57
+
58
+ export function buildCapabilityDiagnostics(capabilities) {
59
+ const recommendations = capabilities
60
+ .map((entry) => entry.recommendation)
61
+ .filter(Boolean);
62
+
63
+ const errors = capabilities
64
+ .filter((entry) => entry.state === CAPABILITY_STATES.ERROR)
65
+ .map((entry) => ({
66
+ agent: entry.id,
67
+ message: entry.error ?? entry.recommendation ?? "Inspection failed."
68
+ }));
69
+
70
+ return {
71
+ recommendations,
72
+ errors,
73
+ hasActionableErrors: errors.length > 0
74
+ };
75
+ }
76
+
77
+ export function delegateToAgent(agentId, context, runOptions = {}) {
78
+ const adapter = resolveCapabilityAdapter(agentId);
79
+ return adapter.run(context, runOptions);
80
+ }
@@ -0,0 +1,30 @@
1
+ export const CAPABILITY_STATES = {
2
+ DETECTED: "detected",
3
+ AUTHENTICATED: "authenticated",
4
+ AVAILABLE: "available",
5
+ UNKNOWN: "unknown",
6
+ ERROR: "error"
7
+ };
8
+
9
+ export function isCapabilityState(value) {
10
+ return Object.values(CAPABILITY_STATES).includes(value);
11
+ }
12
+
13
+ export function formatCapabilityState(state) {
14
+ switch (state) {
15
+ case CAPABILITY_STATES.DETECTED:
16
+ return "detected";
17
+ case CAPABILITY_STATES.AUTHENTICATED:
18
+ return "authenticated";
19
+ case CAPABILITY_STATES.AVAILABLE:
20
+ return "available";
21
+ case CAPABILITY_STATES.UNKNOWN:
22
+ return "unknown";
23
+ case CAPABILITY_STATES.ERROR:
24
+ return "error";
25
+ default: {
26
+ const _exhaustive = state;
27
+ return String(_exhaustive);
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,47 @@
1
+ import { spawnSync } from "node:child_process";
2
+
3
+ const DEFAULT_TIMEOUT_MS = 5000;
4
+
5
+ export function isExecutableAvailable(command, { env = process.env } = {}) {
6
+ const result = spawnSync("which", [command], { encoding: "utf8", env });
7
+ return result.status === 0 && result.stdout.trim().length > 0;
8
+ }
9
+
10
+ export function probeCommand(command, args = [], {
11
+ cwd = process.cwd(),
12
+ env = process.env,
13
+ timeoutMs = DEFAULT_TIMEOUT_MS
14
+ } = {}) {
15
+ const result = spawnSync(command, args, {
16
+ cwd,
17
+ env,
18
+ encoding: "utf8",
19
+ timeout: timeoutMs
20
+ });
21
+
22
+ return {
23
+ ok: result.status === 0,
24
+ status: result.status,
25
+ stdout: (result.stdout ?? "").trim(),
26
+ stderr: (result.stderr ?? "").trim(),
27
+ error: result.error?.message ?? null,
28
+ timedOut: result.error?.code === "ETIMEDOUT"
29
+ };
30
+ }
31
+
32
+ export function parseVersionFromOutput(output) {
33
+ if (!output) return null;
34
+
35
+ const match = output.match(/(\d+\.\d+(?:\.\d+)?(?:[-+][\w.]+)?)/);
36
+ return match?.[1] ?? null;
37
+ }
38
+
39
+ export function resolveProbeState({ detected, cliAvailable, authReady, probeError, opaque = false }) {
40
+ if (probeError) return "error";
41
+ if (opaque) return "unknown";
42
+ if (!detected && !cliAvailable) return "unknown";
43
+ if (cliAvailable && authReady) return "available";
44
+ if (authReady) return "authenticated";
45
+ if (detected || cliAvailable) return "detected";
46
+ return "unknown";
47
+ }
@@ -0,0 +1,209 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { Box, Text, useApp, useInput } from "ink";
3
+ import { BRAND } from "../brand/index.js";
4
+ import { PLAN_ACTIONS, buildActionPlan, buildReadOnlyDiagnostics } from "../action-planner.js";
5
+ import { buildProfileJson, resolveProfile } from "../profile.js";
6
+ import {
7
+ ORCHESTRATOR_MENU,
8
+ ORCHESTRATOR_VIEWS,
9
+ formatAgentStatusLines,
10
+ formatPlanLines,
11
+ formatProfileLines
12
+ } from "./orchestrator-state.js";
13
+
14
+ const COLORS = {
15
+ accent: "cyan",
16
+ success: "green",
17
+ warning: "yellow",
18
+ danger: "red",
19
+ muted: "gray"
20
+ };
21
+
22
+ export function OrchestratorApp({
23
+ homeDir,
24
+ workspaceRoot,
25
+ packageName,
26
+ packageRoot,
27
+ cliVersion,
28
+ onComplete
29
+ }) {
30
+ const { exit } = useApp();
31
+ const [view, setView] = useState(ORCHESTRATOR_VIEWS.HOME);
32
+ const [menuIndex, setMenuIndex] = useState(0);
33
+ const [loading, setLoading] = useState(true);
34
+ const [error, setError] = useState(null);
35
+ const [diagnostics, setDiagnostics] = useState(null);
36
+ const [profileJson, setProfileJson] = useState(null);
37
+ const [plan, setPlan] = useState(null);
38
+
39
+ useEffect(() => {
40
+ let cancelled = false;
41
+
42
+ async function load() {
43
+ try {
44
+ const [diag, profileResolved] = await Promise.all([
45
+ buildReadOnlyDiagnostics({ homeDir, workspaceRoot, packageName, packageRoot, cliVersion }),
46
+ resolveProfile({ homeDir, workspaceRoot })
47
+ ]);
48
+
49
+ if (cancelled) return;
50
+ setDiagnostics(diag);
51
+ setProfileJson(buildProfileJson(profileResolved));
52
+ setLoading(false);
53
+ } catch (loadError) {
54
+ if (cancelled) return;
55
+ setError(loadError instanceof Error ? loadError.message : String(loadError));
56
+ setLoading(false);
57
+ }
58
+ }
59
+
60
+ load();
61
+ return () => {
62
+ cancelled = true;
63
+ };
64
+ }, [homeDir, workspaceRoot, packageName, packageRoot, cliVersion]);
65
+
66
+ const finish = (outcome) => {
67
+ onComplete(outcome);
68
+ exit();
69
+ };
70
+
71
+ useInput((inputKey, key) => {
72
+ if (key.escape) {
73
+ if (view === ORCHESTRATOR_VIEWS.HOME) {
74
+ finish({ cancelled: true });
75
+ return;
76
+ }
77
+ setView(ORCHESTRATOR_VIEWS.HOME);
78
+ setPlan(null);
79
+ return;
80
+ }
81
+
82
+ if (loading || error) return;
83
+
84
+ if (view === ORCHESTRATOR_VIEWS.CONFIRM) {
85
+ if (inputKey.toLowerCase() === "y") {
86
+ finish({ cancelled: false, action: plan?.action ?? PLAN_ACTIONS.SETUP, confirmed: true, plan });
87
+ }
88
+ if (inputKey.toLowerCase() === "n") {
89
+ setView(ORCHESTRATOR_VIEWS.HOME);
90
+ setPlan(null);
91
+ }
92
+ return;
93
+ }
94
+
95
+ if (view !== ORCHESTRATOR_VIEWS.HOME) return;
96
+
97
+ if (key.upArrow) {
98
+ setMenuIndex((index) => Math.max(0, index - 1));
99
+ return;
100
+ }
101
+
102
+ if (key.downArrow) {
103
+ setMenuIndex((index) => Math.min(ORCHESTRATOR_MENU.length - 1, index + 1));
104
+ return;
105
+ }
106
+
107
+ if (!key.return) return;
108
+
109
+ const item = ORCHESTRATOR_MENU[menuIndex];
110
+ if (item.action === "setup") {
111
+ buildActionPlan({
112
+ action: PLAN_ACTIONS.SETUP,
113
+ homeDir,
114
+ workspaceRoot,
115
+ packageName,
116
+ options: { packageRoot, cliVersion }
117
+ }).then((builtPlan) => {
118
+ setPlan(builtPlan);
119
+ setView(ORCHESTRATOR_VIEWS.CONFIRM);
120
+ }).catch((planError) => {
121
+ setError(planError instanceof Error ? planError.message : String(planError));
122
+ });
123
+ return;
124
+ }
125
+
126
+ setView(item.view);
127
+ });
128
+
129
+ if (loading) {
130
+ return React.createElement(Box, { flexDirection: "column" },
131
+ React.createElement(Text, { bold: true, color: COLORS.accent }, BRAND.displayName),
132
+ React.createElement(Text, { color: COLORS.muted }, "Loading agent capabilities…")
133
+ );
134
+ }
135
+
136
+ if (error) {
137
+ return React.createElement(Box, { flexDirection: "column" },
138
+ React.createElement(Text, { bold: true, color: COLORS.danger }, "Orchestrator error"),
139
+ React.createElement(Text, null, error),
140
+ React.createElement(Text, { dimColor: true }, "Esc to exit")
141
+ );
142
+ }
143
+
144
+ return React.createElement(Box, { flexDirection: "column" },
145
+ React.createElement(Text, { bold: true, color: COLORS.accent }, `${BRAND.displayName} orchestrator`),
146
+ React.createElement(Text, { color: COLORS.muted }, "Provider-neutral agent coordinator · no model imposed"),
147
+ React.createElement(Text, null, ""),
148
+ renderView({ view, diagnostics, profileJson, plan, menuIndex }),
149
+ React.createElement(Text, null, ""),
150
+ React.createElement(Text, { dimColor: true }, footerHint(view))
151
+ );
152
+ }
153
+
154
+ function renderView({ view, diagnostics, profileJson, plan, menuIndex }) {
155
+ switch (view) {
156
+ case ORCHESTRATOR_VIEWS.HOME:
157
+ return React.createElement(Box, { flexDirection: "column" },
158
+ React.createElement(Text, { bold: true }, "Menu"),
159
+ ORCHESTRATOR_MENU.map((item, index) =>
160
+ React.createElement(Text, {
161
+ key: item.id,
162
+ color: index === menuIndex ? COLORS.accent : undefined,
163
+ bold: index === menuIndex
164
+ }, `${index === menuIndex ? "› " : " "}${item.label}`)
165
+ ),
166
+ React.createElement(Text, null, ""),
167
+ React.createElement(Text, { bold: true }, "Snapshot"),
168
+ React.createElement(Text, null, `Agents detected: ${diagnostics.diagnostics.detected}/${diagnostics.capabilities.length}`),
169
+ React.createElement(Text, null, `Available: ${diagnostics.diagnostics.available}`)
170
+ );
171
+ case ORCHESTRATOR_VIEWS.AGENTS:
172
+ return React.createElement(Box, { flexDirection: "column" },
173
+ React.createElement(Text, { bold: true }, "Agent capabilities"),
174
+ formatAgentStatusLines(diagnostics.capabilities)
175
+ .map((line) => React.createElement(Text, { key: line }, line))
176
+ );
177
+ case ORCHESTRATOR_VIEWS.PROFILE:
178
+ return React.createElement(Box, { flexDirection: "column" },
179
+ React.createElement(Text, { bold: true }, "Profile"),
180
+ formatProfileLines(profileJson)
181
+ .map((line) => React.createElement(Text, { key: line }, line))
182
+ );
183
+ case ORCHESTRATOR_VIEWS.PLAN:
184
+ case ORCHESTRATOR_VIEWS.CONFIRM:
185
+ return React.createElement(Box, { flexDirection: "column" },
186
+ React.createElement(Text, { bold: true }, view === ORCHESTRATOR_VIEWS.CONFIRM ? "Confirm plan" : "Plan"),
187
+ plan && formatPlanLines(plan).map((line) => React.createElement(Text, { key: line }, line)),
188
+ view === ORCHESTRATOR_VIEWS.CONFIRM && React.createElement(Text, { color: COLORS.warning }, "Y confirm · N decline")
189
+ );
190
+ case ORCHESTRATOR_VIEWS.HELP:
191
+ return React.createElement(Box, { flexDirection: "column" },
192
+ React.createElement(Text, { bold: true }, "Help"),
193
+ React.createElement(Text, null, "Kairo coordinates installed agent CLIs — it is not a coding model."),
194
+ React.createElement(Text, null, "Use explicit commands for scripts: setup, install, status, doctor, sync."),
195
+ React.createElement(Text, null, "Profiles: ~/.harness/profile.json and .harness/kairo.json (project wins)."),
196
+ React.createElement(Text, null, "Credentials are never stored by Kairo.")
197
+ );
198
+ default: {
199
+ const _exhaustive = view;
200
+ return React.createElement(Text, null, `Unknown view: ${_exhaustive}`);
201
+ }
202
+ }
203
+ }
204
+
205
+ function footerHint(view) {
206
+ if (view === ORCHESTRATOR_VIEWS.HOME) return "↑↓ navigate · Enter select · Esc quit";
207
+ if (view === ORCHESTRATOR_VIEWS.CONFIRM) return "Y confirm · N decline · Esc back";
208
+ return "Esc back to menu";
209
+ }
@@ -0,0 +1,78 @@
1
+ import { stdin as input, stdout as output } from "node:process";
2
+ import { canUseSetupInk } from "./terminal.js";
3
+
4
+ export function canUseOrchestratorShell({
5
+ interactive = Boolean(input.isTTY && output.isTTY),
6
+ term = process.env.TERM ?? "",
7
+ columns = output.columns ?? 80,
8
+ forceInk = process.env.HARNESS_INK !== "0"
9
+ } = {}) {
10
+ return canUseSetupInk({ interactive, term, columns, forceInk });
11
+ }
12
+
13
+ export const ORCHESTRATOR_VIEWS = {
14
+ HOME: "home",
15
+ AGENTS: "agents",
16
+ PROFILE: "profile",
17
+ PLAN: "plan",
18
+ CONFIRM: "confirm",
19
+ HELP: "help"
20
+ };
21
+
22
+ export const ORCHESTRATOR_MENU = [
23
+ { id: "status", label: "Diagnostics", view: ORCHESTRATOR_VIEWS.HOME },
24
+ { id: "agents", label: "Agents", view: ORCHESTRATOR_VIEWS.AGENTS },
25
+ { id: "profile", label: "Profile", view: ORCHESTRATOR_VIEWS.PROFILE },
26
+ { id: "plan-setup", label: "Plan setup", view: ORCHESTRATOR_VIEWS.PLAN, action: "setup" },
27
+ { id: "help", label: "Help", view: ORCHESTRATOR_VIEWS.HELP }
28
+ ];
29
+
30
+ export function formatAgentStatusLines(capabilities) {
31
+ return capabilities.map((entry) => {
32
+ const auth = entry.authenticated == null ? "n/a" : (entry.authenticated ? "yes" : "no");
33
+ const version = entry.version ?? "unknown";
34
+ return `${entry.label.padEnd(14)} ${entry.state.padEnd(14)} v${version} auth=${auth}`;
35
+ });
36
+ }
37
+
38
+ export function formatProfileLines(profileJson) {
39
+ const lines = [
40
+ `Coordinator: ${profileJson.coordinator ?? "none"}`,
41
+ `Default agents: ${formatAgentsLabel(profileJson.defaultAgents)}`,
42
+ `Apply mode: ${profileJson.applyMode}`
43
+ ];
44
+
45
+ if (profileJson.sources.global) {
46
+ lines.push(`Global: ${profileJson.sources.global}`);
47
+ }
48
+
49
+ if (profileJson.sources.project) {
50
+ lines.push(`Project: ${profileJson.sources.project}`);
51
+ }
52
+
53
+ lines.push(`Precedence: ${profileJson.sources.precedence}`);
54
+ return lines;
55
+ }
56
+
57
+ export function formatPlanLines(plan) {
58
+ const lines = [`Action: ${plan.action}`, ""];
59
+ for (const step of plan.steps) {
60
+ lines.push(` • ${step}`);
61
+ }
62
+
63
+ if (plan.warnings.length > 0) {
64
+ lines.push("", "Warnings:");
65
+ for (const warning of plan.warnings) {
66
+ lines.push(` ! ${warning}`);
67
+ }
68
+ }
69
+
70
+ return lines;
71
+ }
72
+
73
+ function formatAgentsLabel(agents) {
74
+ if (agents === "detected") return "detected";
75
+ if (agents === "all") return "all";
76
+ if (Array.isArray(agents)) return agents.join(", ");
77
+ return String(agents);
78
+ }
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+ import { render } from "ink";
3
+ import { OrchestratorApp } from "./orchestrator-app.js";
4
+
5
+ export async function runOrchestratorInk({
6
+ homeDir,
7
+ workspaceRoot,
8
+ packageRoot,
9
+ packageName,
10
+ cliVersion,
11
+ renderImpl = render
12
+ }) {
13
+ return new Promise((resolve) => {
14
+ const { waitUntilExit } = renderImpl(
15
+ React.createElement(OrchestratorApp, {
16
+ homeDir,
17
+ workspaceRoot,
18
+ packageRoot,
19
+ packageName,
20
+ cliVersion,
21
+ onComplete: resolve
22
+ })
23
+ );
24
+
25
+ waitUntilExit().catch((error) => {
26
+ resolve({ cancelled: true, error });
27
+ });
28
+ });
29
+ }
@@ -0,0 +1,139 @@
1
+ import { stdin as input, stdout as output } from "node:process";
2
+ import { resolveHomeDir } from "./paths.js";
3
+ import { runGlobalSetup } from "./global-cli.js";
4
+ import { PLAN_ACTIONS, buildReadOnlyDiagnostics, shouldExecutePlan } from "./action-planner.js";
5
+ import { canUseOrchestratorShell } from "./ink/orchestrator-state.js";
6
+ import { runOrchestratorInk as defaultRunOrchestratorInk } from "./ink/run-orchestrator-ink.js";
7
+ import { formatCliCommand } from "./brand/cli.js";
8
+ import { BRAND } from "./brand/index.js";
9
+
10
+ export { canUseOrchestratorShell };
11
+
12
+ export function shouldOpenOrchestratorShell({
13
+ interactive = Boolean(input.isTTY && output.isTTY),
14
+ json = false,
15
+ hasImplicitFlags = false
16
+ } = {}) {
17
+ if (!interactive || json) return false;
18
+ if (hasImplicitFlags) return false;
19
+ return canUseOrchestratorShell({ interactive });
20
+ }
21
+
22
+ export async function runOrchestratorShell({
23
+ packageRoot,
24
+ packageManifest,
25
+ workspaceRoot,
26
+ interactive = Boolean(input.isTTY && output.isTTY),
27
+ runOrchestratorInkImpl = defaultRunOrchestratorInk
28
+ }) {
29
+ if (!interactive) {
30
+ throw new Error(
31
+ `Non-interactive shell requires an explicit command. Try ${formatCliCommand("help")} or ${formatCliCommand("setup --yes")}.`
32
+ );
33
+ }
34
+
35
+ if (!canUseOrchestratorShell({ interactive })) {
36
+ throw new Error(
37
+ `Interactive shell requires a capable TTY. Use ${formatCliCommand("setup --simple")} or explicit commands.`
38
+ );
39
+ }
40
+
41
+ const homeDir = resolveHomeDir();
42
+ const outcome = await runOrchestratorInkImpl({
43
+ homeDir,
44
+ workspaceRoot,
45
+ packageRoot,
46
+ packageName: packageManifest.name,
47
+ cliVersion: packageManifest.version
48
+ });
49
+
50
+ if (outcome.error) {
51
+ throw outcome.error;
52
+ }
53
+
54
+ if (outcome.cancelled) {
55
+ return { cancelled: true, wrote: false };
56
+ }
57
+
58
+ if (outcome.confirmed && outcome.action === PLAN_ACTIONS.SETUP) {
59
+ const setupOutcome = await runGlobalSetup(
60
+ {
61
+ cwd: workspaceRoot,
62
+ adapters: null,
63
+ components: null,
64
+ noDefaultComponents: false,
65
+ dryRun: false,
66
+ yes: false,
67
+ confirm: false,
68
+ preflight: true,
69
+ preflightExplicit: false,
70
+ yesExplicit: false,
71
+ confirmExplicit: false,
72
+ json: false,
73
+ interactive: true,
74
+ simple: false
75
+ },
76
+ packageManifest,
77
+ packageRoot
78
+ );
79
+
80
+ return {
81
+ cancelled: Boolean(setupOutcome.cancelled),
82
+ wrote: !setupOutcome.cancelled && !setupOutcome.result?.dryRun,
83
+ action: PLAN_ACTIONS.SETUP,
84
+ setupOutcome
85
+ };
86
+ }
87
+
88
+ return { cancelled: false, wrote: false, action: outcome.action ?? null };
89
+ }
90
+
91
+ export async function runOrchestratorDiagnostics({
92
+ homeDir,
93
+ workspaceRoot,
94
+ packageName,
95
+ packageRoot,
96
+ cliVersion,
97
+ json = false
98
+ }) {
99
+ const diagnostics = await buildReadOnlyDiagnostics({
100
+ homeDir,
101
+ workspaceRoot,
102
+ packageName,
103
+ packageRoot,
104
+ cliVersion
105
+ });
106
+
107
+ if (json) {
108
+ return diagnostics;
109
+ }
110
+
111
+ console.log(commandHeader("orchestrator — agent capability diagnostics"));
112
+ console.log(`Home: ${homeDir}`);
113
+ console.log(`Workspace: ${workspaceRoot}`);
114
+ console.log("");
115
+
116
+ for (const capability of diagnostics.capabilities) {
117
+ console.log(
118
+ ` ${capability.label.padEnd(14)} ${capability.state.padEnd(14)} detected=${capability.detected ? "yes" : "no"}`
119
+ );
120
+ }
121
+
122
+ console.log("");
123
+ console.log("Recommendations:");
124
+ for (const recommendation of diagnostics.recommendations) {
125
+ console.log(` - ${recommendation}`);
126
+ }
127
+
128
+ return diagnostics;
129
+ }
130
+
131
+ function commandHeader(title) {
132
+ return `${BRAND.displayName} ${title}`;
133
+ }
134
+
135
+ export function assertPlanExecution(plan, { confirmed = false } = {}) {
136
+ if (!shouldExecutePlan(plan, { confirmed })) {
137
+ throw new Error("Plan declined. No writes or installations were performed.");
138
+ }
139
+ }
@@ -15,6 +15,7 @@ export function harnessHomePaths(homeDir) {
15
15
  root,
16
16
  statePath: join(root, "state.json"),
17
17
  policyPath: join(root, "policy.json"),
18
+ profilePath: join(root, "profile.json"),
18
19
  historyPath: join(root, "history.jsonl"),
19
20
  coreDir: join(root, "core"),
20
21
  backupsDir: join(root, "backups")