@bunshin-cc/bunshin 0.1.1 → 0.1.3
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 +3 -1
- package/dist/bin.js +116 -30
- package/package.json +11 -10
package/README.md
CHANGED
|
@@ -14,7 +14,9 @@ bunshin setup --harness codex
|
|
|
14
14
|
|
|
15
15
|
In Bunshin, open **Agent keys** and create a key for this computer. Paste it into the hidden setup prompt. Setup reads your role, team, and permissions from the workspace, saves the key in a private file outside the project, and configures four project hooks. It does not need a separate model API key.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Run `codex` interactively in the same project. Review and accept its project trust prompt, then run `/hooks` and review and trust the four Bunshin hooks. Project trust and hook trust are separate steps. If `/hooks` does not list Bunshin, check that you opened the connected project and trusted its configuration. This guide tests interactive sessions; `codex exec` is not a substitute for this setup check.
|
|
18
|
+
|
|
19
|
+
For Claude Code, use `bunshin setup --harness claude-code`, then start a new session in the project.
|
|
18
20
|
|
|
19
21
|
For Pi, use Node.js 22.19 or later and run `bunshin setup --harness pi`. It installs a project extension in `.pi/extensions/bunshin.js`. Start Pi in the project (or run `/reload` in an existing session), review its project resources, and check `/bunshin status`. The extension ships in this npm package; you do not need a second package or a repository checkout.
|
|
20
22
|
|
package/dist/bin.js
CHANGED
|
@@ -405,6 +405,7 @@ var init_store = __esm({
|
|
|
405
405
|
var sync_exports = {};
|
|
406
406
|
__export(sync_exports, {
|
|
407
407
|
canSync: () => canSync,
|
|
408
|
+
mothershipLookup: () => mothershipLookup,
|
|
408
409
|
mothershipRecall: () => mothershipRecall,
|
|
409
410
|
pendingConflictQuestion: () => pendingConflictQuestion,
|
|
410
411
|
resolveConflict: () => resolveConflict,
|
|
@@ -440,6 +441,14 @@ async function mothershipRecall(config, req, timeoutMs = 8e3) {
|
|
|
440
441
|
return null;
|
|
441
442
|
}
|
|
442
443
|
}
|
|
444
|
+
async function mothershipLookup(config, ids, timeoutMs = 8e3) {
|
|
445
|
+
if (!canSync(config) || !ids.length) return null;
|
|
446
|
+
try {
|
|
447
|
+
return await api(config, "POST", "/lessons/lookup", { ids }, timeoutMs);
|
|
448
|
+
} catch {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
443
452
|
async function runSync(store) {
|
|
444
453
|
const config = store.config();
|
|
445
454
|
const result = { pushedLessons: 0, pushedTraces: 0, pulledConflicts: 0, errors: [] };
|
|
@@ -650,6 +659,7 @@ async function askCodexCli(opts) {
|
|
|
650
659
|
const outFile = path4.join(os.tmpdir(), `bunshin-codex-${process.pid}-${Date.now()}.txt`);
|
|
651
660
|
const args = ["exec", "--skip-git-repo-check", "--ephemeral", "-s", "read-only", "-o", outFile];
|
|
652
661
|
if (opts.model) args.push("-m", opts.model);
|
|
662
|
+
if (opts.effort) args.push("-c", `model_reasoning_effort="${opts.effort}"`);
|
|
653
663
|
args.push("-");
|
|
654
664
|
const prompt = `${opts.system}${jsonOnlyInstruction(opts.schema)}
|
|
655
665
|
|
|
@@ -753,9 +763,9 @@ function mergeClaudeSettings(settings, { bin = "bunshin", harness = "claude-code
|
|
|
753
763
|
for (const { event, arg, timeout } of HOOK_EVENTS) {
|
|
754
764
|
const groups = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
|
|
755
765
|
const hook = bunshinHookEntry(arg, timeout, bin);
|
|
766
|
+
hook.command = `BUNSHIN_HARNESS=${harness} ${hook.command}`;
|
|
756
767
|
if (harness === "codex") {
|
|
757
768
|
delete hook._bunshin;
|
|
758
|
-
hook.command = `BUNSHIN_HARNESS=codex ${hook.command}`;
|
|
759
769
|
if (event === "UserPromptSubmit") {
|
|
760
770
|
hook.async = true;
|
|
761
771
|
hook.timeout = 30;
|
|
@@ -1861,6 +1871,8 @@ var init_prompts = __esm({
|
|
|
1861
1871
|
|
|
1862
1872
|
2. IS THERE A REUSABLE WORKFLOW? If the session carried out a multi-step, end-to-end procedure that someone would plausibly need to repeat (release, migration, end-to-end test of a ticket, onboarding a service, an incident runbook), emit a WORKFLOW lesson: preconditions, ordered imperative steps each with how to verify it succeeded, and what "done" looks like. Write the steps so an agent with no memory of this session could execute them reliably; include exact commands, paths and checks where they were shown. Otherwise emit FACT lessons: short, one idea each, the trap and the fix.
|
|
1863
1873
|
|
|
1874
|
+
Workflow evidence is mandatory. Include verificationEvidence: exact, contiguous quotes from successful TOOL RESULT output in this excerpt showing that the procedure's checks actually ran. A user request, a plan, code printed in an answer, a tool invocation without its result, or the assistant saying "done" is not execution evidence. Do not turn a proposed procedure into a workflow, even if the proposal gives exact paths and commands. Every workflow needs at least two steps, a verification check for every step, and an explicit outcome. Without observed verification, omit the workflow. You may save a useful human-stated requirement or reported process as a FACT, clearly attributed as such; never imply that requested files already exist or that planned checks passed.
|
|
1875
|
+
|
|
1864
1876
|
3. ATTRIBUTION. For every served lesson listed, decide whether the agent ACTUALLY USED it. Default is false. On-topic is not used. Used means the agent's behaviour visibly depended on it: it followed the workflow, avoided the trap, cited the fact, chose differently because of it. Classify each served lesson exactly one way: used, irrelevant (did not apply to this task), redundant (applied but the agent already knew or the repo showed it), relevant-but-ignored (applied and would have helped, agent did not act on it). Then list MISSED lessons: ids from the full index that were relevant and would have helped but were not served.
|
|
1865
1877
|
|
|
1866
1878
|
Also give the session an outcome (success / failure / unknown) and whether the user had to correct the agent.
|
|
@@ -1902,7 +1914,8 @@ Content rules: write lessons for a stranger, name systems and teams explicitly,
|
|
|
1902
1914
|
preconditions: { type: "array", items: { type: "string" } },
|
|
1903
1915
|
steps: { type: "array", items: workflowStep },
|
|
1904
1916
|
outcome: { type: "string" },
|
|
1905
|
-
estimatedMinutes: { type: "integer" }
|
|
1917
|
+
estimatedMinutes: { type: "integer" },
|
|
1918
|
+
verificationEvidence: { type: "array", items: { type: "string" }, description: "For workflows, exact quotes from successful tool results showing completed verification. Not user requests or assistant claims." }
|
|
1906
1919
|
}
|
|
1907
1920
|
}
|
|
1908
1921
|
},
|
|
@@ -1974,10 +1987,11 @@ function redact(text) {
|
|
|
1974
1987
|
if (name === "email") {
|
|
1975
1988
|
return /^(?:no[._-]?reply|do[._-]?not[._-]?reply)@|@example\./i.test(match) ? match : "[REDACTED:email]";
|
|
1976
1989
|
}
|
|
1977
|
-
if (name === "bearer" || name === "assigned-secret") {
|
|
1990
|
+
if (name === "bearer" || name === "assigned-secret" || name === "json-secret") {
|
|
1978
1991
|
const key = String(groups[0] ?? "");
|
|
1979
1992
|
const value = String(groups[1] ?? "");
|
|
1980
1993
|
if (PLACEHOLDER_RE.test(value)) return match;
|
|
1994
|
+
if (name === "json-secret") return `${JSON.stringify(key)}:"[REDACTED:json-secret]"`;
|
|
1981
1995
|
return `${key}=[REDACTED:${name}]`;
|
|
1982
1996
|
}
|
|
1983
1997
|
return `[REDACTED:${name}]`;
|
|
@@ -1990,6 +2004,7 @@ var init_redact2 = __esm({
|
|
|
1990
2004
|
"src/redact.ts"() {
|
|
1991
2005
|
"use strict";
|
|
1992
2006
|
PATTERNS = [
|
|
2007
|
+
["bunshin-key", /\bbsk_[A-Za-z0-9]{8}_[A-Za-z0-9]{32}\b/g],
|
|
1993
2008
|
["aws-key", /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|ANPA)[0-9A-Z]{16}\b/g],
|
|
1994
2009
|
["github-pat", /\bgh[pousr]_[A-Za-z0-9]{20,}\b/g],
|
|
1995
2010
|
["anthropic-key", /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g],
|
|
@@ -2004,6 +2019,7 @@ var init_redact2 = __esm({
|
|
|
2004
2019
|
"assigned-secret",
|
|
2005
2020
|
/\b([A-Za-z0-9_.-]*(?:secret|passwd|password|api[_-]?key|access[_-]?key|private[_-]?key|client[_-]?secret|auth[_-]?token|session[_-]?token)[A-Za-z0-9_.-]*)\s*[:=]\s*['"]?([^\s'"#,;]{6,})['"]?/gi
|
|
2006
2021
|
],
|
|
2022
|
+
["json-secret", /"([A-Za-z0-9_.-]*(?:secret|passwd|password|api[_-]?key|access[_-]?key|private[_-]?key|client[_-]?secret|auth[_-]?token|session[_-]?token)[A-Za-z0-9_.-]*)"\s*:\s*"([^"\r\n]{6,})"/gi],
|
|
2007
2023
|
["card", /\b(?:\d[ -]?){13,19}\b/g],
|
|
2008
2024
|
["email", /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g]
|
|
2009
2025
|
];
|
|
@@ -2116,18 +2132,26 @@ function summarizeInput(input) {
|
|
|
2116
2132
|
}
|
|
2117
2133
|
return JSON.stringify(obj);
|
|
2118
2134
|
}
|
|
2119
|
-
function
|
|
2135
|
+
function bounded(text, maxChars) {
|
|
2136
|
+
if (text.length <= maxChars) return text;
|
|
2137
|
+
const marker = "\n[... middle omitted ...]\n";
|
|
2138
|
+
if (maxChars <= marker.length) return text.slice(0, Math.max(0, maxChars));
|
|
2139
|
+
const head = Math.floor((maxChars - marker.length) / 2);
|
|
2140
|
+
return text.slice(0, head) + marker + text.slice(-(maxChars - marker.length - head));
|
|
2141
|
+
}
|
|
2142
|
+
function digest(facts, { from = 0, maxChars = 6e4, toolResultChars = 6e3 } = {}) {
|
|
2120
2143
|
const parts = [];
|
|
2121
2144
|
for (const t of facts.turns.slice(from)) {
|
|
2145
|
+
const text = redact(t.text);
|
|
2122
2146
|
if (t.role === "user") parts.push(`[USER]
|
|
2123
|
-
${
|
|
2147
|
+
${bounded(text, maxChars)}`);
|
|
2124
2148
|
else if (t.role === "assistant") parts.push(`[ASSISTANT]
|
|
2125
|
-
${
|
|
2149
|
+
${bounded(text, maxChars)}`);
|
|
2126
2150
|
else if (t.tool === "result") parts.push(`[TOOL RESULT${t.isError ? " \u2014 ERROR" : ""}]
|
|
2127
|
-
${
|
|
2128
|
-
else parts.push(`[TOOL ${t.tool}] ${truncate(
|
|
2151
|
+
${bounded(text, toolResultChars)}`);
|
|
2152
|
+
else parts.push(`[TOOL ${t.tool}] ${truncate(text, 400)}`);
|
|
2129
2153
|
}
|
|
2130
|
-
return
|
|
2154
|
+
return bounded(parts.join("\n\n"), maxChars);
|
|
2131
2155
|
}
|
|
2132
2156
|
function recentContext(file, { turns = 2, maxChars = 6e3 } = {}) {
|
|
2133
2157
|
const facts = parseTranscript(file);
|
|
@@ -2173,27 +2197,53 @@ async function runRecall(store, opts) {
|
|
|
2173
2197
|
const rules = new Map(store.listRecallLessons().map((r) => [r.id, r]));
|
|
2174
2198
|
let degraded;
|
|
2175
2199
|
if (connected && !opts.offline) {
|
|
2176
|
-
const
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2200
|
+
const cached = prepareRecall(local.map((lesson) => ({ lesson })), { task, ...profile }, profile).candidates.slice(0, 60).map((candidate) => candidate.lesson);
|
|
2201
|
+
const cachedIds = new Set(cached.map((lesson) => lesson.id));
|
|
2202
|
+
const [remote, lookup] = await Promise.all([
|
|
2203
|
+
mothershipRecall(config, {
|
|
2204
|
+
task,
|
|
2205
|
+
userId: config.userId,
|
|
2206
|
+
role: config.role,
|
|
2207
|
+
orgUnit: config.orgUnit,
|
|
2208
|
+
limit: 8,
|
|
2209
|
+
recentContext: history,
|
|
2210
|
+
dryRun: opts.dryRun
|
|
2211
|
+
}, remaining(8e3)),
|
|
2212
|
+
cached.length ? mothershipLookup(config, [...cachedIds], remaining(8e3)) : null
|
|
2213
|
+
]);
|
|
2214
|
+
const sameIdentity = (p) => p?.orgId === config.orgId && p.userId === config.userId;
|
|
2215
|
+
const remoteOK = remote && (!remote.profile || sameIdentity(remote.profile));
|
|
2216
|
+
const lookupOK = lookup && sameIdentity(lookup.profile);
|
|
2217
|
+
if (remoteOK || lookupOK) {
|
|
2218
|
+
profile = (remoteOK ? remote.profile : void 0) ?? (lookupOK ? lookup.profile : void 0) ?? profile;
|
|
2219
|
+
byId.clear();
|
|
2220
|
+
const refreshed = /* @__PURE__ */ new Map();
|
|
2221
|
+
if (lookupOK) {
|
|
2222
|
+
const unavailable = new Set(lookup.unavailableIds);
|
|
2223
|
+
for (const lesson of cached) {
|
|
2224
|
+
if (unavailable.has(lesson.id) && lesson.version === 1 && (!config.lastSyncAt || lesson.provenance.createdAt > config.lastSyncAt)) byId.set(lesson.id, lesson);
|
|
2225
|
+
}
|
|
2226
|
+
for (const lesson of lookup.lessons) {
|
|
2227
|
+
if (cachedIds.has(lesson.id) && lesson.provenance.orgId === profile.orgId) refreshed.set(lesson.id, lesson);
|
|
2228
|
+
}
|
|
2229
|
+
} else if (cached.length) {
|
|
2230
|
+
degraded = "local cache refresh unavailable; using current hosted knowledge";
|
|
2231
|
+
}
|
|
2232
|
+
for (const lesson of remoteOK ? remote.lessons : []) {
|
|
2188
2233
|
if (lesson.provenance.orgId !== profile.orgId) continue;
|
|
2234
|
+
const checked = refreshed.get(lesson.id);
|
|
2235
|
+
if (!checked || lesson.version > checked.version) refreshed.set(lesson.id, lesson);
|
|
2236
|
+
}
|
|
2237
|
+
for (const lesson of refreshed.values()) {
|
|
2189
2238
|
byId.set(lesson.id, lesson);
|
|
2190
2239
|
if (!opts.dryRun) store.saveLesson(lesson, { reindex: false });
|
|
2191
2240
|
}
|
|
2192
|
-
for (const rule of remote.recallLessons) rules.set(rule.id, rule);
|
|
2241
|
+
for (const rule of remoteOK ? remote.recallLessons : []) rules.set(rule.id, rule);
|
|
2193
2242
|
if (!opts.dryRun) {
|
|
2194
|
-
for (const conflict of remote.pendingConflicts) store.saveConflict(conflict);
|
|
2195
|
-
if (
|
|
2243
|
+
for (const conflict of remoteOK ? remote.pendingConflicts : []) store.saveConflict(conflict);
|
|
2244
|
+
if (refreshed.size) store.rebuildIndex();
|
|
2196
2245
|
}
|
|
2246
|
+
if (!remoteOK) degraded = "hosted search unavailable; using refreshed locally authored knowledge";
|
|
2197
2247
|
} else {
|
|
2198
2248
|
degraded = "mothership unavailable or identity mismatch; using locally authored knowledge";
|
|
2199
2249
|
}
|
|
@@ -2433,13 +2483,49 @@ async function reflectSession(store, opts) {
|
|
|
2433
2483
|
return { ...empty, skipped: res.error };
|
|
2434
2484
|
}
|
|
2435
2485
|
const out = res.value;
|
|
2436
|
-
const lessonIds =
|
|
2486
|
+
const lessonIds = [];
|
|
2487
|
+
for (const raw of out.lessons) {
|
|
2488
|
+
const rejection = workflowRejection(raw, facts, excerpt);
|
|
2489
|
+
if (rejection) {
|
|
2490
|
+
store.log("reflect-workflow-rejected", {
|
|
2491
|
+
session: opts.sessionId,
|
|
2492
|
+
title: redact(raw.title),
|
|
2493
|
+
reason: rejection,
|
|
2494
|
+
evidence: Array.isArray(raw.verificationEvidence) ? raw.verificationEvidence.filter((quote) => typeof quote === "string").map((quote) => redact(quote).slice(0, 1e3)) : []
|
|
2495
|
+
});
|
|
2496
|
+
continue;
|
|
2497
|
+
}
|
|
2498
|
+
const lesson = store.saveLesson(buildLesson(raw, store, opts), { reindex: false });
|
|
2499
|
+
lessonIds.push(lesson.id);
|
|
2500
|
+
if (lesson.kind === "workflow") store.appendTraceEvent(opts.sessionId, {
|
|
2501
|
+
type: "workflow-evidence",
|
|
2502
|
+
lessonId: lesson.id,
|
|
2503
|
+
quotes: raw.verificationEvidence.map(redact)
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2437
2506
|
const { used, missed } = applyAttribution(store, opts.sessionId, out, served);
|
|
2438
2507
|
store.rebuildIndex();
|
|
2439
2508
|
store.writeSession(opts.sessionId, { ...store.readSession(opts.sessionId), reflectedMessages: facts.turns.length, lastReflectAt: nowIso() });
|
|
2440
2509
|
store.log("reflect", { session: opts.sessionId, lessons: lessonIds, used, missed, outcome: out.outcome, corrected: out.corrected });
|
|
2441
2510
|
return { lessonIds, used, missed, attribution: out.attribution ?? [] };
|
|
2442
2511
|
}
|
|
2512
|
+
function workflowRejection(raw, facts, excerpt) {
|
|
2513
|
+
if (raw.kind !== "workflow") return;
|
|
2514
|
+
if (!Array.isArray(raw.steps) || raw.steps.length < 2 || raw.steps.some((step) => !step.action?.trim() || !step.verify?.trim()) || !raw.outcome?.trim()) {
|
|
2515
|
+
return "workflow requires two steps, per-step checks, and an outcome";
|
|
2516
|
+
}
|
|
2517
|
+
const normalize = (text) => redact(text).replace(/\s+/g, " ").trim();
|
|
2518
|
+
const visible = normalize(excerpt);
|
|
2519
|
+
const results = facts.turns.filter((turn) => turn.role === "tool" && turn.tool === "result" && !turn.isError).map((turn) => normalize(turn.text));
|
|
2520
|
+
const evidence = raw.verificationEvidence;
|
|
2521
|
+
if (!Array.isArray(evidence) || !evidence.length) return "workflow requires verification quotes from successful tool results";
|
|
2522
|
+
for (const [index, quote] of evidence.entries()) {
|
|
2523
|
+
if (typeof quote !== "string") return `workflow verification quote ${index + 1} is not text`;
|
|
2524
|
+
const text = normalize(quote);
|
|
2525
|
+
if (!text || !results.some((result) => result.includes(text))) return `workflow verification quote ${index + 1} does not match a successful tool result`;
|
|
2526
|
+
if (!visible.includes(text)) return `workflow verification quote ${index + 1} is outside the visible excerpt`;
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2443
2529
|
function countFacts(turns) {
|
|
2444
2530
|
return {
|
|
2445
2531
|
turns,
|
|
@@ -3026,7 +3112,7 @@ async function main(argv, opts = {}) {
|
|
|
3026
3112
|
}) : process.env.BUNSHIN_API_KEY || (!apiUrl || apiUrl === existing.apiUrl) && existing.apiKey || await readKey2();
|
|
3027
3113
|
out("Connecting to Bunshin\u2026\n");
|
|
3028
3114
|
const result = await setupProject2({ cwd, harness, apiKey: key, apiUrl, noHooks: bool(args.flags["no-hooks"]) });
|
|
3029
|
-
out(`Connected as ${result.profile.role} in ${result.profile.orgUnit}.
|
|
3115
|
+
out(`Connected as ${result.profile.role}${result.profile.orgUnit ? ` in ${result.profile.orgUnit}` : " (company-wide)"}.
|
|
3030
3116
|
`);
|
|
3031
3117
|
out(`Project: ${cwd}
|
|
3032
3118
|
Key saved privately outside the project.
|
|
@@ -3034,7 +3120,7 @@ Key saved privately outside the project.
|
|
|
3034
3120
|
if (result.hooks?.error) throw new Error(`Connected, but hooks could not be configured: ${result.hooks.error}. Fix the file and run setup again.`);
|
|
3035
3121
|
if (result.hooks) out(`${result.hooks.installed} ${harness === "pi" ? "extension" : "hooks"} configured in ${result.hooks.file}
|
|
3036
3122
|
`);
|
|
3037
|
-
if (harness === "codex" && result.hooks) out("
|
|
3123
|
+
if (harness === "codex" && result.hooks) out("Run codex interactively in this project. Review and accept project trust, then run /hooks and review and trust the four Bunshin hooks.\n");
|
|
3038
3124
|
else if (harness === "pi" && result.hooks) out("Open Pi in this project and approve its project extension. Check it with /bunshin status.\n");
|
|
3039
3125
|
else if (result.hooks) out("Open a new Claude Code session in this project to load the hooks.\n");
|
|
3040
3126
|
else out("Hooks are disabled. Run setup again without --no-hooks to configure them.\n");
|
|
@@ -3054,7 +3140,7 @@ Key saved privately outside the project.
|
|
|
3054
3140
|
}
|
|
3055
3141
|
const store = openStore(args, cwd);
|
|
3056
3142
|
if (!store) {
|
|
3057
|
-
err("bunshin:
|
|
3143
|
+
err("bunshin: this project is not connected. Run `bunshin setup --harness codex` (or use --harness claude-code / --harness pi). For offline use, run `bunshin init`.\n");
|
|
3058
3144
|
return 1;
|
|
3059
3145
|
}
|
|
3060
3146
|
try {
|
|
@@ -3138,7 +3224,7 @@ async function cmdInit(args, cwd, out, err) {
|
|
|
3138
3224
|
if (result.hooks.error) err(` hooks: ${result.hooks.error}
|
|
3139
3225
|
`);
|
|
3140
3226
|
}
|
|
3141
|
-
if (result.config.harness === "codex" && result.hooks?.installed) out("\
|
|
3227
|
+
if (result.config.harness === "codex" && result.hooks?.installed) out("\nRun codex interactively in this project. Review and accept project trust, then run /hooks and review and trust the four Bunshin hooks.\n");
|
|
3142
3228
|
if (!result.config.apiUrl) out("\nTo connect this project to your workspace, run `bunshin setup --harness " + result.config.harness + "`.\n");
|
|
3143
3229
|
for (const w of result.warnings) err(`! ${w}
|
|
3144
3230
|
`);
|
|
@@ -3172,7 +3258,7 @@ function cmdStatus(store, args, out) {
|
|
|
3172
3258
|
mothership: config.apiUrl ? { apiUrl: config.apiUrl, key: config.apiKey ? "set" : "missing", lastSyncAt: config.lastSyncAt ?? null } : null,
|
|
3173
3259
|
llmBackend: resolveBackend(config) ?? "none (set ANTHROPIC_API_KEY or install the claude/codex CLI)",
|
|
3174
3260
|
hooks: { [config.harness]: hooks },
|
|
3175
|
-
hookFiles,
|
|
3261
|
+
hookFiles: hookFiles.filter((file) => fs9.existsSync(file)),
|
|
3176
3262
|
recallQuality: last50 ? { precision: last50.precision, recall: last50.recall, f1: last50.f1, traces: last50.traces } : null
|
|
3177
3263
|
};
|
|
3178
3264
|
if (bool(args.flags.json)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bunshin-cc/bunshin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Shared memory for company AI agents. Connect Codex, Claude Code, or Pi to Bunshin.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
"engines": {
|
|
15
15
|
"node": ">=20"
|
|
16
16
|
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc -p tsconfig.json --noEmit && esbuild src/bin.ts --bundle --platform=node --target=node20 --format=esm --external:@anthropic-ai/sdk --external:yaml --outfile=dist/bin.js && esbuild ../../adapters/pi/index.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/pi-extension.js",
|
|
19
|
+
"dev": "tsx src/bin.ts",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
22
|
+
"prepack": "pnpm build"
|
|
23
|
+
},
|
|
17
24
|
"dependencies": {
|
|
18
25
|
"@anthropic-ai/sdk": "^0.127.0",
|
|
19
26
|
"yaml": "^2.8.0"
|
|
@@ -24,7 +31,7 @@
|
|
|
24
31
|
"tsx": "^4.20.0",
|
|
25
32
|
"typescript": "^5.8.0",
|
|
26
33
|
"vitest": "^3.2.0",
|
|
27
|
-
"@bunshin/core": "
|
|
34
|
+
"@bunshin/core": "workspace:*"
|
|
28
35
|
},
|
|
29
36
|
"license": "MIT",
|
|
30
37
|
"homepage": "https://bunshin.cc",
|
|
@@ -47,11 +54,5 @@
|
|
|
47
54
|
"claude-code",
|
|
48
55
|
"memory",
|
|
49
56
|
"knowledge"
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
"build": "tsc -p tsconfig.json --noEmit && esbuild src/bin.ts --bundle --platform=node --target=node20 --format=esm --external:@anthropic-ai/sdk --external:yaml --outfile=dist/bin.js && esbuild ../../adapters/pi/index.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/pi-extension.js",
|
|
53
|
-
"dev": "tsx src/bin.ts",
|
|
54
|
-
"test": "vitest run",
|
|
55
|
-
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
56
|
-
}
|
|
57
|
-
}
|
|
57
|
+
]
|
|
58
|
+
}
|