@jaggerxtrm/specialists 3.0.1 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -18281,6 +18281,10 @@ function parseArgs(argv) {
18281
18281
  result.scope = value;
18282
18282
  continue;
18283
18283
  }
18284
+ if (token === "--json") {
18285
+ result.json = true;
18286
+ continue;
18287
+ }
18284
18288
  }
18285
18289
  return result;
18286
18290
  }
@@ -18300,6 +18304,10 @@ async function run3() {
18300
18304
  if (args.scope) {
18301
18305
  specialists = specialists.filter((s) => s.scope === args.scope);
18302
18306
  }
18307
+ if (args.json) {
18308
+ console.log(JSON.stringify(specialists, null, 2));
18309
+ return;
18310
+ }
18303
18311
  if (specialists.length === 0) {
18304
18312
  console.log("No specialists found.");
18305
18313
  return;
@@ -19038,23 +19046,86 @@ function statusColor(status) {
19038
19046
  }
19039
19047
  }
19040
19048
  async function run8() {
19049
+ const argv = process.argv.slice(3);
19050
+ const jsonMode = argv.includes("--json");
19051
+ const loader = new SpecialistLoader;
19052
+ const allSpecialists = await loader.list();
19053
+ const piInstalled = isInstalled("pi");
19054
+ const piVersion = piInstalled ? cmd("pi", ["--version"]) : null;
19055
+ const piModels = piInstalled ? cmd("pi", ["--list-models"]) : null;
19056
+ const piProviders = piModels ? new Set(piModels.stdout.split(`
19057
+ `).slice(1).map((line) => line.split(/\s+/)[0]).filter(Boolean)) : new Set;
19058
+ const bdInstalled = isInstalled("bd");
19059
+ const bdVersion = bdInstalled ? cmd("bd", ["--version"]) : null;
19060
+ const beadsPresent = existsSync5(join8(process.cwd(), ".beads"));
19061
+ const specialistsBin = cmd("which", ["specialists"]);
19062
+ const jobsDir = join8(process.cwd(), ".specialists", "jobs");
19063
+ let jobs = [];
19064
+ if (existsSync5(jobsDir)) {
19065
+ const supervisor = new Supervisor({
19066
+ runner: null,
19067
+ runOptions: null,
19068
+ jobsDir
19069
+ });
19070
+ jobs = supervisor.listJobs();
19071
+ }
19072
+ const stalenessMap = {};
19073
+ for (const s of allSpecialists) {
19074
+ stalenessMap[s.name] = await checkStaleness(s);
19075
+ }
19076
+ if (jsonMode) {
19077
+ const output = {
19078
+ specialists: {
19079
+ count: allSpecialists.length,
19080
+ items: allSpecialists.map((s) => ({
19081
+ name: s.name,
19082
+ scope: s.scope,
19083
+ model: s.model,
19084
+ description: s.description,
19085
+ staleness: stalenessMap[s.name]
19086
+ }))
19087
+ },
19088
+ pi: {
19089
+ installed: piInstalled,
19090
+ version: piVersion?.stdout ?? null,
19091
+ providers: [...piProviders]
19092
+ },
19093
+ beads: {
19094
+ installed: bdInstalled,
19095
+ version: bdVersion?.stdout ?? null,
19096
+ initialized: beadsPresent
19097
+ },
19098
+ mcp: {
19099
+ specialists_installed: specialistsBin.ok,
19100
+ binary_path: specialistsBin.ok ? specialistsBin.stdout : null
19101
+ },
19102
+ jobs: jobs.map((j) => ({
19103
+ id: j.id,
19104
+ specialist: j.specialist,
19105
+ status: j.status,
19106
+ elapsed_s: j.elapsed_s,
19107
+ current_tool: j.current_tool ?? null,
19108
+ error: j.error ?? null
19109
+ }))
19110
+ };
19111
+ console.log(JSON.stringify(output, null, 2));
19112
+ return;
19113
+ }
19041
19114
  console.log(`
19042
19115
  ${bold6("specialists status")}
19043
19116
  `);
19044
19117
  section("Specialists");
19045
- const loader = new SpecialistLoader;
19046
- const all = await loader.list();
19047
- if (all.length === 0) {
19118
+ if (allSpecialists.length === 0) {
19048
19119
  warn(`no specialists found — run ${yellow5("specialists init")} to scaffold`);
19049
19120
  } else {
19050
- const byScope = all.reduce((acc, s) => {
19121
+ const byScope = allSpecialists.reduce((acc, s) => {
19051
19122
  acc[s.scope] = (acc[s.scope] ?? 0) + 1;
19052
19123
  return acc;
19053
19124
  }, {});
19054
19125
  const scopeSummary = Object.entries(byScope).map(([scope, n]) => `${n} ${scope}`).join(", ");
19055
- ok2(`${all.length} found ${dim6(`(${scopeSummary})`)}`);
19056
- for (const s of all) {
19057
- const staleness = await checkStaleness(s);
19126
+ ok2(`${allSpecialists.length} found ${dim6(`(${scopeSummary})`)}`);
19127
+ for (const s of allSpecialists) {
19128
+ const staleness = stalenessMap[s.name];
19058
19129
  if (staleness === "AGED") {
19059
19130
  warn(`${s.name} ${red("AGED")} ${dim6(s.scope)}`);
19060
19131
  } else if (staleness === "STALE") {
@@ -19063,31 +19134,25 @@ ${bold6("specialists status")}
19063
19134
  }
19064
19135
  }
19065
19136
  section("pi (coding agent runtime)");
19066
- if (!isInstalled("pi")) {
19137
+ if (!piInstalled) {
19067
19138
  fail(`pi not installed — run ${yellow5("specialists install")}`);
19068
19139
  } else {
19069
- const version2 = cmd("pi", ["--version"]);
19070
- const models = cmd("pi", ["--list-models"]);
19071
- const providers = new Set(models.stdout.split(`
19072
- `).slice(1).map((line) => line.split(/\s+/)[0]).filter(Boolean));
19073
- const vStr = version2.ok ? `v${version2.stdout}` : "unknown version";
19074
- const pStr = providers.size > 0 ? `${providers.size} provider${providers.size > 1 ? "s" : ""} active ${dim6(`(${[...providers].join(", ")})`)} ` : yellow5("no providers configured — run pi config");
19140
+ const vStr = piVersion?.ok ? `v${piVersion.stdout}` : "unknown version";
19141
+ const pStr = piProviders.size > 0 ? `${piProviders.size} provider${piProviders.size > 1 ? "s" : ""} active ${dim6(`(${[...piProviders].join(", ")})`)} ` : yellow5("no providers configured — run pi config");
19075
19142
  ok2(`${vStr} — ${pStr}`);
19076
19143
  }
19077
19144
  section("beads (issue tracker)");
19078
- if (!isInstalled("bd")) {
19145
+ if (!bdInstalled) {
19079
19146
  fail(`bd not installed — run ${yellow5("specialists install")}`);
19080
19147
  } else {
19081
- const bdVersion = cmd("bd", ["--version"]);
19082
- ok2(`bd installed${bdVersion.ok ? ` ${dim6(bdVersion.stdout)}` : ""}`);
19083
- if (existsSync5(join8(process.cwd(), ".beads"))) {
19148
+ ok2(`bd installed${bdVersion?.ok ? ` ${dim6(bdVersion.stdout)}` : ""}`);
19149
+ if (beadsPresent) {
19084
19150
  ok2(".beads/ present in project");
19085
19151
  } else {
19086
19152
  warn(`.beads/ not found — run ${yellow5("bd init")} to enable issue tracking`);
19087
19153
  }
19088
19154
  }
19089
19155
  section("MCP");
19090
- const specialistsBin = cmd("which", ["specialists"]);
19091
19156
  if (!specialistsBin.ok) {
19092
19157
  fail(`specialists not installed globally — run ${yellow5("npm install -g @jaggerxtrm/specialists")}`);
19093
19158
  } else {
@@ -19095,21 +19160,12 @@ ${bold6("specialists status")}
19095
19160
  info(`verify registration: claude mcp get specialists`);
19096
19161
  info(`re-register: specialists install`);
19097
19162
  }
19098
- const jobsDir = join8(process.cwd(), ".specialists", "jobs");
19099
- if (existsSync5(jobsDir)) {
19100
- const supervisor = new Supervisor({
19101
- runner: null,
19102
- runOptions: null,
19103
- jobsDir
19104
- });
19105
- const jobs = supervisor.listJobs();
19106
- if (jobs.length > 0) {
19107
- section("Active Jobs");
19108
- for (const job of jobs) {
19109
- const elapsed = formatElapsed(job);
19110
- const detail = job.status === "error" ? red(job.error?.slice(0, 40) ?? "error") : job.current_tool ? dim6(`tool: ${job.current_tool}`) : dim6(job.current_event ?? "");
19111
- console.log(` ${dim6(job.id)} ${job.specialist.padEnd(20)} ${statusColor(job.status).padEnd(7)} ${elapsed.padStart(6)} ${detail}`);
19112
- }
19163
+ if (jobs.length > 0) {
19164
+ section("Active Jobs");
19165
+ for (const job of jobs) {
19166
+ const elapsed = formatElapsed(job);
19167
+ const detail = job.status === "error" ? red(job.error?.slice(0, 40) ?? "error") : job.current_tool ? dim6(`tool: ${job.current_tool}`) : dim6(job.current_event ?? "");
19168
+ console.log(` ${dim6(job.id)} ${job.specialist.padEnd(20)} ${statusColor(job.status).padEnd(7)} ${elapsed.padStart(6)} ${detail}`);
19113
19169
  }
19114
19170
  }
19115
19171
  console.log();
@@ -19298,42 +19354,654 @@ var init_stop = __esm(() => {
19298
19354
  init_supervisor();
19299
19355
  });
19300
19356
 
19357
+ // src/cli/quickstart.ts
19358
+ var exports_quickstart = {};
19359
+ __export(exports_quickstart, {
19360
+ run: () => run12
19361
+ });
19362
+ function section2(title) {
19363
+ const bar = "─".repeat(60);
19364
+ return `
19365
+ ${bold7(cyan6(title))}
19366
+ ${dim10(bar)}`;
19367
+ }
19368
+ function cmd2(s) {
19369
+ return yellow7(s);
19370
+ }
19371
+ function flag(s) {
19372
+ return green7(s);
19373
+ }
19374
+ async function run12() {
19375
+ const lines = [
19376
+ "",
19377
+ bold7("specialists · Quick Start Guide"),
19378
+ dim10("One MCP server. Multiple AI backends. Intelligent orchestration."),
19379
+ ""
19380
+ ];
19381
+ lines.push(section2("1. Installation"));
19382
+ lines.push("");
19383
+ lines.push(` ${cmd2("npm install -g @jaggerxtrm/specialists")} # install globally`);
19384
+ lines.push(` ${cmd2("specialists install")} # full-stack setup:`);
19385
+ lines.push(` ${dim10(" # pi · beads · dolt · MCP · hooks")}`);
19386
+ lines.push("");
19387
+ lines.push(` Verify everything is healthy:`);
19388
+ lines.push(` ${cmd2("specialists status")} # shows pi, beads, MCP, active jobs`);
19389
+ lines.push("");
19390
+ lines.push(section2("2. Initialize a Project"));
19391
+ lines.push("");
19392
+ lines.push(` Run once per project root:`);
19393
+ lines.push(` ${cmd2("specialists init")} # creates specialists/, .specialists/, AGENTS.md`);
19394
+ lines.push("");
19395
+ lines.push(` What this creates:`);
19396
+ lines.push(` ${dim10("specialists/")} — put your .specialist.yaml files here`);
19397
+ lines.push(` ${dim10(".specialists/")} — runtime data (jobs/, ready/) — gitignored`);
19398
+ lines.push(` ${dim10("AGENTS.md")} — context block injected into Claude sessions`);
19399
+ lines.push("");
19400
+ lines.push(section2("3. Discover Specialists"));
19401
+ lines.push("");
19402
+ lines.push(` ${cmd2("specialists list")} # all specialists (project + user)`);
19403
+ lines.push(` ${cmd2("specialists list")} ${flag("--scope project")} # project-scoped only`);
19404
+ lines.push(` ${cmd2("specialists list")} ${flag("--scope user")} # user-scoped (~/.specialists/)`);
19405
+ lines.push(` ${cmd2("specialists list")} ${flag("--category analysis")} # filter by category`);
19406
+ lines.push(` ${cmd2("specialists list")} ${flag("--json")} # machine-readable JSON`);
19407
+ lines.push("");
19408
+ lines.push(` Scopes (searched in order):`);
19409
+ lines.push(` ${blue("project")} ./specialists/*.specialist.yaml`);
19410
+ lines.push(` ${blue("user")} ~/.specialists/*.specialist.yaml`);
19411
+ lines.push(` ${blue("system")} bundled specialists (shipped with the package)`);
19412
+ lines.push("");
19413
+ lines.push(section2("4. Running a Specialist"));
19414
+ lines.push("");
19415
+ lines.push(` ${bold7("Foreground")} (streams output to stdout):`);
19416
+ lines.push(` ${cmd2("specialists run code-review")} ${flag("--prompt")} ${dim10('"Review src/api.ts for security issues"')}`);
19417
+ lines.push("");
19418
+ lines.push(` ${bold7("Background")} (returns a job ID immediately):`);
19419
+ lines.push(` ${cmd2("specialists run code-review")} ${flag("--prompt")} ${dim10('"..."')} ${flag("--background")}`);
19420
+ lines.push(` ${dim10(" # → Job started: job_a1b2c3d4")}`);
19421
+ lines.push("");
19422
+ lines.push(` Override model for one run:`);
19423
+ lines.push(` ${cmd2("specialists run code-review")} ${flag("--model")} ${dim10("anthropic/claude-opus-4-6")} ${flag("--prompt")} ${dim10('"..."')}`);
19424
+ lines.push("");
19425
+ lines.push(` Run without beads issue tracking:`);
19426
+ lines.push(` ${cmd2("specialists run code-review")} ${flag("--no-beads")} ${flag("--prompt")} ${dim10('"..."')}`);
19427
+ lines.push("");
19428
+ lines.push(` Pipe a prompt from stdin:`);
19429
+ lines.push(` ${cmd2("cat my-brief.md | specialists run code-review")}`);
19430
+ lines.push("");
19431
+ lines.push(section2("5. Background Job Lifecycle"));
19432
+ lines.push("");
19433
+ lines.push(` ${bold7("Watch progress")} — stream events as they arrive:`);
19434
+ lines.push(` ${cmd2("specialists feed job_a1b2c3d4")} # print events so far`);
19435
+ lines.push(` ${cmd2("specialists feed job_a1b2c3d4")} ${flag("--follow")} # tail and stream live updates`);
19436
+ lines.push("");
19437
+ lines.push(` ${bold7("Read results")} — print the final output:`);
19438
+ lines.push(` ${cmd2("specialists result job_a1b2c3d4")} # exits 1 if still running`);
19439
+ lines.push("");
19440
+ lines.push(` ${bold7("Cancel a job")}:`);
19441
+ lines.push(` ${cmd2("specialists stop job_a1b2c3d4")} # sends SIGTERM to the agent process`);
19442
+ lines.push("");
19443
+ lines.push(` ${bold7("Job files")} in ${dim10(".specialists/jobs/<job-id>/")}:`);
19444
+ lines.push(` ${dim10("status.json")} — id, specialist, status, pid, started_at, elapsed_s, current_tool`);
19445
+ lines.push(` ${dim10("events.jsonl")} — one JSON event per line (tool_use, text, agent_end, error …)`);
19446
+ lines.push(` ${dim10("result.txt")} — final output (written when status=done)`);
19447
+ lines.push("");
19448
+ lines.push(section2("6. Editing Specialists"));
19449
+ lines.push("");
19450
+ lines.push(` Change a field without opening the YAML manually:`);
19451
+ lines.push(` ${cmd2("specialists edit code-review")} ${flag("--model")} ${dim10("anthropic/claude-sonnet-4-6")}`);
19452
+ lines.push(` ${cmd2("specialists edit code-review")} ${flag("--description")} ${dim10('"Updated description"')}`);
19453
+ lines.push(` ${cmd2("specialists edit code-review")} ${flag("--timeout")} ${dim10("120000")}`);
19454
+ lines.push(` ${cmd2("specialists edit code-review")} ${flag("--permission")} ${dim10("HIGH")}`);
19455
+ lines.push(` ${cmd2("specialists edit code-review")} ${flag("--tags")} ${dim10("analysis,security,review")}`);
19456
+ lines.push("");
19457
+ lines.push(` Preview without writing:`);
19458
+ lines.push(` ${cmd2("specialists edit code-review")} ${flag("--model")} ${dim10("...")} ${flag("--dry-run")}`);
19459
+ lines.push("");
19460
+ lines.push(section2("7. .specialist.yaml Schema"));
19461
+ lines.push("");
19462
+ lines.push(` Full annotated example:`);
19463
+ lines.push("");
19464
+ const schemaLines = [
19465
+ "specialist:",
19466
+ " metadata:",
19467
+ ' name: my-specialist # required · used in "specialists run <name>"',
19468
+ " version: 1.0.0 # semver, for staleness detection",
19469
+ ' description: "What it does" # shown in specialists list',
19470
+ " category: analysis # free-form tag for --category filter",
19471
+ " tags: [review, security] # array of labels",
19472
+ ' updated: "2026-03-11" # ISO date — used for staleness check',
19473
+ "",
19474
+ " execution:",
19475
+ " mode: tool # tool (default) | chat",
19476
+ " model: anthropic/claude-sonnet-4-6 # primary model",
19477
+ " fallback_model: qwen-cli/qwen3-coder # if primary circuit-breaks",
19478
+ " timeout_ms: 120000 # ms before job is killed (default: 120000)",
19479
+ " stall_timeout_ms: 30000 # ms of silence before stall-detection fires",
19480
+ " response_format: markdown # markdown | json | text",
19481
+ " permission_required: MEDIUM # READ_ONLY | LOW | MEDIUM | HIGH",
19482
+ "",
19483
+ " prompt:",
19484
+ " system: | # system prompt (multiline YAML literal block)",
19485
+ " You are …",
19486
+ " user_template: | # optional; $prompt and $context are substituted",
19487
+ " Task: $prompt",
19488
+ " Context: $context",
19489
+ "",
19490
+ " skills:",
19491
+ " paths: # extra skill dirs searched at runtime",
19492
+ " - ./specialists/skills",
19493
+ " - ~/.specialists/skills",
19494
+ "",
19495
+ " capabilities:",
19496
+ " web_search: false # allow web search tool",
19497
+ " file_write: true # allow file writes",
19498
+ "",
19499
+ " beads_integration:",
19500
+ " auto_create: true # create a beads issue per run",
19501
+ " issue_type: task # task | bug | feature",
19502
+ " priority: 2 # 0=critical … 4=backlog"
19503
+ ];
19504
+ for (const l of schemaLines) {
19505
+ lines.push(` ${dim10(l)}`);
19506
+ }
19507
+ lines.push("");
19508
+ lines.push(section2("8. Hook System"));
19509
+ lines.push("");
19510
+ lines.push(` Specialists emits lifecycle events to ${dim10(".specialists/trace.jsonl")}:`);
19511
+ lines.push("");
19512
+ lines.push(` ${bold7("Hook point")} ${bold7("When fired")}`);
19513
+ lines.push(` ${yellow7("specialist:start")} before the agent session begins`);
19514
+ lines.push(` ${yellow7("specialist:token")} on each streamed token (delta)`);
19515
+ lines.push(` ${yellow7("specialist:done")} after successful completion`);
19516
+ lines.push(` ${yellow7("specialist:error")} on failure or timeout`);
19517
+ lines.push("");
19518
+ lines.push(` Each event line in trace.jsonl:`);
19519
+ lines.push(` ${dim10('{"t":"<ISO>","hook":"specialist:done","specialist":"code-review","durationMs":4120}')}`);
19520
+ lines.push("");
19521
+ lines.push(` Tail the trace file to observe all activity:`);
19522
+ lines.push(` ${cmd2("tail -f .specialists/trace.jsonl | jq .")}`);
19523
+ lines.push("");
19524
+ lines.push(section2("9. MCP Integration (Claude Code)"));
19525
+ lines.push("");
19526
+ lines.push(` After ${cmd2("specialists install")}, these MCP tools are available to Claude:`);
19527
+ lines.push("");
19528
+ lines.push(` ${bold7("specialist_init")} — bootstrap: bd init + list specialists`);
19529
+ lines.push(` ${bold7("list_specialists")} — discover specialists (project/user/system)`);
19530
+ lines.push(` ${bold7("use_specialist")} — full lifecycle: load → agents.md → run → output`);
19531
+ lines.push(` ${bold7("run_parallel")} — concurrent or pipeline execution`);
19532
+ lines.push(` ${bold7("start_specialist")} — async job start, returns job ID`);
19533
+ lines.push(` ${bold7("poll_specialist")} — poll job status/output by ID`);
19534
+ lines.push(` ${bold7("stop_specialist")} — cancel a running job by ID`);
19535
+ lines.push(` ${bold7("specialist_status")} — circuit breaker health + staleness`);
19536
+ lines.push("");
19537
+ lines.push(section2("10. Common Workflows"));
19538
+ lines.push("");
19539
+ lines.push(` ${bold7("Foreground review, save to file:")}`);
19540
+ lines.push(` ${cmd2('specialists run code-review --prompt "Audit src/" > review.md')}`);
19541
+ lines.push("");
19542
+ lines.push(` ${bold7("Fire-and-forget, check later:")}`);
19543
+ lines.push(` ${cmd2('specialists run deep-analysis --prompt "..." --background')}`);
19544
+ lines.push(` ${cmd2("specialists feed <job-id> --follow")}`);
19545
+ lines.push(` ${cmd2("specialists result <job-id> > analysis.md")}`);
19546
+ lines.push("");
19547
+ lines.push(` ${bold7("Override model for a single run:")}`);
19548
+ lines.push(` ${cmd2('specialists run code-review --model anthropic/claude-opus-4-6 --prompt "..."')}`);
19549
+ lines.push("");
19550
+ lines.push(dim10("─".repeat(62)));
19551
+ lines.push(` ${dim10("specialists help")} command list ${dim10("specialists <cmd> --help")} per-command flags`);
19552
+ lines.push(` ${dim10("specialists status")} health check ${dim10("specialists models")} available models`);
19553
+ lines.push("");
19554
+ console.log(lines.join(`
19555
+ `));
19556
+ }
19557
+ var bold7 = (s) => `\x1B[1m${s}\x1B[0m`, dim10 = (s) => `\x1B[2m${s}\x1B[0m`, yellow7 = (s) => `\x1B[33m${s}\x1B[0m`, cyan6 = (s) => `\x1B[36m${s}\x1B[0m`, blue = (s) => `\x1B[34m${s}\x1B[0m`, green7 = (s) => `\x1B[32m${s}\x1B[0m`;
19558
+
19559
+ // src/cli/doctor.ts
19560
+ var exports_doctor = {};
19561
+ __export(exports_doctor, {
19562
+ run: () => run13
19563
+ });
19564
+ import { spawnSync as spawnSync5 } from "node:child_process";
19565
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync as readdirSync2 } from "node:fs";
19566
+ import { homedir as homedir2 } from "node:os";
19567
+ import { join as join12 } from "node:path";
19568
+ function ok3(msg) {
19569
+ console.log(` ${green8("✓")} ${msg}`);
19570
+ }
19571
+ function warn2(msg) {
19572
+ console.log(` ${yellow8("○")} ${msg}`);
19573
+ }
19574
+ function fail2(msg) {
19575
+ console.log(` ${red5("✗")} ${msg}`);
19576
+ }
19577
+ function fix(msg) {
19578
+ console.log(` ${dim11("→ fix:")} ${yellow8(msg)}`);
19579
+ }
19580
+ function hint(msg) {
19581
+ console.log(` ${dim11(msg)}`);
19582
+ }
19583
+ function section3(label) {
19584
+ const line = "─".repeat(Math.max(0, 38 - label.length));
19585
+ console.log(`
19586
+ ${bold8(`── ${label} ${line}`)}`);
19587
+ }
19588
+ function sp(bin, args) {
19589
+ const r = spawnSync5(bin, args, { encoding: "utf8", stdio: "pipe", timeout: 5000 });
19590
+ return { ok: r.status === 0 && !r.error, stdout: (r.stdout ?? "").trim() };
19591
+ }
19592
+ function isInstalled2(bin) {
19593
+ return spawnSync5("which", [bin], { encoding: "utf8", timeout: 2000 }).status === 0;
19594
+ }
19595
+ function checkPi() {
19596
+ section3("pi (coding agent runtime)");
19597
+ if (!isInstalled2("pi")) {
19598
+ fail2("pi not installed");
19599
+ fix("specialists install");
19600
+ return false;
19601
+ }
19602
+ const version2 = sp("pi", ["--version"]);
19603
+ const models = sp("pi", ["--list-models"]);
19604
+ const providers = models.ok ? new Set(models.stdout.split(`
19605
+ `).slice(1).map((line) => line.split(/\s+/)[0]).filter(Boolean)) : new Set;
19606
+ const vStr = version2.ok ? `v${version2.stdout}` : "unknown version";
19607
+ if (providers.size === 0) {
19608
+ warn2(`pi ${vStr} installed but no active providers`);
19609
+ fix("pi config (add at least one API key)");
19610
+ return false;
19611
+ }
19612
+ ok3(`pi ${vStr} — ${providers.size} provider${providers.size > 1 ? "s" : ""} active ${dim11(`(${[...providers].join(", ")})`)} `);
19613
+ return true;
19614
+ }
19615
+ function checkHooks() {
19616
+ section3("Claude Code hooks (7 expected)");
19617
+ let allPresent = true;
19618
+ for (const name of HOOK_NAMES) {
19619
+ const dest = join12(HOOKS_DIR, name);
19620
+ if (!existsSync8(dest)) {
19621
+ fail2(`${name} ${red5("missing")}`);
19622
+ fix("specialists install (reinstalls all hooks)");
19623
+ allPresent = false;
19624
+ } else {
19625
+ ok3(name);
19626
+ }
19627
+ }
19628
+ if (allPresent && existsSync8(SETTINGS_FILE)) {
19629
+ try {
19630
+ const settings = JSON.parse(readFileSync6(SETTINGS_FILE, "utf8"));
19631
+ const hooks = settings.hooks ?? {};
19632
+ const allEntries = [
19633
+ ...hooks.PreToolUse ?? [],
19634
+ ...hooks.PostToolUse ?? [],
19635
+ ...hooks.Stop ?? [],
19636
+ ...hooks.UserPromptSubmit ?? [],
19637
+ ...hooks.SessionStart ?? []
19638
+ ];
19639
+ const wiredCommands = new Set(allEntries.flatMap((e) => (e.hooks ?? []).map((h) => h.command ?? "")));
19640
+ const guardWired = [...wiredCommands].some((c) => c.includes("specialists-main-guard"));
19641
+ if (!guardWired) {
19642
+ warn2("specialists-main-guard not wired in settings.json");
19643
+ fix("specialists install (rewires hooks in settings.json)");
19644
+ allPresent = false;
19645
+ } else {
19646
+ hint(`Hooks wired in ${SETTINGS_FILE}`);
19647
+ }
19648
+ } catch {
19649
+ warn2(`Could not parse ${SETTINGS_FILE}`);
19650
+ allPresent = false;
19651
+ }
19652
+ }
19653
+ return allPresent;
19654
+ }
19655
+ function checkMCP() {
19656
+ section3("MCP registration");
19657
+ const check2 = sp("claude", ["mcp", "get", MCP_NAME]);
19658
+ if (!check2.ok) {
19659
+ fail2(`MCP server '${MCP_NAME}' not registered`);
19660
+ fix(`specialists install (or: claude mcp add --scope user ${MCP_NAME} -- specialists)`);
19661
+ return false;
19662
+ }
19663
+ ok3(`MCP server '${MCP_NAME}' registered`);
19664
+ return true;
19665
+ }
19666
+ function checkRuntimeDirs() {
19667
+ section3(".specialists/ runtime directories");
19668
+ const cwd = process.cwd();
19669
+ const rootDir = join12(cwd, ".specialists");
19670
+ const jobsDir = join12(rootDir, "jobs");
19671
+ const readyDir = join12(rootDir, "ready");
19672
+ let allOk = true;
19673
+ if (!existsSync8(rootDir)) {
19674
+ warn2(".specialists/ not found in current project");
19675
+ fix("specialists init");
19676
+ allOk = false;
19677
+ } else {
19678
+ ok3(".specialists/ present");
19679
+ for (const [subDir, label] of [[jobsDir, "jobs"], [readyDir, "ready"]]) {
19680
+ if (!existsSync8(subDir)) {
19681
+ warn2(`.specialists/${label}/ missing — auto-creating`);
19682
+ mkdirSync3(subDir, { recursive: true });
19683
+ ok3(`.specialists/${label}/ created`);
19684
+ } else {
19685
+ ok3(`.specialists/${label}/ present`);
19686
+ }
19687
+ }
19688
+ }
19689
+ return allOk;
19690
+ }
19691
+ function checkZombieJobs() {
19692
+ section3("Background jobs");
19693
+ const jobsDir = join12(process.cwd(), ".specialists", "jobs");
19694
+ if (!existsSync8(jobsDir)) {
19695
+ hint("No .specialists/jobs/ — skipping");
19696
+ return true;
19697
+ }
19698
+ let entries;
19699
+ try {
19700
+ entries = readdirSync2(jobsDir);
19701
+ } catch {
19702
+ entries = [];
19703
+ }
19704
+ if (entries.length === 0) {
19705
+ ok3("No jobs found");
19706
+ return true;
19707
+ }
19708
+ let zombies = 0;
19709
+ let total = 0;
19710
+ let running = 0;
19711
+ for (const jobId of entries) {
19712
+ const statusPath = join12(jobsDir, jobId, "status.json");
19713
+ if (!existsSync8(statusPath))
19714
+ continue;
19715
+ try {
19716
+ const status = JSON.parse(readFileSync6(statusPath, "utf8"));
19717
+ total++;
19718
+ if (status.status === "running" || status.status === "starting") {
19719
+ const pid = status.pid;
19720
+ if (pid) {
19721
+ let alive = false;
19722
+ try {
19723
+ process.kill(pid, 0);
19724
+ alive = true;
19725
+ } catch {}
19726
+ if (alive) {
19727
+ running++;
19728
+ } else {
19729
+ zombies++;
19730
+ warn2(`${jobId} ${yellow8("ZOMBIE")} ${dim11(`pid ${pid} not found, status=${status.status}`)}`);
19731
+ fix(`Edit .specialists/jobs/${jobId}/status.json → set "status": "error"`);
19732
+ }
19733
+ }
19734
+ }
19735
+ } catch {}
19736
+ }
19737
+ if (zombies === 0) {
19738
+ const detail = running > 0 ? `, ${running} currently running` : ", none currently running";
19739
+ ok3(`${total} job${total !== 1 ? "s" : ""} checked${detail}`);
19740
+ }
19741
+ return zombies === 0;
19742
+ }
19743
+ async function run13() {
19744
+ console.log(`
19745
+ ${bold8("specialists doctor")}
19746
+ `);
19747
+ const piOk = checkPi();
19748
+ const hooksOk = checkHooks();
19749
+ const mcpOk = checkMCP();
19750
+ const dirsOk = checkRuntimeDirs();
19751
+ const jobsOk = checkZombieJobs();
19752
+ const allOk = piOk && hooksOk && mcpOk && dirsOk && jobsOk;
19753
+ console.log("");
19754
+ if (allOk) {
19755
+ console.log(` ${green8("✓")} ${bold8("All checks passed")} — specialists is healthy`);
19756
+ } else {
19757
+ console.log(` ${yellow8("○")} ${bold8("Some checks failed")} — follow the fix hints above`);
19758
+ console.log(` ${dim11("specialists install fixes most issues automatically.")}`);
19759
+ }
19760
+ console.log("");
19761
+ }
19762
+ var bold8 = (s) => `\x1B[1m${s}\x1B[0m`, dim11 = (s) => `\x1B[2m${s}\x1B[0m`, green8 = (s) => `\x1B[32m${s}\x1B[0m`, yellow8 = (s) => `\x1B[33m${s}\x1B[0m`, red5 = (s) => `\x1B[31m${s}\x1B[0m`, HOME, CLAUDE_DIR, HOOKS_DIR, SETTINGS_FILE, MCP_NAME = "specialists", HOOK_NAMES;
19763
+ var init_doctor = __esm(() => {
19764
+ HOME = homedir2();
19765
+ CLAUDE_DIR = join12(HOME, ".claude");
19766
+ HOOKS_DIR = join12(CLAUDE_DIR, "hooks");
19767
+ SETTINGS_FILE = join12(CLAUDE_DIR, "settings.json");
19768
+ HOOK_NAMES = [
19769
+ "specialists-main-guard.mjs",
19770
+ "beads-edit-gate.mjs",
19771
+ "beads-commit-gate.mjs",
19772
+ "beads-stop-gate.mjs",
19773
+ "beads-close-memory-prompt.mjs",
19774
+ "specialists-complete.mjs",
19775
+ "specialists-session-start.mjs"
19776
+ ];
19777
+ });
19778
+
19779
+ // src/cli/setup.ts
19780
+ var exports_setup = {};
19781
+ __export(exports_setup, {
19782
+ run: () => run14
19783
+ });
19784
+ import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
19785
+ import { homedir as homedir3 } from "node:os";
19786
+ import { join as join13, resolve } from "node:path";
19787
+ function ok4(msg) {
19788
+ console.log(` ${green9("✓")} ${msg}`);
19789
+ }
19790
+ function skip2(msg) {
19791
+ console.log(` ${yellow9("○")} ${msg}`);
19792
+ }
19793
+ function resolveTarget(target) {
19794
+ switch (target) {
19795
+ case "global":
19796
+ return join13(homedir3(), ".claude", "CLAUDE.md");
19797
+ case "agents":
19798
+ return join13(process.cwd(), "AGENTS.md");
19799
+ case "project":
19800
+ default:
19801
+ return join13(process.cwd(), "CLAUDE.md");
19802
+ }
19803
+ }
19804
+ function parseArgs5() {
19805
+ const argv = process.argv.slice(3);
19806
+ let target = "project";
19807
+ let dryRun = false;
19808
+ for (let i = 0;i < argv.length; i++) {
19809
+ const token = argv[i];
19810
+ if (token === "--global" || token === "-g") {
19811
+ target = "global";
19812
+ continue;
19813
+ }
19814
+ if (token === "--agents" || token === "-a") {
19815
+ target = "agents";
19816
+ continue;
19817
+ }
19818
+ if (token === "--project" || token === "-p") {
19819
+ target = "project";
19820
+ continue;
19821
+ }
19822
+ if (token === "--dry-run") {
19823
+ dryRun = true;
19824
+ continue;
19825
+ }
19826
+ }
19827
+ return { target, dryRun };
19828
+ }
19829
+ async function run14() {
19830
+ const { target, dryRun } = parseArgs5();
19831
+ const filePath = resolve(resolveTarget(target));
19832
+ const label = target === "global" ? "~/.claude/CLAUDE.md" : filePath.replace(process.cwd() + "/", "");
19833
+ console.log(`
19834
+ ${bold9("specialists setup")}
19835
+ `);
19836
+ console.log(` Target: ${yellow9(label)}${dryRun ? dim12(" (dry-run)") : ""}
19837
+ `);
19838
+ if (existsSync9(filePath)) {
19839
+ const existing = readFileSync7(filePath, "utf8");
19840
+ if (existing.includes(MARKER)) {
19841
+ skip2(`${label} already contains Specialists Workflow section`);
19842
+ console.log(`
19843
+ ${dim12("To force-update, remove the ## Specialists Workflow section and re-run.")}
19844
+ `);
19845
+ return;
19846
+ }
19847
+ if (dryRun) {
19848
+ console.log(dim12("─".repeat(60)));
19849
+ console.log(dim12("Would append to existing file:"));
19850
+ console.log("");
19851
+ console.log(WORKFLOW_BLOCK);
19852
+ console.log(dim12("─".repeat(60)));
19853
+ return;
19854
+ }
19855
+ const separator = existing.trimEnd().endsWith(`
19856
+ `) ? `
19857
+ ` : `
19858
+
19859
+ `;
19860
+ writeFileSync4(filePath, existing.trimEnd() + separator + WORKFLOW_BLOCK, "utf8");
19861
+ ok4(`Appended Specialists Workflow section to ${label}`);
19862
+ } else {
19863
+ if (dryRun) {
19864
+ console.log(dim12("─".repeat(60)));
19865
+ console.log(dim12(`Would create ${label}:`));
19866
+ console.log("");
19867
+ console.log(WORKFLOW_BLOCK);
19868
+ console.log(dim12("─".repeat(60)));
19869
+ return;
19870
+ }
19871
+ writeFileSync4(filePath, WORKFLOW_BLOCK, "utf8");
19872
+ ok4(`Created ${label} with Specialists Workflow section`);
19873
+ }
19874
+ console.log("");
19875
+ console.log(` ${dim12("Next steps:")}`);
19876
+ console.log(` • Restart Claude Code to pick up the new context`);
19877
+ console.log(` • Run ${yellow9("specialists list")} to see available specialists`);
19878
+ console.log(` • Run ${yellow9("specialist_init")} in a new session to bootstrap context`);
19879
+ console.log("");
19880
+ }
19881
+ var bold9 = (s) => `\x1B[1m${s}\x1B[0m`, dim12 = (s) => `\x1B[2m${s}\x1B[0m`, green9 = (s) => `\x1B[32m${s}\x1B[0m`, yellow9 = (s) => `\x1B[33m${s}\x1B[0m`, MARKER = "## Specialists Workflow", WORKFLOW_BLOCK = `## Specialists Workflow
19882
+
19883
+ > Injected by \`specialists setup\`. Keep this section — agents use it for context.
19884
+
19885
+ ### When to use specialists
19886
+
19887
+ Specialists are autonomous AI agents (running via the \`specialists\` MCP server)
19888
+ optimised for heavy tasks: code review, deep bug analysis, test generation,
19889
+ architecture design. Use them instead of doing the work yourself when the task
19890
+ would benefit from a fresh perspective, a second opinion, or a different model.
19891
+
19892
+ ### Quick reference
19893
+
19894
+ \`\`\`
19895
+ # List available specialists
19896
+ specialists list # all scopes
19897
+ specialists list --scope project # this project only
19898
+
19899
+ # Run a specialist (foreground — streams output)
19900
+ specialists run <name> --prompt "..."
19901
+
19902
+ # Run async (background — immediate job ID)
19903
+ specialists run <name> --prompt "..." --background
19904
+ → Job started: job_a1b2c3d4
19905
+
19906
+ # Watch / get results
19907
+ specialists feed job_a1b2c3d4 --follow # tail live events
19908
+ specialists result job_a1b2c3d4 # read final output
19909
+ specialists stop job_a1b2c3d4 # cancel if needed
19910
+ \`\`\`
19911
+
19912
+ ### MCP tools (available in this session)
19913
+
19914
+ | Tool | Purpose |
19915
+ |------|---------|
19916
+ | \`specialist_init\` | Bootstrap: bd init + list specialists |
19917
+ | \`list_specialists\` | Discover specialists across scopes |
19918
+ | \`use_specialist\` | Run foreground: load → inject context → execute → output |
19919
+ | \`start_specialist\` | Start async: returns job ID immediately |
19920
+ | \`poll_specialist\` | Poll job status + delta output by ID |
19921
+ | \`stop_specialist\` | Cancel a running job |
19922
+ | \`run_parallel\` | Run multiple specialists concurrently or as a pipeline |
19923
+ | \`specialist_status\` | Circuit breaker health + staleness |
19924
+
19925
+ ### Completion banner format
19926
+
19927
+ When a specialist finishes, you may see:
19928
+
19929
+ \`\`\`
19930
+ ✓ bead unitAI-xxx 4.1s anthropic/claude-sonnet-4-6
19931
+ \`\`\`
19932
+
19933
+ This means:
19934
+ - The specialist completed successfully
19935
+ - A beads issue (\`unitAI-xxx\`) was created to track the run
19936
+ - The result can be fetched with \`specialists result <job-id>\`
19937
+
19938
+ ### When NOT to use specialists
19939
+
19940
+ - Simple single-file edits — just do it directly
19941
+ - Tasks that need interactive back-and-forth — use foreground mode or work yourself
19942
+ - Short read-only queries — faster to answer directly
19943
+ `;
19944
+ var init_setup = () => {};
19945
+
19301
19946
  // src/cli/help.ts
19302
19947
  var exports_help = {};
19303
19948
  __export(exports_help, {
19304
- run: () => run12
19949
+ run: () => run15
19305
19950
  });
19306
- async function run12() {
19951
+ function formatGroup(label, entries) {
19952
+ const colWidth = Math.max(...entries.map(([cmd3]) => cmd3.length));
19953
+ return [
19954
+ "",
19955
+ bold10(cyan7(label)),
19956
+ ...entries.map(([cmd3, desc]) => ` ${cmd3.padEnd(colWidth)} ${dim13(desc)}`)
19957
+ ];
19958
+ }
19959
+ async function run15() {
19307
19960
  const lines = [
19308
19961
  "",
19309
- bold7("specialists <command>"),
19962
+ bold10("specialists <command> [options]"),
19310
19963
  "",
19311
- "Commands:",
19312
- ...COMMANDS.map(([cmd2, desc]) => ` ${cmd2.padEnd(COL_WIDTH)} ${dim10(desc)}`),
19964
+ dim13("One MCP server. Multiple AI backends. Intelligent orchestration."),
19965
+ ...formatGroup("Setup", SETUP),
19966
+ ...formatGroup("Discovery", DISCOVERY),
19967
+ ...formatGroup("Running", RUNNING),
19968
+ ...formatGroup("Jobs", JOBS),
19969
+ ...formatGroup("Other", OTHER),
19313
19970
  "",
19314
- dim10("Run 'specialists <command> --help' for command-specific options."),
19971
+ dim13("Run 'specialists <command> --help' for command-specific options."),
19972
+ dim13("Run 'specialists quickstart' for a full getting-started guide."),
19315
19973
  ""
19316
19974
  ];
19317
19975
  console.log(lines.join(`
19318
19976
  `));
19319
19977
  }
19320
- var bold7 = (s) => `\x1B[1m${s}\x1B[0m`, dim10 = (s) => `\x1B[2m${s}\x1B[0m`, COMMANDS, COL_WIDTH;
19978
+ var bold10 = (s) => `\x1B[1m${s}\x1B[0m`, dim13 = (s) => `\x1B[2m${s}\x1B[0m`, cyan7 = (s) => `\x1B[36m${s}\x1B[0m`, SETUP, DISCOVERY, RUNNING, JOBS, OTHER;
19321
19979
  var init_help = __esm(() => {
19322
- COMMANDS = [
19980
+ SETUP = [
19323
19981
  ["install", "Full-stack installer: pi, beads, dolt, MCP registration, hooks"],
19982
+ ["init", "Scaffold specialists/, .specialists/, AGENTS.md in current project"],
19983
+ ["setup", "Inject workflow context block into CLAUDE.md or AGENTS.md"],
19984
+ ["quickstart", "Rich getting-started guide with examples and YAML schema reference"],
19985
+ ["doctor", "Health check: pi, hooks, MCP registration, dirs, zombie jobs"]
19986
+ ];
19987
+ DISCOVERY = [
19324
19988
  ["list", "List available specialists with model and description"],
19325
19989
  ["models", "List models available on pi, flagged with thinking/images support"],
19326
- ["version", "Print installed version"],
19327
- ["init", "Initialize specialists in the current project"],
19328
- ["edit", "Edit a specialist field (e.g. --model, --description)"],
19990
+ ["status", "Show system health (pi, beads, MCP, jobs)"]
19991
+ ];
19992
+ RUNNING = [
19329
19993
  ["run", "Run a specialist with a prompt (--background for async)"],
19330
- ["result", "Print result of a background job"],
19994
+ ["edit", "Edit a specialist field (e.g. --model, --description)"]
19995
+ ];
19996
+ JOBS = [
19331
19997
  ["feed", "Tail events for a background job (--follow to stream)"],
19332
- ["stop", "Send SIGTERM to a running background job"],
19333
- ["status", "Show system health (pi, beads, MCP, jobs)"],
19998
+ ["result", "Print result of a background job"],
19999
+ ["stop", "Send SIGTERM to a running background job"]
20000
+ ];
20001
+ OTHER = [
20002
+ ["version", "Print installed version"],
19334
20003
  ["help", "Show this help message"]
19335
20004
  ];
19336
- COL_WIDTH = Math.max(...COMMANDS.map(([cmd2]) => cmd2.length));
19337
20005
  });
19338
20006
 
19339
20007
  // node_modules/zod/v4/core/core.js
@@ -27107,8 +27775,26 @@ class SpecialistsServer {
27107
27775
 
27108
27776
  // src/index.ts
27109
27777
  var sub = process.argv[2];
27110
- async function run13() {
27778
+ var next = process.argv[3];
27779
+ function wantsHelp() {
27780
+ return next === "--help" || next === "-h";
27781
+ }
27782
+ async function run16() {
27111
27783
  if (sub === "install") {
27784
+ if (wantsHelp()) {
27785
+ console.log([
27786
+ "",
27787
+ "Usage: specialists install",
27788
+ "",
27789
+ "Full-stack setup: installs pi, beads, dolt, registers the MCP server,",
27790
+ "and installs session hooks and skills for Claude Code.",
27791
+ "",
27792
+ "No flags — just run it.",
27793
+ ""
27794
+ ].join(`
27795
+ `));
27796
+ return;
27797
+ }
27112
27798
  const { run: handler } = await Promise.resolve().then(() => (init_install(), exports_install));
27113
27799
  return handler();
27114
27800
  }
@@ -27117,41 +27803,284 @@ async function run13() {
27117
27803
  return handler();
27118
27804
  }
27119
27805
  if (sub === "list") {
27806
+ if (wantsHelp()) {
27807
+ console.log([
27808
+ "",
27809
+ "Usage: specialists list [options]",
27810
+ "",
27811
+ "List available specialists across all scopes.",
27812
+ "",
27813
+ "Options:",
27814
+ " --scope <project|user> Filter by scope",
27815
+ " --category <name> Filter by category tag",
27816
+ " --json Output as JSON array",
27817
+ "",
27818
+ "Examples:",
27819
+ " specialists list",
27820
+ " specialists list --scope project",
27821
+ " specialists list --category analysis",
27822
+ " specialists list --json",
27823
+ ""
27824
+ ].join(`
27825
+ `));
27826
+ return;
27827
+ }
27120
27828
  const { run: handler } = await Promise.resolve().then(() => (init_list(), exports_list));
27121
27829
  return handler();
27122
27830
  }
27123
27831
  if (sub === "models") {
27832
+ if (wantsHelp()) {
27833
+ console.log([
27834
+ "",
27835
+ "Usage: specialists models",
27836
+ "",
27837
+ "List all models available on pi, with thinking and image support flags.",
27838
+ "",
27839
+ "No flags.",
27840
+ ""
27841
+ ].join(`
27842
+ `));
27843
+ return;
27844
+ }
27124
27845
  const { run: handler } = await Promise.resolve().then(() => (init_models(), exports_models));
27125
27846
  return handler();
27126
27847
  }
27127
27848
  if (sub === "init") {
27849
+ if (wantsHelp()) {
27850
+ console.log([
27851
+ "",
27852
+ "Usage: specialists init",
27853
+ "",
27854
+ "Initialize specialists in the current project:",
27855
+ " • Creates specialists/ — put .specialist.yaml files here",
27856
+ " • Creates .specialists/ — runtime data (gitignored)",
27857
+ " • Adds .specialists/ to .gitignore",
27858
+ " • Scaffolds AGENTS.md — context injected into Claude sessions",
27859
+ "",
27860
+ "Safe to run on an existing project (skips already-present items).",
27861
+ ""
27862
+ ].join(`
27863
+ `));
27864
+ return;
27865
+ }
27128
27866
  const { run: handler } = await Promise.resolve().then(() => (init_init(), exports_init));
27129
27867
  return handler();
27130
27868
  }
27131
27869
  if (sub === "edit") {
27870
+ if (wantsHelp()) {
27871
+ console.log([
27872
+ "",
27873
+ "Usage: specialists edit <name> --<field> <value> [options]",
27874
+ "",
27875
+ "Edit a field in a .specialist.yaml without opening the file.",
27876
+ "",
27877
+ "Editable fields:",
27878
+ " --model <value> Primary execution model",
27879
+ " --fallback-model <value> Fallback model (used on circuit-break)",
27880
+ " --description <value> One-line description",
27881
+ " --permission <value> READ_ONLY | LOW | MEDIUM | HIGH",
27882
+ " --timeout <ms> Timeout in milliseconds",
27883
+ " --tags <a,b,c> Comma-separated list of tags",
27884
+ "",
27885
+ "Options:",
27886
+ " --dry-run Preview the change without writing",
27887
+ " --scope <project|user> Disambiguate if same name exists in multiple scopes",
27888
+ "",
27889
+ "Examples:",
27890
+ " specialists edit code-review --model anthropic/claude-opus-4-6",
27891
+ " specialists edit code-review --permission HIGH --dry-run",
27892
+ " specialists edit code-review --tags analysis,security",
27893
+ ""
27894
+ ].join(`
27895
+ `));
27896
+ return;
27897
+ }
27132
27898
  const { run: handler } = await Promise.resolve().then(() => (init_edit(), exports_edit));
27133
27899
  return handler();
27134
27900
  }
27135
27901
  if (sub === "run") {
27902
+ if (wantsHelp()) {
27903
+ console.log([
27904
+ "",
27905
+ "Usage: specialists run <name> [options]",
27906
+ "",
27907
+ "Run a specialist. Streams output to stdout by default.",
27908
+ "Reads prompt from stdin if --prompt is not provided.",
27909
+ "",
27910
+ "Options:",
27911
+ " --prompt <text> Prompt to send to the specialist (required unless piped)",
27912
+ " --model <model> Override the model for this run only",
27913
+ " --background Run async; prints job ID and exits immediately",
27914
+ " --no-beads Skip creating a beads issue for this run",
27915
+ "",
27916
+ "Examples:",
27917
+ ' specialists run code-review --prompt "Audit src/api.ts"',
27918
+ ' specialists run code-review --prompt "..." --background',
27919
+ " cat brief.md | specialists run deep-analysis",
27920
+ ' specialists run code-review --model anthropic/claude-opus-4-6 --prompt "..."',
27921
+ "",
27922
+ "See also:",
27923
+ " specialists feed --help (tail events for a background job)",
27924
+ " specialists result --help (read background job output)",
27925
+ ""
27926
+ ].join(`
27927
+ `));
27928
+ return;
27929
+ }
27136
27930
  const { run: handler } = await Promise.resolve().then(() => (init_run(), exports_run));
27137
27931
  return handler();
27138
27932
  }
27139
27933
  if (sub === "status") {
27934
+ if (wantsHelp()) {
27935
+ console.log([
27936
+ "",
27937
+ "Usage: specialists status [options]",
27938
+ "",
27939
+ "Show system health: pi runtime, beads installation, MCP registration,",
27940
+ "and all active background jobs.",
27941
+ "",
27942
+ "Options:",
27943
+ " --json Output as JSON",
27944
+ "",
27945
+ "Examples:",
27946
+ " specialists status",
27947
+ " specialists status --json",
27948
+ ""
27949
+ ].join(`
27950
+ `));
27951
+ return;
27952
+ }
27140
27953
  const { run: handler } = await Promise.resolve().then(() => (init_status(), exports_status));
27141
27954
  return handler();
27142
27955
  }
27143
27956
  if (sub === "result") {
27957
+ if (wantsHelp()) {
27958
+ console.log([
27959
+ "",
27960
+ "Usage: specialists result <job-id>",
27961
+ "",
27962
+ "Print the final output of a completed background job.",
27963
+ "Exits with code 1 if the job is still running or failed.",
27964
+ "",
27965
+ "Examples:",
27966
+ " specialists result job_a1b2c3d4",
27967
+ " specialists result job_a1b2c3d4 > output.md",
27968
+ "",
27969
+ "See also:",
27970
+ " specialists feed <job-id> --follow (stream live events)",
27971
+ " specialists status (list all active jobs)",
27972
+ ""
27973
+ ].join(`
27974
+ `));
27975
+ return;
27976
+ }
27144
27977
  const { run: handler } = await Promise.resolve().then(() => (init_result(), exports_result));
27145
27978
  return handler();
27146
27979
  }
27147
27980
  if (sub === "feed") {
27981
+ if (wantsHelp()) {
27982
+ console.log([
27983
+ "",
27984
+ "Usage: specialists feed <job-id> [options]",
27985
+ " specialists feed --job <job-id> [options]",
27986
+ "",
27987
+ "Print events emitted by a background job.",
27988
+ "",
27989
+ "Options:",
27990
+ " --follow, -f Stay open and stream new events as they arrive",
27991
+ " (exits automatically when job completes)",
27992
+ "",
27993
+ "Examples:",
27994
+ " specialists feed job_a1b2c3d4",
27995
+ " specialists feed job_a1b2c3d4 --follow",
27996
+ " specialists feed --job job_a1b2c3d4 -f",
27997
+ "",
27998
+ "Event types: tool_use · tool_result · text · agent_end · error",
27999
+ ""
28000
+ ].join(`
28001
+ `));
28002
+ return;
28003
+ }
27148
28004
  const { run: handler } = await Promise.resolve().then(() => (init_feed(), exports_feed));
27149
28005
  return handler();
27150
28006
  }
27151
28007
  if (sub === "stop") {
28008
+ if (wantsHelp()) {
28009
+ console.log([
28010
+ "",
28011
+ "Usage: specialists stop <job-id>",
28012
+ "",
28013
+ "Send SIGTERM to the agent process for a running background job.",
28014
+ "Has no effect if the job is already done or errored.",
28015
+ "",
28016
+ "Examples:",
28017
+ " specialists stop job_a1b2c3d4",
28018
+ ""
28019
+ ].join(`
28020
+ `));
28021
+ return;
28022
+ }
27152
28023
  const { run: handler } = await Promise.resolve().then(() => (init_stop(), exports_stop));
27153
28024
  return handler();
27154
28025
  }
28026
+ if (sub === "quickstart") {
28027
+ const { run: handler } = await Promise.resolve().then(() => exports_quickstart);
28028
+ return handler();
28029
+ }
28030
+ if (sub === "doctor") {
28031
+ if (wantsHelp()) {
28032
+ console.log([
28033
+ "",
28034
+ "Usage: specialists doctor",
28035
+ "",
28036
+ "Health check for your specialists installation:",
28037
+ " 1. pi installed and has at least one active provider",
28038
+ " 2. All 7 Claude Code hooks present and wired in settings.json",
28039
+ " 3. MCP server registered (claude mcp get specialists)",
28040
+ " 4. .specialists/jobs/ and .specialists/ready/ dirs exist",
28041
+ " 5. No zombie jobs (running status but dead PID)",
28042
+ "",
28043
+ "Prints fix hints for each failure.",
28044
+ "Auto-creates missing runtime directories.",
28045
+ "",
28046
+ "Examples:",
28047
+ " specialists doctor",
28048
+ ""
28049
+ ].join(`
28050
+ `));
28051
+ return;
28052
+ }
28053
+ const { run: handler } = await Promise.resolve().then(() => (init_doctor(), exports_doctor));
28054
+ return handler();
28055
+ }
28056
+ if (sub === "setup") {
28057
+ if (wantsHelp()) {
28058
+ console.log([
28059
+ "",
28060
+ "Usage: specialists setup [options]",
28061
+ "",
28062
+ "Inject the Specialists Workflow context block into AGENTS.md or CLAUDE.md.",
28063
+ "This teaches agents in that project how to use specialists.",
28064
+ "",
28065
+ "Options:",
28066
+ " --project, -p Write to ./CLAUDE.md (default)",
28067
+ " --agents, -a Write to ./AGENTS.md",
28068
+ " --global, -g Write to ~/.claude/CLAUDE.md",
28069
+ " --dry-run Preview the block without writing",
28070
+ "",
28071
+ "Examples:",
28072
+ " specialists setup # → ./CLAUDE.md",
28073
+ " specialists setup --agents # → ./AGENTS.md",
28074
+ " specialists setup --global # → ~/.claude/CLAUDE.md",
28075
+ " specialists setup --dry-run # preview only",
28076
+ ""
28077
+ ].join(`
28078
+ `));
28079
+ return;
28080
+ }
28081
+ const { run: handler } = await Promise.resolve().then(() => (init_setup(), exports_setup));
28082
+ return handler();
28083
+ }
27155
28084
  if (sub === "help" || sub === "--help" || sub === "-h") {
27156
28085
  const { run: handler } = await Promise.resolve().then(() => (init_help(), exports_help));
27157
28086
  return handler();
@@ -27165,7 +28094,7 @@ Run 'specialists help' to see available commands.`);
27165
28094
  const server = new SpecialistsServer;
27166
28095
  await server.start();
27167
28096
  }
27168
- run13().catch((error2) => {
28097
+ run16().catch((error2) => {
27169
28098
  logger.error(`Fatal error: ${error2}`);
27170
28099
  process.exit(1);
27171
28100
  });