@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.
Files changed (3) hide show
  1. package/README.md +3 -1
  2. package/dist/bin.js +116 -30
  3. 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
- Open a new Codex session in the same project, run `/hooks`, and review and trust the four Bunshin hooks. For Claude Code, use `bunshin setup --harness claude-code`, then start a new session in the project.
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 digest(facts, { from = 0, maxChars = 6e4, toolResultChars = 600 } = {}) {
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
- ${truncate(t.text, maxChars)}`);
2147
+ ${bounded(text, maxChars)}`);
2124
2148
  else if (t.role === "assistant") parts.push(`[ASSISTANT]
2125
- ${truncate(t.text, maxChars)}`);
2149
+ ${bounded(text, maxChars)}`);
2126
2150
  else if (t.tool === "result") parts.push(`[TOOL RESULT${t.isError ? " \u2014 ERROR" : ""}]
2127
- ${truncate(t.text, toolResultChars)}`);
2128
- else parts.push(`[TOOL ${t.tool}] ${truncate(t.text, 400)}`);
2151
+ ${bounded(text, toolResultChars)}`);
2152
+ else parts.push(`[TOOL ${t.tool}] ${truncate(text, 400)}`);
2129
2153
  }
2130
- return redact(truncate(parts.join("\n\n"), maxChars));
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 remote = await mothershipRecall(config, {
2177
- task,
2178
- userId: config.userId,
2179
- role: config.role,
2180
- orgUnit: config.orgUnit,
2181
- limit: 8,
2182
- recentContext: history,
2183
- dryRun: opts.dryRun
2184
- }, remaining(8e3));
2185
- if (remote && (!remote.profile || remote.profile.orgId === config.orgId && remote.profile.userId === config.userId)) {
2186
- if (remote.profile) profile = remote.profile;
2187
- for (const lesson of remote.lessons) {
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 (remote.lessons.length) store.rebuildIndex();
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 = out.lessons.map((raw) => store.saveLesson(buildLesson(raw, store, opts), { reindex: false }).id);
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("Open a new Codex session in this project, run /hooks, and review and trust the four Bunshin hooks.\n");
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: no .bunshin store found. Run `bunshin init` first.\n");
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("\nOpen a new Codex session, run /hooks, and review and trust the four Bunshin hooks.\n");
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.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": "0.1.0"
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
- "scripts": {
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
+ }