@jaggerxtrm/specialists 3.3.3 → 3.4.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.
Files changed (26) hide show
  1. package/README.md +4 -2
  2. package/bin/install.js +15 -204
  3. package/config/skills/using-specialists/SKILL.md +1 -1
  4. package/config/specialists/debugger.specialist.yaml +111 -0
  5. package/config/specialists/explorer.specialist.yaml +1 -1
  6. package/dist/index.js +877 -623
  7. package/package.json +1 -1
  8. package/config/skills/specialists-usage-workspace/iteration-1/eval-bead-background/old_skill/outputs/result.md +0 -105
  9. package/config/skills/specialists-usage-workspace/iteration-1/eval-bead-background/with_skill/outputs/result.md +0 -93
  10. package/config/skills/specialists-usage-workspace/iteration-1/eval-fresh-setup/old_skill/outputs/result.md +0 -113
  11. package/config/skills/specialists-usage-workspace/iteration-1/eval-fresh-setup/with_skill/outputs/result.md +0 -131
  12. package/config/skills/specialists-usage-workspace/iteration-1/eval-yaml-debug/old_skill/outputs/result.md +0 -159
  13. package/config/skills/specialists-usage-workspace/iteration-1/eval-yaml-debug/with_skill/outputs/result.md +0 -150
  14. package/config/skills/specialists-usage-workspace/iteration-2/eval-bug-investigation/with_skill/outputs/result.md +0 -180
  15. package/config/skills/specialists-usage-workspace/iteration-2/eval-bug-investigation/with_skill/timing.json +0 -5
  16. package/config/skills/specialists-usage-workspace/iteration-2/eval-bug-investigation/without_skill/outputs/result.md +0 -223
  17. package/config/skills/specialists-usage-workspace/iteration-2/eval-bug-investigation/without_skill/timing.json +0 -5
  18. package/config/skills/specialists-usage-workspace/iteration-2/eval-code-review/with_skill/timing.json +0 -5
  19. package/config/skills/specialists-usage-workspace/iteration-2/eval-code-review/without_skill/outputs/result.md +0 -146
  20. package/config/skills/specialists-usage-workspace/iteration-2/eval-code-review/without_skill/timing.json +0 -5
  21. package/config/skills/specialists-usage-workspace/iteration-2/eval-test-coverage/with_skill/outputs/result.md +0 -89
  22. package/config/skills/specialists-usage-workspace/iteration-2/eval-test-coverage/with_skill/timing.json +0 -5
  23. package/config/skills/specialists-usage-workspace/iteration-2/eval-test-coverage/without_skill/outputs/result.md +0 -96
  24. package/config/skills/specialists-usage-workspace/iteration-2/eval-test-coverage/without_skill/timing.json +0 -5
  25. package/config/skills/specialists-usage-workspace/skill-snapshot/SKILL.md.old +0 -237
  26. package/config/specialists/bug-hunt.specialist.yaml +0 -96
package/dist/index.js CHANGED
@@ -17410,7 +17410,98 @@ var init_dist = __esm(() => {
17410
17410
  });
17411
17411
 
17412
17412
  // src/specialist/schema.ts
17413
+ function formatPath(path) {
17414
+ return path.map((p) => typeof p === "number" ? `[${p}]` : p).join(".");
17415
+ }
17416
+ function getFriendlyMessage(issue2) {
17417
+ const path = formatPath(issue2.path);
17418
+ if (issue2.code === "invalid_string" && issue2.validation === "regex") {
17419
+ if (path.includes("name")) {
17420
+ return `Invalid specialist name: must be kebab-case (lowercase letters, numbers, hyphens). Got: "${issue2.path.at(-1) === "name" ? "invalid value" : "see schema"}"`;
17421
+ }
17422
+ if (path.includes("version")) {
17423
+ return `Invalid version: must be semver format (e.g., "1.0.0"). Got value that doesn't match pattern.`;
17424
+ }
17425
+ }
17426
+ if (issue2.code === "invalid_enum_value") {
17427
+ const allowed = issue2.options.map((o) => `"${o}"`).join(", ");
17428
+ if (path.includes("permission_required")) {
17429
+ return `Invalid permission_required: must be one of ${allowed}. This controls which pi tools are available.`;
17430
+ }
17431
+ if (path.includes("mode")) {
17432
+ return `Invalid execution.mode: must be one of ${allowed}.`;
17433
+ }
17434
+ if (path.includes("beads_integration")) {
17435
+ return `Invalid beads_integration: must be one of ${allowed}.`;
17436
+ }
17437
+ return `Invalid value at "${path}": expected one of ${allowed}, got "${issue2.received}"`;
17438
+ }
17439
+ if (issue2.code === "invalid_type") {
17440
+ return `Invalid type at "${path}": expected ${issue2.expected}, got ${issue2.received}`;
17441
+ }
17442
+ if (issue2.code === "invalid_literal") {
17443
+ return `Invalid value at "${path}": expected "${issue2.expected}"`;
17444
+ }
17445
+ if (issue2.code === "missing_key") {
17446
+ return `Missing required field: "${formatPath(issue2.path)}"`;
17447
+ }
17448
+ return issue2.message;
17449
+ }
17450
+ async function validateSpecialist(yamlContent) {
17451
+ const errors5 = [];
17452
+ const warnings = [];
17453
+ let raw;
17454
+ try {
17455
+ raw = $parse(yamlContent);
17456
+ } catch (e) {
17457
+ const msg = e instanceof Error ? e.message : String(e);
17458
+ errors5.push({
17459
+ path: "yaml",
17460
+ message: `YAML parse error: ${msg}`,
17461
+ code: "yaml_parse_error"
17462
+ });
17463
+ return { valid: false, errors: errors5, warnings };
17464
+ }
17465
+ const result = SpecialistSchema.safeParse(raw);
17466
+ if (!result.success) {
17467
+ for (const issue2 of result.error.issues) {
17468
+ errors5.push({
17469
+ path: formatPath(issue2.path),
17470
+ message: getFriendlyMessage(issue2),
17471
+ code: issue2.code
17472
+ });
17473
+ }
17474
+ } else {
17475
+ const spec = result.data;
17476
+ if (spec.specialist.prompt.normalize_template) {
17477
+ warnings.push("prompt.normalize_template is deprecated (Mercury compat) and will be ignored");
17478
+ }
17479
+ if (spec.specialist.execution.preferred_profile) {
17480
+ warnings.push("execution.preferred_profile is deprecated (Agent Forge compat) and will be ignored");
17481
+ }
17482
+ if (spec.specialist.execution.approval_mode) {
17483
+ warnings.push("execution.approval_mode is deprecated (Agent Forge compat) and will be ignored");
17484
+ }
17485
+ if (!spec.specialist.execution.model.includes("/")) {
17486
+ warnings.push(`Model "${spec.specialist.execution.model}" doesn't include a provider prefix. Expected format: "provider/model-id" (e.g., "anthropic/claude-sonnet-4-5")`);
17487
+ }
17488
+ }
17489
+ return { valid: errors5.length === 0, errors: errors5, warnings };
17490
+ }
17413
17491
  async function parseSpecialist(yamlContent) {
17492
+ const result = await validateSpecialist(yamlContent);
17493
+ if (!result.valid) {
17494
+ const errorList = result.errors.map((e) => ` • ${e.message}`).join(`
17495
+ `);
17496
+ throw new Error(`Schema validation failed:
17497
+ ${errorList}`);
17498
+ }
17499
+ if (result.warnings.length > 0) {
17500
+ process.stderr.write(`[specialists] warnings:
17501
+ ${result.warnings.map((w) => ` ⚠ ${w}`).join(`
17502
+ `)}
17503
+ `);
17504
+ }
17414
17505
  const raw = $parse(yamlContent);
17415
17506
  return SpecialistSchema.parseAsync(raw);
17416
17507
  }
@@ -17767,13 +17858,36 @@ class PiAgentSession {
17767
17858
  handler?.(event);
17768
17859
  return;
17769
17860
  }
17770
- if (type === "message_start" && event.message?.role === "assistant") {
17771
- const { provider, model } = event.message ?? {};
17772
- if (provider || model) {
17773
- this.options.onMeta?.({ backend: provider ?? "", model: model ?? "" });
17861
+ if (type === "message_start") {
17862
+ const role = event.message?.role;
17863
+ if (role === "assistant") {
17864
+ this.options.onEvent?.("message_start_assistant");
17865
+ const { provider, model } = event.message ?? {};
17866
+ if (provider || model) {
17867
+ this.options.onMeta?.({ backend: provider ?? "", model: model ?? "" });
17868
+ }
17869
+ } else if (role === "toolResult") {
17870
+ this.options.onEvent?.("message_start_tool_result");
17871
+ }
17872
+ return;
17873
+ }
17874
+ if (type === "message_end") {
17875
+ const role = event.message?.role;
17876
+ if (role === "assistant") {
17877
+ this.options.onEvent?.("message_end_assistant");
17878
+ } else if (role === "toolResult") {
17879
+ this.options.onEvent?.("message_end_tool_result");
17774
17880
  }
17775
17881
  return;
17776
17882
  }
17883
+ if (type === "turn_start") {
17884
+ this.options.onEvent?.("turn_start");
17885
+ return;
17886
+ }
17887
+ if (type === "turn_end") {
17888
+ this.options.onEvent?.("turn_end");
17889
+ return;
17890
+ }
17777
17891
  if (type === "agent_end") {
17778
17892
  const messages = event.messages ?? [];
17779
17893
  const last = [...messages].reverse().find((m) => m.role === "assistant");
@@ -17786,11 +17900,11 @@ class PiAgentSession {
17786
17900
  return;
17787
17901
  }
17788
17902
  if (type === "tool_execution_start") {
17789
- this.options.onEvent?.("tool_execution");
17903
+ this.options.onEvent?.("tool_execution_start");
17790
17904
  return;
17791
17905
  }
17792
17906
  if (type === "tool_execution_update") {
17793
- this.options.onEvent?.("tool_execution");
17907
+ this.options.onEvent?.("tool_execution_update");
17794
17908
  return;
17795
17909
  }
17796
17910
  if (type === "tool_execution_end") {
@@ -17888,7 +18002,17 @@ class PiAgentSession {
17888
18002
  if (this._killed)
17889
18003
  return;
17890
18004
  this.proc?.stdin?.end();
17891
- await this._donePromise?.catch(() => {});
18005
+ if (this.proc) {
18006
+ await new Promise((resolve) => {
18007
+ this.proc.on("close", () => resolve());
18008
+ setTimeout(() => {
18009
+ if (this.proc && !this._killed) {
18010
+ this.proc.kill();
18011
+ }
18012
+ resolve();
18013
+ }, 2000);
18014
+ });
18015
+ }
17892
18016
  }
17893
18017
  kill() {
17894
18018
  if (this._killed)
@@ -18267,6 +18391,7 @@ ${preScripts.map((s) => ` • ${s.run}${s.inject_output ? " → $pre_script_o
18267
18391
  let sessionBackend = model;
18268
18392
  let session;
18269
18393
  let keepAliveActive = false;
18394
+ let sessionClosed = false;
18270
18395
  try {
18271
18396
  session = await this.sessionFactory({
18272
18397
  model,
@@ -18305,6 +18430,7 @@ ${preScripts.map((s) => ` • ${s.run}${s.inject_output ? " → $pre_script_o
18305
18430
  onResumeReady(resumeFn, closeFn);
18306
18431
  } else {
18307
18432
  await session.close();
18433
+ sessionClosed = true;
18308
18434
  }
18309
18435
  const postScripts = spec.specialist.skills?.scripts?.filter((s) => s.phase === "post") ?? [];
18310
18436
  for (const script of postScripts)
@@ -18329,7 +18455,7 @@ ${preScripts.map((s) => ` • ${s.run}${s.inject_output ? " → $pre_script_o
18329
18455
  });
18330
18456
  throw err;
18331
18457
  } finally {
18332
- if (!keepAliveActive) {
18458
+ if (!keepAliveActive && !sessionClosed) {
18333
18459
  session?.kill();
18334
18460
  }
18335
18461
  }
@@ -18463,6 +18589,23 @@ function mapCallbackEventToTimelineEvent(callbackEvent, context) {
18463
18589
  phase: "start",
18464
18590
  tool_call_id: context.toolCallId
18465
18591
  };
18592
+ case "tool_execution_start":
18593
+ return {
18594
+ t,
18595
+ type: TIMELINE_EVENT_TYPES.TOOL,
18596
+ tool: context.tool ?? "unknown",
18597
+ phase: "start",
18598
+ tool_call_id: context.toolCallId
18599
+ };
18600
+ case "tool_execution_update":
18601
+ case "tool_execution":
18602
+ return {
18603
+ t,
18604
+ type: TIMELINE_EVENT_TYPES.TOOL,
18605
+ tool: context.tool ?? "unknown",
18606
+ phase: "update",
18607
+ tool_call_id: context.toolCallId
18608
+ };
18466
18609
  case "tool_execution_end":
18467
18610
  return {
18468
18611
  t,
@@ -18472,6 +18615,18 @@ function mapCallbackEventToTimelineEvent(callbackEvent, context) {
18472
18615
  tool_call_id: context.toolCallId,
18473
18616
  is_error: context.isError
18474
18617
  };
18618
+ case "message_start_assistant":
18619
+ return { t, type: TIMELINE_EVENT_TYPES.MESSAGE, phase: "start", role: "assistant" };
18620
+ case "message_end_assistant":
18621
+ return { t, type: TIMELINE_EVENT_TYPES.MESSAGE, phase: "end", role: "assistant" };
18622
+ case "message_start_tool_result":
18623
+ return { t, type: TIMELINE_EVENT_TYPES.MESSAGE, phase: "start", role: "toolResult" };
18624
+ case "message_end_tool_result":
18625
+ return { t, type: TIMELINE_EVENT_TYPES.MESSAGE, phase: "end", role: "toolResult" };
18626
+ case "turn_start":
18627
+ return { t, type: TIMELINE_EVENT_TYPES.TURN, phase: "start" };
18628
+ case "turn_end":
18629
+ return { t, type: TIMELINE_EVENT_TYPES.TURN, phase: "end" };
18475
18630
  case "text":
18476
18631
  return { t, type: TIMELINE_EVENT_TYPES.TEXT };
18477
18632
  case "agent_end":
@@ -18552,6 +18707,8 @@ var init_timeline_events = __esm(() => {
18552
18707
  THINKING: "thinking",
18553
18708
  TOOL: "tool",
18554
18709
  TEXT: "text",
18710
+ MESSAGE: "message",
18711
+ TURN: "turn",
18555
18712
  RUN_COMPLETE: "run_complete",
18556
18713
  DONE: "done",
18557
18714
  AGENT_END: "agent_end"
@@ -18647,6 +18804,7 @@ class Supervisor {
18647
18804
  return jobs.sort((a, b) => b.started_at_ms - a.started_at_ms);
18648
18805
  }
18649
18806
  writeStatusFile(id, data) {
18807
+ mkdirSync(this.jobDir(id), { recursive: true });
18650
18808
  const path = this.statusPath(id);
18651
18809
  const tmp = path + ".tmp";
18652
18810
  writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
@@ -18714,6 +18872,14 @@ class Supervisor {
18714
18872
  pid: process.pid
18715
18873
  };
18716
18874
  this.writeStatusFile(id, initialStatus);
18875
+ writeFileSync(join3(this.opts.jobsDir, "latest"), `${id}
18876
+ `, "utf-8");
18877
+ this.opts.onJobStarted?.({ id });
18878
+ let statusSnapshot = initialStatus;
18879
+ const setStatus = (updates) => {
18880
+ statusSnapshot = { ...statusSnapshot, ...updates };
18881
+ this.writeStatusFile(id, statusSnapshot);
18882
+ };
18717
18883
  const eventsFd = openSync(this.eventsPath(id), "a");
18718
18884
  const appendTimelineEvent = (event) => {
18719
18885
  try {
@@ -18725,10 +18891,13 @@ class Supervisor {
18725
18891
  };
18726
18892
  appendTimelineEvent(createRunStartEvent(runOptions.name));
18727
18893
  const fifoPath = join3(dir, "steer.pipe");
18728
- try {
18729
- execFileSync("mkfifo", [fifoPath]);
18730
- this.updateStatus(id, { fifo_path: fifoPath });
18731
- } catch {}
18894
+ const needsFifo = runOptions.keepAlive;
18895
+ if (needsFifo) {
18896
+ try {
18897
+ execFileSync("mkfifo", [fifoPath]);
18898
+ setStatus({ fifo_path: fifoPath });
18899
+ } catch {}
18900
+ }
18732
18901
  let textLogged = false;
18733
18902
  let currentTool = "";
18734
18903
  let currentToolCallId = "";
@@ -18736,6 +18905,8 @@ class Supervisor {
18736
18905
  let steerFn;
18737
18906
  let resumeFn;
18738
18907
  let closeFn;
18908
+ let fifoReadStream;
18909
+ let fifoReadline;
18739
18910
  const sigtermHandler = () => killFn?.();
18740
18911
  process.once("SIGTERM", sigtermHandler);
18741
18912
  try {
@@ -18743,11 +18914,12 @@ class Supervisor {
18743
18914
  const toolMatch = delta.match(/⚙ (.+?)…/);
18744
18915
  if (toolMatch) {
18745
18916
  currentTool = toolMatch[1];
18746
- this.updateStatus(id, { current_tool: currentTool });
18917
+ setStatus({ current_tool: currentTool });
18747
18918
  }
18919
+ this.opts.onProgress?.(delta);
18748
18920
  }, (eventType) => {
18749
18921
  const now = Date.now();
18750
- this.updateStatus(id, {
18922
+ setStatus({
18751
18923
  status: "running",
18752
18924
  current_event: eventType,
18753
18925
  last_event_at_ms: now,
@@ -18764,54 +18936,58 @@ class Supervisor {
18764
18936
  appendTimelineEvent({ t: Date.now(), type: TIMELINE_EVENT_TYPES.TEXT });
18765
18937
  }
18766
18938
  }, (meta) => {
18767
- this.updateStatus(id, { model: meta.model, backend: meta.backend });
18939
+ setStatus({ model: meta.model, backend: meta.backend });
18768
18940
  appendTimelineEvent(createMetaEvent(meta.model, meta.backend));
18941
+ this.opts.onMeta?.(meta);
18769
18942
  }, (fn) => {
18770
18943
  killFn = fn;
18771
18944
  }, (beadId) => {
18772
- this.updateStatus(id, { bead_id: beadId });
18945
+ setStatus({ bead_id: beadId });
18773
18946
  }, (fn) => {
18774
18947
  steerFn = fn;
18775
- if (existsSync4(fifoPath)) {
18776
- const rl = createInterface({ input: createReadStream(fifoPath, { flags: "r+" }) });
18777
- rl.on("line", (line) => {
18778
- try {
18779
- const parsed = JSON.parse(line);
18780
- if (parsed?.type === "steer" && typeof parsed.message === "string") {
18781
- steerFn?.(parsed.message).catch(() => {});
18782
- } else if (parsed?.type === "prompt" && typeof parsed.message === "string") {
18783
- if (resumeFn) {
18784
- this.updateStatus(id, { status: "running", current_event: "starting" });
18785
- resumeFn(parsed.message).then((output) => {
18786
- writeFileSync(this.resultPath(id), output, "utf-8");
18787
- this.updateStatus(id, {
18788
- status: "waiting",
18789
- current_event: "waiting",
18790
- elapsed_s: Math.round((Date.now() - startedAtMs) / 1000),
18791
- last_event_at_ms: Date.now()
18792
- });
18793
- }).catch((err) => {
18794
- this.updateStatus(id, { status: "error", error: err?.message ?? String(err) });
18948
+ if (!needsFifo || !existsSync4(fifoPath))
18949
+ return;
18950
+ fifoReadStream = createReadStream(fifoPath, { flags: "r+" });
18951
+ fifoReadline = createInterface({ input: fifoReadStream });
18952
+ fifoReadline.on("line", (line) => {
18953
+ try {
18954
+ const parsed = JSON.parse(line);
18955
+ if (parsed?.type === "steer" && typeof parsed.message === "string") {
18956
+ steerFn?.(parsed.message).catch(() => {});
18957
+ } else if (parsed?.type === "prompt" && typeof parsed.message === "string") {
18958
+ if (resumeFn) {
18959
+ setStatus({ status: "running", current_event: "starting" });
18960
+ resumeFn(parsed.message).then((output) => {
18961
+ mkdirSync(this.jobDir(id), { recursive: true });
18962
+ writeFileSync(this.resultPath(id), output, "utf-8");
18963
+ setStatus({
18964
+ status: "waiting",
18965
+ current_event: "waiting",
18966
+ elapsed_s: Math.round((Date.now() - startedAtMs) / 1000),
18967
+ last_event_at_ms: Date.now()
18795
18968
  });
18796
- }
18797
- } else if (parsed?.type === "close") {
18798
- closeFn?.().catch(() => {});
18969
+ }).catch((err) => {
18970
+ setStatus({ status: "error", error: err?.message ?? String(err) });
18971
+ });
18799
18972
  }
18800
- } catch {}
18801
- });
18802
- rl.on("error", () => {});
18803
- }
18973
+ } else if (parsed?.type === "close") {
18974
+ closeFn?.().catch(() => {});
18975
+ }
18976
+ } catch {}
18977
+ });
18978
+ fifoReadline.on("error", () => {});
18804
18979
  }, (rFn, cFn) => {
18805
18980
  resumeFn = rFn;
18806
18981
  closeFn = cFn;
18807
- this.updateStatus(id, { status: "waiting", current_event: "waiting" });
18982
+ setStatus({ status: "waiting", current_event: "waiting" });
18808
18983
  });
18809
18984
  const elapsed = Math.round((Date.now() - startedAtMs) / 1000);
18985
+ mkdirSync(this.jobDir(id), { recursive: true });
18810
18986
  writeFileSync(this.resultPath(id), result.output, "utf-8");
18811
18987
  if (result.beadId) {
18812
18988
  this.opts.beadsClient?.updateBeadNotes(result.beadId, formatBeadNotes(result));
18813
18989
  }
18814
- this.updateStatus(id, {
18990
+ setStatus({
18815
18991
  status: "done",
18816
18992
  elapsed_s: elapsed,
18817
18993
  last_event_at_ms: Date.now(),
@@ -18824,12 +19000,13 @@ class Supervisor {
18824
19000
  backend: result.backend,
18825
19001
  bead_id: result.beadId
18826
19002
  }));
19003
+ mkdirSync(this.readyDir(), { recursive: true });
18827
19004
  writeFileSync(join3(this.readyDir(), id), "", "utf-8");
18828
19005
  return id;
18829
19006
  } catch (err) {
18830
19007
  const elapsed = Math.round((Date.now() - startedAtMs) / 1000);
18831
19008
  const errorMsg = err?.message ?? String(err);
18832
- this.updateStatus(id, {
19009
+ setStatus({
18833
19010
  status: "error",
18834
19011
  elapsed_s: elapsed,
18835
19012
  error: errorMsg
@@ -18840,6 +19017,12 @@ class Supervisor {
18840
19017
  throw err;
18841
19018
  } finally {
18842
19019
  process.removeListener("SIGTERM", sigtermHandler);
19020
+ try {
19021
+ fifoReadline?.close();
19022
+ } catch {}
19023
+ try {
19024
+ fifoReadStream?.destroy();
19025
+ } catch {}
18843
19026
  try {
18844
19027
  fsyncSync(eventsFd);
18845
19028
  } catch {}
@@ -18862,14 +19045,21 @@ var exports_install = {};
18862
19045
  __export(exports_install, {
18863
19046
  run: () => run
18864
19047
  });
18865
- import { execFileSync as execFileSync2 } from "node:child_process";
18866
- import { fileURLToPath } from "node:url";
18867
- import { dirname as dirname2, join as join8 } from "node:path";
18868
19048
  async function run() {
18869
- const installerPath = join8(dirname2(fileURLToPath(import.meta.url)), "..", "bin", "install.js");
18870
- execFileSync2(process.execPath, [installerPath], { stdio: "inherit" });
19049
+ console.log("");
19050
+ console.log(yellow("⚠ DEPRECATED: `specialists install` is deprecated"));
19051
+ console.log("");
19052
+ console.log(` Use ${bold("specialists init")} instead.`);
19053
+ console.log("");
19054
+ console.log(" The init command:");
19055
+ console.log(" • creates specialists/ and .specialists/ directories");
19056
+ console.log(" • registers the MCP server in .mcp.json");
19057
+ console.log(" • injects workflow context into AGENTS.md/CLAUDE.md");
19058
+ console.log("");
19059
+ console.log(` ${dim("Run: specialists init --help for full details")}`);
19060
+ console.log("");
18871
19061
  }
18872
- var init_install = () => {};
19062
+ var bold = (s) => `\x1B[1m${s}\x1B[0m`, yellow = (s) => `\x1B[33m${s}\x1B[0m`, dim = (s) => `\x1B[2m${s}\x1B[0m`;
18873
19063
 
18874
19064
  // src/cli/version.ts
18875
19065
  var exports_version = {};
@@ -18877,9 +19067,23 @@ __export(exports_version, {
18877
19067
  run: () => run2
18878
19068
  });
18879
19069
  import { createRequire as createRequire2 } from "node:module";
19070
+ import { fileURLToPath } from "node:url";
19071
+ import { dirname as dirname2, join as join8 } from "node:path";
19072
+ import { existsSync as existsSync6 } from "node:fs";
18880
19073
  async function run2() {
18881
19074
  const req = createRequire2(import.meta.url);
18882
- const pkg = req("../package.json");
19075
+ const here = dirname2(fileURLToPath(import.meta.url));
19076
+ const bundlePkgPath = join8(here, "..", "package.json");
19077
+ const sourcePkgPath = join8(here, "..", "..", "package.json");
19078
+ let pkg;
19079
+ if (existsSync6(bundlePkgPath)) {
19080
+ pkg = req("../package.json");
19081
+ } else if (existsSync6(sourcePkgPath)) {
19082
+ pkg = req("../../package.json");
19083
+ } else {
19084
+ console.error("Cannot find package.json");
19085
+ process.exit(1);
19086
+ }
18883
19087
  console.log(`${pkg.name} v${pkg.version}`);
18884
19088
  }
18885
19089
  var init_version = () => {};
@@ -18944,19 +19148,19 @@ async function run3() {
18944
19148
  }
18945
19149
  const nameWidth = Math.max(...specialists.map((s) => s.name.length), 4);
18946
19150
  console.log(`
18947
- ${bold(`Specialists (${specialists.length})`)}
19151
+ ${bold2(`Specialists (${specialists.length})`)}
18948
19152
  `);
18949
19153
  for (const s of specialists) {
18950
19154
  const name = cyan(s.name.padEnd(nameWidth));
18951
- const scopeTag = s.scope === "default" ? green("[default]") : yellow("[user]");
18952
- const model = dim(s.model);
19155
+ const scopeTag = s.scope === "default" ? green("[default]") : yellow2("[user]");
19156
+ const model = dim2(s.model);
18953
19157
  const desc = s.description.length > 80 ? s.description.slice(0, 79) + "…" : s.description;
18954
19158
  console.log(` ${name} ${scopeTag} ${model}`);
18955
- console.log(` ${" ".repeat(nameWidth)} ${dim(desc)}`);
19159
+ console.log(` ${" ".repeat(nameWidth)} ${dim2(desc)}`);
18956
19160
  console.log();
18957
19161
  }
18958
19162
  }
18959
- var dim = (s) => `\x1B[2m${s}\x1B[0m`, bold = (s) => `\x1B[1m${s}\x1B[0m`, cyan = (s) => `\x1B[36m${s}\x1B[0m`, green = (s) => `\x1B[32m${s}\x1B[0m`, yellow = (s) => `\x1B[33m${s}\x1B[0m`, ArgParseError;
19163
+ var dim2 = (s) => `\x1B[2m${s}\x1B[0m`, bold2 = (s) => `\x1B[1m${s}\x1B[0m`, cyan = (s) => `\x1B[36m${s}\x1B[0m`, green = (s) => `\x1B[32m${s}\x1B[0m`, yellow2 = (s) => `\x1B[33m${s}\x1B[0m`, ArgParseError;
18960
19164
  var init_list = __esm(() => {
18961
19165
  init_loader();
18962
19166
  ArgParseError = class ArgParseError extends Error {
@@ -19043,31 +19247,31 @@ async function run4() {
19043
19247
  }
19044
19248
  const total = models.length;
19045
19249
  console.log(`
19046
- ${bold2(`Models on pi`)} ${dim2(`(${total} total)`)}
19250
+ ${bold3(`Models on pi`)} ${dim3(`(${total} total)`)}
19047
19251
  `);
19048
19252
  for (const [provider, pModels] of byProvider) {
19049
- console.log(` ${cyan2(provider)} ${dim2(`${pModels.length} model${pModels.length !== 1 ? "s" : ""}`)}`);
19253
+ console.log(` ${cyan2(provider)} ${dim3(`${pModels.length} model${pModels.length !== 1 ? "s" : ""}`)}`);
19050
19254
  const modelWidth = Math.max(...pModels.map((m) => m.model.length));
19051
19255
  for (const m of pModels) {
19052
19256
  const key = `${m.provider}/${m.model}`;
19053
19257
  const inUse = usedBy.get(key);
19054
19258
  const flags = [
19055
- m.thinking ? green2("thinking") : dim2("·"),
19056
- m.images ? dim2("images") : ""
19259
+ m.thinking ? green2("thinking") : dim3("·"),
19260
+ m.images ? dim3("images") : ""
19057
19261
  ].filter(Boolean).join(" ");
19058
- const ctx = dim2(`ctx ${m.context}`);
19059
- const usedLabel = inUse ? ` ${yellow2("←")} ${dim2(inUse.join(", "))}` : "";
19262
+ const ctx = dim3(`ctx ${m.context}`);
19263
+ const usedLabel = inUse ? ` ${yellow3("←")} ${dim3(inUse.join(", "))}` : "";
19060
19264
  console.log(` ${m.model.padEnd(modelWidth)} ${ctx.padEnd(18)} ${flags}${usedLabel}`);
19061
19265
  }
19062
19266
  console.log();
19063
19267
  }
19064
19268
  if (!args.used) {
19065
- console.log(dim2(` --provider <name> filter by provider`));
19066
- console.log(dim2(` --used only show models used by your specialists`));
19269
+ console.log(dim3(` --provider <name> filter by provider`));
19270
+ console.log(dim3(` --used only show models used by your specialists`));
19067
19271
  console.log();
19068
19272
  }
19069
19273
  }
19070
- var bold2 = (s) => `\x1B[1m${s}\x1B[0m`, dim2 = (s) => `\x1B[2m${s}\x1B[0m`, cyan2 = (s) => `\x1B[36m${s}\x1B[0m`, yellow2 = (s) => `\x1B[33m${s}\x1B[0m`, green2 = (s) => `\x1B[32m${s}\x1B[0m`;
19274
+ var bold3 = (s) => `\x1B[1m${s}\x1B[0m`, dim3 = (s) => `\x1B[2m${s}\x1B[0m`, cyan2 = (s) => `\x1B[36m${s}\x1B[0m`, yellow3 = (s) => `\x1B[33m${s}\x1B[0m`, green2 = (s) => `\x1B[32m${s}\x1B[0m`;
19071
19275
  var init_models = __esm(() => {
19072
19276
  init_loader();
19073
19277
  });
@@ -19077,17 +19281,17 @@ var exports_init = {};
19077
19281
  __export(exports_init, {
19078
19282
  run: () => run5
19079
19283
  });
19080
- import { copyFileSync, cpSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
19284
+ import { copyFileSync, cpSync, existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
19081
19285
  import { join as join9 } from "node:path";
19082
19286
  import { fileURLToPath as fileURLToPath2 } from "node:url";
19083
19287
  function ok(msg) {
19084
19288
  console.log(` ${green3("✓")} ${msg}`);
19085
19289
  }
19086
19290
  function skip(msg) {
19087
- console.log(` ${yellow3("○")} ${msg}`);
19291
+ console.log(` ${yellow4("○")} ${msg}`);
19088
19292
  }
19089
19293
  function loadJson(path, fallback) {
19090
- if (!existsSync6(path))
19294
+ if (!existsSync7(path))
19091
19295
  return structuredClone(fallback);
19092
19296
  try {
19093
19297
  return JSON.parse(readFileSync3(path, "utf-8"));
@@ -19102,10 +19306,10 @@ function saveJson(path, value) {
19102
19306
  function resolvePackagePath(relativePath) {
19103
19307
  const configPath = `config/${relativePath}`;
19104
19308
  let resolved = fileURLToPath2(new URL(`../${configPath}`, import.meta.url));
19105
- if (existsSync6(resolved))
19309
+ if (existsSync7(resolved))
19106
19310
  return resolved;
19107
19311
  resolved = fileURLToPath2(new URL(`../../${configPath}`, import.meta.url));
19108
- if (existsSync6(resolved))
19312
+ if (existsSync7(resolved))
19109
19313
  return resolved;
19110
19314
  return null;
19111
19315
  }
@@ -19121,7 +19325,7 @@ function copyCanonicalSpecialists(cwd) {
19121
19325
  skip("no specialist files found in package");
19122
19326
  return;
19123
19327
  }
19124
- if (!existsSync6(targetDir)) {
19328
+ if (!existsSync7(targetDir)) {
19125
19329
  mkdirSync2(targetDir, { recursive: true });
19126
19330
  }
19127
19331
  let copied = 0;
@@ -19129,7 +19333,7 @@ function copyCanonicalSpecialists(cwd) {
19129
19333
  for (const file of files) {
19130
19334
  const src = join9(sourceDir, file);
19131
19335
  const dest = join9(targetDir, file);
19132
- if (existsSync6(dest)) {
19336
+ if (existsSync7(dest)) {
19133
19337
  skipped++;
19134
19338
  } else {
19135
19339
  copyFileSync(src, dest);
@@ -19143,19 +19347,19 @@ function copyCanonicalSpecialists(cwd) {
19143
19347
  skip(`${skipped} specialist${skipped === 1 ? "" : "s"} already exist (not overwritten)`);
19144
19348
  }
19145
19349
  }
19146
- function copyCanonicalHooks(cwd) {
19350
+ function installProjectHooks(cwd) {
19147
19351
  const sourceDir = resolvePackagePath("hooks");
19148
19352
  if (!sourceDir) {
19149
19353
  skip("no canonical hooks found in package");
19150
19354
  return;
19151
19355
  }
19152
- const targetDir = join9(cwd, ".specialists", "default", "hooks");
19356
+ const targetDir = join9(cwd, ".claude", "hooks");
19153
19357
  const hooks = readdirSync2(sourceDir).filter((f) => f.endsWith(".mjs"));
19154
19358
  if (hooks.length === 0) {
19155
19359
  skip("no hook files found in package");
19156
19360
  return;
19157
19361
  }
19158
- if (!existsSync6(targetDir)) {
19362
+ if (!existsSync7(targetDir)) {
19159
19363
  mkdirSync2(targetDir, { recursive: true });
19160
19364
  }
19161
19365
  let copied = 0;
@@ -19163,7 +19367,7 @@ function copyCanonicalHooks(cwd) {
19163
19367
  for (const file of hooks) {
19164
19368
  const src = join9(sourceDir, file);
19165
19369
  const dest = join9(targetDir, file);
19166
- if (existsSync6(dest)) {
19370
+ if (existsSync7(dest)) {
19167
19371
  skipped++;
19168
19372
  } else {
19169
19373
  copyFileSync(src, dest);
@@ -19171,16 +19375,16 @@ function copyCanonicalHooks(cwd) {
19171
19375
  }
19172
19376
  }
19173
19377
  if (copied > 0) {
19174
- ok(`copied ${copied} hook${copied === 1 ? "" : "s"} to .specialists/default/hooks/`);
19378
+ ok(`installed ${copied} hook${copied === 1 ? "" : "s"} to .claude/hooks/`);
19175
19379
  }
19176
19380
  if (skipped > 0) {
19177
19381
  skip(`${skipped} hook${skipped === 1 ? "" : "s"} already exist (not overwritten)`);
19178
19382
  }
19179
19383
  }
19180
- function ensureProjectHooks(cwd) {
19384
+ function ensureProjectHookWiring(cwd) {
19181
19385
  const settingsPath = join9(cwd, ".claude", "settings.json");
19182
19386
  const settingsDir = join9(cwd, ".claude");
19183
- if (!existsSync6(settingsDir)) {
19387
+ if (!existsSync7(settingsDir)) {
19184
19388
  mkdirSync2(settingsDir, { recursive: true });
19185
19389
  }
19186
19390
  const settings = loadJson(settingsPath, {});
@@ -19194,8 +19398,8 @@ function ensureProjectHooks(cwd) {
19194
19398
  changed = true;
19195
19399
  }
19196
19400
  }
19197
- addHook("UserPromptSubmit", "node .specialists/default/hooks/specialists-complete.mjs");
19198
- addHook("SessionStart", "node .specialists/default/hooks/specialists-session-start.mjs");
19401
+ addHook("UserPromptSubmit", "node .claude/hooks/specialists-complete.mjs");
19402
+ addHook("SessionStart", "node .claude/hooks/specialists-session-start.mjs");
19199
19403
  if (changed) {
19200
19404
  saveJson(settingsPath, settings);
19201
19405
  ok("wired specialists hooks in .claude/settings.json");
@@ -19203,7 +19407,7 @@ function ensureProjectHooks(cwd) {
19203
19407
  skip(".claude/settings.json already has specialists hooks");
19204
19408
  }
19205
19409
  }
19206
- function copyCanonicalSkills(cwd) {
19410
+ function installProjectSkills(cwd) {
19207
19411
  const sourceDir = resolvePackagePath("skills");
19208
19412
  if (!sourceDir) {
19209
19413
  skip("no canonical skills found in package");
@@ -19214,44 +19418,39 @@ function copyCanonicalSkills(cwd) {
19214
19418
  skip("no skill directories found in package");
19215
19419
  return;
19216
19420
  }
19217
- const targetDir = join9(cwd, ".specialists", "default", "skills");
19218
- if (!existsSync6(targetDir)) {
19219
- mkdirSync2(targetDir, { recursive: true });
19220
- }
19221
- let copied = 0;
19222
- let skipped = 0;
19223
- for (const skill of skills) {
19224
- const src = join9(sourceDir, skill);
19225
- const dest = join9(targetDir, skill);
19226
- if (existsSync6(dest)) {
19227
- skipped++;
19228
- } else {
19229
- cpSync(src, dest, { recursive: true });
19230
- copied++;
19421
+ const targetDirs = [
19422
+ join9(cwd, ".claude", "skills"),
19423
+ join9(cwd, ".pi", "skills")
19424
+ ];
19425
+ let totalCopied = 0;
19426
+ let totalSkipped = 0;
19427
+ for (const targetDir of targetDirs) {
19428
+ if (!existsSync7(targetDir)) {
19429
+ mkdirSync2(targetDir, { recursive: true });
19430
+ }
19431
+ for (const skill of skills) {
19432
+ const src = join9(sourceDir, skill);
19433
+ const dest = join9(targetDir, skill);
19434
+ if (existsSync7(dest)) {
19435
+ totalSkipped++;
19436
+ } else {
19437
+ cpSync(src, dest, { recursive: true });
19438
+ totalCopied++;
19439
+ }
19231
19440
  }
19232
19441
  }
19233
- if (copied > 0) {
19234
- ok(`copied ${copied} skill${copied === 1 ? "" : "s"} to .specialists/default/skills/`);
19442
+ if (totalCopied > 0) {
19443
+ ok(`installed ${skills.length} skill${skills.length === 1 ? "" : "s"} to .claude/skills/ and .pi/skills/`);
19235
19444
  }
19236
- if (skipped > 0) {
19237
- skip(`${skipped} skill${skipped === 1 ? "" : "s"} already exist (not overwritten)`);
19445
+ if (totalSkipped > 0) {
19446
+ skip(`${totalSkipped} skill location${totalSkipped === 1 ? "" : "s"} already exist (not overwritten)`);
19238
19447
  }
19239
19448
  }
19240
19449
  function createUserDirs(cwd) {
19241
- const userDirs = [
19242
- join9(cwd, ".specialists", "user", "specialists"),
19243
- join9(cwd, ".specialists", "user", "hooks"),
19244
- join9(cwd, ".specialists", "user", "skills")
19245
- ];
19246
- let created = 0;
19247
- for (const dir of userDirs) {
19248
- if (!existsSync6(dir)) {
19249
- mkdirSync2(dir, { recursive: true });
19250
- created++;
19251
- }
19252
- }
19253
- if (created > 0) {
19254
- ok("created .specialists/user/ directories for custom assets");
19450
+ const userDir = join9(cwd, ".specialists", "user", "specialists");
19451
+ if (!existsSync7(userDir)) {
19452
+ mkdirSync2(userDir, { recursive: true });
19453
+ ok("created .specialists/user/specialists/ for custom specialists");
19255
19454
  }
19256
19455
  }
19257
19456
  function createRuntimeDirs(cwd) {
@@ -19261,7 +19460,7 @@ function createRuntimeDirs(cwd) {
19261
19460
  ];
19262
19461
  let created = 0;
19263
19462
  for (const dir of runtimeDirs) {
19264
- if (!existsSync6(dir)) {
19463
+ if (!existsSync7(dir)) {
19265
19464
  mkdirSync2(dir, { recursive: true });
19266
19465
  created++;
19267
19466
  }
@@ -19285,7 +19484,7 @@ function ensureProjectMcp(cwd) {
19285
19484
  }
19286
19485
  function ensureGitignore(cwd) {
19287
19486
  const gitignorePath = join9(cwd, ".gitignore");
19288
- const existing = existsSync6(gitignorePath) ? readFileSync3(gitignorePath, "utf-8") : "";
19487
+ const existing = existsSync7(gitignorePath) ? readFileSync3(gitignorePath, "utf-8") : "";
19289
19488
  let added = 0;
19290
19489
  const lines = existing.split(`
19291
19490
  `);
@@ -19306,7 +19505,7 @@ function ensureGitignore(cwd) {
19306
19505
  }
19307
19506
  function ensureAgentsMd(cwd) {
19308
19507
  const agentsPath = join9(cwd, "AGENTS.md");
19309
- if (existsSync6(agentsPath)) {
19508
+ if (existsSync7(agentsPath)) {
19310
19509
  const existing = readFileSync3(agentsPath, "utf-8");
19311
19510
  if (existing.includes(AGENTS_MARKER)) {
19312
19511
  skip("AGENTS.md already has Specialists section");
@@ -19324,40 +19523,42 @@ function ensureAgentsMd(cwd) {
19324
19523
  async function run5() {
19325
19524
  const cwd = process.cwd();
19326
19525
  console.log(`
19327
- ${bold3("specialists init")}
19526
+ ${bold4("specialists init")}
19328
19527
  `);
19329
19528
  copyCanonicalSpecialists(cwd);
19330
- copyCanonicalHooks(cwd);
19331
- copyCanonicalSkills(cwd);
19332
19529
  createUserDirs(cwd);
19333
19530
  createRuntimeDirs(cwd);
19334
19531
  ensureGitignore(cwd);
19335
19532
  ensureAgentsMd(cwd);
19336
19533
  ensureProjectMcp(cwd);
19337
- ensureProjectHooks(cwd);
19534
+ installProjectHooks(cwd);
19535
+ ensureProjectHookWiring(cwd);
19536
+ installProjectSkills(cwd);
19338
19537
  console.log(`
19339
- ${bold3("Done!")}
19538
+ ${bold4("Done!")}
19340
19539
  `);
19341
- console.log(` ${dim3("Directory structure:")}`);
19540
+ console.log(` ${dim4("Project-local installation:")}`);
19541
+ console.log(` .claude/hooks/ ${dim4("# hooks (Claude Code)")}`);
19542
+ console.log(` .claude/settings.json ${dim4("# hook wiring")}`);
19543
+ console.log(` .claude/skills/ ${dim4("# skills (Claude Code)")}`);
19544
+ console.log(` .pi/skills/ ${dim4("# skills (pi)")}`);
19545
+ console.log("");
19546
+ console.log(` ${dim4(".specialists/ structure:")}`);
19342
19547
  console.log(` .specialists/`);
19343
- console.log(` ├── default/ ${dim3("# canonical assets (from init)")}`);
19344
- console.log(` │ ├── specialists/`);
19345
- console.log(` ├── hooks/`);
19346
- console.log(` │ └── skills/`);
19347
- console.log(` ├── user/ ${dim3("# your custom additions")}`);
19348
- console.log(` │ ├── specialists/`);
19349
- console.log(` │ ├── hooks/`);
19350
- console.log(` │ └── skills/`);
19351
- console.log(` ├── jobs/ ${dim3("# runtime (gitignored)")}`);
19352
- console.log(` └── ready/ ${dim3("# runtime (gitignored)")}`);
19548
+ console.log(` ├── default/ ${dim4("# canonical specialists (from init)")}`);
19549
+ console.log(` │ └── specialists/`);
19550
+ console.log(` ├── user/ ${dim4("# your custom specialists")}`);
19551
+ console.log(` │ └── specialists/`);
19552
+ console.log(` ├── jobs/ ${dim4("# runtime (gitignored)")}`);
19553
+ console.log(` └── ready/ ${dim4("# runtime (gitignored)")}`);
19353
19554
  console.log(`
19354
- ${dim3("Next steps:")}`);
19355
- console.log(` 1. Run ${yellow3("specialists list")} to see available specialists`);
19356
- console.log(` 2. Add custom specialists to ${yellow3(".specialists/user/specialists/")}`);
19357
- console.log(` 3. Restart Claude Code to pick up changes
19555
+ ${dim4("Next steps:")}`);
19556
+ console.log(` 1. Run ${yellow4("specialists list")} to see available specialists`);
19557
+ console.log(` 2. Add custom specialists to ${yellow4(".specialists/user/specialists/")}`);
19558
+ console.log(` 3. Restart Claude Code or pi to pick up changes
19358
19559
  `);
19359
19560
  }
19360
- var bold3 = (s) => `\x1B[1m${s}\x1B[0m`, green3 = (s) => `\x1B[32m${s}\x1B[0m`, yellow3 = (s) => `\x1B[33m${s}\x1B[0m`, dim3 = (s) => `\x1B[2m${s}\x1B[0m`, AGENTS_BLOCK, AGENTS_MARKER = "## Specialists", GITIGNORE_ENTRIES, MCP_FILE = ".mcp.json", MCP_SERVER_NAME = "specialists", MCP_SERVER_CONFIG;
19561
+ var bold4 = (s) => `\x1B[1m${s}\x1B[0m`, green3 = (s) => `\x1B[32m${s}\x1B[0m`, yellow4 = (s) => `\x1B[33m${s}\x1B[0m`, dim4 = (s) => `\x1B[2m${s}\x1B[0m`, AGENTS_BLOCK, AGENTS_MARKER = "## Specialists", GITIGNORE_ENTRIES, MCP_FILE = ".mcp.json", MCP_SERVER_NAME = "specialists", MCP_SERVER_CONFIG;
19361
19562
  var init_init = __esm(() => {
19362
19563
  AGENTS_BLOCK = `
19363
19564
  ## Specialists
@@ -19373,13 +19574,126 @@ Add custom specialists to \`.specialists/user/specialists/\` to extend the defau
19373
19574
  MCP_SERVER_CONFIG = { command: "specialists", args: [] };
19374
19575
  });
19375
19576
 
19577
+ // src/cli/validate.ts
19578
+ var exports_validate = {};
19579
+ __export(exports_validate, {
19580
+ run: () => run6,
19581
+ parseArgs: () => parseArgs3,
19582
+ ArgParseError: () => ArgParseError2
19583
+ });
19584
+ import { readFile as readFile2 } from "node:fs/promises";
19585
+ import { existsSync as existsSync8 } from "node:fs";
19586
+ import { join as join10 } from "node:path";
19587
+ function parseArgs3(argv) {
19588
+ const name = argv[0];
19589
+ if (!name || name.startsWith("--")) {
19590
+ throw new ArgParseError2("Usage: specialists validate <name> [--json]");
19591
+ }
19592
+ const json = argv.includes("--json");
19593
+ return { name, json };
19594
+ }
19595
+ function findSpecialistFile(name) {
19596
+ const scanDirs = [
19597
+ join10(process.cwd(), ".specialists", "user", "specialists"),
19598
+ join10(process.cwd(), ".specialists", "default", "specialists"),
19599
+ join10(process.cwd(), "specialists")
19600
+ ];
19601
+ for (const dir of scanDirs) {
19602
+ const candidate = join10(dir, `${name}.specialist.yaml`);
19603
+ if (existsSync8(candidate)) {
19604
+ return candidate;
19605
+ }
19606
+ }
19607
+ return;
19608
+ }
19609
+ async function run6() {
19610
+ let args;
19611
+ try {
19612
+ args = parseArgs3(process.argv.slice(3));
19613
+ } catch (err) {
19614
+ if (err instanceof ArgParseError2) {
19615
+ console.error(`Error: ${err.message}`);
19616
+ process.exit(1);
19617
+ }
19618
+ throw err;
19619
+ }
19620
+ const { name, json } = args;
19621
+ const filePath = findSpecialistFile(name);
19622
+ if (!filePath) {
19623
+ if (json) {
19624
+ console.log(JSON.stringify({ valid: false, errors: [{ path: "name", message: `Specialist not found: ${name}`, code: "not_found" }] }));
19625
+ } else {
19626
+ console.error(`${red("✗")} Specialist not found: ${cyan3(name)}`);
19627
+ }
19628
+ process.exit(1);
19629
+ }
19630
+ let content;
19631
+ try {
19632
+ content = await readFile2(filePath, "utf-8");
19633
+ } catch (e) {
19634
+ const msg = e instanceof Error ? e.message : String(e);
19635
+ if (json) {
19636
+ console.log(JSON.stringify({ valid: false, errors: [{ path: "file", message: `Failed to read file: ${msg}`, code: "read_error" }] }));
19637
+ } else {
19638
+ console.error(`${red("✗")} Failed to read file: ${msg}`);
19639
+ }
19640
+ process.exit(1);
19641
+ }
19642
+ const result = await validateSpecialist(content);
19643
+ if (json) {
19644
+ console.log(JSON.stringify({
19645
+ valid: result.valid,
19646
+ errors: result.errors,
19647
+ warnings: result.warnings,
19648
+ file: filePath
19649
+ }, null, 2));
19650
+ process.exit(result.valid ? 0 : 1);
19651
+ }
19652
+ console.log(`
19653
+ ${bold5("Validating")} ${cyan3(name)} ${dim5(`(${filePath})`)}
19654
+ `);
19655
+ if (result.valid) {
19656
+ console.log(`${green4("✓")} Schema validation passed
19657
+ `);
19658
+ } else {
19659
+ console.log(`${red("✗")} Schema validation failed:
19660
+ `);
19661
+ for (const error2 of result.errors) {
19662
+ console.log(` ${red("•")} ${error2.message}`);
19663
+ if (error2.path && error2.path !== "yaml") {
19664
+ console.log(` ${dim5(`path: ${error2.path}`)}`);
19665
+ }
19666
+ }
19667
+ console.log();
19668
+ }
19669
+ if (result.warnings.length > 0) {
19670
+ console.log(`${yellow5("Warnings")}:
19671
+ `);
19672
+ for (const warning of result.warnings) {
19673
+ console.log(` ${yellow5("⚠")} ${warning}`);
19674
+ }
19675
+ console.log();
19676
+ }
19677
+ process.exit(result.valid ? 0 : 1);
19678
+ }
19679
+ var bold5 = (s) => `\x1B[1m${s}\x1B[0m`, dim5 = (s) => `\x1B[2m${s}\x1B[0m`, green4 = (s) => `\x1B[32m${s}\x1B[0m`, red = (s) => `\x1B[31m${s}\x1B[0m`, yellow5 = (s) => `\x1B[33m${s}\x1B[0m`, cyan3 = (s) => `\x1B[36m${s}\x1B[0m`, ArgParseError2;
19680
+ var init_validate = __esm(() => {
19681
+ init_schema();
19682
+ ArgParseError2 = class ArgParseError2 extends Error {
19683
+ constructor(message) {
19684
+ super(message);
19685
+ this.name = "ArgParseError";
19686
+ }
19687
+ };
19688
+ });
19689
+
19376
19690
  // src/cli/edit.ts
19377
19691
  var exports_edit = {};
19378
19692
  __export(exports_edit, {
19379
- run: () => run6
19693
+ run: () => run7
19380
19694
  });
19381
19695
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "node:fs";
19382
- function parseArgs3(argv) {
19696
+ function parseArgs4(argv) {
19383
19697
  const name = argv[0];
19384
19698
  if (!name || name.startsWith("--")) {
19385
19699
  console.error("Usage: specialists|sp edit <name> --<field> <value> [--dry-run]");
@@ -19398,8 +19712,8 @@ function parseArgs3(argv) {
19398
19712
  }
19399
19713
  if (token === "--scope") {
19400
19714
  const v = argv[++i];
19401
- if (v !== "project" && v !== "user") {
19402
- console.error(`Error: --scope must be "project" or "user", got: "${v ?? ""}"`);
19715
+ if (v !== "default" && v !== "user") {
19716
+ console.error(`Error: --scope must be "default" or "user", got: "${v ?? ""}"`);
19403
19717
  process.exit(1);
19404
19718
  }
19405
19719
  scope = v;
@@ -19441,8 +19755,8 @@ function setIn(doc2, path, value) {
19441
19755
  node.set(leaf, value);
19442
19756
  }
19443
19757
  }
19444
- async function run6() {
19445
- const args = parseArgs3(process.argv.slice(3));
19758
+ async function run7() {
19759
+ const args = parseArgs4(process.argv.slice(3));
19446
19760
  const { name, field, value, dryRun, scope } = args;
19447
19761
  const loader = new SpecialistLoader;
19448
19762
  const all = await loader.list();
@@ -19450,7 +19764,7 @@ async function run6() {
19450
19764
  if (!match) {
19451
19765
  const hint = scope ? ` (scope: ${scope})` : "";
19452
19766
  console.error(`Error: specialist "${name}" not found${hint}`);
19453
- console.error(` Run ${yellow4("specialists list")} to see available specialists`);
19767
+ console.error(` Run ${yellow6("specialists list")} to see available specialists`);
19454
19768
  process.exit(1);
19455
19769
  }
19456
19770
  const raw = readFileSync4(match.filePath, "utf-8");
@@ -19466,10 +19780,10 @@ async function run6() {
19466
19780
  const updated = doc2.toString();
19467
19781
  if (dryRun) {
19468
19782
  console.log(`
19469
- ${bold4(`[dry-run] ${match.filePath}`)}
19783
+ ${bold6(`[dry-run] ${match.filePath}`)}
19470
19784
  `);
19471
- console.log(dim4("--- current"));
19472
- console.log(dim4(`+++ updated`));
19785
+ console.log(dim6("--- current"));
19786
+ console.log(dim6(`+++ updated`));
19473
19787
  const oldLines = raw.split(`
19474
19788
  `);
19475
19789
  const newLines = updated.split(`
@@ -19477,8 +19791,8 @@ ${bold4(`[dry-run] ${match.filePath}`)}
19477
19791
  newLines.forEach((line, i) => {
19478
19792
  if (line !== oldLines[i]) {
19479
19793
  if (oldLines[i] !== undefined)
19480
- console.log(dim4(`- ${oldLines[i]}`));
19481
- console.log(green4(`+ ${line}`));
19794
+ console.log(dim6(`- ${oldLines[i]}`));
19795
+ console.log(green5(`+ ${line}`));
19482
19796
  }
19483
19797
  });
19484
19798
  console.log();
@@ -19486,9 +19800,9 @@ ${bold4(`[dry-run] ${match.filePath}`)}
19486
19800
  }
19487
19801
  writeFileSync5(match.filePath, updated, "utf-8");
19488
19802
  const displayValue = field === "tags" ? `[${typedValue.join(", ")}]` : String(typedValue);
19489
- console.log(`${green4("✓")} ${bold4(name)}: ${yellow4(field)} = ${displayValue}` + dim4(` (${match.filePath})`));
19803
+ console.log(`${green5("✓")} ${bold6(name)}: ${yellow6(field)} = ${displayValue}` + dim6(` (${match.filePath})`));
19490
19804
  }
19491
- var bold4 = (s) => `\x1B[1m${s}\x1B[0m`, green4 = (s) => `\x1B[32m${s}\x1B[0m`, yellow4 = (s) => `\x1B[33m${s}\x1B[0m`, dim4 = (s) => `\x1B[2m${s}\x1B[0m`, FIELD_MAP, VALID_PERMISSIONS;
19805
+ var bold6 = (s) => `\x1B[1m${s}\x1B[0m`, green5 = (s) => `\x1B[32m${s}\x1B[0m`, yellow6 = (s) => `\x1B[33m${s}\x1B[0m`, dim6 = (s) => `\x1B[2m${s}\x1B[0m`, FIELD_MAP, VALID_PERMISSIONS;
19492
19806
  var init_edit = __esm(() => {
19493
19807
  init_dist();
19494
19808
  init_loader();
@@ -19506,10 +19820,10 @@ var init_edit = __esm(() => {
19506
19820
  // src/cli/run.ts
19507
19821
  var exports_run = {};
19508
19822
  __export(exports_run, {
19509
- run: () => run7
19823
+ run: () => run8
19510
19824
  });
19511
- import { join as join10 } from "node:path";
19512
- async function parseArgs4(argv) {
19825
+ import { join as join11 } from "node:path";
19826
+ async function parseArgs5(argv) {
19513
19827
  const name = argv[0];
19514
19828
  if (!name || name.startsWith("--")) {
19515
19829
  console.error('Usage: specialists|sp run <name> [--prompt "..."] [--bead <id>] [--context-depth <n>] [--model <model>] [--no-beads] [--keep-alive]');
@@ -19568,11 +19882,11 @@ async function parseArgs4(argv) {
19568
19882
  }
19569
19883
  return { name, prompt, beadId, model, noBeads, keepAlive, contextDepth };
19570
19884
  }
19571
- async function run7() {
19572
- const args = await parseArgs4(process.argv.slice(3));
19885
+ async function run8() {
19886
+ const args = await parseArgs5(process.argv.slice(3));
19573
19887
  const loader = new SpecialistLoader;
19574
19888
  const circuitBreaker = new CircuitBreaker;
19575
- const hooks = new HookEmitter({ tracePath: join10(process.cwd(), ".specialists", "trace.jsonl") });
19889
+ const hooks = new HookEmitter({ tracePath: join11(process.cwd(), ".specialists", "trace.jsonl") });
19576
19890
  const beadsClient = args.noBeads ? undefined : new BeadsClient;
19577
19891
  const beadReader = beadsClient ?? new BeadsClient;
19578
19892
  let prompt = args.prompt;
@@ -19584,7 +19898,7 @@ async function run7() {
19584
19898
  }
19585
19899
  const blockers = args.contextDepth > 0 ? beadReader.getCompletedBlockers(args.beadId, args.contextDepth) : [];
19586
19900
  if (blockers.length > 0) {
19587
- process.stderr.write(dim5(`
19901
+ process.stderr.write(dim7(`
19588
19902
  [context: ${blockers.length} completed dep${blockers.length > 1 ? "s" : ""} injected]
19589
19903
  `));
19590
19904
  }
@@ -19601,57 +19915,70 @@ async function run7() {
19601
19915
  circuitBreaker,
19602
19916
  beadsClient
19603
19917
  });
19604
- process.stderr.write(`
19605
- ${bold5(`Running ${cyan3(args.name)}`)}
19606
-
19607
- `);
19608
- let trackingBeadId;
19609
- const result = await runner.run({
19610
- name: args.name,
19611
- prompt,
19612
- variables,
19613
- backendOverride: args.model,
19614
- inputBeadId: args.beadId
19615
- }, (delta) => process.stdout.write(delta), undefined, (meta) => process.stderr.write(dim5(`
19918
+ const jobsDir = join11(process.cwd(), ".specialists", "jobs");
19919
+ const supervisor = new Supervisor({
19920
+ runner,
19921
+ runOptions: {
19922
+ name: args.name,
19923
+ prompt,
19924
+ variables,
19925
+ backendOverride: args.model,
19926
+ inputBeadId: args.beadId,
19927
+ keepAlive: args.keepAlive
19928
+ },
19929
+ jobsDir,
19930
+ beadsClient,
19931
+ onProgress: (delta) => process.stdout.write(delta),
19932
+ onMeta: (meta) => process.stderr.write(dim7(`
19616
19933
  [${meta.backend} / ${meta.model}]
19617
19934
 
19618
- `)), (killFn) => {
19619
- process.on("SIGINT", () => {
19620
- process.stderr.write(`
19935
+ `)),
19936
+ onJobStarted: ({ id }) => process.stderr.write(dim7(`[job started: ${id}]
19937
+ `))
19938
+ });
19939
+ try {
19940
+ await loader.get(args.name);
19941
+ } catch (err) {
19942
+ process.stderr.write(`Error: ${err?.message ?? err}
19943
+ `);
19944
+ process.exit(1);
19945
+ }
19946
+ process.stderr.write(`
19947
+ ${bold7(`Running ${cyan4(args.name)}`)}
19621
19948
 
19622
- Interrupted.
19623
19949
  `);
19624
- killFn();
19625
- process.exit(130);
19626
- });
19627
- }, (beadId) => {
19628
- trackingBeadId = beadId;
19629
- process.stderr.write(dim5(`
19630
- [bead: ${beadId}]
19631
- `));
19632
- });
19633
- if (result.output && !result.output.endsWith(`
19634
- `))
19635
- process.stdout.write(`
19950
+ let jobId;
19951
+ try {
19952
+ jobId = await supervisor.run();
19953
+ } catch (err) {
19954
+ process.stderr.write(`Error: ${err?.message ?? err}
19636
19955
  `);
19637
- const secs = (result.durationMs / 1000).toFixed(1);
19638
- const effectiveBeadId = args.beadId ?? trackingBeadId;
19956
+ process.exit(1);
19957
+ }
19958
+ const status = supervisor.readStatus(jobId);
19959
+ const secs = ((status?.last_event_at_ms ?? Date.now()) - (status?.started_at_ms ?? Date.now())) / 1000;
19639
19960
  const footer = [
19640
- effectiveBeadId ? `bead ${effectiveBeadId}` : "",
19641
- `${secs}s`,
19642
- dim5(result.model)
19961
+ `job ${jobId}`,
19962
+ status?.bead_id ? `bead ${status.bead_id}` : "",
19963
+ `${secs.toFixed(1)}s`,
19964
+ status?.model ? dim7(`${status.backend}/${status.model}`) : ""
19643
19965
  ].filter(Boolean).join(" ");
19644
19966
  process.stderr.write(`
19645
- ${green5("✓")} ${footer}
19967
+ ${green6("✓")} ${footer}
19646
19968
 
19647
19969
  `);
19970
+ process.stderr.write(dim7(`Poll: specialists poll ${jobId} --json
19971
+
19972
+ `));
19973
+ process.exit(0);
19648
19974
  }
19649
- var bold5 = (s) => `\x1B[1m${s}\x1B[0m`, dim5 = (s) => `\x1B[2m${s}\x1B[0m`, green5 = (s) => `\x1B[32m${s}\x1B[0m`, cyan3 = (s) => `\x1B[36m${s}\x1B[0m`;
19975
+ var bold7 = (s) => `\x1B[1m${s}\x1B[0m`, dim7 = (s) => `\x1B[2m${s}\x1B[0m`, green6 = (s) => `\x1B[32m${s}\x1B[0m`, cyan4 = (s) => `\x1B[36m${s}\x1B[0m`;
19650
19976
  var init_run = __esm(() => {
19651
19977
  init_loader();
19652
19978
  init_runner();
19653
19979
  init_hooks();
19654
19980
  init_beads();
19981
+ init_supervisor();
19655
19982
  });
19656
19983
 
19657
19984
  // src/cli/format-helpers.ts
@@ -19692,9 +20019,9 @@ class JobColorMap {
19692
20019
  }
19693
20020
  }
19694
20021
  function formatEventLine(event, options) {
19695
- const ts = dim6(formatTime(event.t));
19696
- const label = options.colorize(bold6(getEventLabel(event.type).padEnd(5)));
19697
- const prefix = `${options.colorize(`[${options.jobId}]`)} ${options.specialist}${options.beadId ? ` ${dim6(`[${options.beadId}]`)}` : ""}`;
20022
+ const ts = dim8(formatTime(event.t));
20023
+ const label = options.colorize(bold8(getEventLabel(event.type).padEnd(5)));
20024
+ const prefix = `${options.colorize(`[${options.jobId}]`)} ${options.specialist}${options.beadId ? ` ${dim8(`[${options.beadId}]`)}` : ""}`;
19698
20025
  const detailParts = [];
19699
20026
  if (event.type === "meta") {
19700
20027
  detailParts.push(`model=${event.model}`);
@@ -19723,19 +20050,26 @@ function formatEventLine(event, options) {
19723
20050
  detailParts.push("kind=assistant");
19724
20051
  } else if (event.type === "thinking") {
19725
20052
  detailParts.push("kind=model");
20053
+ } else if (event.type === "message") {
20054
+ detailParts.push(`phase=${event.phase}`);
20055
+ detailParts.push(`role=${event.role}`);
20056
+ } else if (event.type === "turn") {
20057
+ detailParts.push(`phase=${event.phase}`);
19726
20058
  }
19727
- const detail = detailParts.length > 0 ? dim6(detailParts.join(" ")) : "";
20059
+ const detail = detailParts.length > 0 ? dim8(detailParts.join(" ")) : "";
19728
20060
  return `${ts} ${prefix} ${label}${detail ? ` ${detail}` : ""}`.trimEnd();
19729
20061
  }
19730
- var dim6 = (s) => `\x1B[2m${s}\x1B[0m`, bold6 = (s) => `\x1B[1m${s}\x1B[0m`, cyan4 = (s) => `\x1B[36m${s}\x1B[0m`, yellow5 = (s) => `\x1B[33m${s}\x1B[0m`, red = (s) => `\x1B[31m${s}\x1B[0m`, green6 = (s) => `\x1B[32m${s}\x1B[0m`, blue = (s) => `\x1B[34m${s}\x1B[0m`, magenta = (s) => `\x1B[35m${s}\x1B[0m`, JOB_COLORS, EVENT_LABELS;
20062
+ var dim8 = (s) => `\x1B[2m${s}\x1B[0m`, bold8 = (s) => `\x1B[1m${s}\x1B[0m`, cyan5 = (s) => `\x1B[36m${s}\x1B[0m`, yellow7 = (s) => `\x1B[33m${s}\x1B[0m`, red2 = (s) => `\x1B[31m${s}\x1B[0m`, green7 = (s) => `\x1B[32m${s}\x1B[0m`, blue = (s) => `\x1B[34m${s}\x1B[0m`, magenta = (s) => `\x1B[35m${s}\x1B[0m`, JOB_COLORS, EVENT_LABELS;
19731
20063
  var init_format_helpers = __esm(() => {
19732
- JOB_COLORS = [cyan4, yellow5, magenta, green6, blue, red];
20064
+ JOB_COLORS = [cyan5, yellow7, magenta, green7, blue, red2];
19733
20065
  EVENT_LABELS = {
19734
20066
  run_start: "START",
19735
20067
  meta: "META",
19736
20068
  thinking: "THINK",
19737
20069
  tool: "TOOL",
19738
20070
  text: "TEXT",
20071
+ message: "MSG",
20072
+ turn: "TURN",
19739
20073
  run_complete: "DONE",
19740
20074
  done: "DONE",
19741
20075
  agent_end: "DONE",
@@ -19746,27 +20080,27 @@ var init_format_helpers = __esm(() => {
19746
20080
  // src/cli/status.ts
19747
20081
  var exports_status = {};
19748
20082
  __export(exports_status, {
19749
- run: () => run8
20083
+ run: () => run9
19750
20084
  });
19751
20085
  import { spawnSync as spawnSync6 } from "node:child_process";
19752
- import { existsSync as existsSync7 } from "node:fs";
19753
- import { join as join11 } from "node:path";
20086
+ import { existsSync as existsSync9 } from "node:fs";
20087
+ import { join as join12 } from "node:path";
19754
20088
  function ok2(msg) {
19755
- console.log(` ${green6("✓")} ${msg}`);
20089
+ console.log(` ${green7("✓")} ${msg}`);
19756
20090
  }
19757
20091
  function warn(msg) {
19758
- console.log(` ${yellow5("○")} ${msg}`);
20092
+ console.log(` ${yellow7("○")} ${msg}`);
19759
20093
  }
19760
20094
  function fail(msg) {
19761
- console.log(` ${red("✗")} ${msg}`);
20095
+ console.log(` ${red2("✗")} ${msg}`);
19762
20096
  }
19763
20097
  function info(msg) {
19764
- console.log(` ${dim6(msg)}`);
20098
+ console.log(` ${dim8(msg)}`);
19765
20099
  }
19766
20100
  function section(label) {
19767
20101
  const line = "─".repeat(Math.max(0, 38 - label.length));
19768
20102
  console.log(`
19769
- ${bold6(`── ${label} ${line}`)}`);
20103
+ ${bold8(`── ${label} ${line}`)}`);
19770
20104
  }
19771
20105
  function cmd(bin, args) {
19772
20106
  const r = spawnSync6(bin, args, {
@@ -19789,18 +20123,18 @@ function formatElapsed2(s) {
19789
20123
  function statusColor(status) {
19790
20124
  switch (status) {
19791
20125
  case "running":
19792
- return cyan4(status);
20126
+ return cyan5(status);
19793
20127
  case "done":
19794
- return green6(status);
20128
+ return green7(status);
19795
20129
  case "error":
19796
- return red(status);
20130
+ return red2(status);
19797
20131
  case "starting":
19798
- return yellow5(status);
20132
+ return yellow7(status);
19799
20133
  default:
19800
20134
  return status;
19801
20135
  }
19802
20136
  }
19803
- async function run8() {
20137
+ async function run9() {
19804
20138
  const argv = process.argv.slice(3);
19805
20139
  const jsonMode = argv.includes("--json");
19806
20140
  const loader = new SpecialistLoader;
@@ -19812,11 +20146,11 @@ async function run8() {
19812
20146
  `).slice(1).map((line) => line.split(/\s+/)[0]).filter(Boolean)) : new Set;
19813
20147
  const bdInstalled = isInstalled("bd");
19814
20148
  const bdVersion = bdInstalled ? cmd("bd", ["--version"]) : null;
19815
- const beadsPresent = existsSync7(join11(process.cwd(), ".beads"));
20149
+ const beadsPresent = existsSync9(join12(process.cwd(), ".beads"));
19816
20150
  const specialistsBin = cmd("which", ["specialists"]);
19817
- const jobsDir = join11(process.cwd(), ".specialists", "jobs");
20151
+ const jobsDir = join12(process.cwd(), ".specialists", "jobs");
19818
20152
  let jobs = [];
19819
- if (existsSync7(jobsDir)) {
20153
+ if (existsSync9(jobsDir)) {
19820
20154
  const supervisor = new Supervisor({
19821
20155
  runner: null,
19822
20156
  runOptions: null,
@@ -19867,60 +20201,62 @@ async function run8() {
19867
20201
  return;
19868
20202
  }
19869
20203
  console.log(`
19870
- ${bold6("specialists status")}
20204
+ ${bold8("specialists status")}
19871
20205
  `);
19872
20206
  section("Specialists");
19873
20207
  if (allSpecialists.length === 0) {
19874
- warn(`no specialists found — run ${yellow5("specialists init")} to scaffold`);
20208
+ warn(`no specialists found — run ${yellow7("specialists init")} to scaffold`);
19875
20209
  } else {
19876
20210
  const byScope = allSpecialists.reduce((acc, s) => {
19877
20211
  acc[s.scope] = (acc[s.scope] ?? 0) + 1;
19878
20212
  return acc;
19879
20213
  }, {});
19880
20214
  const scopeSummary = Object.entries(byScope).map(([scope, n]) => `${n} ${scope}`).join(", ");
19881
- ok2(`${allSpecialists.length} found ${dim6(`(${scopeSummary})`)}`);
20215
+ ok2(`${allSpecialists.length} found ${dim8(`(${scopeSummary})`)}`);
19882
20216
  for (const s of allSpecialists) {
19883
20217
  const staleness = stalenessMap[s.name];
19884
20218
  if (staleness === "AGED") {
19885
- warn(`${s.name} ${red("AGED")} ${dim6(s.scope)}`);
20219
+ warn(`${s.name} ${red2("AGED")} ${dim8(s.scope)}`);
19886
20220
  } else if (staleness === "STALE") {
19887
- warn(`${s.name} ${yellow5("STALE")} ${dim6(s.scope)}`);
20221
+ warn(`${s.name} ${yellow7("STALE")} ${dim8(s.scope)}`);
19888
20222
  }
19889
20223
  }
19890
20224
  }
19891
20225
  section("pi (coding agent runtime)");
19892
20226
  if (!piInstalled) {
19893
- fail(`pi not installed — install ${yellow5("pi")} first`);
20227
+ fail(`pi not installed — install ${yellow7("pi")} first`);
19894
20228
  } else {
19895
20229
  const vStr = piVersion?.ok ? `v${piVersion.stdout}` : "unknown version";
19896
- const pStr = piProviders.size > 0 ? `${piProviders.size} provider${piProviders.size > 1 ? "s" : ""} active ${dim6(`(${[...piProviders].join(", ")})`)} ` : yellow5("no providers configured — run pi config");
20230
+ const pStr = piProviders.size > 0 ? `${piProviders.size} provider${piProviders.size > 1 ? "s" : ""} active ${dim8(`(${[...piProviders].join(", ")})`)} ` : yellow7("no providers configured — run pi config");
19897
20231
  ok2(`${vStr} — ${pStr}`);
19898
20232
  }
19899
20233
  section("beads (issue tracker)");
19900
20234
  if (!bdInstalled) {
19901
- fail(`bd not installed — install ${yellow5("bd")} first`);
20235
+ fail(`bd not installed — install ${yellow7("bd")} first`);
19902
20236
  } else {
19903
- ok2(`bd installed${bdVersion?.ok ? ` ${dim6(bdVersion.stdout)}` : ""}`);
20237
+ ok2(`bd installed${bdVersion?.ok ? ` ${dim8(bdVersion.stdout)}` : ""}`);
19904
20238
  if (beadsPresent) {
19905
20239
  ok2(".beads/ present in project");
19906
20240
  } else {
19907
- warn(`.beads/ not found — run ${yellow5("bd init")} to enable issue tracking`);
20241
+ warn(`.beads/ not found — run ${yellow7("bd init")} to enable issue tracking`);
19908
20242
  }
19909
20243
  }
19910
20244
  section("MCP");
19911
20245
  if (!specialistsBin.ok) {
19912
- fail(`specialists not installed globally — run ${yellow5("npm install -g @jaggerxtrm/specialists")}`);
20246
+ fail(`specialists not installed globally — run ${yellow7("npm install -g @jaggerxtrm/specialists")}`);
19913
20247
  } else {
19914
- ok2(`specialists binary installed ${dim6(specialistsBin.stdout)}`);
20248
+ ok2(`specialists binary installed ${dim8(specialistsBin.stdout)}`);
19915
20249
  info(`verify registration: claude mcp get specialists`);
19916
20250
  info(`re-register: specialists install`);
19917
20251
  }
19918
- if (jobs.length > 0) {
19919
- section("Active Jobs");
20252
+ section("Active Jobs");
20253
+ if (jobs.length === 0) {
20254
+ info(" (none)");
20255
+ } else {
19920
20256
  for (const job of jobs) {
19921
20257
  const elapsed = formatElapsed2(job);
19922
- const detail = job.status === "error" ? red(job.error?.slice(0, 40) ?? "error") : job.current_tool ? dim6(`tool: ${job.current_tool}`) : dim6(job.current_event ?? "");
19923
- console.log(` ${dim6(job.id)} ${job.specialist.padEnd(20)} ${statusColor(job.status).padEnd(7)} ${elapsed.padStart(6)} ${detail}`);
20258
+ const detail = job.status === "error" ? red2(job.error?.slice(0, 40) ?? "error") : job.current_tool ? dim8(`tool: ${job.current_tool}`) : dim8(job.current_event ?? "");
20259
+ console.log(` ${dim8(job.id)} ${job.specialist.padEnd(20)} ${statusColor(job.status).padEnd(7)} ${elapsed.padStart(6)} ${detail}`);
19924
20260
  }
19925
20261
  }
19926
20262
  console.log();
@@ -19934,51 +20270,57 @@ var init_status = __esm(() => {
19934
20270
  // src/cli/result.ts
19935
20271
  var exports_result = {};
19936
20272
  __export(exports_result, {
19937
- run: () => run9
20273
+ run: () => run10
19938
20274
  });
19939
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "node:fs";
19940
- import { join as join12 } from "node:path";
19941
- async function run9() {
20275
+ import { existsSync as existsSync10, readFileSync as readFileSync5 } from "node:fs";
20276
+ import { join as join13 } from "node:path";
20277
+ async function run10() {
19942
20278
  const jobId = process.argv[3];
19943
20279
  if (!jobId) {
19944
20280
  console.error("Usage: specialists|sp result <job-id>");
19945
20281
  process.exit(1);
19946
20282
  }
19947
- const jobsDir = join12(process.cwd(), ".specialists", "jobs");
20283
+ const jobsDir = join13(process.cwd(), ".specialists", "jobs");
19948
20284
  const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
19949
20285
  const status = supervisor.readStatus(jobId);
19950
20286
  if (!status) {
19951
20287
  console.error(`No job found: ${jobId}`);
19952
20288
  process.exit(1);
19953
20289
  }
20290
+ const resultPath = join13(jobsDir, jobId, "result.txt");
19954
20291
  if (status.status === "running" || status.status === "starting") {
19955
- process.stderr.write(`${dim7(`Job ${jobId} is still ${status.status}. Use 'specialists feed --job ${jobId}' to follow.`)}
20292
+ if (!existsSync10(resultPath)) {
20293
+ process.stderr.write(`${dim9(`Job ${jobId} is still ${status.status}. Use 'specialists feed --job ${jobId}' to follow.`)}
19956
20294
  `);
19957
- process.exit(1);
20295
+ process.exit(1);
20296
+ }
20297
+ process.stderr.write(`${dim9(`Job ${jobId} is currently ${status.status}. Showing last completed output while it continues.`)}
20298
+ `);
20299
+ process.stdout.write(readFileSync5(resultPath, "utf-8"));
20300
+ return;
19958
20301
  }
19959
20302
  if (status.status === "error") {
19960
- process.stderr.write(`${red2(`Job ${jobId} failed:`)} ${status.error ?? "unknown error"}
20303
+ process.stderr.write(`${red3(`Job ${jobId} failed:`)} ${status.error ?? "unknown error"}
19961
20304
  `);
19962
20305
  process.exit(1);
19963
20306
  }
19964
- const resultPath = join12(jobsDir, jobId, "result.txt");
19965
- if (!existsSync8(resultPath)) {
20307
+ if (!existsSync10(resultPath)) {
19966
20308
  console.error(`Result file not found for job ${jobId}`);
19967
20309
  process.exit(1);
19968
20310
  }
19969
20311
  process.stdout.write(readFileSync5(resultPath, "utf-8"));
19970
20312
  }
19971
- var dim7 = (s) => `\x1B[2m${s}\x1B[0m`, red2 = (s) => `\x1B[31m${s}\x1B[0m`;
20313
+ var dim9 = (s) => `\x1B[2m${s}\x1B[0m`, red3 = (s) => `\x1B[31m${s}\x1B[0m`;
19972
20314
  var init_result = __esm(() => {
19973
20315
  init_supervisor();
19974
20316
  });
19975
20317
 
19976
20318
  // src/specialist/timeline-query.ts
19977
- import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "node:fs";
19978
- import { join as join13 } from "node:path";
20319
+ import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "node:fs";
20320
+ import { join as join14 } from "node:path";
19979
20321
  function readJobEvents(jobDir) {
19980
- const eventsPath = join13(jobDir, "events.jsonl");
19981
- if (!existsSync9(eventsPath))
20322
+ const eventsPath = join14(jobDir, "events.jsonl");
20323
+ if (!existsSync11(eventsPath))
19982
20324
  return [];
19983
20325
  const content = readFileSync6(eventsPath, "utf-8");
19984
20326
  const lines = content.split(`
@@ -19993,15 +20335,15 @@ function readJobEvents(jobDir) {
19993
20335
  return events;
19994
20336
  }
19995
20337
  function readJobEventsById(jobsDir, jobId) {
19996
- return readJobEvents(join13(jobsDir, jobId));
20338
+ return readJobEvents(join14(jobsDir, jobId));
19997
20339
  }
19998
20340
  function readAllJobEvents(jobsDir) {
19999
- if (!existsSync9(jobsDir))
20341
+ if (!existsSync11(jobsDir))
20000
20342
  return [];
20001
20343
  const batches = [];
20002
20344
  const entries = readdirSync3(jobsDir);
20003
20345
  for (const entry of entries) {
20004
- const jobDir = join13(jobsDir, entry);
20346
+ const jobDir = join14(jobsDir, entry);
20005
20347
  try {
20006
20348
  const stat2 = __require("node:fs").statSync(jobDir);
20007
20349
  if (!stat2.isDirectory())
@@ -20010,10 +20352,10 @@ function readAllJobEvents(jobsDir) {
20010
20352
  continue;
20011
20353
  }
20012
20354
  const jobId = entry;
20013
- const statusPath = join13(jobDir, "status.json");
20355
+ const statusPath = join14(jobDir, "status.json");
20014
20356
  let specialist = "unknown";
20015
20357
  let beadId;
20016
- if (existsSync9(statusPath)) {
20358
+ if (existsSync11(statusPath)) {
20017
20359
  try {
20018
20360
  const status = JSON.parse(readFileSync6(statusPath, "utf-8"));
20019
20361
  specialist = status.specialist ?? "unknown";
@@ -20076,10 +20418,10 @@ var init_timeline_query = __esm(() => {
20076
20418
  // src/cli/feed.ts
20077
20419
  var exports_feed = {};
20078
20420
  __export(exports_feed, {
20079
- run: () => run10
20421
+ run: () => run11
20080
20422
  });
20081
- import { existsSync as existsSync10 } from "node:fs";
20082
- import { join as join14 } from "node:path";
20423
+ import { existsSync as existsSync12 } from "node:fs";
20424
+ import { join as join15 } from "node:path";
20083
20425
  function getHumanEventKey(event) {
20084
20426
  switch (event.type) {
20085
20427
  case "meta":
@@ -20090,6 +20432,10 @@ function getHumanEventKey(event) {
20090
20432
  return "text";
20091
20433
  case "thinking":
20092
20434
  return "thinking";
20435
+ case "message":
20436
+ return `message:${event.role}:${event.phase}`;
20437
+ case "turn":
20438
+ return `turn:${event.phase}`;
20093
20439
  case "run_start":
20094
20440
  return `run_start:${event.specialist}:${event.bead_id ?? ""}`;
20095
20441
  case "run_complete":
@@ -20127,7 +20473,7 @@ function parseSince(value) {
20127
20473
  }
20128
20474
  return;
20129
20475
  }
20130
- function parseArgs5(argv) {
20476
+ function parseArgs6(argv) {
20131
20477
  let jobId;
20132
20478
  let specialist;
20133
20479
  let since;
@@ -20172,7 +20518,7 @@ function parseArgs5(argv) {
20172
20518
  function printSnapshot(merged, options) {
20173
20519
  if (merged.length === 0) {
20174
20520
  if (!options.json)
20175
- console.log(dim6("No events found."));
20521
+ console.log(dim8("No events found."));
20176
20522
  return;
20177
20523
  }
20178
20524
  const colorMap = new JobColorMap;
@@ -20218,13 +20564,13 @@ async function followMerged(jobsDir, options) {
20218
20564
  const initialBatchCount = filteredBatches().length;
20219
20565
  if (!options.forever && initialBatchCount > 0 && completedJobs.size === initialBatchCount) {
20220
20566
  if (!options.json) {
20221
- process.stderr.write(dim6(`All jobs complete.
20567
+ process.stderr.write(dim8(`All jobs complete.
20222
20568
  `));
20223
20569
  }
20224
20570
  return;
20225
20571
  }
20226
20572
  if (!options.json) {
20227
- process.stderr.write(dim6(`Following... (Ctrl+C to stop)
20573
+ process.stderr.write(dim8(`Following... (Ctrl+C to stop)
20228
20574
  `));
20229
20575
  }
20230
20576
  const lastPrintedEventKey = new Map;
@@ -20271,11 +20617,37 @@ async function followMerged(jobsDir, options) {
20271
20617
  }, 500);
20272
20618
  });
20273
20619
  }
20274
- async function run10() {
20275
- const options = parseArgs5(process.argv.slice(3));
20276
- const jobsDir = join14(process.cwd(), ".specialists", "jobs");
20277
- if (!existsSync10(jobsDir)) {
20278
- console.log(dim6("No jobs directory found."));
20620
+ function showUsage() {
20621
+ console.log(`Usage: specialists feed <job-id> [options]
20622
+ specialists feed -f [--forever]
20623
+
20624
+ Read background job events.
20625
+
20626
+ Modes:
20627
+ specialists feed <job-id> Replay events for one job
20628
+ specialists feed <job-id> -f Follow one job until completion
20629
+ specialists feed -f Follow all jobs globally
20630
+
20631
+ Options:
20632
+ -f, --follow Follow live updates
20633
+ --forever Keep following in global mode even when all jobs complete
20634
+
20635
+ Examples:
20636
+ specialists feed 49adda
20637
+ specialists feed 49adda --follow
20638
+ specialists feed -f
20639
+ specialists feed -f --forever
20640
+ `);
20641
+ }
20642
+ async function run11() {
20643
+ const options = parseArgs6(process.argv.slice(3));
20644
+ if (!options.jobId && !options.follow) {
20645
+ showUsage();
20646
+ process.exit(1);
20647
+ }
20648
+ const jobsDir = join15(process.cwd(), ".specialists", "jobs");
20649
+ if (!existsSync12(jobsDir)) {
20650
+ console.log(dim8("No jobs directory found."));
20279
20651
  return;
20280
20652
  }
20281
20653
  if (options.follow) {
@@ -20299,11 +20671,11 @@ var init_feed = __esm(() => {
20299
20671
  // src/cli/poll.ts
20300
20672
  var exports_poll = {};
20301
20673
  __export(exports_poll, {
20302
- run: () => run11
20674
+ run: () => run12
20303
20675
  });
20304
- import { existsSync as existsSync11, readFileSync as readFileSync7 } from "node:fs";
20305
- import { join as join15 } from "node:path";
20306
- function parseArgs6(argv) {
20676
+ import { existsSync as existsSync13, readFileSync as readFileSync7 } from "node:fs";
20677
+ import { join as join16 } from "node:path";
20678
+ function parseArgs7(argv) {
20307
20679
  let jobId;
20308
20680
  let cursor = 0;
20309
20681
  let json = false;
@@ -20328,11 +20700,11 @@ function parseArgs6(argv) {
20328
20700
  }
20329
20701
  return { jobId, cursor, json };
20330
20702
  }
20331
- async function run11() {
20332
- const { jobId, cursor, json } = parseArgs6(process.argv.slice(3));
20333
- const jobsDir = join15(process.cwd(), ".specialists", "jobs");
20334
- const jobDir = join15(jobsDir, jobId);
20335
- if (!existsSync11(jobDir)) {
20703
+ async function run12() {
20704
+ const { jobId, cursor, json } = parseArgs7(process.argv.slice(3));
20705
+ const jobsDir = join16(process.cwd(), ".specialists", "jobs");
20706
+ const jobDir = join16(jobsDir, jobId);
20707
+ if (!existsSync13(jobDir)) {
20336
20708
  const result2 = {
20337
20709
  job_id: jobId,
20338
20710
  status: "error",
@@ -20346,16 +20718,16 @@ async function run11() {
20346
20718
  console.log(JSON.stringify(result2));
20347
20719
  process.exit(1);
20348
20720
  }
20349
- const statusPath = join15(jobDir, "status.json");
20721
+ const statusPath = join16(jobDir, "status.json");
20350
20722
  let status = null;
20351
- if (existsSync11(statusPath)) {
20723
+ if (existsSync13(statusPath)) {
20352
20724
  try {
20353
20725
  status = JSON.parse(readFileSync7(statusPath, "utf-8"));
20354
20726
  } catch {}
20355
20727
  }
20356
- const resultPath = join15(jobDir, "result.txt");
20728
+ const resultPath = join16(jobDir, "result.txt");
20357
20729
  let output = "";
20358
- if (existsSync11(resultPath)) {
20730
+ if (existsSync13(resultPath)) {
20359
20731
  try {
20360
20732
  output = readFileSync7(resultPath, "utf-8");
20361
20733
  } catch {}
@@ -20410,18 +20782,18 @@ var init_poll = __esm(() => {
20410
20782
  // src/cli/steer.ts
20411
20783
  var exports_steer = {};
20412
20784
  __export(exports_steer, {
20413
- run: () => run12
20785
+ run: () => run13
20414
20786
  });
20415
- import { join as join16 } from "node:path";
20787
+ import { join as join17 } from "node:path";
20416
20788
  import { writeFileSync as writeFileSync6 } from "node:fs";
20417
- async function run12() {
20789
+ async function run13() {
20418
20790
  const jobId = process.argv[3];
20419
20791
  const message = process.argv[4];
20420
20792
  if (!jobId || !message) {
20421
20793
  console.error('Usage: specialists|sp steer <job-id> "<message>"');
20422
20794
  process.exit(1);
20423
20795
  }
20424
- const jobsDir = join16(process.cwd(), ".specialists", "jobs");
20796
+ const jobsDir = join17(process.cwd(), ".specialists", "jobs");
20425
20797
  const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
20426
20798
  const status = supervisor.readStatus(jobId);
20427
20799
  if (!status) {
@@ -20434,7 +20806,7 @@ async function run12() {
20434
20806
  process.exit(1);
20435
20807
  }
20436
20808
  if (!status.fifo_path) {
20437
- process.stderr.write(`${red3("Error:")} Job ${jobId} has no steer pipe.
20809
+ process.stderr.write(`${red4("Error:")} Job ${jobId} has no steer pipe.
20438
20810
  `);
20439
20811
  process.stderr.write(`Only jobs started with --background support mid-run steering.
20440
20812
  `);
@@ -20444,15 +20816,15 @@ async function run12() {
20444
20816
  const payload = JSON.stringify({ type: "steer", message }) + `
20445
20817
  `;
20446
20818
  writeFileSync6(status.fifo_path, payload, { flag: "a" });
20447
- process.stdout.write(`${green7("✓")} Steer message sent to job ${jobId}
20819
+ process.stdout.write(`${green8("✓")} Steer message sent to job ${jobId}
20448
20820
  `);
20449
20821
  } catch (err) {
20450
- process.stderr.write(`${red3("Error:")} Failed to write to steer pipe: ${err?.message}
20822
+ process.stderr.write(`${red4("Error:")} Failed to write to steer pipe: ${err?.message}
20451
20823
  `);
20452
20824
  process.exit(1);
20453
20825
  }
20454
20826
  }
20455
- var green7 = (s) => `\x1B[32m${s}\x1B[0m`, red3 = (s) => `\x1B[31m${s}\x1B[0m`;
20827
+ var green8 = (s) => `\x1B[32m${s}\x1B[0m`, red4 = (s) => `\x1B[31m${s}\x1B[0m`;
20456
20828
  var init_steer = __esm(() => {
20457
20829
  init_supervisor();
20458
20830
  });
@@ -20460,18 +20832,18 @@ var init_steer = __esm(() => {
20460
20832
  // src/cli/follow-up.ts
20461
20833
  var exports_follow_up = {};
20462
20834
  __export(exports_follow_up, {
20463
- run: () => run13
20835
+ run: () => run14
20464
20836
  });
20465
- import { join as join17 } from "node:path";
20837
+ import { join as join18 } from "node:path";
20466
20838
  import { writeFileSync as writeFileSync7 } from "node:fs";
20467
- async function run13() {
20839
+ async function run14() {
20468
20840
  const jobId = process.argv[3];
20469
20841
  const message = process.argv[4];
20470
20842
  if (!jobId || !message) {
20471
20843
  console.error('Usage: specialists|sp follow-up <job-id> "<message>"');
20472
20844
  process.exit(1);
20473
20845
  }
20474
- const jobsDir = join17(process.cwd(), ".specialists", "jobs");
20846
+ const jobsDir = join18(process.cwd(), ".specialists", "jobs");
20475
20847
  const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
20476
20848
  const status = supervisor.readStatus(jobId);
20477
20849
  if (!status) {
@@ -20479,14 +20851,14 @@ async function run13() {
20479
20851
  process.exit(1);
20480
20852
  }
20481
20853
  if (status.status !== "waiting") {
20482
- process.stderr.write(`${red4("Error:")} Job ${jobId} is not in waiting state (status: ${status.status}).
20854
+ process.stderr.write(`${red5("Error:")} Job ${jobId} is not in waiting state (status: ${status.status}).
20483
20855
  `);
20484
20856
  process.stderr.write(`Only jobs started with --keep-alive and --background support follow-up prompts.
20485
20857
  `);
20486
20858
  process.exit(1);
20487
20859
  }
20488
20860
  if (!status.fifo_path) {
20489
- process.stderr.write(`${red4("Error:")} Job ${jobId} has no steer pipe.
20861
+ process.stderr.write(`${red5("Error:")} Job ${jobId} has no steer pipe.
20490
20862
  `);
20491
20863
  process.exit(1);
20492
20864
  }
@@ -20494,17 +20866,17 @@ async function run13() {
20494
20866
  const payload = JSON.stringify({ type: "prompt", message }) + `
20495
20867
  `;
20496
20868
  writeFileSync7(status.fifo_path, payload, { flag: "a" });
20497
- process.stdout.write(`${green8("✓")} Follow-up sent to job ${jobId}
20869
+ process.stdout.write(`${green9("✓")} Follow-up sent to job ${jobId}
20498
20870
  `);
20499
20871
  process.stdout.write(` Use 'specialists feed ${jobId} --follow' to watch the response.
20500
20872
  `);
20501
20873
  } catch (err) {
20502
- process.stderr.write(`${red4("Error:")} Failed to write to steer pipe: ${err?.message}
20874
+ process.stderr.write(`${red5("Error:")} Failed to write to steer pipe: ${err?.message}
20503
20875
  `);
20504
20876
  process.exit(1);
20505
20877
  }
20506
20878
  }
20507
- var green8 = (s) => `\x1B[32m${s}\x1B[0m`, red4 = (s) => `\x1B[31m${s}\x1B[0m`;
20879
+ var green9 = (s) => `\x1B[32m${s}\x1B[0m`, red5 = (s) => `\x1B[31m${s}\x1B[0m`;
20508
20880
  var init_follow_up = __esm(() => {
20509
20881
  init_supervisor();
20510
20882
  });
@@ -20512,16 +20884,16 @@ var init_follow_up = __esm(() => {
20512
20884
  // src/cli/stop.ts
20513
20885
  var exports_stop = {};
20514
20886
  __export(exports_stop, {
20515
- run: () => run14
20887
+ run: () => run15
20516
20888
  });
20517
- import { join as join18 } from "node:path";
20518
- async function run14() {
20889
+ import { join as join19 } from "node:path";
20890
+ async function run15() {
20519
20891
  const jobId = process.argv[3];
20520
20892
  if (!jobId) {
20521
20893
  console.error("Usage: specialists|sp stop <job-id>");
20522
20894
  process.exit(1);
20523
20895
  }
20524
- const jobsDir = join18(process.cwd(), ".specialists", "jobs");
20896
+ const jobsDir = join19(process.cwd(), ".specialists", "jobs");
20525
20897
  const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
20526
20898
  const status = supervisor.readStatus(jobId);
20527
20899
  if (!status) {
@@ -20529,31 +20901,31 @@ async function run14() {
20529
20901
  process.exit(1);
20530
20902
  }
20531
20903
  if (status.status === "done" || status.status === "error") {
20532
- process.stderr.write(`${dim8(`Job ${jobId} is already ${status.status}.`)}
20904
+ process.stderr.write(`${dim10(`Job ${jobId} is already ${status.status}.`)}
20533
20905
  `);
20534
20906
  return;
20535
20907
  }
20536
20908
  if (!status.pid) {
20537
- process.stderr.write(`${red5(`No PID recorded for job ${jobId}.`)}
20909
+ process.stderr.write(`${red6(`No PID recorded for job ${jobId}.`)}
20538
20910
  `);
20539
20911
  process.exit(1);
20540
20912
  }
20541
20913
  try {
20542
20914
  process.kill(status.pid, "SIGTERM");
20543
- process.stdout.write(`${green9("✓")} Sent SIGTERM to PID ${status.pid} (job ${jobId})
20915
+ process.stdout.write(`${green10("✓")} Sent SIGTERM to PID ${status.pid} (job ${jobId})
20544
20916
  `);
20545
20917
  } catch (err) {
20546
20918
  if (err.code === "ESRCH") {
20547
- process.stderr.write(`${red5(`Process ${status.pid} not found.`)} Job may have already completed.
20919
+ process.stderr.write(`${red6(`Process ${status.pid} not found.`)} Job may have already completed.
20548
20920
  `);
20549
20921
  } else {
20550
- process.stderr.write(`${red5("Error:")} ${err.message}
20922
+ process.stderr.write(`${red6("Error:")} ${err.message}
20551
20923
  `);
20552
20924
  process.exit(1);
20553
20925
  }
20554
20926
  }
20555
20927
  }
20556
- var green9 = (s) => `\x1B[32m${s}\x1B[0m`, red5 = (s) => `\x1B[31m${s}\x1B[0m`, dim8 = (s) => `\x1B[2m${s}\x1B[0m`;
20928
+ var green10 = (s) => `\x1B[32m${s}\x1B[0m`, red6 = (s) => `\x1B[31m${s}\x1B[0m`, dim10 = (s) => `\x1B[2m${s}\x1B[0m`;
20557
20929
  var init_stop = __esm(() => {
20558
20930
  init_supervisor();
20559
20931
  });
@@ -20561,33 +20933,33 @@ var init_stop = __esm(() => {
20561
20933
  // src/cli/quickstart.ts
20562
20934
  var exports_quickstart = {};
20563
20935
  __export(exports_quickstart, {
20564
- run: () => run15
20936
+ run: () => run16
20565
20937
  });
20566
20938
  function section2(title) {
20567
20939
  const bar = "─".repeat(60);
20568
20940
  return `
20569
- ${bold7(cyan5(title))}
20570
- ${dim9(bar)}`;
20941
+ ${bold9(cyan6(title))}
20942
+ ${dim11(bar)}`;
20571
20943
  }
20572
20944
  function cmd2(s) {
20573
- return yellow6(s);
20945
+ return yellow8(s);
20574
20946
  }
20575
20947
  function flag(s) {
20576
- return green10(s);
20948
+ return green11(s);
20577
20949
  }
20578
- async function run15() {
20950
+ async function run16() {
20579
20951
  const lines = [
20580
20952
  "",
20581
- bold7("specialists · Quick Start Guide"),
20582
- dim9("One MCP server. Multiple AI backends. Intelligent orchestration."),
20583
- dim9("Tip: sp is a shorter alias — sp run, sp list, sp feed etc. work identically."),
20953
+ bold9("specialists · Quick Start Guide"),
20954
+ dim11("One MCP server. Multiple AI backends. Intelligent orchestration."),
20955
+ dim11("Tip: sp is a shorter alias — sp run, sp list, sp feed etc. work identically."),
20584
20956
  ""
20585
20957
  ];
20586
20958
  lines.push(section2("1. Installation"));
20587
20959
  lines.push("");
20588
20960
  lines.push(` ${cmd2("npm install -g @jaggerxtrm/specialists")} # install globally`);
20589
- lines.push(` ${cmd2("specialists install")} # project setup:`);
20590
- lines.push(` ${dim9(" # checks pi · bd · xt, then wires MCP + hooks")}`);
20961
+ lines.push(` ${cmd2("specialists init")} # project setup:`);
20962
+ lines.push(` ${dim11(" # creates dirs, wires MCP + hooks, injects context")}`);
20591
20963
  lines.push("");
20592
20964
  lines.push(` Verify everything is healthy:`);
20593
20965
  lines.push(` ${cmd2("specialists status")} # shows pi, beads, MCP, active jobs`);
@@ -20598,9 +20970,9 @@ async function run15() {
20598
20970
  lines.push(` ${cmd2("specialists init")} # creates specialists/, .specialists/, AGENTS.md`);
20599
20971
  lines.push("");
20600
20972
  lines.push(` What this creates:`);
20601
- lines.push(` ${dim9("specialists/")} — put your .specialist.yaml files here`);
20602
- lines.push(` ${dim9(".specialists/")} — runtime data (jobs/, ready/) — gitignored`);
20603
- lines.push(` ${dim9("AGENTS.md")} — context block injected into Claude sessions`);
20973
+ lines.push(` ${dim11("specialists/")} — put your .specialist.yaml files here`);
20974
+ lines.push(` ${dim11(".specialists/")} — runtime data (jobs/, ready/) — gitignored`);
20975
+ lines.push(` ${dim11("AGENTS.md")} — context block injected into Claude sessions`);
20604
20976
  lines.push("");
20605
20977
  lines.push(section2("3. Discover Specialists"));
20606
20978
  lines.push("");
@@ -20616,66 +20988,64 @@ async function run15() {
20616
20988
  lines.push("");
20617
20989
  lines.push(section2("4. Running a Specialist"));
20618
20990
  lines.push("");
20619
- lines.push(` ${bold7("Foreground")} (streams output to stdout):`);
20620
- lines.push(` ${cmd2("specialists run code-review")} ${flag("--prompt")} ${dim9('"Review src/api.ts for security issues"')}`);
20621
- lines.push("");
20622
- lines.push(` ${bold7("Background")} (returns a job ID immediately):`);
20623
- lines.push(` ${cmd2("specialists run code-review")} ${flag("--prompt")} ${dim9('"..."')} ${flag("--background")}`);
20624
- lines.push(` ${dim9(" # → Job started: job_a1b2c3d4")}`);
20991
+ lines.push(` ${bold9("Foreground")} (streams output to stdout):`);
20992
+ lines.push(` ${cmd2("specialists run code-review")} ${flag("--prompt")} ${dim11('"Review src/api.ts for security issues"')}`);
20625
20993
  lines.push("");
20626
- lines.push(` ${bold7("Follow")} (background + stream live output in one command):`);
20627
- lines.push(` ${cmd2("specialists run code-review")} ${flag("--prompt")} ${dim9('"..."')} ${flag("--follow")}`);
20628
- lines.push(` ${dim9(" # starts in background, streams output live, exits when complete")}`);
20994
+ lines.push(` ${bold9("Tracked run")} (linked to a beads issue for workflow integration):`);
20995
+ lines.push(` ${cmd2("specialists run code-review")} ${flag("--bead")} ${dim11("unitAI-abc")}`);
20996
+ lines.push(` ${dim11(" # uses bead description as prompt, tracks result in issue")}`);
20629
20997
  lines.push("");
20630
20998
  lines.push(` Override model for one run:`);
20631
- lines.push(` ${cmd2("specialists run code-review")} ${flag("--model")} ${dim9("anthropic/claude-opus-4-6")} ${flag("--prompt")} ${dim9('"..."')}`);
20999
+ lines.push(` ${cmd2("specialists run code-review")} ${flag("--model")} ${dim11("anthropic/claude-opus-4-6")} ${flag("--prompt")} ${dim11('"..."')}`);
20632
21000
  lines.push("");
20633
21001
  lines.push(` Run without beads issue tracking:`);
20634
- lines.push(` ${cmd2("specialists run code-review")} ${flag("--no-beads")} ${flag("--prompt")} ${dim9('"..."')}`);
21002
+ lines.push(` ${cmd2("specialists run code-review")} ${flag("--no-beads")} ${flag("--prompt")} ${dim11('"..."')}`);
20635
21003
  lines.push("");
20636
21004
  lines.push(` Pipe a prompt from stdin:`);
20637
21005
  lines.push(` ${cmd2("cat my-brief.md | specialists run code-review")}`);
20638
21006
  lines.push("");
20639
21007
  lines.push(section2("5. Background Job Lifecycle"));
20640
21008
  lines.push("");
20641
- lines.push(` ${bold7("Watch progress")} stream events as they arrive:`);
21009
+ lines.push(` Use Claude Code's native backgrounding or run in a separate terminal.`);
21010
+ lines.push("");
21011
+ lines.push(` ${bold9("Watch progress")} — stream events as they arrive:`);
20642
21012
  lines.push(` ${cmd2("specialists feed job_a1b2c3d4")} # print events so far`);
20643
21013
  lines.push(` ${cmd2("specialists feed job_a1b2c3d4")} ${flag("--follow")} # tail and stream live updates`);
20644
21014
  lines.push("");
20645
- lines.push(` ${bold7("Read results")} — print the final output:`);
21015
+ lines.push(` ${bold9("Read results")} — print the final output:`);
20646
21016
  lines.push(` ${cmd2("specialists result job_a1b2c3d4")} # exits 1 if still running`);
20647
21017
  lines.push("");
20648
- lines.push(` ${bold7("Steer a running job")} — redirect the agent mid-run without cancelling:`);
21018
+ lines.push(` ${bold9("Steer a running job")} — redirect the agent mid-run without cancelling:`);
20649
21019
  lines.push(` ${cmd2("specialists steer job_a1b2c3d4")} ${flag('"focus only on supervisor.ts"')}`);
20650
- lines.push(` ${dim9(" # delivered after current tool calls finish, before the next LLM call")}`);
21020
+ lines.push(` ${dim11(" # delivered after current tool calls finish, before the next LLM call")}`);
20651
21021
  lines.push("");
20652
- lines.push(` ${bold7("Keep-alive multi-turn")} — start with ${flag("--keep-alive")}, then follow up:`);
20653
- lines.push(` ${cmd2("specialists run bug-hunt")} ${flag("--bead unitAI-abc --keep-alive --background")}`);
20654
- lines.push(` ${dim9(" # → Job started: a1b2c3 (status: waiting after first turn)")}`);
21022
+ lines.push(` ${bold9("Keep-alive multi-turn")} — start with ${flag("--keep-alive")}, then follow up:`);
21023
+ lines.push(` ${cmd2("specialists run debugger")} ${flag("--bead unitAI-abc --keep-alive")}`);
21024
+ lines.push(` ${dim11(" # → status: waiting after first turn")}`);
20655
21025
  lines.push(` ${cmd2("specialists result a1b2c3")} # read first turn`);
20656
21026
  lines.push(` ${cmd2("specialists follow-up a1b2c3")} ${flag('"now write the fix"')} # next turn, same Pi context`);
20657
21027
  lines.push(` ${cmd2("specialists feed a1b2c3")} ${flag("--follow")} # watch response`);
20658
21028
  lines.push("");
20659
- lines.push(` ${bold7("Cancel a job")}:`);
21029
+ lines.push(` ${bold9("Cancel a job")}:`);
20660
21030
  lines.push(` ${cmd2("specialists stop job_a1b2c3d4")} # sends SIGTERM to the agent process`);
20661
21031
  lines.push("");
20662
- lines.push(` ${bold7("Job files")} in ${dim9(".specialists/jobs/<job-id>/")}:`);
20663
- lines.push(` ${dim9("status.json")} — id, specialist, status, pid, started_at, elapsed_s, current_tool`);
20664
- lines.push(` ${dim9("events.jsonl")} — one JSON event per line (tool_use, text, agent_end, error …)`);
20665
- lines.push(` ${dim9("result.txt")} — final output (written when status=done)`);
20666
- lines.push(` ${dim9("steer.pipe")} — named FIFO for mid-run steering (removed on job completion)`);
21032
+ lines.push(` ${bold9("Job files")} in ${dim11(".specialists/jobs/<job-id>/")}:`);
21033
+ lines.push(` ${dim11("status.json")} — id, specialist, status, pid, started_at, elapsed_s, current_tool`);
21034
+ lines.push(` ${dim11("events.jsonl")} — one JSON event per line (tool_use, text, agent_end, error …)`);
21035
+ lines.push(` ${dim11("result.txt")} — final output (written when status=done)`);
21036
+ lines.push(` ${dim11("steer.pipe")} — named FIFO for mid-run steering (removed on job completion)`);
20667
21037
  lines.push("");
20668
21038
  lines.push(section2("6. Editing Specialists"));
20669
21039
  lines.push("");
20670
21040
  lines.push(` Change a field without opening the YAML manually:`);
20671
- lines.push(` ${cmd2("specialists edit code-review")} ${flag("--model")} ${dim9("anthropic/claude-sonnet-4-6")}`);
20672
- lines.push(` ${cmd2("specialists edit code-review")} ${flag("--description")} ${dim9('"Updated description"')}`);
20673
- lines.push(` ${cmd2("specialists edit code-review")} ${flag("--timeout")} ${dim9("120000")}`);
20674
- lines.push(` ${cmd2("specialists edit code-review")} ${flag("--permission")} ${dim9("HIGH")}`);
20675
- lines.push(` ${cmd2("specialists edit code-review")} ${flag("--tags")} ${dim9("analysis,security,review")}`);
21041
+ lines.push(` ${cmd2("specialists edit code-review")} ${flag("--model")} ${dim11("anthropic/claude-sonnet-4-6")}`);
21042
+ lines.push(` ${cmd2("specialists edit code-review")} ${flag("--description")} ${dim11('"Updated description"')}`);
21043
+ lines.push(` ${cmd2("specialists edit code-review")} ${flag("--timeout")} ${dim11("120000")}`);
21044
+ lines.push(` ${cmd2("specialists edit code-review")} ${flag("--permission")} ${dim11("HIGH")}`);
21045
+ lines.push(` ${cmd2("specialists edit code-review")} ${flag("--tags")} ${dim11("analysis,security,review")}`);
20676
21046
  lines.push("");
20677
21047
  lines.push(` Preview without writing:`);
20678
- lines.push(` ${cmd2("specialists edit code-review")} ${flag("--model")} ${dim9("...")} ${flag("--dry-run")}`);
21048
+ lines.push(` ${cmd2("specialists edit code-review")} ${flag("--model")} ${dim11("...")} ${flag("--dry-run")}`);
20679
21049
  lines.push("");
20680
21050
  lines.push(section2("7. .specialist.yaml Schema"));
20681
21051
  lines.push("");
@@ -20722,100 +21092,98 @@ async function run15() {
20722
21092
  " priority: 2 # 0=critical … 4=backlog"
20723
21093
  ];
20724
21094
  for (const l of schemaLines) {
20725
- lines.push(` ${dim9(l)}`);
21095
+ lines.push(` ${dim11(l)}`);
20726
21096
  }
20727
21097
  lines.push("");
20728
21098
  lines.push(section2("8. Hook System"));
20729
21099
  lines.push("");
20730
- lines.push(` Specialists emits lifecycle events to ${dim9(".specialists/trace.jsonl")}:`);
21100
+ lines.push(` Specialists emits lifecycle events to ${dim11(".specialists/trace.jsonl")}:`);
20731
21101
  lines.push("");
20732
- lines.push(` ${bold7("Hook point")} ${bold7("When fired")}`);
20733
- lines.push(` ${yellow6("specialist:start")} before the agent session begins`);
20734
- lines.push(` ${yellow6("specialist:token")} on each streamed token (delta)`);
20735
- lines.push(` ${yellow6("specialist:done")} after successful completion`);
20736
- lines.push(` ${yellow6("specialist:error")} on failure or timeout`);
21102
+ lines.push(` ${bold9("Hook point")} ${bold9("When fired")}`);
21103
+ lines.push(` ${yellow8("specialist:start")} before the agent session begins`);
21104
+ lines.push(` ${yellow8("specialist:token")} on each streamed token (delta)`);
21105
+ lines.push(` ${yellow8("specialist:done")} after successful completion`);
21106
+ lines.push(` ${yellow8("specialist:error")} on failure or timeout`);
20737
21107
  lines.push("");
20738
21108
  lines.push(` Each event line in trace.jsonl:`);
20739
- lines.push(` ${dim9('{"t":"<ISO>","hook":"specialist:done","specialist":"code-review","durationMs":4120}')}`);
21109
+ lines.push(` ${dim11('{"t":"<ISO>","hook":"specialist:done","specialist":"code-review","durationMs":4120}')}`);
20740
21110
  lines.push("");
20741
21111
  lines.push(` Tail the trace file to observe all activity:`);
20742
21112
  lines.push(` ${cmd2("tail -f .specialists/trace.jsonl | jq .")}`);
20743
21113
  lines.push("");
20744
21114
  lines.push(section2("9. MCP Integration (Claude Code)"));
20745
21115
  lines.push("");
20746
- lines.push(` After ${cmd2("specialists install")}, these MCP tools are available to Claude:`);
21116
+ lines.push(` After ${cmd2("specialists init")}, these MCP tools are available to Claude:`);
20747
21117
  lines.push("");
20748
- lines.push(` ${bold7("specialist_init")} — bootstrap: bd init + list specialists`);
20749
- lines.push(` ${bold7("list_specialists")} — discover specialists (project/user/system)`);
20750
- lines.push(` ${bold7("use_specialist")} — full lifecycle: load → agents.md → run → output`);
20751
- lines.push(` ${bold7("run_parallel")} — concurrent or pipeline execution`);
20752
- lines.push(` ${bold7("start_specialist")} — async job start, returns job ID`);
20753
- lines.push(` ${bold7("poll_specialist")} — poll job status/output by ID`);
20754
- lines.push(` ${bold7("steer_specialist")} — send a mid-run message to a running job`);
20755
- lines.push(` ${bold7("follow_up_specialist")} — send a next-turn prompt to a keep-alive session`);
20756
- lines.push(` ${bold7("stop_specialist")} — cancel a running job by ID`);
20757
- lines.push(` ${bold7("specialist_status")} — circuit breaker health + staleness`);
21118
+ lines.push(` ${bold9("specialist_init")} — bootstrap: bd init + list specialists`);
21119
+ lines.push(` ${bold9("list_specialists")} — discover specialists (project/user/system)`);
21120
+ lines.push(` ${bold9("use_specialist")} — full lifecycle: load → agents.md → run → output`);
21121
+ lines.push(` ${bold9("run_parallel")} — concurrent or pipeline execution`);
21122
+ lines.push(` ${bold9("start_specialist")} — async job start, returns job ID`);
21123
+ lines.push(` ${bold9("poll_specialist")} — poll job status/output by ID`);
21124
+ lines.push(` ${bold9("steer_specialist")} — send a mid-run message to a running job`);
21125
+ lines.push(` ${bold9("follow_up_specialist")} — send a next-turn prompt to a keep-alive session`);
21126
+ lines.push(` ${bold9("stop_specialist")} — cancel a running job by ID`);
21127
+ lines.push(` ${bold9("specialist_status")} — circuit breaker health + staleness`);
20758
21128
  lines.push("");
20759
21129
  lines.push(section2("10. Common Workflows"));
20760
21130
  lines.push("");
20761
- lines.push(` ${bold7("Foreground review, save to file:")}`);
21131
+ lines.push(` ${bold9("Foreground review, save to file:")}`);
20762
21132
  lines.push(` ${cmd2('specialists run code-review --prompt "Audit src/" > review.md')}`);
20763
21133
  lines.push("");
20764
- lines.push(` ${bold7("Fire-and-forget, check later:")}`);
20765
- lines.push(` ${cmd2('specialists run deep-analysis --prompt "..." --background')}`);
20766
- lines.push(` ${cmd2("specialists feed <job-id> --follow")}`);
20767
- lines.push(` ${cmd2("specialists result <job-id> > analysis.md")}`);
21134
+ lines.push(` ${bold9("Tracked run with beads integration:")}`);
21135
+ lines.push(` ${cmd2("specialists run deep-analysis --bead unitAI-abc")}`);
21136
+ lines.push(` ${dim11(" # prompt from bead, result tracked in bead")}`);
20768
21137
  lines.push("");
20769
- lines.push(` ${bold7("Steer a job mid-run:")}`);
20770
- lines.push(` ${cmd2('specialists run deep-analysis --prompt "..." --background')}`);
21138
+ lines.push(` ${bold9("Steer a job mid-run:")}`);
20771
21139
  lines.push(` ${cmd2('specialists steer <job-id> "focus only on the auth module"')}`);
20772
21140
  lines.push(` ${cmd2("specialists result <job-id>")}`);
20773
21141
  lines.push("");
20774
- lines.push(` ${bold7("Multi-turn keep-alive (iterative work):")}`);
20775
- lines.push(` ${cmd2("specialists run bug-hunt --bead unitAI-abc --keep-alive --background")}`);
21142
+ lines.push(` ${bold9("Multi-turn keep-alive (iterative work):")}`);
21143
+ lines.push(` ${cmd2("specialists run debugger --bead unitAI-abc --keep-alive")}`);
20776
21144
  lines.push(` ${cmd2("specialists result <job-id>")}`);
20777
21145
  lines.push(` ${cmd2('specialists follow-up <job-id> "now write the fix for the root cause"')}`);
20778
21146
  lines.push(` ${cmd2("specialists feed <job-id> --follow")}`);
20779
21147
  lines.push("");
20780
- lines.push(` ${bold7("Override model for a single run:")}`);
21148
+ lines.push(` ${bold9("Override model for a single run:")}`);
20781
21149
  lines.push(` ${cmd2('specialists run code-review --model anthropic/claude-opus-4-6 --prompt "..."')}`);
20782
21150
  lines.push("");
20783
- lines.push(dim9("─".repeat(62)));
20784
- lines.push(` ${dim9("specialists help")} command list ${dim9("specialists <cmd> --help")} per-command flags`);
20785
- lines.push(` ${dim9("specialists status")} health check ${dim9("specialists models")} available models`);
21151
+ lines.push(dim11("─".repeat(62)));
21152
+ lines.push(` ${dim11("specialists help")} command list ${dim11("specialists <cmd> --help")} per-command flags`);
21153
+ lines.push(` ${dim11("specialists status")} health check ${dim11("specialists models")} available models`);
20786
21154
  lines.push("");
20787
21155
  console.log(lines.join(`
20788
21156
  `));
20789
21157
  }
20790
- var bold7 = (s) => `\x1B[1m${s}\x1B[0m`, dim9 = (s) => `\x1B[2m${s}\x1B[0m`, yellow6 = (s) => `\x1B[33m${s}\x1B[0m`, cyan5 = (s) => `\x1B[36m${s}\x1B[0m`, blue2 = (s) => `\x1B[34m${s}\x1B[0m`, green10 = (s) => `\x1B[32m${s}\x1B[0m`;
21158
+ var bold9 = (s) => `\x1B[1m${s}\x1B[0m`, dim11 = (s) => `\x1B[2m${s}\x1B[0m`, yellow8 = (s) => `\x1B[33m${s}\x1B[0m`, cyan6 = (s) => `\x1B[36m${s}\x1B[0m`, blue2 = (s) => `\x1B[34m${s}\x1B[0m`, green11 = (s) => `\x1B[32m${s}\x1B[0m`;
20791
21159
 
20792
21160
  // src/cli/doctor.ts
20793
21161
  var exports_doctor = {};
20794
21162
  __export(exports_doctor, {
20795
- run: () => run16
21163
+ run: () => run17
20796
21164
  });
20797
21165
  import { spawnSync as spawnSync7 } from "node:child_process";
20798
- import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync8, readdirSync as readdirSync4 } from "node:fs";
20799
- import { join as join19 } from "node:path";
21166
+ import { existsSync as existsSync14, mkdirSync as mkdirSync3, readFileSync as readFileSync8, readdirSync as readdirSync4 } from "node:fs";
21167
+ import { join as join20 } from "node:path";
20800
21168
  function ok3(msg) {
20801
- console.log(` ${green11("✓")} ${msg}`);
21169
+ console.log(` ${green12("✓")} ${msg}`);
20802
21170
  }
20803
21171
  function warn2(msg) {
20804
- console.log(` ${yellow7("○")} ${msg}`);
21172
+ console.log(` ${yellow9("○")} ${msg}`);
20805
21173
  }
20806
21174
  function fail2(msg) {
20807
- console.log(` ${red6("✗")} ${msg}`);
21175
+ console.log(` ${red7("✗")} ${msg}`);
20808
21176
  }
20809
21177
  function fix(msg) {
20810
- console.log(` ${dim10("→ fix:")} ${yellow7(msg)}`);
21178
+ console.log(` ${dim12("→ fix:")} ${yellow9(msg)}`);
20811
21179
  }
20812
21180
  function hint(msg) {
20813
- console.log(` ${dim10(msg)}`);
21181
+ console.log(` ${dim12(msg)}`);
20814
21182
  }
20815
21183
  function section3(label) {
20816
21184
  const line = "─".repeat(Math.max(0, 38 - label.length));
20817
21185
  console.log(`
20818
- ${bold8(`── ${label} ${line}`)}`);
21186
+ ${bold10(`── ${label} ${line}`)}`);
20819
21187
  }
20820
21188
  function sp(bin, args) {
20821
21189
  const r = spawnSync7(bin, args, { encoding: "utf8", stdio: "pipe", timeout: 5000 });
@@ -20825,7 +21193,7 @@ function isInstalled2(bin) {
20825
21193
  return spawnSync7("which", [bin], { encoding: "utf8", timeout: 2000 }).status === 0;
20826
21194
  }
20827
21195
  function loadJson2(path) {
20828
- if (!existsSync12(path))
21196
+ if (!existsSync14(path))
20829
21197
  return null;
20830
21198
  try {
20831
21199
  return JSON.parse(readFileSync8(path, "utf8"));
@@ -20850,9 +21218,19 @@ function checkPi() {
20850
21218
  fix("pi config (add at least one API key)");
20851
21219
  return false;
20852
21220
  }
20853
- ok3(`pi ${vStr} — ${providers.size} provider${providers.size > 1 ? "s" : ""} active ${dim10(`(${[...providers].join(", ")})`)}`);
21221
+ ok3(`pi ${vStr} — ${providers.size} provider${providers.size > 1 ? "s" : ""} active ${dim12(`(${[...providers].join(", ")})`)}`);
20854
21222
  return true;
20855
21223
  }
21224
+ function checkSpAlias() {
21225
+ section3("sp alias (specialists shortcut)");
21226
+ if (isInstalled2("sp")) {
21227
+ ok3("sp alias installed");
21228
+ return true;
21229
+ }
21230
+ fail2("sp alias not found in PATH");
21231
+ fix("npm install -g @jaggerxtrm/specialists@latest (reinstall to create symlink)");
21232
+ return false;
21233
+ }
20856
21234
  function checkBd() {
20857
21235
  section3("beads (issue tracker)");
20858
21236
  if (!isInstalled2("bd")) {
@@ -20860,8 +21238,8 @@ function checkBd() {
20860
21238
  fix("install beads (bd) first");
20861
21239
  return false;
20862
21240
  }
20863
- ok3(`bd installed ${dim10(sp("bd", ["--version"]).stdout || "")}`);
20864
- if (existsSync12(join19(CWD, ".beads")))
21241
+ ok3(`bd installed ${dim12(sp("bd", ["--version"]).stdout || "")}`);
21242
+ if (existsSync14(join20(CWD, ".beads")))
20865
21243
  ok3(".beads/ present in project");
20866
21244
  else
20867
21245
  warn2(".beads/ not found in project");
@@ -20874,16 +21252,16 @@ function checkXt() {
20874
21252
  fix("install xtrm-tools first");
20875
21253
  return false;
20876
21254
  }
20877
- ok3(`xt installed ${dim10(sp("xt", ["--version"]).stdout || "")}`);
21255
+ ok3(`xt installed ${dim12(sp("xt", ["--version"]).stdout || "")}`);
20878
21256
  return true;
20879
21257
  }
20880
21258
  function checkHooks() {
20881
21259
  section3("Claude Code hooks (2 expected)");
20882
21260
  let allPresent = true;
20883
21261
  for (const name of HOOK_NAMES) {
20884
- const dest = join19(HOOKS_DIR, name);
20885
- if (!existsSync12(dest)) {
20886
- fail2(`${name} ${red6("missing")}`);
21262
+ const dest = join20(HOOKS_DIR, name);
21263
+ if (!existsSync14(dest)) {
21264
+ fail2(`${name} ${red7("missing")}`);
20887
21265
  fix("specialists install");
20888
21266
  allPresent = false;
20889
21267
  } else {
@@ -20926,18 +21304,18 @@ function checkMCP() {
20926
21304
  }
20927
21305
  function checkRuntimeDirs() {
20928
21306
  section3(".specialists/ runtime directories");
20929
- const rootDir = join19(CWD, ".specialists");
20930
- const jobsDir = join19(rootDir, "jobs");
20931
- const readyDir = join19(rootDir, "ready");
21307
+ const rootDir = join20(CWD, ".specialists");
21308
+ const jobsDir = join20(rootDir, "jobs");
21309
+ const readyDir = join20(rootDir, "ready");
20932
21310
  let allOk = true;
20933
- if (!existsSync12(rootDir)) {
21311
+ if (!existsSync14(rootDir)) {
20934
21312
  warn2(".specialists/ not found in current project");
20935
21313
  fix("specialists init");
20936
21314
  allOk = false;
20937
21315
  } else {
20938
21316
  ok3(".specialists/ present");
20939
21317
  for (const [subDir, label] of [[jobsDir, "jobs"], [readyDir, "ready"]]) {
20940
- if (!existsSync12(subDir)) {
21318
+ if (!existsSync14(subDir)) {
20941
21319
  warn2(`.specialists/${label}/ missing — auto-creating`);
20942
21320
  mkdirSync3(subDir, { recursive: true });
20943
21321
  ok3(`.specialists/${label}/ created`);
@@ -20950,8 +21328,8 @@ function checkRuntimeDirs() {
20950
21328
  }
20951
21329
  function checkZombieJobs() {
20952
21330
  section3("Background jobs");
20953
- const jobsDir = join19(CWD, ".specialists", "jobs");
20954
- if (!existsSync12(jobsDir)) {
21331
+ const jobsDir = join20(CWD, ".specialists", "jobs");
21332
+ if (!existsSync14(jobsDir)) {
20955
21333
  hint("No .specialists/jobs/ — skipping");
20956
21334
  return true;
20957
21335
  }
@@ -20969,8 +21347,8 @@ function checkZombieJobs() {
20969
21347
  let total = 0;
20970
21348
  let running = 0;
20971
21349
  for (const jobId of entries) {
20972
- const statusPath = join19(jobsDir, jobId, "status.json");
20973
- if (!existsSync12(statusPath))
21350
+ const statusPath = join20(jobsDir, jobId, "status.json");
21351
+ if (!existsSync14(statusPath))
20974
21352
  continue;
20975
21353
  try {
20976
21354
  const status = JSON.parse(readFileSync8(statusPath, "utf8"));
@@ -20987,7 +21365,7 @@ function checkZombieJobs() {
20987
21365
  running++;
20988
21366
  else {
20989
21367
  zombies++;
20990
- warn2(`${jobId} ${yellow7("ZOMBIE")} ${dim10(`pid ${pid} not found, status=${status.status}`)}`);
21368
+ warn2(`${jobId} ${yellow9("ZOMBIE")} ${dim12(`pid ${pid} not found, status=${status.status}`)}`);
20991
21369
  fix(`Edit .specialists/jobs/${jobId}/status.json → set "status": "error"`);
20992
21370
  }
20993
21371
  }
@@ -21000,11 +21378,12 @@ function checkZombieJobs() {
21000
21378
  }
21001
21379
  return zombies === 0;
21002
21380
  }
21003
- async function run16() {
21381
+ async function run17() {
21004
21382
  console.log(`
21005
- ${bold8("specialists doctor")}
21383
+ ${bold10("specialists doctor")}
21006
21384
  `);
21007
21385
  const piOk = checkPi();
21386
+ const spOk = checkSpAlias();
21008
21387
  const bdOk = checkBd();
21009
21388
  const xtOk = checkXt();
21010
21389
  const hooksOk = checkHooks();
@@ -21014,21 +21393,21 @@ ${bold8("specialists doctor")}
21014
21393
  const allOk = piOk && bdOk && xtOk && hooksOk && mcpOk && dirsOk && jobsOk;
21015
21394
  console.log("");
21016
21395
  if (allOk) {
21017
- console.log(` ${green11("✓")} ${bold8("All checks passed")} — specialists is healthy`);
21396
+ console.log(` ${green12("✓")} ${bold10("All checks passed")} — specialists is healthy`);
21018
21397
  } else {
21019
- console.log(` ${yellow7("○")} ${bold8("Some checks failed")} — follow the fix hints above`);
21020
- console.log(` ${dim10("specialists install fixes hook + MCP registration; pi, bd, and xt must be installed separately.")}`);
21398
+ console.log(` ${yellow9("○")} ${bold10("Some checks failed")} — follow the fix hints above`);
21399
+ console.log(` ${dim12("specialists install fixes hook + MCP registration; pi, bd, and xt must be installed separately.")}`);
21021
21400
  }
21022
21401
  console.log("");
21023
21402
  }
21024
- var bold8 = (s) => `\x1B[1m${s}\x1B[0m`, dim10 = (s) => `\x1B[2m${s}\x1B[0m`, green11 = (s) => `\x1B[32m${s}\x1B[0m`, yellow7 = (s) => `\x1B[33m${s}\x1B[0m`, red6 = (s) => `\x1B[31m${s}\x1B[0m`, CWD, CLAUDE_DIR, SPECIALISTS_DIR, HOOKS_DIR, SETTINGS_FILE, MCP_FILE2, HOOK_NAMES;
21403
+ var bold10 = (s) => `\x1B[1m${s}\x1B[0m`, dim12 = (s) => `\x1B[2m${s}\x1B[0m`, green12 = (s) => `\x1B[32m${s}\x1B[0m`, yellow9 = (s) => `\x1B[33m${s}\x1B[0m`, red7 = (s) => `\x1B[31m${s}\x1B[0m`, CWD, CLAUDE_DIR, SPECIALISTS_DIR, HOOKS_DIR, SETTINGS_FILE, MCP_FILE2, HOOK_NAMES;
21025
21404
  var init_doctor = __esm(() => {
21026
21405
  CWD = process.cwd();
21027
- CLAUDE_DIR = join19(CWD, ".claude");
21028
- SPECIALISTS_DIR = join19(CWD, ".specialists");
21029
- HOOKS_DIR = join19(SPECIALISTS_DIR, "default", "hooks");
21030
- SETTINGS_FILE = join19(CLAUDE_DIR, "settings.json");
21031
- MCP_FILE2 = join19(CWD, ".mcp.json");
21406
+ CLAUDE_DIR = join20(CWD, ".claude");
21407
+ SPECIALISTS_DIR = join20(CWD, ".specialists");
21408
+ HOOKS_DIR = join20(SPECIALISTS_DIR, "default", "hooks");
21409
+ SETTINGS_FILE = join20(CLAUDE_DIR, "settings.json");
21410
+ MCP_FILE2 = join20(CWD, ".mcp.json");
21032
21411
  HOOK_NAMES = [
21033
21412
  "specialists-complete.mjs",
21034
21413
  "specialists-session-start.mjs"
@@ -21038,191 +21417,49 @@ var init_doctor = __esm(() => {
21038
21417
  // src/cli/setup.ts
21039
21418
  var exports_setup = {};
21040
21419
  __export(exports_setup, {
21041
- run: () => run17
21420
+ run: () => run18
21042
21421
  });
21043
- import { existsSync as existsSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "node:fs";
21044
- import { homedir as homedir3 } from "node:os";
21045
- import { join as join20, resolve as resolve2 } from "node:path";
21046
- function ok4(msg) {
21047
- console.log(` ${green12("")} ${msg}`);
21048
- }
21049
- function skip2(msg) {
21050
- console.log(` ${yellow8("○")} ${msg}`);
21051
- }
21052
- function resolveTarget(target) {
21053
- switch (target) {
21054
- case "global":
21055
- return join20(homedir3(), ".claude", "CLAUDE.md");
21056
- case "agents":
21057
- return join20(process.cwd(), "AGENTS.md");
21058
- case "project":
21059
- default:
21060
- return join20(process.cwd(), "CLAUDE.md");
21061
- }
21062
- }
21063
- function parseArgs7() {
21064
- const argv = process.argv.slice(3);
21065
- let target = "project";
21066
- let dryRun = false;
21067
- for (let i = 0;i < argv.length; i++) {
21068
- const token = argv[i];
21069
- if (token === "--global" || token === "-g") {
21070
- target = "global";
21071
- continue;
21072
- }
21073
- if (token === "--agents" || token === "-a") {
21074
- target = "agents";
21075
- continue;
21076
- }
21077
- if (token === "--project" || token === "-p") {
21078
- target = "project";
21079
- continue;
21080
- }
21081
- if (token === "--dry-run") {
21082
- dryRun = true;
21083
- continue;
21084
- }
21085
- }
21086
- return { target, dryRun };
21087
- }
21088
- async function run17() {
21089
- const { target, dryRun } = parseArgs7();
21090
- const filePath = resolve2(resolveTarget(target));
21091
- const label = target === "global" ? "~/.claude/CLAUDE.md" : filePath.replace(process.cwd() + "/", "");
21092
- console.log(`
21093
- ${bold9("specialists setup")}
21094
- `);
21095
- console.log(` Target: ${yellow8(label)}${dryRun ? dim11(" (dry-run)") : ""}
21096
- `);
21097
- if (existsSync13(filePath)) {
21098
- const existing = readFileSync9(filePath, "utf8");
21099
- if (existing.includes(MARKER)) {
21100
- skip2(`${label} already contains Specialists Workflow section`);
21101
- console.log(`
21102
- ${dim11("To force-update, remove the ## Specialists Workflow section and re-run.")}
21103
- `);
21104
- return;
21105
- }
21106
- if (dryRun) {
21107
- console.log(dim11("─".repeat(60)));
21108
- console.log(dim11("Would append to existing file:"));
21109
- console.log("");
21110
- console.log(WORKFLOW_BLOCK);
21111
- console.log(dim11("─".repeat(60)));
21112
- return;
21113
- }
21114
- const separator = existing.trimEnd().endsWith(`
21115
- `) ? `
21116
- ` : `
21117
-
21118
- `;
21119
- writeFileSync8(filePath, existing.trimEnd() + separator + WORKFLOW_BLOCK, "utf8");
21120
- ok4(`Appended Specialists Workflow section to ${label}`);
21121
- } else {
21122
- if (dryRun) {
21123
- console.log(dim11("─".repeat(60)));
21124
- console.log(dim11(`Would create ${label}:`));
21125
- console.log("");
21126
- console.log(WORKFLOW_BLOCK);
21127
- console.log(dim11("─".repeat(60)));
21128
- return;
21129
- }
21130
- writeFileSync8(filePath, WORKFLOW_BLOCK, "utf8");
21131
- ok4(`Created ${label} with Specialists Workflow section`);
21132
- }
21422
+ async function run18() {
21423
+ console.log("");
21424
+ console.log(yellow10("⚠ DEPRECATED: `specialists setup` is deprecated"));
21425
+ console.log("");
21426
+ console.log(` Use ${bold11("specialists init")} instead.`);
21133
21427
  console.log("");
21134
- console.log(` ${dim11("Next steps:")}`);
21135
- console.log(` Restart Claude Code to pick up the new context`);
21136
- console.log(` Run ${yellow8("specialists list")} to see available specialists`);
21137
- console.log(` Run ${yellow8("specialist_init")} in a new session to bootstrap context`);
21428
+ console.log(" The init command handles all setup tasks:");
21429
+ console.log(" creates specialists/ and .specialists/ directories");
21430
+ console.log(" adds .specialists/ to .gitignore");
21431
+ console.log(" registers the MCP server in .mcp.json");
21432
+ console.log(" • injects workflow context into AGENTS.md/CLAUDE.md");
21433
+ console.log("");
21434
+ console.log(" Options:");
21435
+ console.log(" --force-workflow Overwrite existing workflow blocks");
21436
+ console.log("");
21437
+ console.log(` ${dim13("Run: specialists init --help for full details")}`);
21138
21438
  console.log("");
21139
21439
  }
21140
- var bold9 = (s) => `\x1B[1m${s}\x1B[0m`, dim11 = (s) => `\x1B[2m${s}\x1B[0m`, green12 = (s) => `\x1B[32m${s}\x1B[0m`, yellow8 = (s) => `\x1B[33m${s}\x1B[0m`, MARKER = "## Specialists Workflow", WORKFLOW_BLOCK = `## Specialists Workflow
21141
-
21142
- > Injected by \`specialists setup\`. Keep this section — agents use it for context.
21143
-
21144
- ### When to use specialists
21145
-
21146
- Specialists are autonomous AI agents (running via the \`specialists\` MCP server)
21147
- optimised for heavy tasks: code review, deep bug analysis, test generation,
21148
- architecture design. Use them instead of doing the work yourself when the task
21149
- would benefit from a fresh perspective, a second opinion, or a different model.
21150
-
21151
- ### Quick reference
21152
-
21153
- \`\`\`
21154
- # List available specialists
21155
- specialists list # all scopes
21156
- specialists list --scope project # this project only
21157
-
21158
- # Run a specialist (foreground — streams output)
21159
- specialists run <name> --prompt "..."
21160
-
21161
- # Run async (background — immediate job ID)
21162
- specialists run <name> --prompt "..." --background
21163
- → Job started: job_a1b2c3d4
21164
-
21165
- # Watch / get results
21166
- specialists feed job_a1b2c3d4 --follow # tail live events
21167
- specialists result job_a1b2c3d4 # read final output
21168
- specialists stop job_a1b2c3d4 # cancel if needed
21169
- \`\`\`
21170
-
21171
- ### MCP tools (available in this session)
21172
-
21173
- | Tool | Purpose |
21174
- |------|---------|
21175
- | \`specialist_init\` | Bootstrap: bd init + list specialists |
21176
- | \`list_specialists\` | Discover specialists across scopes |
21177
- | \`use_specialist\` | Run foreground: load → inject context → execute → output |
21178
- | \`start_specialist\` | Start async: returns job ID immediately |
21179
- | \`poll_specialist\` | Poll job status + delta output by ID |
21180
- | \`stop_specialist\` | Cancel a running job |
21181
- | \`run_parallel\` | Run multiple specialists concurrently or as a pipeline |
21182
- | \`specialist_status\` | Circuit breaker health + staleness |
21183
-
21184
- ### Completion banner format
21185
-
21186
- When a specialist finishes, you may see:
21187
-
21188
- \`\`\`
21189
- ✓ bead unitAI-xxx 4.1s anthropic/claude-sonnet-4-6
21190
- \`\`\`
21191
-
21192
- This means:
21193
- - The specialist completed successfully
21194
- - A beads issue (\`unitAI-xxx\`) was created to track the run
21195
- - The result can be fetched with \`specialists result <job-id>\`
21196
-
21197
- ### When NOT to use specialists
21198
-
21199
- - Simple single-file edits — just do it directly
21200
- - Tasks that need interactive back-and-forth — use foreground mode or work yourself
21201
- - Short read-only queries — faster to answer directly
21202
- `;
21203
- var init_setup = () => {};
21440
+ var bold11 = (s) => `\x1B[1m${s}\x1B[0m`, yellow10 = (s) => `\x1B[33m${s}\x1B[0m`, dim13 = (s) => `\x1B[2m${s}\x1B[0m`;
21204
21441
 
21205
21442
  // src/cli/help.ts
21206
21443
  var exports_help = {};
21207
21444
  __export(exports_help, {
21208
- run: () => run18
21445
+ run: () => run19
21209
21446
  });
21210
21447
  function formatCommands(entries) {
21211
21448
  const width = Math.max(...entries.map(([cmd3]) => cmd3.length));
21212
21449
  return entries.map(([cmd3, desc]) => ` ${cmd3.padEnd(width)} ${desc}`);
21213
21450
  }
21214
- async function run18() {
21451
+ async function run19() {
21215
21452
  const lines = [
21216
21453
  "",
21217
21454
  "Specialists lets you run project-scoped specialist agents with a bead-first workflow.",
21218
21455
  "",
21219
- bold10("Usage:"),
21456
+ bold12("Usage:"),
21220
21457
  " specialists|sp [command]",
21221
21458
  " specialists|sp [command] --help",
21222
21459
  "",
21223
- dim12(" sp is a shorter alias — sp run, sp list, sp feed etc. all work identically."),
21460
+ dim14(" sp is a shorter alias — sp run, sp list, sp feed etc. all work identically."),
21224
21461
  "",
21225
- bold10("Common flows:"),
21462
+ bold12("Common flows:"),
21226
21463
  "",
21227
21464
  " Tracked work (primary)",
21228
21465
  ' bd create "Task title" -t task -p 1 --json',
@@ -21244,19 +21481,19 @@ async function run18() {
21244
21481
  " or run in a separate terminal and poll with:",
21245
21482
  " specialists poll <job-id> --json",
21246
21483
  "",
21247
- bold10("Core commands:"),
21484
+ bold12("Core commands:"),
21248
21485
  ...formatCommands(CORE_COMMANDS),
21249
21486
  "",
21250
- bold10("Extended commands:"),
21487
+ bold12("Extended commands:"),
21251
21488
  ...formatCommands(EXTENDED_COMMANDS),
21252
21489
  "",
21253
- bold10("xtrm worktree commands:"),
21490
+ bold12("xtrm worktree commands:"),
21254
21491
  ...formatCommands(WORKTREE_COMMANDS),
21255
21492
  "",
21256
- bold10("Examples:"),
21493
+ bold12("Examples:"),
21257
21494
  " specialists init",
21258
21495
  " specialists list",
21259
- " specialists run bug-hunt --bead unitAI-123",
21496
+ " specialists run debugger --bead unitAI-123",
21260
21497
  ' specialists run codebase-explorer --prompt "Map the CLI architecture"',
21261
21498
  " specialists poll abc123 --json # check job status",
21262
21499
  " specialists feed -f # stream all job events",
@@ -21264,7 +21501,7 @@ async function run18() {
21264
21501
  ' specialists follow-up <job-id> "now write the fix"',
21265
21502
  " specialists result <job-id>",
21266
21503
  "",
21267
- bold10("More help:"),
21504
+ bold12("More help:"),
21268
21505
  " specialists quickstart Full guide and workflow reference",
21269
21506
  " specialists run --help Run command details and flags",
21270
21507
  " specialists poll --help Job status polling details",
@@ -21273,17 +21510,18 @@ async function run18() {
21273
21510
  " specialists init --help Bootstrap behavior and workflow injection",
21274
21511
  " specialists feed --help Job event streaming details",
21275
21512
  "",
21276
- dim12("Project model: specialists are project-only; user-scope discovery is deprecated."),
21513
+ dim14("Project model: specialists are project-only; user-scope discovery is deprecated."),
21277
21514
  ""
21278
21515
  ];
21279
21516
  console.log(lines.join(`
21280
21517
  `));
21281
21518
  }
21282
- var bold10 = (s) => `\x1B[1m${s}\x1B[0m`, dim12 = (s) => `\x1B[2m${s}\x1B[0m`, CORE_COMMANDS, EXTENDED_COMMANDS, WORKTREE_COMMANDS;
21519
+ var bold12 = (s) => `\x1B[1m${s}\x1B[0m`, dim14 = (s) => `\x1B[2m${s}\x1B[0m`, CORE_COMMANDS, EXTENDED_COMMANDS, WORKTREE_COMMANDS;
21283
21520
  var init_help = __esm(() => {
21284
21521
  CORE_COMMANDS = [
21285
21522
  ["init", "Bootstrap a project: dirs, workflow injection, project MCP registration"],
21286
21523
  ["list", "List specialists in this project"],
21524
+ ["validate", "Validate a specialist YAML against the schema"],
21287
21525
  ["run", "Run a specialist with --bead for tracked work or --prompt for ad-hoc work"],
21288
21526
  ["feed", "Tail job events; use -f to follow all jobs"],
21289
21527
  ["poll", "Machine-readable job status polling (for scripts/Claude Code)"],
@@ -29279,23 +29517,20 @@ var next = process.argv[3];
29279
29517
  function wantsHelp() {
29280
29518
  return next === "--help" || next === "-h";
29281
29519
  }
29282
- async function run19() {
29520
+ async function run20() {
29283
29521
  if (sub === "install") {
29284
29522
  if (wantsHelp()) {
29285
29523
  console.log([
29286
29524
  "",
29287
- "Usage: specialists install",
29288
- "",
29289
- "Project setup: checks pi/bd/xt prerequisites, registers the MCP server,",
29290
- "and installs specialists-specific project hooks.",
29525
+ "⚠ DEPRECATED: Use `specialists init` instead.",
29291
29526
  "",
29292
- "No flags just run it.",
29527
+ "The install command is deprecated. Run `specialists init` for project setup.",
29293
29528
  ""
29294
29529
  ].join(`
29295
29530
  `));
29296
29531
  return;
29297
29532
  }
29298
- const { run: handler } = await Promise.resolve().then(() => (init_install(), exports_install));
29533
+ const { run: handler } = await Promise.resolve().then(() => exports_install);
29299
29534
  return handler();
29300
29535
  }
29301
29536
  if (sub === "version" || sub === "--version" || sub === "-v") {
@@ -29384,6 +29619,38 @@ async function run19() {
29384
29619
  const { run: handler } = await Promise.resolve().then(() => (init_init(), exports_init));
29385
29620
  return handler();
29386
29621
  }
29622
+ if (sub === "validate") {
29623
+ if (wantsHelp()) {
29624
+ console.log([
29625
+ "",
29626
+ "Usage: specialists validate <name> [--json]",
29627
+ "",
29628
+ "Validate a specialist YAML file against the schema.",
29629
+ "",
29630
+ "What it checks:",
29631
+ " - YAML syntax is valid",
29632
+ " - Required fields are present (name, version, description, category, model)",
29633
+ " - Field values match expected formats (kebab-case names, semver versions)",
29634
+ " - Enum values are valid (permission_required, mode, beads_integration)",
29635
+ "",
29636
+ "Options:",
29637
+ " --json Output validation result as JSON",
29638
+ "",
29639
+ "Examples:",
29640
+ " specialists validate my-specialist",
29641
+ " specialists validate my-specialist --json",
29642
+ "",
29643
+ "Exit codes:",
29644
+ " 0 — validation passed",
29645
+ " 1 — validation failed (errors) or specialist not found",
29646
+ ""
29647
+ ].join(`
29648
+ `));
29649
+ return;
29650
+ }
29651
+ const { run: handler } = await Promise.resolve().then(() => (init_validate(), exports_validate));
29652
+ return handler();
29653
+ }
29387
29654
  if (sub === "edit") {
29388
29655
  if (wantsHelp()) {
29389
29656
  console.log([
@@ -29402,7 +29669,7 @@ async function run19() {
29402
29669
  "",
29403
29670
  "Options:",
29404
29671
  " --dry-run Preview the change without writing",
29405
- " --scope <project|user> Disambiguate if same name exists in multiple scopes",
29672
+ " --scope <default|user> Disambiguate if same name exists in multiple scopes",
29406
29673
  "",
29407
29674
  "Examples:",
29408
29675
  " specialists edit code-review --model anthropic/claude-opus-4-6",
@@ -29437,8 +29704,8 @@ async function run19() {
29437
29704
  " --keep-alive Keep session alive for follow-up prompts",
29438
29705
  "",
29439
29706
  "Examples:",
29440
- " specialists run bug-hunt --bead unitAI-55d",
29441
- " specialists run bug-hunt --bead unitAI-55d --context-depth 2",
29707
+ " specialists run debugger --bead unitAI-55d",
29708
+ " specialists run debugger --bead unitAI-55d --context-depth 2",
29442
29709
  ' specialists run code-review --prompt "Audit src/api.ts"',
29443
29710
  " cat brief.md | specialists run report-generator",
29444
29711
  "",
@@ -29603,7 +29870,7 @@ async function run19() {
29603
29870
  ' specialists steer a1b2c3 "skip tests, just fix the bug"',
29604
29871
  "",
29605
29872
  "Notes:",
29606
- " - Only works for jobs started with --background.",
29873
+ " - Only works for running jobs.",
29607
29874
  " - Delivery is best-effort: the agent processes it on its next turn.",
29608
29875
  ""
29609
29876
  ].join(`
@@ -29622,14 +29889,14 @@ async function run19() {
29622
29889
  "Send a follow-up prompt to a waiting keep-alive specialist session.",
29623
29890
  "The Pi session retains full conversation history between turns.",
29624
29891
  "",
29625
- "Requires: job started with --keep-alive --background.",
29892
+ "Requires: job started with --keep-alive.",
29626
29893
  "",
29627
29894
  "Examples:",
29628
29895
  ' specialists follow-up a1b2c3 "Now write the fix for the bug you found"',
29629
29896
  ' specialists follow-up a1b2c3 "Focus only on the auth module"',
29630
29897
  "",
29631
29898
  "Workflow:",
29632
- " specialists run bug-hunt --bead <id> --keep-alive --background",
29899
+ " specialists run debugger --bead <id> --keep-alive",
29633
29900
  " # → Job started: a1b2c3 (status: waiting after first turn)",
29634
29901
  " specialists result a1b2c3 # read first turn output",
29635
29902
  ' specialists follow-up a1b2c3 "..." # send next prompt',
@@ -29702,28 +29969,15 @@ async function run19() {
29702
29969
  if (wantsHelp()) {
29703
29970
  console.log([
29704
29971
  "",
29705
- "Usage: specialists setup [options]",
29706
- "",
29707
- "Inject the Specialists Workflow context block into AGENTS.md or CLAUDE.md.",
29708
- "This teaches agents in that project how to use specialists.",
29972
+ "⚠ DEPRECATED: Use `specialists init` instead.",
29709
29973
  "",
29710
- "Options:",
29711
- " --project, -p Write to ./CLAUDE.md (default)",
29712
- " --agents, -a Write to ./AGENTS.md",
29713
- " --global, -g Write to ~/.claude/CLAUDE.md",
29714
- " --dry-run Preview the block without writing",
29715
- "",
29716
- "Examples:",
29717
- " specialists setup # → ./CLAUDE.md",
29718
- " specialists setup --agents # → ./AGENTS.md",
29719
- " specialists setup --global # → ~/.claude/CLAUDE.md",
29720
- " specialists setup --dry-run # preview only",
29974
+ "The setup command is deprecated. Run `specialists init` for project setup.",
29721
29975
  ""
29722
29976
  ].join(`
29723
29977
  `));
29724
29978
  return;
29725
29979
  }
29726
- const { run: handler } = await Promise.resolve().then(() => (init_setup(), exports_setup));
29980
+ const { run: handler } = await Promise.resolve().then(() => exports_setup);
29727
29981
  return handler();
29728
29982
  }
29729
29983
  if (sub === "help" || sub === "--help" || sub === "-h") {
@@ -29739,7 +29993,7 @@ Run 'specialists help' to see available commands.`);
29739
29993
  const server = new SpecialistsServer;
29740
29994
  await server.start();
29741
29995
  }
29742
- run19().catch((error2) => {
29996
+ run20().catch((error2) => {
29743
29997
  logger.error(`Fatal error: ${error2}`);
29744
29998
  process.exit(1);
29745
29999
  });