@bunshin-cc/bunshin 0.5.0 → 0.5.2
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/dist/bin.js +268 -54
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -953,17 +953,17 @@ var init_document = __esm({
|
|
|
953
953
|
});
|
|
954
954
|
|
|
955
955
|
// ../core/src/v2/sections/markdown.ts
|
|
956
|
-
function repoSectionId(repo,
|
|
957
|
-
return anchor ? `repo:${repo}/${
|
|
956
|
+
function repoSectionId(repo, path25, anchor = "") {
|
|
957
|
+
return anchor ? `repo:${repo}/${path25}#${anchor}` : `repo:${repo}/${path25}`;
|
|
958
958
|
}
|
|
959
|
-
function docKind(
|
|
960
|
-
const base =
|
|
959
|
+
function docKind(path25) {
|
|
960
|
+
const base = path25.split("/").pop()?.toLowerCase() ?? "";
|
|
961
961
|
if (base === "testing.md") return "verification";
|
|
962
962
|
if (base === "skill.md") return "skill";
|
|
963
963
|
return "spec";
|
|
964
964
|
}
|
|
965
|
-
function defaultPaths(
|
|
966
|
-
const dir =
|
|
965
|
+
function defaultPaths(path25) {
|
|
966
|
+
const dir = path25.split("/").slice(0, -1).join("/");
|
|
967
967
|
if (!dir || dir.startsWith(".claude/") || dir.startsWith(".codex/")) return [];
|
|
968
968
|
return [`${dir}/**`];
|
|
969
969
|
}
|
|
@@ -1108,11 +1108,11 @@ function lintSkills(sections) {
|
|
|
1108
1108
|
}
|
|
1109
1109
|
const state = /* @__PURE__ */ new Map();
|
|
1110
1110
|
const cycles = /* @__PURE__ */ new Set();
|
|
1111
|
-
const dfs = (n,
|
|
1111
|
+
const dfs = (n, path25) => {
|
|
1112
1112
|
state.set(n, 1);
|
|
1113
1113
|
for (const d of edges[n] ?? []) {
|
|
1114
|
-
if (state.get(d) === 1) cycles.add([...
|
|
1115
|
-
else if (!state.get(d)) dfs(d, [...
|
|
1114
|
+
if (state.get(d) === 1) cycles.add([...path25.slice(path25.indexOf(d)), d].join(" -> "));
|
|
1115
|
+
else if (!state.get(d)) dfs(d, [...path25, d]);
|
|
1116
1116
|
}
|
|
1117
1117
|
state.set(n, 2);
|
|
1118
1118
|
};
|
|
@@ -1199,8 +1199,8 @@ function parsePythonSkills(text2) {
|
|
|
1199
1199
|
}
|
|
1200
1200
|
return { skills, constants };
|
|
1201
1201
|
}
|
|
1202
|
-
function harnessSectionId(
|
|
1203
|
-
return `harness:${
|
|
1202
|
+
function harnessSectionId(path25, symbol) {
|
|
1203
|
+
return `harness:${path25}#${symbol}`;
|
|
1204
1204
|
}
|
|
1205
1205
|
var DEF_RE;
|
|
1206
1206
|
var init_python = __esm({
|
|
@@ -1326,8 +1326,8 @@ function globToRegExp(glob) {
|
|
|
1326
1326
|
globCache.set(glob, out);
|
|
1327
1327
|
return out;
|
|
1328
1328
|
}
|
|
1329
|
-
function matchesGlob(
|
|
1330
|
-
const p =
|
|
1329
|
+
function matchesGlob(path25, glob) {
|
|
1330
|
+
const p = path25.replace(/^\.\//, "").replace(/\\/g, "/");
|
|
1331
1331
|
return globToRegExp(glob.replace(/^\.\//, "")).test(p);
|
|
1332
1332
|
}
|
|
1333
1333
|
var DEFAULT_EMBEDDERS, globCache;
|
|
@@ -1418,10 +1418,10 @@ function push(sections, reader, query) {
|
|
|
1418
1418
|
for (const s of recallable(sections, reader)) {
|
|
1419
1419
|
const triggers = [...(s.triggers ?? []).filter((t) => t.anchor), { anchor: "", paths: s.paths, entities: s.entities, skills: s.skills }];
|
|
1420
1420
|
for (const t of triggers) {
|
|
1421
|
-
const
|
|
1422
|
-
const entity =
|
|
1423
|
-
const skill =
|
|
1424
|
-
const trigger =
|
|
1421
|
+
const path25 = paths.find((p) => t.paths.some((g) => matchesGlob(p, g)));
|
|
1422
|
+
const entity = path25 ? void 0 : t.entities.find((e) => entities.has(e) || text2 && text2.includes(e));
|
|
1423
|
+
const skill = path25 || entity ? void 0 : skills.find((k) => t.skills.includes(k));
|
|
1424
|
+
const trigger = path25 ? `path:${path25}` : entity ? `entity:${entity}` : skill ? `skill:${skill}` : "";
|
|
1425
1425
|
if (trigger) {
|
|
1426
1426
|
hits.set(s.id, { section: s, trigger, anchor: t.anchor });
|
|
1427
1427
|
break;
|
|
@@ -1666,6 +1666,7 @@ var init_redact = __esm({
|
|
|
1666
1666
|
{ kind: "aws-key", re: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|ANPA)[0-9A-Z]{16}\b/g },
|
|
1667
1667
|
{ kind: "github-pat", re: /\bgh[pousr]_[A-Za-z0-9]{20,}\b/g },
|
|
1668
1668
|
{ kind: "github-fine-pat", re: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g },
|
|
1669
|
+
{ kind: "bunshin-key", re: /\bbsk_[A-Za-z0-9]{8}_[A-Za-z0-9]{16,}\b/g },
|
|
1669
1670
|
{ kind: "anthropic-key", re: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g },
|
|
1670
1671
|
{ kind: "openai-key", re: /\bsk-(?:proj-|live-)?[A-Za-z0-9_-]{20,}\b/g },
|
|
1671
1672
|
{ kind: "slack-token", re: /\bxox[abposr]-[A-Za-z0-9-]{10,}\b/g },
|
|
@@ -5258,9 +5259,9 @@ function buildEpisode(store, session, transcriptPath, now = /* @__PURE__ */ new
|
|
|
5258
5259
|
const from = reflectCursor(session, turns);
|
|
5259
5260
|
const fresh = sinceTurn(scan, from);
|
|
5260
5261
|
const signals = { ...sessionSignals({ scan: fresh, startedAt: session.startedAt, lastActivityAt: session.lastActivityAt, partial: from > 0 }), ...outcome ? { outcome: outcome.status } : {} };
|
|
5261
|
-
const gate = sessionGate(signals, { now: now.toISOString(), cacheTtlMs: config.reflect.cacheTtlMs });
|
|
5262
5262
|
const id = episodeIdFor(view.ctx.id, session.sessionId);
|
|
5263
5263
|
const previous = v2.readEpisode(id);
|
|
5264
|
+
const gate = sessionGate(signals, { now: now.toISOString(), cacheTtlMs: config.reflect.cacheTtlMs });
|
|
5264
5265
|
const upload = config.upload ?? "redacted";
|
|
5265
5266
|
const calls = v2.readCalls(session.sessionId);
|
|
5266
5267
|
const snippets = [.../* @__PURE__ */ new Set([...v2.readSnippets(session.sessionId), ...scan.snippets])];
|
|
@@ -5279,7 +5280,8 @@ function buildEpisode(store, session, transcriptPath, now = /* @__PURE__ */ new
|
|
|
5279
5280
|
suggestionIds: [.../* @__PURE__ */ new Set([...previous?.suggestionIds ?? [], ...session.suggestionIds])],
|
|
5280
5281
|
// "edge" only while this machine's reflections cover every turn; new work is open again.
|
|
5281
5282
|
reflected: previous?.reflected === "edge" && from >= turns ? "edge" : "none",
|
|
5282
|
-
gate
|
|
5283
|
+
// Nothing new since the last reflection: the header keeps the gate that judged that work.
|
|
5284
|
+
gate: turns <= from && previous?.gate ? previous.gate : gate,
|
|
5283
5285
|
upload,
|
|
5284
5286
|
turns,
|
|
5285
5287
|
reflectedTurns: from,
|
|
@@ -8180,22 +8182,44 @@ Common flags: --api-url URL (default: this project's, else https://api.bunshin.c
|
|
|
8180
8182
|
// src/v2/runner.ts
|
|
8181
8183
|
var runner_exports = {};
|
|
8182
8184
|
__export(runner_exports, {
|
|
8185
|
+
LAUNCHD_LABEL: () => LAUNCHD_LABEL,
|
|
8183
8186
|
ROUTINE_PROMPT: () => ROUTINE_PROMPT,
|
|
8184
8187
|
cmdRunner: () => cmdRunner,
|
|
8185
8188
|
connection: () => connection,
|
|
8189
|
+
cronLine: () => cronLine,
|
|
8190
|
+
installPrompt: () => installPrompt,
|
|
8191
|
+
installSchedule: () => installSchedule,
|
|
8192
|
+
launchdPlist: () => launchdPlist,
|
|
8193
|
+
readInstalled: () => readInstalled,
|
|
8194
|
+
removeSchedule: () => removeSchedule,
|
|
8186
8195
|
renderJob: () => renderJob,
|
|
8187
8196
|
runOnce: () => runOnce,
|
|
8197
|
+
runnerHome: () => runnerHome,
|
|
8198
|
+
scheduledCommand: () => scheduledCommand,
|
|
8188
8199
|
setupText: () => setupText
|
|
8189
8200
|
});
|
|
8201
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
8190
8202
|
import fs23 from "node:fs";
|
|
8191
8203
|
import os5 from "node:os";
|
|
8204
|
+
import path20 from "node:path";
|
|
8205
|
+
function runnerHome(home = process.env.BUNSHIN_RUNNER_HOME || os5.homedir()) {
|
|
8206
|
+
return path20.join(home, ".bunshin", "runner");
|
|
8207
|
+
}
|
|
8208
|
+
function readInstalled(home) {
|
|
8209
|
+
try {
|
|
8210
|
+
return JSON.parse(fs23.readFileSync(path20.join(runnerHome(home), "config.json"), "utf8"));
|
|
8211
|
+
} catch {
|
|
8212
|
+
return null;
|
|
8213
|
+
}
|
|
8214
|
+
}
|
|
8192
8215
|
function connection(args, cwd) {
|
|
8193
|
-
const
|
|
8216
|
+
const saved = readInstalled();
|
|
8217
|
+
const store = saved && path20.resolve(cwd).startsWith(runnerHome()) ? null : Store.discover(cwd);
|
|
8194
8218
|
const config = store?.config();
|
|
8195
|
-
const key = process.env.BUNSHIN_RUNNER_KEY || process.env.BUNSHIN_API_KEY || config?.apiKey || "";
|
|
8196
|
-
const apiUrl = (str5(args.flags["api-url"])
|
|
8219
|
+
const key = str5(args.flags.key) || process.env.BUNSHIN_RUNNER_KEY || process.env.BUNSHIN_API_KEY || config?.apiKey || saved?.key || "";
|
|
8220
|
+
const apiUrl = (str5(args.flags["api-url"]) || process.env.BUNSHIN_API_URL || config?.apiUrl || saved?.apiUrl || DEFAULT_API).replace(/\/$/, "");
|
|
8197
8221
|
if (!key) throw new Error("no key: set BUNSHIN_RUNNER_KEY (create a runner key in the console: Runners \u2192 New runner key) or run inside a connected project");
|
|
8198
|
-
const name = str5(args.flags.name) ?? process.env.BUNSHIN_RUNNER_NAME ?? `${os5.hostname().split(".")[0] || "runner"}`;
|
|
8222
|
+
const name = str5(args.flags.name) ?? process.env.BUNSHIN_RUNNER_NAME ?? saved?.name ?? `${os5.hostname().split(".")[0] || "runner"}`;
|
|
8199
8223
|
return { apiUrl, key, name: name.replace(/[^\w.:@/-]/g, "-").slice(0, 80) };
|
|
8200
8224
|
}
|
|
8201
8225
|
async function call(c, method, route, body, timeoutMs = 6e4) {
|
|
@@ -8228,9 +8252,10 @@ async function lease(c, args, max = 1) {
|
|
|
8228
8252
|
return (await call(c, "POST", "/lease", { max, runner: c.name, ...kinds ? { kinds } : {} }, 12e4)).body.jobs;
|
|
8229
8253
|
}
|
|
8230
8254
|
function modelConfig(args) {
|
|
8231
|
-
const
|
|
8255
|
+
const saved = readInstalled();
|
|
8256
|
+
const backend = str5(args.flags.backend) ?? saved?.backend ?? "auto";
|
|
8232
8257
|
if (!["auto", "sdk", "claude-cli", "codex-cli", "pi-cli"].includes(backend)) throw new Error("--backend must be auto, sdk, claude-cli, codex-cli or pi-cli");
|
|
8233
|
-
const model = str5(args.flags.model) ?? process.env.BUNSHIN_MODEL;
|
|
8258
|
+
const model = str5(args.flags.model) ?? process.env.BUNSHIN_MODEL ?? saved?.model;
|
|
8234
8259
|
const harness = backend === "codex-cli" ? "codex" : backend === "pi-cli" ? "pi" : "claude-code";
|
|
8235
8260
|
return { ...DEFAULT_CONFIG, backend, harness, ...model ? { model } : {}, ...model && backend === "codex-cli" ? { codexModel: model } : {}, ...model && backend === "pi-cli" ? { piModel: model } : {} };
|
|
8236
8261
|
}
|
|
@@ -8243,7 +8268,7 @@ async function runOnce(c, args, out, err) {
|
|
|
8243
8268
|
const [job] = await lease(c, args);
|
|
8244
8269
|
if (!job) break;
|
|
8245
8270
|
const started = Date.now();
|
|
8246
|
-
const res = await askJson(config, { system: job.system, user: job.prompt, schema: job.schema, enforceSchema: false, timeoutMs: 15 * 6e4, maxTokens: 16e3 });
|
|
8271
|
+
const res = await askJson(config, { system: job.system, user: job.prompt, schema: job.schema, enforceSchema: false, timeoutMs: 15 * 6e4, maxTokens: 16e3, cwd: workDir() });
|
|
8247
8272
|
const model = config.backend === "codex-cli" ? config.codexModel ?? "codex default" : config.backend === "pi-cli" ? config.piModel ?? "pi default" : config.model;
|
|
8248
8273
|
if (!res.ok) {
|
|
8249
8274
|
const r2 = await call(c, "POST", `/jobs/${job.id}/fail`, { error: res.error.slice(0, 1900), model, runner: c.name });
|
|
@@ -8280,8 +8305,183 @@ If you cannot answer it, run: bunshin runner fail ${job.id} --error "<why>"
|
|
|
8280
8305
|
Then run \`bunshin runner next\` again until it says there are no jobs.
|
|
8281
8306
|
`;
|
|
8282
8307
|
}
|
|
8308
|
+
function workDir() {
|
|
8309
|
+
const dir = path20.join(runnerHome(), "work");
|
|
8310
|
+
fs23.mkdirSync(dir, { recursive: true });
|
|
8311
|
+
return dir;
|
|
8312
|
+
}
|
|
8313
|
+
function which(name) {
|
|
8314
|
+
for (const d of [...(process.env.PATH || "").split(path20.delimiter), path20.join(os5.homedir(), ".local", "bin")]) {
|
|
8315
|
+
if (d && fs23.existsSync(path20.join(d, name))) return path20.join(d, name);
|
|
8316
|
+
}
|
|
8317
|
+
return null;
|
|
8318
|
+
}
|
|
8319
|
+
function scheduledCommand(backend) {
|
|
8320
|
+
const node = path20.dirname(process.execPath);
|
|
8321
|
+
const npx = fs23.existsSync(path20.join(node, "npx")) ? path20.join(node, "npx") : which("npx") ?? "npx";
|
|
8322
|
+
const bin = BIN_OF[backend] ? which(BIN_OF[backend]) : null;
|
|
8323
|
+
const dirs = [.../* @__PURE__ */ new Set([node, ...bin ? [path20.dirname(bin)] : [], path20.join(os5.homedir(), ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"])];
|
|
8324
|
+
return { argv: [npx, "-y", PKG, "runner", "--once"], path: dirs.join(path20.delimiter) };
|
|
8325
|
+
}
|
|
8326
|
+
function launchdPlist(backend, everyMinutes, home = runnerHome()) {
|
|
8327
|
+
const cmd = scheduledCommand(backend);
|
|
8328
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
8329
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
8330
|
+
<plist version="1.0">
|
|
8331
|
+
<dict>
|
|
8332
|
+
<key>Label</key><string>${LAUNCHD_LABEL}</string>
|
|
8333
|
+
<key>ProgramArguments</key>
|
|
8334
|
+
<array>${cmd.argv.map((a) => `<string>${xml(a)}</string>`).join("")}</array>
|
|
8335
|
+
<key>EnvironmentVariables</key>
|
|
8336
|
+
<dict><key>PATH</key><string>${xml(cmd.path)}</string></dict>
|
|
8337
|
+
<key>WorkingDirectory</key><string>${xml(home)}</string>
|
|
8338
|
+
<key>StartInterval</key><integer>${everyMinutes * 60}</integer>
|
|
8339
|
+
<key>StandardOutPath</key><string>${xml(path20.join(home, "runner.log"))}</string>
|
|
8340
|
+
<key>StandardErrorPath</key><string>${xml(path20.join(home, "runner.log"))}</string>
|
|
8341
|
+
<key>ProcessType</key><string>Background</string>
|
|
8342
|
+
<key>LowPriorityIO</key><true/>
|
|
8343
|
+
</dict>
|
|
8344
|
+
</plist>
|
|
8345
|
+
`;
|
|
8346
|
+
}
|
|
8347
|
+
function cronLine(backend, everyMinutes, home = runnerHome()) {
|
|
8348
|
+
const cmd = scheduledCommand(backend);
|
|
8349
|
+
const when = everyMinutes >= 60 ? `17 */${Math.max(1, Math.round(everyMinutes / 60))} * * *` : `*/${everyMinutes} * * * *`;
|
|
8350
|
+
const q = (v) => `'${v.replace(/'/g, `'\\''`)}'`;
|
|
8351
|
+
return `${when} cd ${q(home)} && PATH=${q(cmd.path)} ${cmd.argv.map(q).join(" ")} >> ${q(path20.join(home, "runner.log"))} 2>&1 ${CRON_MARK}`;
|
|
8352
|
+
}
|
|
8353
|
+
function launchAgentsDir(opts) {
|
|
8354
|
+
return opts.launchAgents ?? path20.join(process.env.BUNSHIN_RUNNER_HOME || os5.homedir(), "Library", "LaunchAgents");
|
|
8355
|
+
}
|
|
8356
|
+
function installSchedule(backend, everyMinutes, opts = {}) {
|
|
8357
|
+
const platform = opts.platform ?? process.platform;
|
|
8358
|
+
const run2 = opts.run ?? realRun;
|
|
8359
|
+
if (platform === "darwin") {
|
|
8360
|
+
const dir = launchAgentsDir(opts);
|
|
8361
|
+
const plist = path20.join(dir, `${LAUNCHD_LABEL}.plist`);
|
|
8362
|
+
fs23.mkdirSync(dir, { recursive: true });
|
|
8363
|
+
fs23.writeFileSync(plist, launchdPlist(backend, everyMinutes));
|
|
8364
|
+
const domain = `gui/${process.getuid?.() ?? 501}`;
|
|
8365
|
+
run2("launchctl", ["bootout", `${domain}/${LAUNCHD_LABEL}`]);
|
|
8366
|
+
const r = run2("launchctl", ["bootstrap", domain, plist]);
|
|
8367
|
+
if (r.status !== 0) throw new Error(`launchctl bootstrap: ${(r.stderr || r.stdout).trim() || `exit ${r.status}`}`);
|
|
8368
|
+
return { kind: "launchd", where: plist, argv: scheduledCommand(backend).argv };
|
|
8369
|
+
}
|
|
8370
|
+
if (platform === "linux" || platform === "freebsd" || platform === "openbsd") {
|
|
8371
|
+
const current = run2("crontab", ["-l"]);
|
|
8372
|
+
const kept = (current.status === 0 ? current.stdout : "").split("\n").filter((l) => l.trim() && !l.includes(CRON_MARK));
|
|
8373
|
+
const r = run2("crontab", ["-"], [...kept, cronLine(backend, everyMinutes), ""].join("\n"));
|
|
8374
|
+
if (r.status !== 0) throw new Error(`crontab: ${(r.stderr || r.stdout).trim() || `exit ${r.status}`}`);
|
|
8375
|
+
return { kind: "cron", where: "crontab", argv: scheduledCommand(backend).argv };
|
|
8376
|
+
}
|
|
8377
|
+
return { kind: "none", where: "", argv: scheduledCommand(backend).argv };
|
|
8378
|
+
}
|
|
8379
|
+
function removeSchedule(opts = {}) {
|
|
8380
|
+
const platform = opts.platform ?? process.platform;
|
|
8381
|
+
const run2 = opts.run ?? realRun;
|
|
8382
|
+
if (platform === "darwin") {
|
|
8383
|
+
const plist = path20.join(launchAgentsDir(opts), `${LAUNCHD_LABEL}.plist`);
|
|
8384
|
+
run2("launchctl", ["bootout", `gui/${process.getuid?.() ?? 501}/${LAUNCHD_LABEL}`]);
|
|
8385
|
+
if (!fs23.existsSync(plist)) return "no schedule";
|
|
8386
|
+
fs23.rmSync(plist, { force: true });
|
|
8387
|
+
return `removed ${plist}`;
|
|
8388
|
+
}
|
|
8389
|
+
const current = run2("crontab", ["-l"]);
|
|
8390
|
+
if (current.status !== 0 || !current.stdout.includes(CRON_MARK)) return "no schedule";
|
|
8391
|
+
run2("crontab", ["-"], current.stdout.split("\n").filter((l) => !l.includes(CRON_MARK)).join("\n"));
|
|
8392
|
+
return "removed the crontab line";
|
|
8393
|
+
}
|
|
8394
|
+
async function install(args, cwd, out, err, opts = {}) {
|
|
8395
|
+
const c = connection(args, cwd);
|
|
8396
|
+
try {
|
|
8397
|
+
await call(c, "GET", "/status");
|
|
8398
|
+
} catch (e) {
|
|
8399
|
+
const m = e instanceof Error ? e.message : String(e);
|
|
8400
|
+
err(/40[13]/.test(m) ? `This key cannot answer model jobs. Create one in the console: Runners \u2192 New runner key (admins only).
|
|
8401
|
+
` : `Could not reach ${c.apiUrl}: ${m}
|
|
8402
|
+
`);
|
|
8403
|
+
return 1;
|
|
8404
|
+
}
|
|
8405
|
+
const config = modelConfig(args);
|
|
8406
|
+
const backend = resolveBackend(config);
|
|
8407
|
+
if (!backend) {
|
|
8408
|
+
err("No model on this computer: install Claude Code (or Codex, or pi), sign in, and paste the command again.\n");
|
|
8409
|
+
return 1;
|
|
8410
|
+
}
|
|
8411
|
+
const every = Math.max(15, Number(str5(args.flags.every) ?? 60) || 60);
|
|
8412
|
+
const home = runnerHome();
|
|
8413
|
+
fs23.mkdirSync(home, { recursive: true, mode: 448 });
|
|
8414
|
+
const saved = { apiUrl: c.apiUrl, key: c.key, backend, ...config.model && str5(args.flags.model) ? { model: config.model } : {}, name: c.name, every };
|
|
8415
|
+
fs23.writeFileSync(path20.join(home, "config.json"), JSON.stringify(saved, null, 2) + "\n", { mode: 384 });
|
|
8416
|
+
fs23.chmodSync(path20.join(home, "config.json"), 384);
|
|
8417
|
+
let schedule;
|
|
8418
|
+
try {
|
|
8419
|
+
schedule = installSchedule(backend, every, opts);
|
|
8420
|
+
} catch (e) {
|
|
8421
|
+
err(`Saved the runner, but could not schedule it: ${e instanceof Error ? e.message : String(e)}
|
|
8422
|
+
Run it yourself: npx -y ${PKG} runner --once
|
|
8423
|
+
`);
|
|
8424
|
+
return 1;
|
|
8425
|
+
}
|
|
8426
|
+
out(`\u2713 Runner "${c.name}" answers ${c.apiUrl}'s model jobs with ${BACKEND_NAME[backend] ?? backend} on this computer.
|
|
8427
|
+
`);
|
|
8428
|
+
out(schedule.kind === "none" ? ` No scheduler here: run \`npx -y ${PKG} runner --once\` on a schedule yourself.
|
|
8429
|
+
` : ` Every ${every} minutes while this computer is on (${schedule.kind}). Log: ${path20.join(home, "runner.log")}
|
|
8430
|
+
`);
|
|
8431
|
+
out(` Stop: npx -y ${PKG} runner uninstall
|
|
8432
|
+
`);
|
|
8433
|
+
if (args.flags.run === false) return 0;
|
|
8434
|
+
out(`
|
|
8435
|
+
Answering what waits now\u2026
|
|
8436
|
+
`);
|
|
8437
|
+
const r = await runOnce(c, args, out, err);
|
|
8438
|
+
out(r.done || r.failed ? `${r.done} answered, ${r.failed} failed.
|
|
8439
|
+
` : `Nothing waiting. The next nightly's work is answered on schedule.
|
|
8440
|
+
`);
|
|
8441
|
+
return r.failed && !r.done ? 1 : 0;
|
|
8442
|
+
}
|
|
8443
|
+
function uninstall(out, opts = {}) {
|
|
8444
|
+
out(`${removeSchedule(opts)}
|
|
8445
|
+
`);
|
|
8446
|
+
const file = path20.join(runnerHome(), "config.json");
|
|
8447
|
+
if (fs23.existsSync(file)) {
|
|
8448
|
+
fs23.rmSync(file, { force: true });
|
|
8449
|
+
out(`removed ${file} (revoke the key in the console: Agent keys)
|
|
8450
|
+
`);
|
|
8451
|
+
}
|
|
8452
|
+
return 0;
|
|
8453
|
+
}
|
|
8454
|
+
function installPrompt(key) {
|
|
8455
|
+
return `Set up this computer as my team's Bunshin runner: it answers Bunshin's queued model work (lessons
|
|
8456
|
+
from work sessions, merging the shared documents) with this computer's own Claude Code / Codex / pi
|
|
8457
|
+
sign-in, about once an hour while the computer is on.
|
|
8458
|
+
|
|
8459
|
+
1. Run: npx -y ${PKG} runner install --key ${key}
|
|
8460
|
+
2. If it says there is no model on this computer, check \`claude --version\` (or codex / pi). If none
|
|
8461
|
+
is installed or signed in, tell me the one command to install or sign in, wait for me, then run
|
|
8462
|
+
step 1 again.
|
|
8463
|
+
3. If it says the key cannot answer jobs or the API cannot be reached, stop and show me its message.
|
|
8464
|
+
4. Finish with what it printed last: how many jobs it answered and how to stop it.
|
|
8465
|
+
|
|
8466
|
+
Do not change any repository or other files.`;
|
|
8467
|
+
}
|
|
8283
8468
|
function setupText(target) {
|
|
8284
8469
|
switch (target) {
|
|
8470
|
+
case "this-computer":
|
|
8471
|
+
return `This computer (Claude Code, Codex or pi signed in): one paste
|
|
8472
|
+
|
|
8473
|
+
1. Console \u2192 Runners \u2192 New runner key. The page shows this command with the key in it:
|
|
8474
|
+
|
|
8475
|
+
npx -y ${PKG} runner install --key <runner key>
|
|
8476
|
+
|
|
8477
|
+
It checks the key, picks the model this computer is signed in to, answers every hour while the
|
|
8478
|
+
computer is on (launchd on macOS, cron on Linux) and answers what waits now.
|
|
8479
|
+
Stop: npx -y ${PKG} runner uninstall
|
|
8480
|
+
|
|
8481
|
+
Or paste this into Claude Code (or Codex, or pi) on that computer, and it does the rest:
|
|
8482
|
+
|
|
8483
|
+
${installPrompt("<runner key>").replace(/^/gm, " ")}
|
|
8484
|
+
`;
|
|
8285
8485
|
case "claude-routine":
|
|
8286
8486
|
return `Claude Code routine (claude.ai/code \u2192 Routines, or /schedule in Claude Code)
|
|
8287
8487
|
|
|
@@ -8349,7 +8549,7 @@ CMD ["bunshin", "runner", "--watch", "--backend", "claude-cli", "--model", "clau
|
|
|
8349
8549
|
17 3 * * * BUNSHIN_RUNNER_KEY=<key> bunshin runner --once --backend claude-cli --model "claude-opus-5-5[1m]" >> ~/.bunshin/runner.log 2>&1
|
|
8350
8550
|
`;
|
|
8351
8551
|
default:
|
|
8352
|
-
return `bunshin runner setup <target>: claude-routine | codex-cloud | github-action | docker | cron
|
|
8552
|
+
return `bunshin runner setup <target>: this-computer | claude-routine | codex-cloud | github-action | docker | cron
|
|
8353
8553
|
|
|
8354
8554
|
Or give the server a key and it runs model work itself (wrangler secret put \u2026):
|
|
8355
8555
|
ANTHROPIC_API_KEY (+ BUNSHIN_MODEL, default claude-opus-5-5)
|
|
@@ -8358,12 +8558,14 @@ Or give the server a key and it runs model work itself (wrangler secret put \u20
|
|
|
8358
8558
|
`;
|
|
8359
8559
|
}
|
|
8360
8560
|
}
|
|
8361
|
-
async function cmdRunner(args, cwd, out, err) {
|
|
8561
|
+
async function cmdRunner(args, cwd, out, err, opts = {}) {
|
|
8362
8562
|
const sub = args._[1] ?? "";
|
|
8363
8563
|
if (sub === "setup") {
|
|
8364
8564
|
out(setupText(args._[2] ?? ""));
|
|
8365
8565
|
return 0;
|
|
8366
8566
|
}
|
|
8567
|
+
if (sub === "install") return install(args, cwd, out, err, opts);
|
|
8568
|
+
if (sub === "uninstall") return uninstall(out, opts);
|
|
8367
8569
|
const c = connection(args, cwd);
|
|
8368
8570
|
if (sub === "status") {
|
|
8369
8571
|
const s = (await call(c, "GET", "/status")).body;
|
|
@@ -8409,7 +8611,7 @@ async function cmdRunner(args, cwd, out, err) {
|
|
|
8409
8611
|
`);
|
|
8410
8612
|
return 0;
|
|
8411
8613
|
}
|
|
8412
|
-
if (sub && sub !== "run") throw new Error(`unknown runner command "${sub}" (run, next, submit, fail, status, setup)`);
|
|
8614
|
+
if (sub && sub !== "run") throw new Error(`unknown runner command "${sub}" (run, next, submit, fail, status, install, uninstall, setup)`);
|
|
8413
8615
|
const watch = args.flags.watch === true;
|
|
8414
8616
|
const interval = Number(str5(args.flags.interval) ?? 300) * 1e3;
|
|
8415
8617
|
for (; ; ) {
|
|
@@ -8422,7 +8624,7 @@ async function cmdRunner(args, cwd, out, err) {
|
|
|
8422
8624
|
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
8423
8625
|
}
|
|
8424
8626
|
}
|
|
8425
|
-
var DEFAULT_API, KINDS2, str5, NPX, ROUTINE_PROMPT;
|
|
8627
|
+
var DEFAULT_API, KINDS2, str5, LAUNCHD_LABEL, CRON_MARK, PKG, BIN_OF, xml, realRun, BACKEND_NAME, NPX, ROUTINE_PROMPT;
|
|
8426
8628
|
var init_runner = __esm({
|
|
8427
8629
|
"src/v2/runner.ts"() {
|
|
8428
8630
|
"use strict";
|
|
@@ -8431,6 +8633,16 @@ var init_runner = __esm({
|
|
|
8431
8633
|
DEFAULT_API = "https://api.bunshin.cc";
|
|
8432
8634
|
KINDS2 = ["reflect", "merge", "create"];
|
|
8433
8635
|
str5 = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
|
|
8636
|
+
LAUNCHD_LABEL = "cc.bunshin.runner";
|
|
8637
|
+
CRON_MARK = "# bunshin-runner";
|
|
8638
|
+
PKG = "@bunshin-cc/bunshin@latest";
|
|
8639
|
+
BIN_OF = { "claude-cli": "claude", "codex-cli": "codex", "pi-cli": "pi" };
|
|
8640
|
+
xml = (v) => v.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
8641
|
+
realRun = (cmd, args, input) => {
|
|
8642
|
+
const r = spawnSync3(cmd, args, { encoding: "utf8", ...input !== void 0 ? { input } : {} });
|
|
8643
|
+
return { status: r.status, stdout: r.stdout ?? "", stderr: r.stderr ?? r.error?.message ?? "" };
|
|
8644
|
+
};
|
|
8645
|
+
BACKEND_NAME = { sdk: "the Anthropic API key in this environment", "claude-cli": "Claude Code", "codex-cli": "Codex", "pi-cli": "pi" };
|
|
8434
8646
|
NPX = "npx -y @bunshin-cc/bunshin@latest";
|
|
8435
8647
|
ROUTINE_PROMPT = `You are this company's Bunshin runner. Bunshin queues model work (reflecting on work sessions,
|
|
8436
8648
|
reconciling the shared documents); you answer it, then stop.
|
|
@@ -8447,7 +8659,7 @@ reconciling the shared documents); you answer it, then stop.
|
|
|
8447
8659
|
// src/v2/importer.ts
|
|
8448
8660
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
8449
8661
|
import fs24 from "node:fs";
|
|
8450
|
-
import
|
|
8662
|
+
import path21 from "node:path";
|
|
8451
8663
|
function rewriteRepoPaths(text2, repoName2, pack) {
|
|
8452
8664
|
const re = new RegExp(`(?<=^|[\\s\`'"(=:])(?:~|\\$\\{?[A-Za-z_][A-Za-z0-9_]*\\}?)?(?:/[\\w.@+-]+)*/${escapeRe(repoName2)}(?=[/\\s\`'")\\]]|[.,;:](?:\\s|$)|$)`, "gm");
|
|
8453
8665
|
return text2.replace(re, `~/.bunshin/library/${pack}`);
|
|
@@ -8486,7 +8698,7 @@ function buildDocs(files, opts) {
|
|
|
8486
8698
|
const main2 = `${dir}/SKILL.md`;
|
|
8487
8699
|
const text2 = rw(files.get(main2).content);
|
|
8488
8700
|
const fm = frontmatterDescription(text2);
|
|
8489
|
-
const name =
|
|
8701
|
+
const name = path21.posix.basename(dir);
|
|
8490
8702
|
const bundle = {};
|
|
8491
8703
|
for (const p of all.filter((x) => x.startsWith(`${dir}/`))) {
|
|
8492
8704
|
claimed.add(p);
|
|
@@ -8587,11 +8799,11 @@ function workingTreeFiles(root) {
|
|
|
8587
8799
|
return fs24.readdirSync(dir, { withFileTypes: true }).flatMap((e) => {
|
|
8588
8800
|
if (e.name === ".git" || e.name === "node_modules") return [];
|
|
8589
8801
|
const rel = base ? `${base}/${e.name}` : e.name;
|
|
8590
|
-
return e.isDirectory() ? walk(
|
|
8802
|
+
return e.isDirectory() ? walk(path21.join(dir, e.name), rel) : e.isFile() ? [rel] : [];
|
|
8591
8803
|
});
|
|
8592
8804
|
})(root);
|
|
8593
8805
|
for (const rel of list2) {
|
|
8594
|
-
const abs =
|
|
8806
|
+
const abs = path21.join(root, rel);
|
|
8595
8807
|
let buf;
|
|
8596
8808
|
try {
|
|
8597
8809
|
buf = fs24.readFileSync(abs);
|
|
@@ -8633,7 +8845,7 @@ function diffDocs(prev, next) {
|
|
|
8633
8845
|
return { docs: next.filter((d) => before.get(d.path) !== fingerprint(d)), removed: [...before.keys()].filter((p) => !after.has(p)) };
|
|
8634
8846
|
}
|
|
8635
8847
|
function readSkillSystem(root, opts) {
|
|
8636
|
-
const repoName2 =
|
|
8848
|
+
const repoName2 = path21.basename(path21.resolve(root));
|
|
8637
8849
|
const build = (files2) => buildDocs(files2, { pack: opts.pack, repoName: repoName2, ...opts.rewrite === false ? { rewrite: false } : {} });
|
|
8638
8850
|
const tree = build(workingTreeFiles(root));
|
|
8639
8851
|
if (!opts.history || !isGitRepo(root)) return { versions: [{ summary: `import ${opts.pack}`, docs: tree, removed: [], complete: true }], docs: tree };
|
|
@@ -8710,7 +8922,7 @@ var init_importer = __esm({
|
|
|
8710
8922
|
escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8711
8923
|
looksGenerated = (text2) => /generated by|do not edit( by hand)?/i.test(text2.slice(0, 600));
|
|
8712
8924
|
isStopHook = (text2) => /"decision"\s*:\s*"block"|decision.{0,4}block/.test(text2) && /REASON=/.test(text2);
|
|
8713
|
-
isUpdateHook = (p, text2) => /auto-?update/i.test(
|
|
8925
|
+
isUpdateHook = (p, text2) => /auto-?update/i.test(path21.basename(p)) || /git (pull|fetch)/.test(text2) && /SessionStart|session start/i.test(text2);
|
|
8714
8926
|
fingerprint = (d) => JSON.stringify(d);
|
|
8715
8927
|
REWRITE_SYSTEM = `You adapt a company's skill documents after its skill system moved into Bunshin.
|
|
8716
8928
|
|
|
@@ -8747,7 +8959,7 @@ __export(skills_exports, {
|
|
|
8747
8959
|
parsePin: () => parsePin
|
|
8748
8960
|
});
|
|
8749
8961
|
import fs25 from "node:fs";
|
|
8750
|
-
import
|
|
8962
|
+
import path22 from "node:path";
|
|
8751
8963
|
function requireOnline(store, what) {
|
|
8752
8964
|
if (!canCall(store.config())) throw new Error(`${what} needs the hosted library: run \`bunshin setup\` first`);
|
|
8753
8965
|
}
|
|
@@ -8756,9 +8968,9 @@ async function cmdImport2(store, args, cwd, out, err) {
|
|
|
8756
8968
|
err("usage: bunshin import skills <dir> [--pack <name>] [--layer org|team:<unit>] [--owner-team <unit>] [--no-history] [--dry-run] [--propose-rewrites]\n");
|
|
8757
8969
|
return 1;
|
|
8758
8970
|
}
|
|
8759
|
-
const root =
|
|
8971
|
+
const root = path22.resolve(cwd, args._[2]);
|
|
8760
8972
|
if (!fs25.existsSync(root)) throw new Error(`${root} does not exist`);
|
|
8761
|
-
const pack = str6(args.flags.pack) ??
|
|
8973
|
+
const pack = str6(args.flags.pack) ?? path22.basename(root);
|
|
8762
8974
|
const history = args.flags.history !== false && !bool4(args.flags["no-history"]);
|
|
8763
8975
|
const read = readSkillSystem(root, { pack, history, ...str6(args.flags.since) ? { since: str6(args.flags.since) } : {} });
|
|
8764
8976
|
const kinds = /* @__PURE__ */ new Map();
|
|
@@ -8793,7 +9005,7 @@ async function cmdImport2(store, args, cwd, out, err) {
|
|
|
8793
9005
|
const propose = args.flags["propose-rewrites"];
|
|
8794
9006
|
if (propose) {
|
|
8795
9007
|
const only = typeof propose === "string" && propose !== "true" ? propose.split(",").map((x) => x.trim()).filter(Boolean) : [];
|
|
8796
|
-
await proposeRewrites(store, read.docs.filter((d) => !only.length || only.some((o) => d.path.includes(o))), pack,
|
|
9008
|
+
await proposeRewrites(store, read.docs.filter((d) => !only.length || only.some((o) => d.path.includes(o))), pack, path22.basename(root), out, err);
|
|
8797
9009
|
}
|
|
8798
9010
|
out("Run `bunshin sync` on each machine (it runs at session start) to install the skills, instructions and files.\n");
|
|
8799
9011
|
return 0;
|
|
@@ -8933,7 +9145,7 @@ ${report.skills} skills, ${report.references} references, ${report.errors.length
|
|
|
8933
9145
|
}
|
|
8934
9146
|
const graph = str6(args.flags.graph) ?? (bool4(args.flags.graph) ? "SKILLS-GRAPH.md" : void 0);
|
|
8935
9147
|
if (graph) {
|
|
8936
|
-
fs25.writeFileSync(
|
|
9148
|
+
fs25.writeFileSync(path22.resolve(cwd, graph), renderSkillGraph(report));
|
|
8937
9149
|
out(`graph written to ${graph}
|
|
8938
9150
|
`);
|
|
8939
9151
|
}
|
|
@@ -9032,8 +9244,8 @@ async function cmdLibrary(store, args, cwd, out, err) {
|
|
|
9032
9244
|
`;
|
|
9033
9245
|
const file = str6(args.flags.write) ?? (bool4(args.flags.write) ? ".claude/skills-version" : void 0);
|
|
9034
9246
|
if (file) {
|
|
9035
|
-
const abs =
|
|
9036
|
-
fs25.mkdirSync(
|
|
9247
|
+
const abs = path22.resolve(cwd, file);
|
|
9248
|
+
fs25.mkdirSync(path22.dirname(abs), { recursive: true });
|
|
9037
9249
|
fs25.writeFileSync(abs, line);
|
|
9038
9250
|
out(`${file}: ${line}`);
|
|
9039
9251
|
} else out(line);
|
|
@@ -9044,11 +9256,11 @@ async function cmdLibrary(store, args, cwd, out, err) {
|
|
|
9044
9256
|
const to = str6(args.flags.to);
|
|
9045
9257
|
if (!to) throw new Error("--to <dir> is required");
|
|
9046
9258
|
const pinFile = str6(args.flags.pin);
|
|
9047
|
-
const pin = str6(args.flags.git) ? { git: str6(args.flags.git) } : pinFile ? parsePin(fs25.readFileSync(
|
|
9259
|
+
const pin = str6(args.flags.git) ? { git: str6(args.flags.git) } : pinFile ? parsePin(fs25.readFileSync(path22.resolve(cwd, pinFile), "utf8")) : parsePin(args._[2] ?? "");
|
|
9048
9260
|
const q = pin.git ? `git=${pin.git}` : pin.version ? `version=${pin.version}` : "";
|
|
9049
9261
|
if (!q) throw new Error("name a version: lib@N, --git <sha>, or --pin <file>");
|
|
9050
9262
|
const snap = await v2api(config, "GET", `/library/snapshot?${q}`, void 0, { timeoutMs: 12e4 });
|
|
9051
|
-
const dir =
|
|
9263
|
+
const dir = path22.resolve(cwd, to);
|
|
9052
9264
|
if (fs25.existsSync(dir) && fs25.readdirSync(dir).length && !bool4(args.flags.force)) throw new Error(`${dir} is not empty (use --force)`);
|
|
9053
9265
|
const pack = str6(args.flags.pack);
|
|
9054
9266
|
const docs = snap.sections.filter((s) => !pack || s.pack === pack);
|
|
@@ -9089,11 +9301,11 @@ __export(reflect_exports, {
|
|
|
9089
9301
|
runReflection: () => runReflection
|
|
9090
9302
|
});
|
|
9091
9303
|
import fs26 from "node:fs";
|
|
9092
|
-
import
|
|
9304
|
+
import path23 from "node:path";
|
|
9093
9305
|
import { createHash as createHash7 } from "node:crypto";
|
|
9094
9306
|
async function runReflection(store, opts) {
|
|
9095
9307
|
const empty = { lessonIds: [], used: [], missed: [], attribution: [] };
|
|
9096
|
-
const lock =
|
|
9308
|
+
const lock = path23.join(store.root, `reflect-${createHash7("sha256").update(opts.sessionId).digest("hex")}.lock`);
|
|
9097
9309
|
const deadline = Date.now() + 66e4;
|
|
9098
9310
|
for (; ; ) {
|
|
9099
9311
|
try {
|
|
@@ -9466,7 +9678,7 @@ __export(cli_exports, {
|
|
|
9466
9678
|
version: () => version
|
|
9467
9679
|
});
|
|
9468
9680
|
import fs27 from "node:fs";
|
|
9469
|
-
import
|
|
9681
|
+
import path24 from "node:path";
|
|
9470
9682
|
function parseArgs(argv) {
|
|
9471
9683
|
const out = { _: [], flags: {} };
|
|
9472
9684
|
const valueless = /* @__PURE__ */ new Set(["json", "quiet", "force", "sync", "global", "help", "version", "dry-run", "offline", "apply", "no-hooks", "remove-hooks", "key-stdin", "debug", "verbose", "robot", "fork", "upload", "index", "all", "legacy", "depth", "local", "skip-github", "propose-rewrites", "clear", "watch", "once"]);
|
|
@@ -9577,7 +9789,7 @@ async function main(argv, opts = {}) {
|
|
|
9577
9789
|
if (harness !== "codex" && harness !== "claude-code" && harness !== "pi") throw new Error("Choose your agent: bunshin setup --harness codex, claude-code, or pi.");
|
|
9578
9790
|
const agent = harness === "claude-code" ? "claude" : harness;
|
|
9579
9791
|
if (!executable2(agent)) throw new Error(`${agent} was not found. Install and sign in to your agent, then run setup again.`);
|
|
9580
|
-
const existing = new Store(
|
|
9792
|
+
const existing = new Store(path24.join(cwd, ".bunshin")).config();
|
|
9581
9793
|
const apiUrl = str7(args.flags["api-url"]);
|
|
9582
9794
|
const key = bool5(args.flags["key-stdin"]) ? await new Promise((resolve, reject) => {
|
|
9583
9795
|
let value = "";
|
|
@@ -9620,7 +9832,7 @@ Key saved privately outside the project.
|
|
|
9620
9832
|
if (cmd === "mcp") {
|
|
9621
9833
|
const { runMcpServer: runMcpServer2 } = await Promise.resolve().then(() => (init_mcp(), mcp_exports));
|
|
9622
9834
|
const root = str7(args.flags.root);
|
|
9623
|
-
await runMcpServer2({ cwd, ...root ? { store: new Store(
|
|
9835
|
+
await runMcpServer2({ cwd, ...root ? { store: new Store(path24.resolve(root)) } : {}, version: version() });
|
|
9624
9836
|
return 0;
|
|
9625
9837
|
}
|
|
9626
9838
|
if (cmd === "demo") {
|
|
@@ -9703,7 +9915,7 @@ ${HELP2}`);
|
|
|
9703
9915
|
function openStore(args, cwd) {
|
|
9704
9916
|
const root = str7(args.flags.root);
|
|
9705
9917
|
if (root) {
|
|
9706
|
-
const store = new Store(
|
|
9918
|
+
const store = new Store(path24.resolve(root));
|
|
9707
9919
|
store.ensureLayout();
|
|
9708
9920
|
return store;
|
|
9709
9921
|
}
|
|
@@ -9762,7 +9974,7 @@ function cmdStatus(store, args, out, v2) {
|
|
|
9762
9974
|
const conflicts = store.listConflicts().filter((c) => c.status === "open");
|
|
9763
9975
|
const metrics = store.readMetrics(void 0);
|
|
9764
9976
|
const last50 = metrics?.windows?.find((w) => w.name === "last50");
|
|
9765
|
-
const hookFiles = config.harness === "codex" ? [.../* @__PURE__ */ new Set([
|
|
9977
|
+
const hookFiles = config.harness === "codex" ? [.../* @__PURE__ */ new Set([path24.join(process.env.CODEX_HOME ?? path24.join(homeDir(), ".codex"), "hooks.json"), codexProjectHooks(path24.dirname(store.root)).file])] : config.harness === "claude-code" ? [path24.join(homeDir(), ".claude", "settings.json"), path24.join(path24.dirname(store.root), ".claude", "settings.local.json")] : config.harness === "pi" ? [path24.join(homeDir(), ".pi/agent/extensions/bunshin.js"), path24.join(path24.dirname(store.root), ".pi/extensions/bunshin.js")] : [];
|
|
9766
9978
|
const hooks = config.harness === "pi" ? hookFiles.filter((file) => {
|
|
9767
9979
|
try {
|
|
9768
9980
|
return fs27.readFileSync(file, "utf8").startsWith("// Managed by Bunshin setup.");
|
|
@@ -10009,7 +10221,7 @@ async function cmdReflect(store, args, cwd, out, err) {
|
|
|
10009
10221
|
}
|
|
10010
10222
|
const { runReflection: runReflection2 } = await Promise.resolve().then(() => (init_reflect4(), reflect_exports));
|
|
10011
10223
|
const result = await runReflection2(store, {
|
|
10012
|
-
sessionId: str7(args.flags.session) ??
|
|
10224
|
+
sessionId: str7(args.flags.session) ?? path24.basename(transcript).replace(/\.jsonl?$/, ""),
|
|
10013
10225
|
transcriptPath: transcript,
|
|
10014
10226
|
cwd,
|
|
10015
10227
|
force: bool5(args.flags.force)
|
|
@@ -10181,7 +10393,9 @@ Usage: bunshin <command> [options]
|
|
|
10181
10393
|
reconciliation) with this machine's model. BUNSHIN_RUNNER_KEY
|
|
10182
10394
|
runner next | submit <job> <file|-> | fail <job> --error <why>
|
|
10183
10395
|
agent mode, for a Claude Code routine or Codex cloud task
|
|
10184
|
-
runner
|
|
10396
|
+
runner install --key <runner key> [--every <minutes>] [--no-run] | uninstall
|
|
10397
|
+
this computer answers it every hour (launchd / cron)
|
|
10398
|
+
runner status | setup [this-computer|claude-routine|codex-cloud|github-action|docker|cron]
|
|
10185
10399
|
reflect --fork --session <id> [--focus "<text>"] (what the session-end hook detaches)
|
|
10186
10400
|
|
|
10187
10401
|
lessons [list] [--kind fact|workflow] [--q <query>] [--json] (legacy lesson store)
|