@join-pantheon/cli 0.26.4 → 0.27.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 +54 -1
- package/dist/agents.js +83 -5
- package/dist/cli.js +105 -14
- package/dist/doctor.js +21 -6
- package/dist/history-view.js +2 -1
- package/dist/hooks-install.js +46 -1
- package/dist/install.js +29 -4
- package/dist/kiro-files.js +56 -0
- package/dist/ledger.js +3 -0
- package/dist/machine.js +5 -1
- package/dist/mcp.js +1 -0
- package/dist/measure.js +31 -3
- package/dist/notice.js +3 -1
- package/dist/notify.js +15 -3
- package/dist/persona.js +1 -1
- package/dist/profile.js +4 -1
- package/dist/relay.js +8 -3
- package/dist/run-failure.js +1 -0
- package/dist/setup-guide.js +1 -1
- package/dist/tray.js +3 -3
- package/dist/workdir.js +30 -0
- package/dist/worker.js +4 -1
- package/helper/macos/menubar.swift +286 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -404,7 +404,7 @@ with no human; the cap and lock governors under two workers per member.
|
|
|
404
404
|
|
|
405
405
|
### Which agent answers
|
|
406
406
|
|
|
407
|
-
The worker runs whichever agent CLI this machine has — `--agent claude-code|codex|cursor|copilot|auto`.
|
|
407
|
+
The worker runs whichever agent CLI this machine has — `--agent claude-code|codex|cursor|copilot|kiro|auto`.
|
|
408
408
|
With `auto` (the default) the order is: the agent a persona pins, then the one you chose at setup,
|
|
409
409
|
then PATH order — and a candidate that is not signed in is skipped and named in the run log, so a
|
|
410
410
|
run never fails on an expired session when another agent could have answered. If nothing is
|
|
@@ -418,6 +418,7 @@ actually works, with the same "no shell unless you said so" rule spelled in its
|
|
|
418
418
|
| codex | `--sandbox read-only` (codex has no edits-without-shell mode) | `--sandbox workspace-write` |
|
|
419
419
|
| cursor | proposes edits, never applies (`-p` without `--force`) | `--force` |
|
|
420
420
|
| copilot | `--allow-tool=pantheon-worker --allow-tool=write` (edits without shell, Copilot's `write` kind) | `--allow-all-tools` |
|
|
421
|
+
| kiro | an agent whose tools are pantheon's plus `read`, `write`, `grep`, `glob` — all pre-approved | the same, plus `shell` |
|
|
421
422
|
|
|
422
423
|
Claude Code, Cursor and Codex are verified with **real live woken runs** — an idle machine answering a
|
|
423
424
|
peer's ASK end to end. Codex quirks the tooling handles for
|
|
@@ -458,6 +459,58 @@ named as that, with `copilot login` as the fix.
|
|
|
458
459
|
also restrict MCP servers to an approved registry; the server is called `pantheon` and its only job
|
|
459
460
|
is moving messages between your teammates' agents — nothing here reads code or runs commands.
|
|
460
461
|
|
|
462
|
+
### Kiro
|
|
463
|
+
|
|
464
|
+
Kiro is one backend covering the **Kiro CLI** (`kiro-cli`) and the **Kiro IDE**, both of which read
|
|
465
|
+
`~/.kiro/settings/mcp.json`; `pantheon install --user` writes pantheon there. Everything below about
|
|
466
|
+
the CLI was checked live against kiro-cli 2.23.1.
|
|
467
|
+
|
|
468
|
+
**Sessions report presence on their own.** The entry names its agent (`PANTHEON_AGENT=kiro`), so a Kiro
|
|
469
|
+
session is labelled Kiro to your teammates and reports busy and idle through its own MCP server. Without
|
|
470
|
+
that label it would take the machine's main agent as its own, and on a Claude Code machine report nothing.
|
|
471
|
+
There is deliberately no repo-level Kiro file: Kiro uses a workspace-level server of the same name whole,
|
|
472
|
+
so a committed copy would replace each teammate's own entry.
|
|
473
|
+
|
|
474
|
+
**Approval.** The entry's `autoApprove` list carries your approval choice, and the Kiro IDE honours it.
|
|
475
|
+
The Kiro CLI ignores it — its default agent asks before each Pantheon call, and has no setting that
|
|
476
|
+
changes that.
|
|
477
|
+
|
|
478
|
+
**The `pantheon` agent, for the CLI.** In kiro-cli the only hooks that fire are the ones inside an agent
|
|
479
|
+
file; neither of its engines reads the standalone `.kiro/hooks/` files, and the built-in default agent
|
|
480
|
+
cannot carry any. So `pantheon hook install` adds an agent called `pantheon`: Kiro's default — the same
|
|
481
|
+
tools (`tools: ["*"]`) and your own MCP servers (`includeMcpJson: true`) — plus Pantheon's tools
|
|
482
|
+
pre-approved per your choice, a `userPromptSubmit` hook that reports a turn as started and prints
|
|
483
|
+
nothing, and a `stop` hook that holds the turn open while a teammate waits. It is opt-in and nothing
|
|
484
|
+
makes it your default:
|
|
485
|
+
|
|
486
|
+
```
|
|
487
|
+
kiro-cli chat --agent pantheon # this session
|
|
488
|
+
kiro-cli agent set-default pantheon # every session
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Checked live: with a teammate's question waiting and a prompt that said only "reply HELLO", the agent
|
|
492
|
+
replied, the stop hook held the turn, and it read, claimed and answered the question through Pantheon
|
|
493
|
+
without one approval prompt. An agent called `pantheon` that you wrote yourself is never overwritten.
|
|
494
|
+
|
|
495
|
+
**Woken runs.** A woken Kiro run is its own agent, `pantheon-worker`, in a `KIRO_HOME` of its own outside
|
|
496
|
+
the checkout. That matters: Kiro starts every MCP server in its config whatever the agent says, so your own
|
|
497
|
+
pantheon entry would otherwise join every run as a second server reporting you present. The run's home
|
|
498
|
+
holds only its agent and your Kiro settings; your sign-in, which lives in Kiro's data store, still applies.
|
|
499
|
+
The agent's `tools` and `allowedTools` are one list — Pantheon's server and `read`, `write`, `grep`,
|
|
500
|
+
`glob`, with `shell` only under `--allow-bash` — so nothing it can reach needs approval, and no trust flag
|
|
501
|
+
is passed. A repo's own committed Kiro MCP servers still start, as the repo's tooling; their tools are not
|
|
502
|
+
in the run's list, and one that fails does not stop the run.
|
|
503
|
+
|
|
504
|
+
**Sign in once with `kiro-cli login`, or set `KIRO_API_KEY`.** Signed out, Kiro does not fail: it opens a
|
|
505
|
+
browser to log in and waits. So a woken run is never started with Kiro signed out, even when a persona pins
|
|
506
|
+
Kiro or it is the only agent on the machine; the run is refused with the reason, and `pantheon doctor` says
|
|
507
|
+
so first. Pantheon reads the sign-in with `kiro-cli whoami --format json`, and only once Kiro has run on the
|
|
508
|
+
machine, because on a fresh install that one call creates `~/.kiro` and Kiro's data store.
|
|
509
|
+
|
|
510
|
+
Not supported: Kiro Crew, which is itself an always-on agent runner and would be a second answerer beside
|
|
511
|
+
Pantheon's worker, and Kiro Mobile, which has nowhere to load an MCP server. The Kiro IDE's own hooks are
|
|
512
|
+
not wired, because they could not be checked here; IDE sessions get the MCP entry and presence.
|
|
513
|
+
|
|
461
514
|
### Personas — telling the worker who to be
|
|
462
515
|
|
|
463
516
|
A worker with no instructions escalates too much and trusts too much. A **persona** is one
|
package/dist/agents.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
2
3
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
4
|
import { homedir, platform } from "node:os";
|
|
4
5
|
import { join, dirname, isAbsolute } from "node:path";
|
|
5
|
-
import { launcher } from "./install.js";
|
|
6
|
+
import { launcher, kiroDir } from "./install.js";
|
|
6
7
|
import { agentSpawn, which } from "./which.js";
|
|
7
8
|
import { PANTHEON_TOOL_NAMES, PANTHEON_TOOLS } from "./tools.js";
|
|
8
9
|
export function launchArgs(agent, prompt, name) {
|
|
@@ -10,6 +11,8 @@ export function launchArgs(agent, prompt, name) {
|
|
|
10
11
|
return ["--name", name, prompt];
|
|
11
12
|
if (agent === "copilot")
|
|
12
13
|
return ["-n", name, "-i", prompt];
|
|
14
|
+
if (agent === "kiro")
|
|
15
|
+
return ["chat", prompt];
|
|
13
16
|
return [prompt];
|
|
14
17
|
}
|
|
15
18
|
export const AGENTS = [
|
|
@@ -17,7 +20,11 @@ export const AGENTS = [
|
|
|
17
20
|
{ id: "codex", label: "Codex", binaries: ["codex"] },
|
|
18
21
|
{ id: "cursor", label: "Cursor", binaries: ["cursor-agent", "agent"] },
|
|
19
22
|
{ id: "copilot", label: "Copilot", binaries: ["copilot"] },
|
|
23
|
+
{ id: "kiro", label: "Kiro", binaries: ["kiro-cli"] },
|
|
20
24
|
];
|
|
25
|
+
export function kiroHome(home = homedir(), env = process.env) {
|
|
26
|
+
return kiroDir(home, env);
|
|
27
|
+
}
|
|
21
28
|
export function copilotHome(home = homedir(), env = process.env) {
|
|
22
29
|
return env.COPILOT_HOME || join(home, ".copilot");
|
|
23
30
|
}
|
|
@@ -57,6 +64,11 @@ export function detectForHooks(projectDir = process.cwd()) {
|
|
|
57
64
|
out.push(backend);
|
|
58
65
|
continue;
|
|
59
66
|
}
|
|
67
|
+
if (backend.id === "kiro") {
|
|
68
|
+
if (existsSync(kiroHome()) || existsSync(join(projectDir, ".kiro")))
|
|
69
|
+
out.push(backend);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
60
72
|
const userDir = backend.id === "claude-code" ? join(homedir(), ".claude")
|
|
61
73
|
: backend.id === "codex" ? join(homedir(), ".codex")
|
|
62
74
|
: join(homedir(), ".cursor");
|
|
@@ -107,7 +119,28 @@ export const SIGN_IN_CHECK = {
|
|
|
107
119
|
codex: { args: ["login", "status"], fix: "run `codex login`" },
|
|
108
120
|
cursor: { args: ["status"], fix: "run `cursor-agent login`" },
|
|
109
121
|
copilot: { args: [], fix: "run `copilot login` (or `gh auth login` — copilot uses gh's sign-in too)" },
|
|
122
|
+
kiro: { args: ["whoami", "--format", "json"], fix: "run `kiro-cli login` (or set KIRO_API_KEY)" },
|
|
110
123
|
};
|
|
124
|
+
export const NEEDS_SIGN_IN_BEFORE_SPAWN = new Set(["kiro"]);
|
|
125
|
+
export function kiroSignedIn(binary, run = runStatus, env = process.env, home = homedir(), os = platform()) {
|
|
126
|
+
if (env.KIRO_API_KEY)
|
|
127
|
+
return { ok: true, detail: "signed in (KIRO_API_KEY in the environment)" };
|
|
128
|
+
if (!kiroHasRunHere(home, env, os))
|
|
129
|
+
return { ok: false, detail: "never signed in on this machine" };
|
|
130
|
+
const r = run(binary, SIGN_IN_CHECK.kiro.args);
|
|
131
|
+
if (r.status === null)
|
|
132
|
+
return { ok: false, detail: `could not tell (${(r.out.split("\n")[0] ?? "").slice(0, 60) || "no answer"}), so it is not run unattended` };
|
|
133
|
+
const line = r.out.split("\n").map((l) => l.trim()).reverse().find((l) => l.startsWith("{"));
|
|
134
|
+
try {
|
|
135
|
+
const j = JSON.parse(line ?? "");
|
|
136
|
+
if (typeof j.accountType === "string" && j.accountType)
|
|
137
|
+
return { ok: true, detail: "signed in" };
|
|
138
|
+
if ("account" in j)
|
|
139
|
+
return j.account ? { ok: true, detail: "signed in" } : { ok: false, detail: "not signed in" };
|
|
140
|
+
}
|
|
141
|
+
catch { }
|
|
142
|
+
return { ok: false, detail: "could not tell (kiro-cli whoami answered in a shape this build does not know), so it is not run unattended" };
|
|
143
|
+
}
|
|
111
144
|
export const COPILOT_TOKEN_VARS = ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"];
|
|
112
145
|
export function copilotSignedIn(run = runStatus, env = process.env, gh = () => !!cliOnPath("gh")) {
|
|
113
146
|
if (COPILOT_TOKEN_VARS.some((k) => env[k]))
|
|
@@ -132,10 +165,12 @@ const runStatus = (binary, args) => {
|
|
|
132
165
|
return { status: err.status ?? null, out: `${err.stdout ?? ""}${err.stderr ?? ""}`.trim() || (err.message ?? "") };
|
|
133
166
|
}
|
|
134
167
|
};
|
|
135
|
-
export const AGENT_HOME_DIR = { "claude-code": ".claude", codex: ".codex", cursor: ".cursor", copilot: ".copilot" };
|
|
168
|
+
export const AGENT_HOME_DIR = { "claude-code": ".claude", codex: ".codex", cursor: ".cursor", copilot: ".copilot", kiro: ".kiro" };
|
|
136
169
|
export function signedIn(backend, binary, run = runStatus, home = homedir()) {
|
|
137
170
|
if (backend.id === "copilot")
|
|
138
171
|
return copilotSignedIn(run);
|
|
172
|
+
if (backend.id === "kiro")
|
|
173
|
+
return kiroSignedIn(binary, run, process.env, home);
|
|
139
174
|
if (!existsSync(join(home, AGENT_HOME_DIR[backend.id])))
|
|
140
175
|
return { ok: false, detail: "never signed in on this machine" };
|
|
141
176
|
const check = SIGN_IN_CHECK[backend.id];
|
|
@@ -159,14 +194,14 @@ export function chooseAgent(preferred, opts = {}) {
|
|
|
159
194
|
if (preferred && preferred !== "auto") {
|
|
160
195
|
const backend = agentById(preferred);
|
|
161
196
|
if (!backend)
|
|
162
|
-
throw new Error(`unknown agent ${preferred} — use claude-code, codex, cursor, copilot, or auto`);
|
|
197
|
+
throw new Error(`unknown agent ${preferred} — use claude-code, codex, cursor, copilot, kiro, or auto`);
|
|
163
198
|
const hit = available.find((a) => a.backend.id === backend.id);
|
|
164
199
|
if (!hit)
|
|
165
200
|
throw new Error(`${backend.label} is not on PATH (looked for ${backend.binaries.join(", ")})`);
|
|
166
201
|
return { backend, binary: hit.binary, signIn: state(backend, hit.binary), because: "pinned", skipped: [] };
|
|
167
202
|
}
|
|
168
203
|
if (!available.length)
|
|
169
|
-
throw new Error("no agent CLI on PATH — install claude, codex, cursor-agent, or
|
|
204
|
+
throw new Error("no agent CLI on PATH — install claude, codex, cursor-agent, copilot, or kiro-cli");
|
|
170
205
|
const order = [];
|
|
171
206
|
const wanted = opts.prefer ? agentById(opts.prefer) : undefined;
|
|
172
207
|
const chosen = wanted ? available.find((a) => a.backend.id === wanted.id) : undefined;
|
|
@@ -182,9 +217,14 @@ export function chooseAgent(preferred, opts = {}) {
|
|
|
182
217
|
return { backend: c.backend, binary: c.binary, ...(s ? { signIn: s } : {}), because: c.because, skipped };
|
|
183
218
|
skipped.push({ backend: c.backend, why: `${s.detail} — ${SIGN_IN_CHECK[c.backend.id].fix}` });
|
|
184
219
|
}
|
|
185
|
-
const first = order[0];
|
|
220
|
+
const first = order.find((o) => !NEEDS_SIGN_IN_BEFORE_SPAWN.has(o.backend.id)) ?? order[0];
|
|
186
221
|
return { backend: first.backend, binary: first.binary, signIn: state(first.backend, first.binary), because: `${first.because} (none signed in)`, skipped };
|
|
187
222
|
}
|
|
223
|
+
export function unattendedRefusal(c) {
|
|
224
|
+
if (!NEEDS_SIGN_IN_BEFORE_SPAWN.has(c.backend.id) || c.signIn?.ok)
|
|
225
|
+
return null;
|
|
226
|
+
return `${c.backend.label} is not signed in (${c.signIn?.detail ?? "not checked"}), and signed out it opens a browser to log in, so it is not started while you are away — ${SIGN_IN_CHECK[c.backend.id].fix}`;
|
|
227
|
+
}
|
|
188
228
|
export function describeChoice(c) {
|
|
189
229
|
const head = `${c.backend.label}${c.signIn ? ` — ${c.signIn.detail}` : ""} (${c.because})`;
|
|
190
230
|
return c.skipped.length ? `${head}; skipped ${c.skipped.map((s) => `${s.backend.label} — ${s.why}`).join("; ")}` : head;
|
|
@@ -283,6 +323,30 @@ export function headlessSpawn(opts) {
|
|
|
283
323
|
...(opts.runHome && existsSync(settings) ? { copyFiles: [{ from: settings, to: join(home, "settings.json") }] } : {}),
|
|
284
324
|
};
|
|
285
325
|
}
|
|
326
|
+
if (opts.backend.id === "kiro") {
|
|
327
|
+
const home = opts.runHome ? join(opts.runHome, "kiro") : join(opts.worktree, ".pantheon", "kiro-home");
|
|
328
|
+
const tools = [...KIRO_WORKER_TOOLS, ...(opts.allowBash ? ["shell"] : []), `@${KIRO_WORKER_SERVER}`];
|
|
329
|
+
const agentName = `${KIRO_WORKER_AGENT}-${randomBytes(8).toString("hex")}`;
|
|
330
|
+
const settings = join(kiroHome(), "settings", "cli.json");
|
|
331
|
+
return {
|
|
332
|
+
command: opts.binary,
|
|
333
|
+
args: ["chat", "--no-interactive", "--agent", agentName, "--require-mcp-startup", "--wrap", "never", opts.prompt],
|
|
334
|
+
env: { ...procEnv, KIRO_HOME: home, KIRO_NO_AUTO_UPDATE: "1" },
|
|
335
|
+
writeFiles: [{
|
|
336
|
+
path: join(home, "agents", `${KIRO_WORKER_AGENT}.json`),
|
|
337
|
+
contents: JSON.stringify({
|
|
338
|
+
name: agentName,
|
|
339
|
+
description: "A Pantheon run: answers a teammate's request on an isolated branch, while its human is away",
|
|
340
|
+
tools,
|
|
341
|
+
allowedTools: tools,
|
|
342
|
+
includeMcpJson: false,
|
|
343
|
+
mcpServers: { [KIRO_WORKER_SERVER]: { command: l.command, args: l.args, env: fileEnv } },
|
|
344
|
+
resources: ["file://.kiro/steering/**/*.md"],
|
|
345
|
+
}, null, 2),
|
|
346
|
+
}],
|
|
347
|
+
...(opts.runHome && existsSync(settings) ? { copyFiles: [{ from: settings, to: join(home, "settings", "cli.json") }] } : {}),
|
|
348
|
+
};
|
|
349
|
+
}
|
|
286
350
|
return {
|
|
287
351
|
command: opts.binary,
|
|
288
352
|
args: [
|
|
@@ -310,6 +374,20 @@ export function headlessSpawn(opts) {
|
|
|
310
374
|
};
|
|
311
375
|
}
|
|
312
376
|
export const COPILOT_WORKER_SERVER = "pantheon-worker";
|
|
377
|
+
export function kiroDataDir(home = homedir(), env = process.env, os = platform()) {
|
|
378
|
+
if (env.KIRO_DATA_DIR)
|
|
379
|
+
return env.KIRO_DATA_DIR;
|
|
380
|
+
if (os === "darwin")
|
|
381
|
+
return join(home, "Library", "Application Support", "kiro-cli");
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
export function kiroHasRunHere(home = homedir(), env = process.env, os = platform()) {
|
|
385
|
+
const data = kiroDataDir(home, env, os);
|
|
386
|
+
return data ? existsSync(join(data, "data.sqlite3")) : existsSync(kiroDir(home, env));
|
|
387
|
+
}
|
|
388
|
+
export const KIRO_WORKER_AGENT = "pantheon-worker";
|
|
389
|
+
export const KIRO_WORKER_SERVER = "pantheon";
|
|
390
|
+
export const KIRO_WORKER_TOOLS = ["read", "write", "grep", "glob"];
|
|
313
391
|
function readCopilotConfig() {
|
|
314
392
|
try {
|
|
315
393
|
const doc = JSON.parse(readFileSync(join(copilotHome(), "config.json"), "utf8"));
|
package/dist/cli.js
CHANGED
|
@@ -326,6 +326,20 @@ async function guidedSetup(cfg, project, opts = {}) {
|
|
|
326
326
|
rl?.close();
|
|
327
327
|
}
|
|
328
328
|
}
|
|
329
|
+
async function workdirFor(cfg, hud) {
|
|
330
|
+
const { loadAll, findMembership } = await import("./config.js");
|
|
331
|
+
const { pickWorkdir } = await import("./workdir.js");
|
|
332
|
+
const all = loadAll();
|
|
333
|
+
const m = all ? findMembership(all, hud.space || cfg.space) : undefined;
|
|
334
|
+
const team = m?.name ?? cfg.space;
|
|
335
|
+
const bindings = all && m ? Object.values(all.bindings ?? {}).filter((b) => b.space === m.id) : [];
|
|
336
|
+
let hints = [];
|
|
337
|
+
if (bindings.length > 1 && hud.goal) {
|
|
338
|
+
const acts = await fetchHistory(cfg).catch(() => []);
|
|
339
|
+
hints = acts.filter((x) => x.goal === hud.goal).flatMap((x) => [x.body, JSON.stringify(x.meta ?? {})]);
|
|
340
|
+
}
|
|
341
|
+
return pickWorkdir(bindings, hints, team);
|
|
342
|
+
}
|
|
329
343
|
async function fetchHistory(cfg, since = 0) {
|
|
330
344
|
const base = cfg.relay.replace(/^ws/, "http").replace(/\/+$/, "");
|
|
331
345
|
const res = await fetch(`${base}/history?space=${encodeURIComponent(cfg.space)}&token=${encodeURIComponent(cfg.token)}&since=${since}`, { signal: AbortSignal.timeout(8000) });
|
|
@@ -428,6 +442,8 @@ function printReady(tools) {
|
|
|
428
442
|
oneTime.set("cursor", `cursor enable the "pantheon" MCP server if it asks`);
|
|
429
443
|
if (/codex/i.test(t.tool))
|
|
430
444
|
oneTime.set("codex", `codex run /hooks inside codex once to approve the wake-up hook`);
|
|
445
|
+
if (/kiro/i.test(t.tool))
|
|
446
|
+
oneTime.set("kiro", `kiro run kiro-cli chat --agent pantheon (or kiro-cli agent set-default pantheon) to keep working while a teammate waits; stay signed in — a woken run never starts Kiro signed out, since Kiro would open a browser`);
|
|
431
447
|
if (/copilot/i.test(t.tool))
|
|
432
448
|
oneTime.set("copilot", `copilot say yes once when it asks to trust this folder; on a Copilot Business or Enterprise plan an org owner must have the "MCP servers in Copilot" policy on`);
|
|
433
449
|
}
|
|
@@ -487,7 +503,7 @@ ${B("control")}
|
|
|
487
503
|
pantheon admit <who> let someone in (--may-wake to allow unattended work here)
|
|
488
504
|
pantheon admit <who> --revoke remove them
|
|
489
505
|
pantheon worker stay answerable while you are away
|
|
490
|
-
(--agent claude-code|codex|cursor|copilot|auto, --persona <name>)
|
|
506
|
+
(--agent claude-code|codex|cursor|copilot|kiro|auto, --persona <name>)
|
|
491
507
|
pantheon policy what this machine's worker may do, in one screen (grant/revoke self-upgrade)
|
|
492
508
|
pantheon persona how a woken worker should behave: list / init <name> / show <name>
|
|
493
509
|
pantheon notifier alert you when a task requires your decision
|
|
@@ -500,7 +516,7 @@ ${B("control")}
|
|
|
500
516
|
pantheon approvals decisions waiting on you; show or answer one
|
|
501
517
|
pantheon stop [relay|worker|notifier|menubar|all] stop it
|
|
502
518
|
pantheon hook install your agent keeps working when a peer is waiting on it
|
|
503
|
-
(auto-detects claude / codex / cursor / copilot on this machine)
|
|
519
|
+
(auto-detects claude / codex / cursor / copilot / kiro on this machine)
|
|
504
520
|
pantheon spend woken runs on this machine, and what pantheon cannot see
|
|
505
521
|
pantheon leave remove what pantheon put on this machine
|
|
506
522
|
pantheon panic cut off all unattended work on this machine, now
|
|
@@ -1318,13 +1334,13 @@ async function main() {
|
|
|
1318
1334
|
const sub = argv[1] ?? "status";
|
|
1319
1335
|
const hookCfg = resolveForHook();
|
|
1320
1336
|
const sessionOf = (raw) => {
|
|
1337
|
+
let fromJson;
|
|
1321
1338
|
try {
|
|
1322
1339
|
const j = JSON.parse(raw);
|
|
1323
|
-
|
|
1324
|
-
}
|
|
1325
|
-
catch {
|
|
1326
|
-
return "unknown";
|
|
1340
|
+
fromJson = j.session_id ?? j.sessionId ?? j.conversation_id;
|
|
1327
1341
|
}
|
|
1342
|
+
catch { }
|
|
1343
|
+
return fromJson ?? process.env.KIRO_SESSION_ID ?? "unknown";
|
|
1328
1344
|
};
|
|
1329
1345
|
const reportIdle = async (session, seen) => {
|
|
1330
1346
|
if (!hookCfg)
|
|
@@ -1339,6 +1355,22 @@ async function main() {
|
|
|
1339
1355
|
}
|
|
1340
1356
|
return false;
|
|
1341
1357
|
};
|
|
1358
|
+
if (sub === "kiro-prompt") {
|
|
1359
|
+
let raw = "";
|
|
1360
|
+
try {
|
|
1361
|
+
for await (const chunk of process.stdin)
|
|
1362
|
+
raw += chunk;
|
|
1363
|
+
}
|
|
1364
|
+
catch { }
|
|
1365
|
+
try {
|
|
1366
|
+
if (hookCfg) {
|
|
1367
|
+
const { reportActivity } = await import("./pending.js");
|
|
1368
|
+
await reportActivity({ cfg: hookCfg, session: `hook:${sessionOf(raw)}`, state: "busy" });
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
catch { }
|
|
1372
|
+
process.exit(0);
|
|
1373
|
+
}
|
|
1342
1374
|
if (sub === "prompt") {
|
|
1343
1375
|
let raw = "";
|
|
1344
1376
|
try {
|
|
@@ -1448,7 +1480,7 @@ async function main() {
|
|
|
1448
1480
|
process.exit(0);
|
|
1449
1481
|
}
|
|
1450
1482
|
}
|
|
1451
|
-
if (sub === "codex-stop" || sub === "copilot-stop") {
|
|
1483
|
+
if (sub === "codex-stop" || sub === "copilot-stop" || sub === "kiro-stop") {
|
|
1452
1484
|
let raw = "";
|
|
1453
1485
|
try {
|
|
1454
1486
|
for await (const chunk of process.stdin)
|
|
@@ -1557,7 +1589,7 @@ async function main() {
|
|
|
1557
1589
|
}
|
|
1558
1590
|
const states = agentHookStates();
|
|
1559
1591
|
if (!states.length) {
|
|
1560
|
-
console.log(`${D("not installed")}\n Install with: ${C("pantheon hook install")} (auto-detects claude / codex / cursor / copilot on PATH)`);
|
|
1592
|
+
console.log(`${D("not installed")}\n Install with: ${C("pantheon hook install")} (auto-detects claude / codex / cursor / copilot / kiro on PATH)`);
|
|
1561
1593
|
return;
|
|
1562
1594
|
}
|
|
1563
1595
|
for (const s of states) {
|
|
@@ -1640,7 +1672,7 @@ async function main() {
|
|
|
1640
1672
|
else {
|
|
1641
1673
|
console.error(` config: none to remove`);
|
|
1642
1674
|
}
|
|
1643
|
-
console.error(`\n ${D("Not touched: the MCP entries in Claude Code and
|
|
1675
|
+
console.error(`\n ${D("Not touched: the MCP entries in Claude Code, Cursor, Copilot, VS Code and Kiro (pantheon install wrote them; remove them there),")}`);
|
|
1644
1676
|
console.error(` ${D("the relay's ledger on disk, and anything you pushed to git. Codex's entry and approval line were removed above.")}`);
|
|
1645
1677
|
if (cfg) {
|
|
1646
1678
|
console.error(`\n ${D(`Your teammates still list you in "${cfg.space}". Ask one of them to run:`)}`);
|
|
@@ -2007,7 +2039,7 @@ async function main() {
|
|
|
2007
2039
|
const publicChange = name !== undefined || title !== undefined || responsibilities !== undefined ||
|
|
2008
2040
|
humanRequired !== undefined || agentCanDecide !== undefined || capabilities !== undefined || keywords !== undefined;
|
|
2009
2041
|
if (!surface && !publicChange) {
|
|
2010
|
-
die("usage: pantheon profile set [--surface codex|claude-code|cursor|copilot] [--name name] [--title title] [--responsibilities a,b] [--human-required a,b] [--agent-can-decide a,b] [--keywords 'domain=word|word;domain=word']");
|
|
2042
|
+
die("usage: pantheon profile set [--surface codex|claude-code|cursor|copilot|kiro] [--name name] [--title title] [--responsibilities a,b] [--human-required a,b] [--agent-can-decide a,b] [--keywords 'domain=word|word;domain=word']");
|
|
2011
2043
|
}
|
|
2012
2044
|
if (flag("space")) {
|
|
2013
2045
|
const pf = loadAll() ?? die("not configured");
|
|
@@ -2262,6 +2294,46 @@ async function main() {
|
|
|
2262
2294
|
}
|
|
2263
2295
|
return;
|
|
2264
2296
|
}
|
|
2297
|
+
if (hud?.action === "reply") {
|
|
2298
|
+
const notSent = (why) => { console.log(`not sent: ${why.split("\n")[0]}`); process.exit(1); };
|
|
2299
|
+
const kind = hud.kind ?? "NOTE";
|
|
2300
|
+
const body = (hud.body ?? "").trim();
|
|
2301
|
+
if (!body)
|
|
2302
|
+
notSent("nothing to send");
|
|
2303
|
+
const c2 = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2304
|
+
await c2.connect().catch((e) => notSent(e.message));
|
|
2305
|
+
const o = hud.decision ? c2.obligations.find((x) => x.id === hud.decision) : undefined;
|
|
2306
|
+
const acts = await fetchHistory(cfg).catch(() => []);
|
|
2307
|
+
const target = acts.find((x) => x.id === (o?.ref ?? hud.act));
|
|
2308
|
+
const goal = o?.goal ?? target?.goal ?? hud.goal ?? null;
|
|
2309
|
+
if (kind === "ANSWER" && !o && !target) {
|
|
2310
|
+
c2.close();
|
|
2311
|
+
notSent("that decision is no longer waiting — nothing to answer");
|
|
2312
|
+
}
|
|
2313
|
+
const ref = kind === "ANSWER" ? (o?.ref ?? target?.id ?? null) : kind === "ASK" ? (target?.id ?? null) : null;
|
|
2314
|
+
const to = kind === "ASK" ? (target && target.from !== cfg.member ? [target.from] : null) : null;
|
|
2315
|
+
if (kind === "ASK" && !to) {
|
|
2316
|
+
c2.close();
|
|
2317
|
+
notSent("cannot tell who raised this — use Show the thread and reply from there");
|
|
2318
|
+
}
|
|
2319
|
+
const sent = await c2.send({ act: kind, body, goal, ref, to, after_seq: c2.readSeq, meta: { surface: "menubar" } })
|
|
2320
|
+
.catch((e) => notSent(e.message));
|
|
2321
|
+
c2.close();
|
|
2322
|
+
console.log(`sent ${kind} id=${sent.id} seq=${sent.seq}`);
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
if (hud?.action === "retract") {
|
|
2326
|
+
const notSent = (why) => { console.log(`not sent: ${why.split("\n")[0]}`); process.exit(1); };
|
|
2327
|
+
if (!hud.act)
|
|
2328
|
+
notSent("nothing to retract");
|
|
2329
|
+
const c2 = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2330
|
+
await c2.connect().catch((e) => notSent(e.message));
|
|
2331
|
+
const sent = await c2.send({ act: "RETRACT", body: "Retracted from the menu bar.", goal: null, ref: hud.act, to: null, meta: { surface: "menubar" } })
|
|
2332
|
+
.catch((e) => notSent(e.message));
|
|
2333
|
+
c2.close();
|
|
2334
|
+
console.log(`sent RETRACT id=${sent.id} seq=${sent.seq}`);
|
|
2335
|
+
return;
|
|
2336
|
+
}
|
|
2265
2337
|
if (hud?.action === "done") {
|
|
2266
2338
|
const c2 = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2267
2339
|
await c2.connect().catch((e) => die(e.message));
|
|
@@ -2278,6 +2350,15 @@ async function main() {
|
|
|
2278
2350
|
return;
|
|
2279
2351
|
}
|
|
2280
2352
|
if (hud?.action === "details") {
|
|
2353
|
+
const wd = await workdirFor(cfg, hud).catch(() => null);
|
|
2354
|
+
if (wd?.path) {
|
|
2355
|
+
try {
|
|
2356
|
+
process.chdir(wd.path);
|
|
2357
|
+
}
|
|
2358
|
+
catch { }
|
|
2359
|
+
}
|
|
2360
|
+
if (wd)
|
|
2361
|
+
console.error(D(` ${wd.note}`));
|
|
2281
2362
|
const acts = await fetchHistory(cfg).catch(() => []);
|
|
2282
2363
|
const a = hud.act ? acts.find((x) => x.id === hud.act) : undefined;
|
|
2283
2364
|
let o;
|
|
@@ -2377,6 +2458,10 @@ async function main() {
|
|
|
2377
2458
|
}
|
|
2378
2459
|
if (hud?.action === "open") {
|
|
2379
2460
|
const p = loadProfile(cfg);
|
|
2461
|
+
const wd = await workdirFor(cfg, hud).catch(() => null);
|
|
2462
|
+
const cwd = wd?.path ?? undefined;
|
|
2463
|
+
if (wd)
|
|
2464
|
+
console.error(D(` ${wd.note}`));
|
|
2380
2465
|
if (hud.tool && isAgentSurface(hud.tool) && p.agent.primary_surface !== hud.tool) {
|
|
2381
2466
|
saveProfile({ ...p, agent: { ...p.agent, primary_surface: hud.tool }, updated_at: Date.now() });
|
|
2382
2467
|
console.error(D(`${hud.tool} is now where your decisions open`));
|
|
@@ -2388,24 +2473,30 @@ async function main() {
|
|
|
2388
2473
|
const { spawnSync } = await import("node:child_process");
|
|
2389
2474
|
{
|
|
2390
2475
|
const sp = agentSpawn(bin, [hud.prompt]);
|
|
2391
|
-
spawnSync(sp.command, sp.args, { stdio: "inherit", ...sp.options });
|
|
2476
|
+
spawnSync(sp.command, sp.args, { stdio: "inherit", ...(cwd ? { cwd } : {}), ...sp.options });
|
|
2392
2477
|
}
|
|
2393
2478
|
return;
|
|
2394
2479
|
}
|
|
2395
2480
|
if (hud.prompt) {
|
|
2396
2481
|
const agent = pickExplainAgent(hud.tool ?? p.agent.primary_surface, detectAvailable());
|
|
2397
2482
|
if (!agent)
|
|
2398
|
-
die("no agent CLI found on this machine (claude, cursor-agent, codex, or
|
|
2483
|
+
die("no agent CLI found on this machine (claude, cursor-agent, codex, copilot, or kiro-cli)");
|
|
2399
2484
|
console.error(D(`opening ${agent.id}…`));
|
|
2400
2485
|
const { spawnSync } = await import("node:child_process");
|
|
2401
2486
|
{
|
|
2402
2487
|
const sp = agentSpawn(agent.binary, launchArgs(agent.id, hud.prompt, sessionName(hud.prompt)));
|
|
2403
|
-
spawnSync(sp.command, sp.args, { stdio: "inherit", ...sp.options });
|
|
2488
|
+
spawnSync(sp.command, sp.args, { stdio: "inherit", ...(cwd ? { cwd } : {}), ...sp.options });
|
|
2404
2489
|
}
|
|
2405
2490
|
return;
|
|
2406
2491
|
}
|
|
2407
2492
|
if (hud.decision)
|
|
2408
2493
|
id = hud.decision;
|
|
2494
|
+
if (cwd) {
|
|
2495
|
+
try {
|
|
2496
|
+
process.chdir(cwd);
|
|
2497
|
+
}
|
|
2498
|
+
catch { }
|
|
2499
|
+
}
|
|
2409
2500
|
}
|
|
2410
2501
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
2411
2502
|
await client.connect().catch((e) => die(e.message));
|
|
@@ -2421,7 +2512,7 @@ async function main() {
|
|
|
2421
2512
|
}
|
|
2422
2513
|
const agent = pickExplainAgent(loadProfile(cfg).agent.primary_surface, detectAvailable());
|
|
2423
2514
|
if (!agent)
|
|
2424
|
-
die("no agent CLI found on this machine (claude, cursor-agent, codex, or
|
|
2515
|
+
die("no agent CLI found on this machine (claude, cursor-agent, codex, copilot, or kiro-cli)");
|
|
2425
2516
|
console.error(D(`opening ${agent.id} with the question…`));
|
|
2426
2517
|
const { spawnSync } = await import("node:child_process");
|
|
2427
2518
|
{
|
package/dist/doctor.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { kiroDefaultIsPantheon, readKiroAgent, kiroAgentFile } from "./kiro-files.js";
|
|
1
2
|
import { execFileSync } from "node:child_process";
|
|
2
3
|
import { instructionBudget } from "./mcp-instructions.js";
|
|
3
4
|
import { describeWindows, scanMcpWindows } from "./mcp-windows.js";
|
|
@@ -14,7 +15,7 @@ import { approvalSummary, chosenApproval, onPath } from "./install.js";
|
|
|
14
15
|
import { hookState, readIntent, agentHookStates } from "./notify.js";
|
|
15
16
|
import { loadPersonas } from "./persona.js";
|
|
16
17
|
import { workerProc, menubarProc, notifierProc } from "./machine.js";
|
|
17
|
-
import { chooseAgent, describeChoice } from "./agents.js";
|
|
18
|
+
import { chooseAgent, describeChoice, unattendedRefusal } from "./agents.js";
|
|
18
19
|
import { onPathNamed } from "./which.js";
|
|
19
20
|
import { VERSION } from "./version.js";
|
|
20
21
|
import { serviceInstalled, servicesUnavailable } from "./service.js";
|
|
@@ -156,10 +157,10 @@ export async function doctor(projectDir) {
|
|
|
156
157
|
if (branch)
|
|
157
158
|
add("pass", "git branch", branch);
|
|
158
159
|
}
|
|
159
|
-
const agents = ["claude", "codex", "cursor-agent", "copilot"].filter((a) => onPathNamed(a));
|
|
160
|
+
const agents = ["claude", "codex", "cursor-agent", "copilot", "kiro-cli"].filter((a) => onPathNamed(a));
|
|
160
161
|
agents.length
|
|
161
162
|
? add("pass", "agent cli", agents.join(", "), undefined, agents.join(", "))
|
|
162
|
-
: add("warn", "agent cli", "found none of claude / codex / cursor-agent / copilot on PATH", "fine if you only drive agents through a GUI (Conductor, Cursor, VS Code), but `pantheon worker` needs a CLI to spawn", "none of claude / codex / cursor-agent / copilot on PATH");
|
|
163
|
+
: add("warn", "agent cli", "found none of claude / codex / cursor-agent / copilot / kiro-cli on PATH", "fine if you only drive agents through a GUI (Conductor, Cursor, VS Code, the Kiro IDE), but `pantheon worker` needs a CLI to spawn", "none of claude / codex / cursor-agent / copilot / kiro-cli on PATH");
|
|
163
164
|
if (agents.length) {
|
|
164
165
|
try {
|
|
165
166
|
const choice = chooseAgent("auto", { prefer: prof.agent.primary_surface });
|
|
@@ -167,7 +168,10 @@ export async function doctor(projectDir) {
|
|
|
167
168
|
const bad = choice.signIn && !choice.signIn.ok && !choice.signIn.unknown;
|
|
168
169
|
const fellBack = !!prof.agent.primary_surface && choice.backend.id !== prof.agent.primary_surface && agents.length > 0;
|
|
169
170
|
const shareLine = `${choice.backend.label} — ${choice.signIn?.detail ?? "not checked"}${choice.skipped.length ? `; ${choice.skipped.length} skipped (not signed in)` : ""}`;
|
|
170
|
-
|
|
171
|
+
const refused = unattendedRefusal(choice);
|
|
172
|
+
if (refused)
|
|
173
|
+
add("warn", "worker agent", line, `woken runs are refused here, not tried: ${refused}`, shareLine);
|
|
174
|
+
else if (bad)
|
|
171
175
|
add("warn", "worker agent", line, `nothing is signed in, so every woken run here fails until you sign in: ${choice.skipped.map((x) => x.why).join("; ") || "sign in to the agent CLI"}`, shareLine);
|
|
172
176
|
else if (fellBack)
|
|
173
177
|
add("warn", "worker agent", line, `you chose ${prof.agent.primary_surface} at setup; runs use ${choice.backend.label} until it is signed in again`, shareLine);
|
|
@@ -175,7 +179,13 @@ export async function doctor(projectDir) {
|
|
|
175
179
|
add("pass", "worker agent", line, undefined, shareLine);
|
|
176
180
|
}
|
|
177
181
|
catch (e) {
|
|
178
|
-
add("warn", "worker agent", e.message, "pantheon setup --agent <claude-code|codex|cursor|copilot> names one that is installed", "could not be chosen");
|
|
182
|
+
add("warn", "worker agent", e.message, "pantheon setup --agent <claude-code|codex|cursor|copilot|kiro> names one that is installed", "could not be chosen");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
{
|
|
186
|
+
const here = readKiroAgent(kiroAgentFile(join(projectDir, ".kiro")));
|
|
187
|
+
if (here && !here.ours) {
|
|
188
|
+
add("warn", "kiro agent", "this repo defines its own agent called pantheon — Kiro uses it instead of yours in this repo", "workspace agents win over your own in Kiro; read .kiro/agents/pantheon.json before running kiro-cli chat --agent pantheon here", "this repo defines its own pantheon agent");
|
|
179
189
|
}
|
|
180
190
|
}
|
|
181
191
|
const budget = instructionBudget(cfg);
|
|
@@ -302,7 +312,7 @@ export async function doctor(projectDir) {
|
|
|
302
312
|
}
|
|
303
313
|
}
|
|
304
314
|
if (!hookStates.length && !hook.installed) {
|
|
305
|
-
add("warn", "wake-up hook", "not installed — your agent will not notice a peer's message until you ask it to", "pantheon hook install --user (auto-detects claude / codex / cursor / copilot on PATH)", "not installed");
|
|
315
|
+
add("warn", "wake-up hook", "not installed — your agent will not notice a peer's message until you ask it to", "pantheon hook install --user (auto-detects claude / codex / cursor / copilot / kiro on PATH)", "not installed");
|
|
306
316
|
}
|
|
307
317
|
else if (!reportsActivity) {
|
|
308
318
|
add("warn", "wake-up hook", `installed (${hook.events?.join(", ")}) but predates the activity hook, so a worker cannot tell your idle editor from one you are using`, "re-run `pantheon hook install --user` to add the UserPromptSubmit half", `installed (${hook.events?.join(", ")}), predates the activity hook`);
|
|
@@ -316,6 +326,11 @@ export async function doctor(projectDir) {
|
|
|
316
326
|
if (hookStates.some((s) => s.agent === "codex")) {
|
|
317
327
|
add("warn", "codex hooks", "installed — but codex skips new or changed hooks until they are approved", "run /hooks inside codex once to approve them (needs codex ≥ 0.115; sessions still report presence over MCP either way)", "installed, not yet approved inside codex");
|
|
318
328
|
}
|
|
329
|
+
if (hookStates.some((s) => s.agent === "kiro")) {
|
|
330
|
+
kiroDefaultIsPantheon()
|
|
331
|
+
? add("pass", "kiro hooks", "in the pantheon agent, which is your Kiro default — every kiro-cli chat keeps working while a teammate waits", undefined, "in the pantheon agent, the default")
|
|
332
|
+
: add("warn", "kiro hooks", "in the pantheon agent — used only when you run kiro-cli chat --agent pantheon", "kiro-cli agent set-default pantheon makes it every session; without it, Kiro sessions still report presence but end their turn with a teammate waiting", "in the pantheon agent, not the default");
|
|
333
|
+
}
|
|
319
334
|
const personas = loadPersonas(projectDir);
|
|
320
335
|
if (personas.length) {
|
|
321
336
|
const broken = personas.filter((p) => p.warnings.length);
|
package/dist/history-view.js
CHANGED
|
@@ -8,7 +8,8 @@ export function selectActs(acts, opts = {}) {
|
|
|
8
8
|
}
|
|
9
9
|
const when = (ts) => new Date(ts).toISOString().slice(0, 19).replace("T", " ") + "Z";
|
|
10
10
|
export function formatAct(a, opts = {}) {
|
|
11
|
-
const
|
|
11
|
+
const surface = a.meta?.surface;
|
|
12
|
+
const who = `${a.from}${surface === "menubar" ? " (from the menu bar)" : a.role && a.role !== "interactive" ? ` (${a.role})` : ""}`;
|
|
12
13
|
const head = `[${a.seq}] ${a.act} from ${who}${a.to?.length ? ` to ${a.to.join(",")}` : ""} ${when(a.ts)} id=${a.id}${a.ref ? ` ref=${a.ref}` : ""}${a.goal ? ` goal=${a.goal}${opts.goalTitle ? ` "${opts.goalTitle}"` : ""}` : ""}`;
|
|
13
14
|
if (opts.short)
|
|
14
15
|
return `${head}\n ${(a.summary ?? a.body).split("\n")[0].slice(0, 160)}`;
|
package/dist/hooks-install.js
CHANGED
|
@@ -2,7 +2,8 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from "node
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { MAX_CONSECUTIVE_BLOCKS, isOurHookCommand } from "./notify.js";
|
|
5
|
-
import { copilotHome } from "./agents.js";
|
|
5
|
+
import { copilotHome, kiroHome } from "./agents.js";
|
|
6
|
+
import { KIRO_AGENT_NAME, kiroAgentFile, kiroAgentDoc, readKiroAgent, writeKiroAgent, kiroDefaultIsPantheon } from "./kiro-files.js";
|
|
6
7
|
function ours(cmd) {
|
|
7
8
|
return isOurHookCommand(cmd);
|
|
8
9
|
}
|
|
@@ -247,4 +248,48 @@ export function removeCopilotHooks(projectDir = process.cwd()) {
|
|
|
247
248
|
}
|
|
248
249
|
return out;
|
|
249
250
|
}
|
|
251
|
+
export { KIRO_AGENT_NAME };
|
|
252
|
+
export function kiroAgentPath(scope, projectDir = process.cwd()) {
|
|
253
|
+
return kiroAgentFile(scope === "user" ? kiroHome() : join(projectDir, ".kiro"));
|
|
254
|
+
}
|
|
255
|
+
export function kiroHookState(scope, projectDir = process.cwd()) {
|
|
256
|
+
const file = kiroAgentPath(scope, projectDir);
|
|
257
|
+
const a = readKiroAgent(file);
|
|
258
|
+
return a?.ours
|
|
259
|
+
? { agent: "kiro", label: "Kiro", installed: true, scope, file, events: a.events }
|
|
260
|
+
: { agent: "kiro", label: "Kiro", installed: false };
|
|
261
|
+
}
|
|
262
|
+
export function installKiroHooks(bin, scope, projectDir = process.cwd(), allowed = []) {
|
|
263
|
+
const file = kiroAgentPath(scope, projectDir);
|
|
264
|
+
const a = readKiroAgent(file);
|
|
265
|
+
if (a && !a.ours)
|
|
266
|
+
return [` skipped ${file} is an agent called "${KIRO_AGENT_NAME}" that pantheon did not write — left as it is`];
|
|
267
|
+
writeKiroAgent(file, kiroAgentDoc(bin, allowed));
|
|
268
|
+
return [
|
|
269
|
+
` ${a ? "updated" : "installed"} ${file} (an agent: Kiro's default, plus pantheon's hooks)`,
|
|
270
|
+
` runs ${bin} hook kiro-stop`,
|
|
271
|
+
` ${bin} hook kiro-prompt`,
|
|
272
|
+
kiroDefaultIsPantheon()
|
|
273
|
+
? ` ${""}(it is your default agent, so every kiro-cli chat uses it)`
|
|
274
|
+
: ` \x1b[33mKiro uses these only in the pantheon agent:\x1b[0m kiro-cli chat --agent pantheon`,
|
|
275
|
+
...(kiroDefaultIsPantheon() ? [] : [` ${""}(or always: kiro-cli agent set-default pantheon — pantheon never changes your default)`]),
|
|
276
|
+
];
|
|
277
|
+
}
|
|
278
|
+
export function removeKiroHooks(projectDir = process.cwd()) {
|
|
279
|
+
const out = [];
|
|
280
|
+
for (const scope of ["project", "user"]) {
|
|
281
|
+
const file = kiroAgentPath(scope, projectDir);
|
|
282
|
+
if (!kiroHookState(scope, projectDir).installed)
|
|
283
|
+
continue;
|
|
284
|
+
try {
|
|
285
|
+
rmSync(file, { force: true });
|
|
286
|
+
out.push(` removed ${file}`);
|
|
287
|
+
}
|
|
288
|
+
catch { }
|
|
289
|
+
}
|
|
290
|
+
if (out.length && kiroDefaultIsPantheon()) {
|
|
291
|
+
out.push(" Kiro's default was the pantheon agent; it now falls back to kiro_default and warns each session — kiro-cli agent set-default kiro_default");
|
|
292
|
+
}
|
|
293
|
+
return out;
|
|
294
|
+
}
|
|
250
295
|
//# sourceMappingURL=hooks-install.js.map
|