@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/dist/install.js
CHANGED
|
@@ -4,11 +4,12 @@ import { homedir, platform } from "node:os";
|
|
|
4
4
|
import { join, dirname, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { grantToolPermissions, claudeSettingsFile, cursorCliConfigFile } from "./grants.js";
|
|
7
|
-
import { PANTHEON_TOOL_NAMES, WRITE_TOOL_NAMES } from "./tools.js";
|
|
7
|
+
import { PANTHEON_TOOL_NAMES, WRITE_TOOL_NAMES, toolsAllowed } from "./tools.js";
|
|
8
8
|
import { load } from "./config.js";
|
|
9
9
|
import { loadProfile } from "./profile.js";
|
|
10
10
|
import { agentSpawn, onPathNamed, which } from "./which.js";
|
|
11
11
|
import { FEEDBACK_ADDRESS } from "./feedback.js";
|
|
12
|
+
import { kiroDir, kiroAgentFile, refreshKiroAgentAllowed } from "./kiro-files.js";
|
|
12
13
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
13
14
|
export function onPath() {
|
|
14
15
|
return onPathNamed("pantheon");
|
|
@@ -40,7 +41,7 @@ function mergeMcpJson(file, entry, shape = {}) {
|
|
|
40
41
|
const existed = existsSync(file);
|
|
41
42
|
doc.mcpServers ??= {};
|
|
42
43
|
const before = JSON.stringify(doc.mcpServers["pantheon"] ?? null);
|
|
43
|
-
doc.mcpServers["pantheon"] = { ...shape, ...entry, env: {} };
|
|
44
|
+
doc.mcpServers["pantheon"] = { ...shape, ...entry, env: shape.env ?? {} };
|
|
44
45
|
if (before === JSON.stringify(doc.mcpServers["pantheon"]))
|
|
45
46
|
return "unchanged";
|
|
46
47
|
mkdirSync(dirname(file), { recursive: true });
|
|
@@ -61,6 +62,12 @@ export function mergeVsCodeMcpJson(file, entry) {
|
|
|
61
62
|
return existed ? "updated" : "created";
|
|
62
63
|
}
|
|
63
64
|
export const copilotMcpConfigFile = (home = homedir(), env = process.env) => join(env.COPILOT_HOME || join(home, ".copilot"), "mcp-config.json");
|
|
65
|
+
export { kiroDir };
|
|
66
|
+
export const kiroMcpConfigFile = (home = homedir(), env = process.env) => join(kiroDir(home, env), "settings", "mcp.json");
|
|
67
|
+
export const kiroEntryShape = (approval) => ({
|
|
68
|
+
autoApprove: toolsAllowed(approval),
|
|
69
|
+
env: { PANTHEON_AGENT: "kiro" },
|
|
70
|
+
});
|
|
64
71
|
export function vsCodeUserDir(home = homedir(), os = platform(), env = process.env) {
|
|
65
72
|
const dir = os === "darwin" ? join(home, "Library", "Application Support", "Code", "User")
|
|
66
73
|
: os === "win32" ? join(env.APPDATA || join(home, "AppData", "Roaming"), "Code", "User")
|
|
@@ -131,6 +138,9 @@ export function install(projectDir, cfg, force, os = platform(), claudeLocal = c
|
|
|
131
138
|
if (existsSync(p) || f === "AGENTS.md")
|
|
132
139
|
out.push(` ${f.padEnd(25)} ${upsertBrief(p, cfg)} (the collaboration brief)`);
|
|
133
140
|
}
|
|
141
|
+
if (existsSync(kiroDir()) || onPathNamed("kiro-cli")) {
|
|
142
|
+
out.push(` .kiro/settings/mcp.json ${"not used".padEnd(9)} Kiro reads ~/.kiro/settings/mcp.json, which pantheon join wrote — a repo copy would override each teammate's own (kiro)`);
|
|
143
|
+
}
|
|
134
144
|
out.push("");
|
|
135
145
|
out.push(" Copilot asks once to trust this folder before it reads the repo's MCP servers and hooks.");
|
|
136
146
|
out.push(" Codex reads ~/.codex/config.toml, which `pantheon join` already wrote at user scope.");
|
|
@@ -251,6 +261,21 @@ function copilotUserScope(entry) {
|
|
|
251
261
|
return { tool, where: file, status: `failed: ${e.message}` };
|
|
252
262
|
}
|
|
253
263
|
}
|
|
264
|
+
function kiroUserScope(entry, approval) {
|
|
265
|
+
const tool = "kiro";
|
|
266
|
+
const file = kiroMcpConfigFile();
|
|
267
|
+
if (!existsSync(kiroDir()) && !onPathNamed("kiro-cli"))
|
|
268
|
+
return { tool, where: "-", status: "skipped (kiro-cli not on PATH)" };
|
|
269
|
+
try {
|
|
270
|
+
const status = mergeMcpJson(file, entry, kiroEntryShape(approval));
|
|
271
|
+
const agent = refreshKiroAgentAllowed(kiroAgentFile(kiroDir()), toolsAllowed(approval));
|
|
272
|
+
const which = approval === "ask-before-sending" ? "reads run without asking; send, open_goal and claim prompt" : `the ${PANTHEON_TOOL_NAMES.length} pantheon tools run without asking`;
|
|
273
|
+
return { tool, where: file, status: `${status} — in the Kiro IDE, ${which}${agent === "absent" ? "; the CLI asks per call unless you use the pantheon agent (pantheon hook install)" : `; in the CLI's pantheon agent too (${agent})`}` };
|
|
274
|
+
}
|
|
275
|
+
catch (e) {
|
|
276
|
+
return { tool, where: file, status: `failed: ${e.message}` };
|
|
277
|
+
}
|
|
278
|
+
}
|
|
254
279
|
function vsCodeUserScope(entry) {
|
|
255
280
|
const tool = "vs code";
|
|
256
281
|
const dir = vsCodeUserDir();
|
|
@@ -268,7 +293,7 @@ export const CODEX_APPROVAL_LINE = `default_tools_approval_mode = "approve"`;
|
|
|
268
293
|
export function approvalSummary(approval) {
|
|
269
294
|
return approval === "ask-before-sending"
|
|
270
295
|
? `your agent asks you before each send, goal or claim in your name; reading and waiting run without a prompt. Unattended runs keep their own per-run approvals — this governs the agent you sit in front of`
|
|
271
|
-
: `pantheon's ${PANTHEON_TOOL_NAMES.length} tools run without a per-call prompt in Claude Code, Cursor and
|
|
296
|
+
: `pantheon's ${PANTHEON_TOOL_NAMES.length} tools run without a per-call prompt in Claude Code, Cursor, Codex, the Kiro IDE and Kiro's pantheon agent — your agent coordinates for you without stopping to ask. Copilot has no allow-list file: it asks once per tool per folder and remembers your answer; Kiro's default CLI agent asks per call`;
|
|
272
297
|
}
|
|
273
298
|
export function codexTomlBlock(entry, env = { PANTHEON_AGENT: "codex" }, approval = "approve") {
|
|
274
299
|
const envToml = Object.entries(env)
|
|
@@ -466,7 +491,7 @@ export function installForUser(approval = chosenApproval()) {
|
|
|
466
491
|
const entry = userEntry();
|
|
467
492
|
return [
|
|
468
493
|
claudeUserScope(entry), cursorUserScope(entry), codexUserScope(entry, approval),
|
|
469
|
-
copilotUserScope(entry), vsCodeUserScope(entry),
|
|
494
|
+
copilotUserScope(entry), vsCodeUserScope(entry), kiroUserScope(entry, approval),
|
|
470
495
|
...grantToolPermissions(approval).map((g) => ({
|
|
471
496
|
tool: g.tool,
|
|
472
497
|
where: g.tool.startsWith("claude") ? claudeSettingsFile() : cursorCliConfigFile(),
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
export const kiroDir = (home = homedir(), env = process.env) => env.KIRO_HOME || join(home, ".kiro");
|
|
5
|
+
export const KIRO_AGENT_NAME = "pantheon";
|
|
6
|
+
export const kiroAgentFile = (kiroBase) => join(kiroBase, "agents", `${KIRO_AGENT_NAME}.json`);
|
|
7
|
+
export const isOurKiroHook = (cmd) => !!cmd && /\bhook\s+kiro-(?:prompt|stop)\b/.test(cmd);
|
|
8
|
+
export const kiroAllowed = (tools) => tools.map((t) => `@pantheon/${t}`);
|
|
9
|
+
export function kiroAgentDoc(bin, allowed) {
|
|
10
|
+
return {
|
|
11
|
+
name: KIRO_AGENT_NAME,
|
|
12
|
+
description: "Kiro's default agent, plus Pantheon: its tools run without asking, and a turn is held open while a teammate is waiting on you",
|
|
13
|
+
tools: ["*"],
|
|
14
|
+
allowedTools: kiroAllowed(allowed),
|
|
15
|
+
includeMcpJson: true,
|
|
16
|
+
hooks: {
|
|
17
|
+
userPromptSubmit: [{ command: `${bin} hook kiro-prompt >/dev/null 2>&1` }],
|
|
18
|
+
stop: [{ command: `${bin} hook kiro-stop` }],
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function readKiroAgent(file) {
|
|
23
|
+
if (!existsSync(file))
|
|
24
|
+
return null;
|
|
25
|
+
try {
|
|
26
|
+
const doc = JSON.parse(readFileSync(file, "utf8"));
|
|
27
|
+
const events = Object.entries(doc.hooks ?? {}).filter(([, es]) => (es ?? []).some((h) => isOurKiroHook(h.command))).map(([e]) => e);
|
|
28
|
+
return { doc, ours: events.length > 0, events };
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return { doc: {}, ours: false, events: [] };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function writeKiroAgent(file, doc) {
|
|
35
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
36
|
+
writeFileSync(file, JSON.stringify(doc, null, 2) + "\n");
|
|
37
|
+
}
|
|
38
|
+
export function refreshKiroAgentAllowed(file, allowed) {
|
|
39
|
+
const a = readKiroAgent(file);
|
|
40
|
+
if (!a?.ours)
|
|
41
|
+
return "absent";
|
|
42
|
+
const want = kiroAllowed(allowed);
|
|
43
|
+
if (JSON.stringify(a.doc.allowedTools) === JSON.stringify(want))
|
|
44
|
+
return "unchanged";
|
|
45
|
+
writeKiroAgent(file, { ...a.doc, allowedTools: want });
|
|
46
|
+
return "updated";
|
|
47
|
+
}
|
|
48
|
+
export function kiroDefaultIsPantheon(base = kiroDir()) {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(readFileSync(join(base, "settings", "cli.json"), "utf8"))["chat.defaultAgent"] === KIRO_AGENT_NAME;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=kiro-files.js.map
|
package/dist/ledger.js
CHANGED
|
@@ -35,6 +35,9 @@ export class Ledger {
|
|
|
35
35
|
since(seq, limit = 500) {
|
|
36
36
|
return this.records.filter((r) => r.seq > seq).slice(0, limit);
|
|
37
37
|
}
|
|
38
|
+
latest(limit = 500) {
|
|
39
|
+
return this.records.slice(Math.max(0, this.records.length - limit));
|
|
40
|
+
}
|
|
38
41
|
byGoal(goal, limit = 200) {
|
|
39
42
|
const hits = this.records.filter((r) => r.goal === goal);
|
|
40
43
|
return hits.slice(Math.max(0, hits.length - limit));
|
package/dist/machine.js
CHANGED
|
@@ -4,7 +4,7 @@ import { homedir, platform } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { CONFIG_DIR } from "./config.js";
|
|
6
6
|
import { plistPath } from "./service.js";
|
|
7
|
-
import { copilotMcpConfigFile, vsCodeUserDir } from "./install.js";
|
|
7
|
+
import { copilotMcpConfigFile, vsCodeUserDir, kiroMcpConfigFile, kiroDir } from "./install.js";
|
|
8
8
|
const sh = (cmd) => {
|
|
9
9
|
try {
|
|
10
10
|
return execFileSync("sh", ["-lc", cmd], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
|
|
@@ -120,6 +120,10 @@ export function wiring() {
|
|
|
120
120
|
const f = join(vscodeDir, "mcp.json");
|
|
121
121
|
out.push({ tool: "vs code", state: existsSync(f) && /"pantheon"/.test(readFileSync(f, "utf8")) ? "wired" : "not wired" });
|
|
122
122
|
}
|
|
123
|
+
if (existsSync(kiroDir())) {
|
|
124
|
+
const f = kiroMcpConfigFile();
|
|
125
|
+
out.push({ tool: "kiro", state: existsSync(f) && /"pantheon"/.test(readFileSync(f, "utf8")) ? "wired" : "not wired" });
|
|
126
|
+
}
|
|
123
127
|
return out;
|
|
124
128
|
}
|
|
125
129
|
export function logFileFor(kind, proc) {
|
package/dist/mcp.js
CHANGED
|
@@ -473,6 +473,7 @@ export async function runMcp() {
|
|
|
473
473
|
url.pathname = "/history";
|
|
474
474
|
url.searchParams.set("space", cfg.space);
|
|
475
475
|
url.searchParams.set("token", cfg.token);
|
|
476
|
+
url.searchParams.set("goal", goal);
|
|
476
477
|
return fetch(url).then(async (res) => {
|
|
477
478
|
const { acts } = (await res.json());
|
|
478
479
|
const hits = acts.filter((a) => a.goal === goal).slice(-(limit ?? 60));
|
package/dist/measure.js
CHANGED
|
@@ -3,7 +3,7 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { resolve, dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { claudeGrantedEffective, cursorGrantedEffective, claudeAllow, cursorAllow, isServerWide } from "./grants.js";
|
|
6
|
-
import { codexApprovalState, copilotMcpConfigFile } from "./install.js";
|
|
6
|
+
import { codexApprovalState, copilotMcpConfigFile, kiroMcpConfigFile } from "./install.js";
|
|
7
7
|
import { existsSync as fileExists, readFileSync } from "node:fs";
|
|
8
8
|
import { dirname as dirOf } from "node:path";
|
|
9
9
|
import { PANTHEON_TOOL_NAMES, WRITE_TOOL_NAMES } from "./tools.js";
|
|
@@ -161,13 +161,41 @@ export function toolsReport(registered, seen, approval, opts = {}) {
|
|
|
161
161
|
}
|
|
162
162
|
const copilot = seen.copilot === undefined || seen.copilot === null ? null
|
|
163
163
|
: seen.copilot ? "Copilot wired (it asks once per tool per folder and remembers)" : "Copilot has no pantheon entry";
|
|
164
|
-
|
|
164
|
+
let kiro = null;
|
|
165
|
+
if (seen.kiro === "absent")
|
|
166
|
+
kiro = "Kiro has no pantheon entry";
|
|
167
|
+
else if (Array.isArray(seen.kiro)) {
|
|
168
|
+
const want = [...(approval === "ask-before-sending" ? PANTHEON_TOOL_NAMES.filter((t) => !WRITE_TOOL_NAMES.includes(t)) : PANTHEON_TOOL_NAMES)].sort();
|
|
169
|
+
const have = [...new Set(seen.kiro)].filter((t) => t.startsWith("pantheon_")).sort();
|
|
170
|
+
const extra = have.filter((t) => !want.includes(t)), missing = want.filter((t) => !have.includes(t));
|
|
171
|
+
if (!extra.length && !missing.length)
|
|
172
|
+
kiro = `Kiro IDE allows ${have.length}`;
|
|
173
|
+
else {
|
|
174
|
+
kiro = `Kiro IDE allows ${have.length}${missing.length ? `, missing ${missing.map((t) => t.replace(/^pantheon_/, "")).join(", ")}` : ""}${extra.length ? `, and allows ${extra.map((t) => t.replace(/^pantheon_/, "")).join(", ")} you chose to be asked about` : ""}`;
|
|
175
|
+
problems.push("Kiro's autoApprove does not match");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const detail = `${head}; ${claude.text}, ${cursor.text}, ${codex}${copilot ? `, ${copilot}` : ""}${kiro ? `, ${kiro}` : ""}`;
|
|
165
179
|
if (!problems.length)
|
|
166
180
|
return { level: "pass", detail };
|
|
167
181
|
return { level: "warn", detail, fix: `${problems.join("; ")} — pantheon install --user rewrites every grant in your own settings to match this build and your approval choice; a rule in this repo's .claude/settings*.json or .cursor/cli.json is yours to edit by hand` };
|
|
168
182
|
}
|
|
169
183
|
export function grantsSeen(projectDir = null) {
|
|
170
|
-
return { claude: claudeGrantedEffective(projectDir), cursor: cursorGrantedEffective(projectDir), codex: codexApprovalState(), copilot: copilotWired() };
|
|
184
|
+
return { claude: claudeGrantedEffective(projectDir), cursor: cursorGrantedEffective(projectDir), codex: codexApprovalState(), copilot: copilotWired(), kiro: kiroApproved() };
|
|
185
|
+
}
|
|
186
|
+
export function kiroApproved(file = kiroMcpConfigFile()) {
|
|
187
|
+
if (!fileExists(dirOf(dirOf(file))))
|
|
188
|
+
return null;
|
|
189
|
+
try {
|
|
190
|
+
const doc = JSON.parse(readFileSync(file, "utf8"));
|
|
191
|
+
const entry = doc.mcpServers?.["pantheon"];
|
|
192
|
+
if (!entry)
|
|
193
|
+
return "absent";
|
|
194
|
+
return Array.isArray(entry.autoApprove) ? entry.autoApprove.filter((t) => typeof t === "string") : [];
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return "absent";
|
|
198
|
+
}
|
|
171
199
|
}
|
|
172
200
|
export function copilotWired(file = copilotMcpConfigFile()) {
|
|
173
201
|
if (!fileExists(dirOf(file)))
|
package/dist/notice.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { decisionPrompt, decisionTitle } from "./decision-ui.js";
|
|
2
|
-
export const TOOL_LABELS = { "claude-code": "Claude Code", codex: "Codex", cursor: "Cursor", copilot: "Copilot", grok: "Grok" };
|
|
2
|
+
export const TOOL_LABELS = { "claude-code": "Claude Code", codex: "Codex", cursor: "Cursor", copilot: "Copilot", kiro: "Kiro", grok: "Grok" };
|
|
3
3
|
export const APP_TOOLS = [
|
|
4
4
|
{ id: "cursor-app", label: "Cursor app", kind: "app", bundle: "com.todesktop.230313mzl4w4u92", deeplink: "cursor://anysphere.cursor-deeplink/prompt?text={prompt}" },
|
|
5
5
|
{ id: "claude-app", label: "Claude app · question copied", kind: "app", bundle: "com.anthropic.claudefordesktop" },
|
|
@@ -67,6 +67,8 @@ const agentLabel = (agent) => {
|
|
|
67
67
|
return "Cursor";
|
|
68
68
|
if (a.includes("copilot"))
|
|
69
69
|
return "Copilot";
|
|
70
|
+
if (a.includes("kiro"))
|
|
71
|
+
return "Kiro";
|
|
70
72
|
return null;
|
|
71
73
|
};
|
|
72
74
|
function whoLine(from, agent, ts, now) {
|
package/dist/notify.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import { chosenApproval } from "./install.js";
|
|
2
|
+
import { toolsAllowed } from "./tools.js";
|
|
1
3
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from "node:fs";
|
|
2
4
|
import { homedir, platform } from "node:os";
|
|
3
5
|
import { dirname, join, resolve } from "node:path";
|
|
4
6
|
import { fileURLToPath } from "node:url";
|
|
5
7
|
import { whichPantheon } from "./service.js";
|
|
6
8
|
import { detectForHooks, agentById } from "./agents.js";
|
|
7
|
-
import { codexHookState, cursorHookState, copilotHookState, installCodexHooks, installCursorHooks, installCopilotHooks, removeCodexHooks, removeCursorHooks, removeCopilotHooks, } from "./hooks-install.js";
|
|
9
|
+
import { codexHookState, cursorHookState, copilotHookState, kiroHookState, installCodexHooks, installCursorHooks, installCopilotHooks, installKiroHooks, removeCodexHooks, removeCursorHooks, removeCopilotHooks, removeKiroHooks, } from "./hooks-install.js";
|
|
8
10
|
export const HOOK_MARKER = "pantheon hook";
|
|
9
|
-
export const HOOK_RE = /(?:pantheon(?:\.cmd|\.exe|\.bat)?"?\s+hook\s|cli\.js"?\s+hook\s+(?:stop|prompt|end|codex-stop|copilot-stop|cursor-stop)\b)/i;
|
|
11
|
+
export const HOOK_RE = /(?:pantheon(?:\.cmd|\.exe|\.bat)?"?\s+hook\s|cli\.js"?\s+hook\s+(?:stop|prompt|end|codex-stop|copilot-stop|cursor-stop|kiro-stop|kiro-prompt)\b)/i;
|
|
10
12
|
export const isOurHookCommand = (cmd) => !!cmd && (cmd.includes(HOOK_MARKER) || HOOK_RE.test(cmd));
|
|
11
13
|
export const hookBin = (bin) => (/\s/.test(bin) ? `"${bin}"` : bin);
|
|
12
14
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
@@ -108,6 +110,9 @@ export function agentHookStates(projectDir = process.cwd(), only) {
|
|
|
108
110
|
const copilot = copilotHookState(scope, projectDir);
|
|
109
111
|
if (copilot.installed)
|
|
110
112
|
out.push(copilot);
|
|
113
|
+
const kiro = kiroHookState(scope, projectDir);
|
|
114
|
+
if (kiro.installed)
|
|
115
|
+
out.push(kiro);
|
|
111
116
|
}
|
|
112
117
|
const byAgent = new Map();
|
|
113
118
|
for (const s of out) {
|
|
@@ -133,6 +138,8 @@ export function repairHook(home) {
|
|
|
133
138
|
missing.push(id);
|
|
134
139
|
if (id === "copilot" && !copilotHookState(intent.scope, projectDir).installed)
|
|
135
140
|
missing.push(id);
|
|
141
|
+
if (id === "kiro" && !kiroHookState(intent.scope, projectDir).installed)
|
|
142
|
+
missing.push(id);
|
|
136
143
|
}
|
|
137
144
|
if (!missing.length)
|
|
138
145
|
return null;
|
|
@@ -184,7 +191,7 @@ export function installHooks(scope, projectDir = process.cwd(), only) {
|
|
|
184
191
|
? only.map((id) => agentById(id)).filter((b) => !!b)
|
|
185
192
|
: available;
|
|
186
193
|
if (!pick.length) {
|
|
187
|
-
throw new Error("no agent found — install claude, codex, cursor-agent, or
|
|
194
|
+
throw new Error("no agent found — install claude, codex, cursor-agent, copilot, or kiro-cli, or open a project that already uses one");
|
|
188
195
|
}
|
|
189
196
|
const lines = [];
|
|
190
197
|
const agents = [];
|
|
@@ -206,6 +213,10 @@ export function installHooks(scope, projectDir = process.cwd(), only) {
|
|
|
206
213
|
lines.push(...installCopilotHooks(bin, scope, projectDir));
|
|
207
214
|
agents.push("copilot");
|
|
208
215
|
}
|
|
216
|
+
else if (backend.id === "kiro") {
|
|
217
|
+
lines.push(...installKiroHooks(bin, scope, projectDir, toolsAllowed(chosenApproval())));
|
|
218
|
+
agents.push("kiro");
|
|
219
|
+
}
|
|
209
220
|
}
|
|
210
221
|
lines.push(` effect when your agent tries to finish, it checks whether a peer is waiting`, ` on you — and keeps going instead of leaving them hanging.`, ` it also reports when a turn starts and ends, so a worker can tell`, ` an idle editor from one you are actually using.`);
|
|
211
222
|
return { lines, agents };
|
|
@@ -219,6 +230,7 @@ export function removeHook(projectDir = process.cwd()) {
|
|
|
219
230
|
out.push(...removeCodexHooks(projectDir));
|
|
220
231
|
out.push(...removeCursorHooks(projectDir));
|
|
221
232
|
out.push(...removeCopilotHooks(projectDir));
|
|
233
|
+
out.push(...removeKiroHooks(projectDir));
|
|
222
234
|
return out.length ? out : [" nothing installed"];
|
|
223
235
|
}
|
|
224
236
|
function removeClaudeHook(projectDir = process.cwd()) {
|
package/dist/persona.js
CHANGED
|
@@ -170,7 +170,7 @@ export const PERSONA_TEMPLATE = `---
|
|
|
170
170
|
# wake_by: alice, bob # only these peers' requests are handled by this persona
|
|
171
171
|
# kinds: ANSWER, ACCEPT # ANSWER (asks), ACCEPT (verdicts), REVISE (objections), CONTINUE (replies/deliveries)
|
|
172
172
|
# goals: launch-* # only goals whose id or title matches
|
|
173
|
-
# agent: auto # claude-code | codex | cursor | copilot | auto
|
|
173
|
+
# agent: auto # claude-code | codex | cursor | copilot | kiro | auto
|
|
174
174
|
---
|
|
175
175
|
Say how this worker should behave when it is woken while you are away — what it
|
|
176
176
|
should take on, what it should refuse, and where its judgment ends and yours
|
package/dist/profile.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { CONFIG_DIR } from "./config.js";
|
|
4
|
-
export const AGENT_SURFACES = ["codex", "claude-code", "cursor", "copilot"];
|
|
4
|
+
export const AGENT_SURFACES = ["codex", "claude-code", "cursor", "copilot", "kiro"];
|
|
5
5
|
export const SURFACE_BUNDLE_IDS = {
|
|
6
6
|
codex: "com.openai.codex",
|
|
7
7
|
"claude-code": "com.anthropic.claudefordesktop",
|
|
8
8
|
cursor: "com.todesktop.230313mzl4w4u92",
|
|
9
9
|
copilot: "com.microsoft.VSCode",
|
|
10
|
+
kiro: "com.amazon.codewhisperer",
|
|
10
11
|
};
|
|
11
12
|
export const profilePath = (home = CONFIG_DIR) => join(home, "profile.json");
|
|
12
13
|
export function isAgentSurface(value) {
|
|
@@ -17,6 +18,8 @@ export function surfaceFromAgent(agent) {
|
|
|
17
18
|
return "cursor";
|
|
18
19
|
if (agent && /copilot/i.test(agent))
|
|
19
20
|
return "copilot";
|
|
21
|
+
if (agent && /kiro/i.test(agent))
|
|
22
|
+
return "kiro";
|
|
20
23
|
if (agent && /claude/i.test(agent))
|
|
21
24
|
return "claude-code";
|
|
22
25
|
return "codex";
|
package/dist/relay.js
CHANGED
|
@@ -288,7 +288,10 @@ export class Relay {
|
|
|
288
288
|
const token = url.searchParams.get("token") ?? "";
|
|
289
289
|
if (!space || space.token !== token)
|
|
290
290
|
return this.json(res, 404, { error: "no such space" });
|
|
291
|
-
|
|
291
|
+
const goal = url.searchParams.get("goal");
|
|
292
|
+
const since = Number(url.searchParams.get("since") ?? 0);
|
|
293
|
+
const acts = goal ? space.ledger.byGoal(goal) : since > 0 ? space.ledger.since(since) : space.ledger.latest();
|
|
294
|
+
return this.json(res, 200, { acts, head: space.ledger.head });
|
|
292
295
|
}
|
|
293
296
|
if (url.pathname === "/obligations") {
|
|
294
297
|
const space = this.spaces.get(url.searchParams.get("space") ?? "");
|
|
@@ -2231,11 +2234,13 @@ pantheon signup --email you@work.com --username you</code></pre>
|
|
|
2231
2234
|
return { code: "GOAL_CLOSED", message: `goal "${goal.title}" is closed` };
|
|
2232
2235
|
if (!goal.members.includes(me.id))
|
|
2233
2236
|
return { code: "NOT_IN_GOAL", message: `you are not a member of "${goal.title}"` };
|
|
2234
|
-
|
|
2237
|
+
const settlesOwnHuman = act === "ANSWER" && !!ref
|
|
2238
|
+
&& [...sp.obligations.values()].some((o) => o.kind === "HUMAN" && o.ref === ref && o.owner === `human:${me.id}`);
|
|
2239
|
+
if (goal.acts >= goal.budget && !settlesOwnHuman) {
|
|
2235
2240
|
return { code: "BUDGET_SPENT", message: `goal "${goal.title}" spent its ${goal.budget}-act budget and needs a human` };
|
|
2236
2241
|
}
|
|
2237
2242
|
const run = sp.lastActor.get(goalId);
|
|
2238
|
-
if (goal.kind !== "worklist" && run && run.member === me.id && run.run >= LIMITS.consecutive && act !== "ESCALATE" && act !== "CLOSE") {
|
|
2243
|
+
if (!settlesOwnHuman && goal.kind !== "worklist" && run && run.member === me.id && run.run >= LIMITS.consecutive && act !== "ESCALATE" && act !== "CLOSE") {
|
|
2239
2244
|
return {
|
|
2240
2245
|
code: "PEER_TURN",
|
|
2241
2246
|
message: `you have sent ${run.run} acts in a row in "${goal.title}". Wait for a peer (pantheon_wait), or ESCALATE if nobody can move it.`,
|
package/dist/run-failure.js
CHANGED
|
@@ -5,6 +5,7 @@ export const SIGN_IN_FIX = {
|
|
|
5
5
|
codex: "run `codex login`",
|
|
6
6
|
cursor: "run `cursor-agent login`",
|
|
7
7
|
copilot: "run `copilot login` (or `gh auth login` — copilot uses gh's sign-in too)",
|
|
8
|
+
kiro: "run `kiro-cli login` (or set KIRO_API_KEY)",
|
|
8
9
|
};
|
|
9
10
|
const sh = (dir, ...args) => execFileSync("git", ["-C", dir, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5_000 }).trim();
|
|
10
11
|
export const gitProbe = {
|
package/dist/setup-guide.js
CHANGED
|
@@ -195,7 +195,7 @@ export async function runGuide(io, ctx, deps = {}, preset = {}) {
|
|
|
195
195
|
: "only people you approve by hand (pantheon admit <name> --may-wake)"}`);
|
|
196
196
|
io.say(` • Alerts: ${shortAlert(a.notifications).toLowerCase()}`);
|
|
197
197
|
io.say(` • Approval: ${a.approval === "approve"
|
|
198
|
-
? "your agent sends to teammates in your name without asking each time (Claude Code, Cursor and
|
|
198
|
+
? "your agent sends to teammates in your name without asking each time (Claude Code, Cursor, Codex, and Kiro's IDE and pantheon agent; Copilot asks once per tool per folder)"
|
|
199
199
|
: "your agent asks you before each send, goal and claim; reading and waiting never prompt"}`);
|
|
200
200
|
io.say(` • Decisions: ${a.decides.length ? `you personally decide ${a.decides.join(", ")}` : "none reserved for you — your agent may decide; add an area when one comes up"}`);
|
|
201
201
|
if (a.wake !== "no" && ctx.project)
|
package/dist/tray.js
CHANGED
|
@@ -131,20 +131,20 @@ async function trayStatusOne(cfg) {
|
|
|
131
131
|
const items = [
|
|
132
132
|
...mineHuman.map((o) => {
|
|
133
133
|
const from = senderOf(o);
|
|
134
|
-
return row(o, "decision", !from || from === cfg.member ? "your agent" : from, decisionPrompt(o.id, decisionTitle(o.body)));
|
|
134
|
+
return { ...row(o, "decision", !from || from === cfg.member ? "your agent" : from, decisionPrompt(o.id, decisionTitle(o.body))), decider: cfg.member };
|
|
135
135
|
}),
|
|
136
136
|
...owed.map((o) => row(o, "owed", "you", owedPrompt(decisionTitle(o.body), o.goal))),
|
|
137
137
|
...waiting.map((o) => {
|
|
138
138
|
const who = o.owner.replace(/^human:/, "");
|
|
139
139
|
const r = row(o, "waiting", who, waitingPrompt(who, decisionTitle(o.body), o.goal));
|
|
140
140
|
return o.owner.startsWith("human:")
|
|
141
|
-
? { ...r, title: `${who} decides: ${r.title}`, headline: `${who} decides: ${r.headline}
|
|
141
|
+
? { ...r, title: `${who} decides: ${r.title}`, headline: `${who} decides: ${r.headline}`, decider: who }
|
|
142
142
|
: r;
|
|
143
143
|
}),
|
|
144
144
|
];
|
|
145
145
|
const profile = loadProfile(cfg);
|
|
146
146
|
const surface = profile.agent.primary_surface;
|
|
147
|
-
const surfaceName = surface === "claude-code" ? "Claude" : surface === "cursor" ? "Cursor" : surface === "copilot" ? "Copilot" : "Codex";
|
|
147
|
+
const surfaceName = surface === "claude-code" ? "Claude" : surface === "cursor" ? "Cursor" : surface === "copilot" ? "Copilot" : surface === "kiro" ? "Kiro" : "Codex";
|
|
148
148
|
const rawWorkerState = workerState();
|
|
149
149
|
return {
|
|
150
150
|
configured: true,
|
package/dist/workdir.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
export function gitActivity(path) {
|
|
4
|
+
for (const f of ["index", "FETCH_HEAD", "HEAD"]) {
|
|
5
|
+
try {
|
|
6
|
+
return statSync(join(path, ".git", f)).mtimeMs;
|
|
7
|
+
}
|
|
8
|
+
catch { }
|
|
9
|
+
}
|
|
10
|
+
return 0;
|
|
11
|
+
}
|
|
12
|
+
export function pickWorkdir(bindings, hints, team, activity = gitActivity, exists = existsSync) {
|
|
13
|
+
const live = bindings.filter((b) => exists(b.path));
|
|
14
|
+
if (!live.length) {
|
|
15
|
+
return {
|
|
16
|
+
path: null,
|
|
17
|
+
note: `no folder on this machine is bound to "${team}", so this opens in your home folder. To fix it, run pantheon use ${team} --here inside the repo.`,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const named = live.filter((b) => hints.some((h) => (b.origin ? h.includes(b.origin) : false) || h.includes(b.path)));
|
|
21
|
+
const pool = named.length ? named : live;
|
|
22
|
+
const chosen = [...pool].sort((x, y) => (activity(y.path) - activity(x.path)) || (y.bound_at - x.bound_at))[0];
|
|
23
|
+
const others = live.filter((b) => b.path !== chosen.path).map((b) => basename(b.path));
|
|
24
|
+
const why = named.length ? "named in this goal" : live.length > 1 ? "your most recent git activity" : "";
|
|
25
|
+
return {
|
|
26
|
+
path: chosen.path,
|
|
27
|
+
note: `in ${basename(chosen.path)}${why ? ` (${why})` : ""}${others.length ? ` — "${team}" is also bound to ${others.join(", ")}` : ""}`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=workdir.js.map
|
package/dist/worker.js
CHANGED
|
@@ -4,7 +4,7 @@ import { mkdirSync, writeFileSync, existsSync, readFileSync } from "node:fs";
|
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
6
6
|
import { PantheonClient } from "./client.js";
|
|
7
|
-
import { headlessSpawn, materializeSpawnFiles, chooseAgent, describeChoice } from "./agents.js";
|
|
7
|
+
import { headlessSpawn, materializeSpawnFiles, chooseAgent, describeChoice, unattendedRefusal } from "./agents.js";
|
|
8
8
|
import { agentSpawn } from "./which.js";
|
|
9
9
|
import { VERSION } from "./version.js";
|
|
10
10
|
import { applyAnswer, loadPolicy, mayAsk, needsSelfUpgrade, recordAsk, savePolicy, useGrant, } from "./worker-policy.js";
|
|
@@ -604,6 +604,9 @@ export class Worker {
|
|
|
604
604
|
this.opts.log(` agent ${said}`);
|
|
605
605
|
this.lastAgentSaid = said;
|
|
606
606
|
}
|
|
607
|
+
const refused = unattendedRefusal(choice);
|
|
608
|
+
if (refused)
|
|
609
|
+
throw new Error(refused);
|
|
607
610
|
const plan = headlessSpawn({ backend: choice.backend, binary: choice.binary, prompt: brief, worktree, cfg: this.cfg, branch, allowBash, runHome });
|
|
608
611
|
materializeSpawnFiles(worktree, plan);
|
|
609
612
|
({ command, args, env } = { command: plan.command, args: plan.args, env: { ...env, ...plan.env } });
|