@jaggerxtrm/specialists 3.0.2 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -257
- package/bin/install.js +156 -266
- package/dist/index.js +2049 -262
- package/hooks/specialists-session-start.mjs +105 -0
- package/package.json +1 -1
- package/specialists/bug-hunt.specialist.yaml +53 -20
- package/specialists/codebase-explorer.specialist.yaml +43 -24
- package/specialists/feature-design.specialist.yaml +48 -29
- package/specialists/planner.specialist.yaml +87 -0
- package/specialists/specialist-author.specialist.yaml +56 -0
- package/specialists/sync-docs.specialist.yaml +53 -0
- package/specialists/xt-merge.specialist.yaml +78 -0
- package/hooks/beads-close-memory-prompt.mjs +0 -47
- package/hooks/beads-commit-gate.mjs +0 -58
- package/hooks/beads-edit-gate.mjs +0 -53
- package/hooks/beads-stop-gate.mjs +0 -52
- package/hooks/specialists-main-guard.mjs +0 -90
package/dist/index.js
CHANGED
|
@@ -8531,6 +8531,7 @@ var require_limitLength = __commonJS((exports) => {
|
|
|
8531
8531
|
var require_pattern = __commonJS((exports) => {
|
|
8532
8532
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8533
8533
|
var code_1 = require_code2();
|
|
8534
|
+
var util_1 = require_util();
|
|
8534
8535
|
var codegen_1 = require_codegen();
|
|
8535
8536
|
var error2 = {
|
|
8536
8537
|
message: ({ schemaCode }) => (0, codegen_1.str)`must match pattern "${schemaCode}"`,
|
|
@@ -8543,10 +8544,18 @@ var require_pattern = __commonJS((exports) => {
|
|
|
8543
8544
|
$data: true,
|
|
8544
8545
|
error: error2,
|
|
8545
8546
|
code(cxt) {
|
|
8546
|
-
const { data, $data, schema, schemaCode, it } = cxt;
|
|
8547
|
+
const { gen, data, $data, schema, schemaCode, it } = cxt;
|
|
8547
8548
|
const u = it.opts.unicodeRegExp ? "u" : "";
|
|
8548
|
-
|
|
8549
|
-
|
|
8549
|
+
if ($data) {
|
|
8550
|
+
const { regExp } = it.opts.code;
|
|
8551
|
+
const regExpCode = regExp.code === "new RegExp" ? (0, codegen_1._)`new RegExp` : (0, util_1.useFunc)(gen, regExp);
|
|
8552
|
+
const valid = gen.let("valid");
|
|
8553
|
+
gen.try(() => gen.assign(valid, (0, codegen_1._)`${regExpCode}(${schemaCode}, ${u}).test(${data})`), () => gen.assign(valid, false));
|
|
8554
|
+
cxt.fail$data((0, codegen_1._)`!${valid}`);
|
|
8555
|
+
} else {
|
|
8556
|
+
const regExp = (0, code_1.usePattern)(cxt, schema);
|
|
8557
|
+
cxt.fail$data((0, codegen_1._)`!${regExp}.test(${data})`);
|
|
8558
|
+
}
|
|
8550
8559
|
}
|
|
8551
8560
|
};
|
|
8552
8561
|
exports.default = def;
|
|
@@ -17524,12 +17533,13 @@ class SpecialistLoader {
|
|
|
17524
17533
|
this.userDir = options.userDir ?? join(homedir(), ".agents", "specialists");
|
|
17525
17534
|
}
|
|
17526
17535
|
getScanDirs() {
|
|
17527
|
-
|
|
17536
|
+
const dirs = [
|
|
17528
17537
|
{ path: join(this.projectDir, "specialists"), scope: "project" },
|
|
17529
17538
|
{ path: join(this.projectDir, ".claude", "specialists"), scope: "project" },
|
|
17530
17539
|
{ path: join(this.projectDir, ".agent-forge", "specialists"), scope: "project" },
|
|
17531
17540
|
{ path: this.userDir, scope: "user" }
|
|
17532
|
-
]
|
|
17541
|
+
];
|
|
17542
|
+
return dirs.filter((d) => existsSync(d.path));
|
|
17533
17543
|
}
|
|
17534
17544
|
async list(category) {
|
|
17535
17545
|
const results = [];
|
|
@@ -17640,6 +17650,9 @@ var init_backendMap = __esm(() => {
|
|
|
17640
17650
|
|
|
17641
17651
|
// src/pi/session.ts
|
|
17642
17652
|
import { spawn } from "node:child_process";
|
|
17653
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
17654
|
+
import { homedir as homedir2 } from "node:os";
|
|
17655
|
+
import { join as join2 } from "node:path";
|
|
17643
17656
|
function mapPermissionToTools(level) {
|
|
17644
17657
|
switch (level?.toUpperCase()) {
|
|
17645
17658
|
case "READ_ONLY":
|
|
@@ -17659,6 +17672,7 @@ class PiAgentSession {
|
|
|
17659
17672
|
options;
|
|
17660
17673
|
proc;
|
|
17661
17674
|
_lastOutput = "";
|
|
17675
|
+
_donePromise;
|
|
17662
17676
|
_doneResolve;
|
|
17663
17677
|
_doneReject;
|
|
17664
17678
|
_agentEndReceived = false;
|
|
@@ -17686,6 +17700,7 @@ class PiAgentSession {
|
|
|
17686
17700
|
const args = [
|
|
17687
17701
|
"--mode",
|
|
17688
17702
|
"rpc",
|
|
17703
|
+
"--no-extensions",
|
|
17689
17704
|
...providerArgs,
|
|
17690
17705
|
"--no-session",
|
|
17691
17706
|
...extraArgs
|
|
@@ -17693,11 +17708,22 @@ class PiAgentSession {
|
|
|
17693
17708
|
const toolsFlag = mapPermissionToTools(this.options.permissionLevel);
|
|
17694
17709
|
if (toolsFlag)
|
|
17695
17710
|
args.push("--tools", toolsFlag);
|
|
17711
|
+
const piExtDir = join2(homedir2(), ".pi", "agent", "extensions");
|
|
17712
|
+
const permLevel = (this.options.permissionLevel ?? "").toUpperCase();
|
|
17713
|
+
if (permLevel !== "READ_ONLY") {
|
|
17714
|
+
const qgPath = join2(piExtDir, "quality-gates");
|
|
17715
|
+
if (existsSync2(qgPath))
|
|
17716
|
+
args.push("-e", qgPath);
|
|
17717
|
+
}
|
|
17718
|
+
const ssPath = join2(piExtDir, "service-skills");
|
|
17719
|
+
if (existsSync2(ssPath))
|
|
17720
|
+
args.push("-e", ssPath);
|
|
17696
17721
|
if (this.options.systemPrompt) {
|
|
17697
17722
|
args.push("--append-system-prompt", this.options.systemPrompt);
|
|
17698
17723
|
}
|
|
17699
17724
|
this.proc = spawn("pi", args, {
|
|
17700
|
-
stdio: ["pipe", "pipe", "inherit"]
|
|
17725
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
17726
|
+
cwd: this.options.cwd
|
|
17701
17727
|
});
|
|
17702
17728
|
const donePromise = new Promise((resolve, reject) => {
|
|
17703
17729
|
this._doneResolve = resolve;
|
|
@@ -17759,34 +17785,11 @@ class PiAgentSession {
|
|
|
17759
17785
|
this._lastOutput = last.content.filter((c) => c.type === "text").map((c) => c.text).join("");
|
|
17760
17786
|
}
|
|
17761
17787
|
this._agentEndReceived = true;
|
|
17762
|
-
this.options.onEvent?.("
|
|
17788
|
+
this.options.onEvent?.("agent_end");
|
|
17763
17789
|
this._doneResolve?.();
|
|
17764
17790
|
return;
|
|
17765
17791
|
}
|
|
17766
|
-
if (type === "thinking_start") {
|
|
17767
|
-
this.options.onEvent?.("thinking");
|
|
17768
|
-
return;
|
|
17769
|
-
}
|
|
17770
|
-
if (type === "thinking_delta") {
|
|
17771
|
-
if (event.delta)
|
|
17772
|
-
this.options.onThinking?.(event.delta);
|
|
17773
|
-
this.options.onEvent?.("thinking");
|
|
17774
|
-
return;
|
|
17775
|
-
}
|
|
17776
|
-
if (type === "thinking_end") {
|
|
17777
|
-
return;
|
|
17778
|
-
}
|
|
17779
|
-
if (type === "toolcall_start") {
|
|
17780
|
-
this.options.onToolStart?.(event.name ?? event.toolName ?? "tool");
|
|
17781
|
-
this.options.onEvent?.("toolcall");
|
|
17782
|
-
return;
|
|
17783
|
-
}
|
|
17784
|
-
if (type === "toolcall_end") {
|
|
17785
|
-
this.options.onEvent?.("toolcall");
|
|
17786
|
-
return;
|
|
17787
|
-
}
|
|
17788
17792
|
if (type === "tool_execution_start") {
|
|
17789
|
-
this.options.onToolStart?.(event.name ?? event.toolName ?? "tool");
|
|
17790
17793
|
this.options.onEvent?.("tool_execution");
|
|
17791
17794
|
return;
|
|
17792
17795
|
}
|
|
@@ -17795,7 +17798,7 @@ class PiAgentSession {
|
|
|
17795
17798
|
return;
|
|
17796
17799
|
}
|
|
17797
17800
|
if (type === "tool_execution_end") {
|
|
17798
|
-
this.options.onToolEnd?.(event.
|
|
17801
|
+
this.options.onToolEnd?.(event.toolName ?? event.name ?? "tool");
|
|
17799
17802
|
this.options.onEvent?.("tool_execution_end");
|
|
17800
17803
|
return;
|
|
17801
17804
|
}
|
|
@@ -17803,9 +17806,30 @@ class PiAgentSession {
|
|
|
17803
17806
|
const ae = event.assistantMessageEvent;
|
|
17804
17807
|
if (!ae)
|
|
17805
17808
|
return;
|
|
17806
|
-
|
|
17807
|
-
|
|
17808
|
-
|
|
17809
|
+
switch (ae.type) {
|
|
17810
|
+
case "text_delta":
|
|
17811
|
+
if (ae.delta)
|
|
17812
|
+
this.options.onToken?.(ae.delta);
|
|
17813
|
+
this.options.onEvent?.("text");
|
|
17814
|
+
break;
|
|
17815
|
+
case "thinking_start":
|
|
17816
|
+
this.options.onEvent?.("thinking");
|
|
17817
|
+
break;
|
|
17818
|
+
case "thinking_delta":
|
|
17819
|
+
if (ae.delta)
|
|
17820
|
+
this.options.onThinking?.(ae.delta);
|
|
17821
|
+
this.options.onEvent?.("thinking");
|
|
17822
|
+
break;
|
|
17823
|
+
case "toolcall_start":
|
|
17824
|
+
this.options.onToolStart?.(ae.name ?? ae.toolName ?? "tool");
|
|
17825
|
+
this.options.onEvent?.("toolcall");
|
|
17826
|
+
break;
|
|
17827
|
+
case "toolcall_end":
|
|
17828
|
+
this.options.onEvent?.("toolcall");
|
|
17829
|
+
break;
|
|
17830
|
+
case "done":
|
|
17831
|
+
this.options.onEvent?.("message_done");
|
|
17832
|
+
break;
|
|
17809
17833
|
}
|
|
17810
17834
|
}
|
|
17811
17835
|
}
|
|
@@ -17831,7 +17855,7 @@ class PiAgentSession {
|
|
|
17831
17855
|
this.proc?.stdin?.write(msg);
|
|
17832
17856
|
}
|
|
17833
17857
|
async waitForDone(timeout) {
|
|
17834
|
-
const donePromise = this._donePromise;
|
|
17858
|
+
const donePromise = this._donePromise ?? Promise.resolve();
|
|
17835
17859
|
if (!timeout)
|
|
17836
17860
|
return donePromise;
|
|
17837
17861
|
return Promise.race([
|
|
@@ -17868,7 +17892,7 @@ class PiAgentSession {
|
|
|
17868
17892
|
if (this._killed)
|
|
17869
17893
|
return;
|
|
17870
17894
|
this.proc?.stdin?.end();
|
|
17871
|
-
await this._donePromise
|
|
17895
|
+
await this._donePromise?.catch(() => {});
|
|
17872
17896
|
}
|
|
17873
17897
|
kill() {
|
|
17874
17898
|
if (this._killed)
|
|
@@ -17892,6 +17916,29 @@ var init_session = __esm(() => {
|
|
|
17892
17916
|
|
|
17893
17917
|
// src/specialist/beads.ts
|
|
17894
17918
|
import { spawnSync } from "node:child_process";
|
|
17919
|
+
function buildBeadContext(bead, completedBlockers = []) {
|
|
17920
|
+
const lines = [`# Task: ${bead.title}`];
|
|
17921
|
+
if (bead.description?.trim()) {
|
|
17922
|
+
lines.push(bead.description.trim());
|
|
17923
|
+
}
|
|
17924
|
+
if (bead.notes?.trim()) {
|
|
17925
|
+
lines.push("", "## Notes", bead.notes.trim());
|
|
17926
|
+
}
|
|
17927
|
+
if (completedBlockers.length > 0) {
|
|
17928
|
+
lines.push("", "## Context from completed dependencies:");
|
|
17929
|
+
for (const blocker of completedBlockers) {
|
|
17930
|
+
lines.push("", `### ${blocker.title} (${blocker.id})`);
|
|
17931
|
+
if (blocker.description?.trim()) {
|
|
17932
|
+
lines.push(blocker.description.trim());
|
|
17933
|
+
}
|
|
17934
|
+
if (blocker.notes?.trim()) {
|
|
17935
|
+
lines.push("", blocker.notes.trim());
|
|
17936
|
+
}
|
|
17937
|
+
}
|
|
17938
|
+
}
|
|
17939
|
+
return lines.join(`
|
|
17940
|
+
`).trim();
|
|
17941
|
+
}
|
|
17895
17942
|
|
|
17896
17943
|
class BeadsClient {
|
|
17897
17944
|
available;
|
|
@@ -17917,12 +17964,66 @@ class BeadsClient {
|
|
|
17917
17964
|
const id = result.stdout?.trim();
|
|
17918
17965
|
return id || null;
|
|
17919
17966
|
}
|
|
17967
|
+
readBead(id) {
|
|
17968
|
+
if (!this.available || !id)
|
|
17969
|
+
return null;
|
|
17970
|
+
const result = spawnSync("bd", ["show", id, "--json"], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], timeout: 5000 });
|
|
17971
|
+
if (result.error || result.status !== 0 || !result.stdout?.trim())
|
|
17972
|
+
return null;
|
|
17973
|
+
try {
|
|
17974
|
+
const parsed = JSON.parse(result.stdout);
|
|
17975
|
+
const bead = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
17976
|
+
if (!bead || typeof bead !== "object" || typeof bead.id !== "string" || typeof bead.title !== "string")
|
|
17977
|
+
return null;
|
|
17978
|
+
return bead;
|
|
17979
|
+
} catch (err) {
|
|
17980
|
+
console.warn(`[specialists] readBead: JSON parse failed for id=${id}: ${err}`);
|
|
17981
|
+
return null;
|
|
17982
|
+
}
|
|
17983
|
+
}
|
|
17984
|
+
getCompletedBlockers(id, depth = 1) {
|
|
17985
|
+
if (!this.available || !id || depth < 1)
|
|
17986
|
+
return [];
|
|
17987
|
+
const result = spawnSync("bd", ["dep", "list", id, "--json"], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], timeout: 5000 });
|
|
17988
|
+
if (result.error || result.status !== 0 || !result.stdout?.trim())
|
|
17989
|
+
return [];
|
|
17990
|
+
let deps;
|
|
17991
|
+
try {
|
|
17992
|
+
deps = JSON.parse(result.stdout);
|
|
17993
|
+
if (!Array.isArray(deps))
|
|
17994
|
+
return [];
|
|
17995
|
+
} catch {
|
|
17996
|
+
return [];
|
|
17997
|
+
}
|
|
17998
|
+
const blockers = deps.filter((d) => d.dependency_type === "blocks" && d.status === "closed");
|
|
17999
|
+
const records = [];
|
|
18000
|
+
for (const dep of blockers) {
|
|
18001
|
+
const record3 = this.readBead(dep.id);
|
|
18002
|
+
if (record3) {
|
|
18003
|
+
records.push(record3);
|
|
18004
|
+
if (depth > 1) {
|
|
18005
|
+
records.push(...this.getCompletedBlockers(dep.id, depth - 1));
|
|
18006
|
+
}
|
|
18007
|
+
}
|
|
18008
|
+
}
|
|
18009
|
+
return records;
|
|
18010
|
+
}
|
|
18011
|
+
addDependency(trackingBeadId, inputBeadId) {
|
|
18012
|
+
if (!this.available || !trackingBeadId || !inputBeadId)
|
|
18013
|
+
return;
|
|
18014
|
+
spawnSync("bd", ["dep", "add", trackingBeadId, inputBeadId], { stdio: "ignore" });
|
|
18015
|
+
}
|
|
17920
18016
|
closeBead(id, status, durationMs, model) {
|
|
17921
18017
|
if (!this.available || !id)
|
|
17922
18018
|
return;
|
|
17923
18019
|
const reason = `${status}, ${Math.round(durationMs)}ms, ${model}`;
|
|
17924
18020
|
spawnSync("bd", ["close", id, "-r", reason], { stdio: "ignore" });
|
|
17925
18021
|
}
|
|
18022
|
+
updateBeadNotes(id, notes) {
|
|
18023
|
+
if (!this.available || !id || !notes)
|
|
18024
|
+
return;
|
|
18025
|
+
spawnSync("bd", ["update", id, "--notes", notes], { stdio: "ignore" });
|
|
18026
|
+
}
|
|
17926
18027
|
auditBead(id, toolName, model, exitCode) {
|
|
17927
18028
|
if (!this.available || !id)
|
|
17928
18029
|
return;
|
|
@@ -18006,7 +18107,14 @@ class SpecialistRunner {
|
|
|
18006
18107
|
const preScripts = spec.specialist.skills?.scripts?.filter((s) => s.phase === "pre") ?? [];
|
|
18007
18108
|
const preResults = preScripts.map((s) => runScript(s.path)).filter((_, i) => preScripts[i].inject_output);
|
|
18008
18109
|
const preScriptOutput = formatScriptOutput(preResults);
|
|
18009
|
-
const
|
|
18110
|
+
const beadVariables = options.inputBeadId ? { bead_context: options.prompt, bead_id: options.inputBeadId } : {};
|
|
18111
|
+
const variables = {
|
|
18112
|
+
prompt: options.prompt,
|
|
18113
|
+
cwd: process.cwd(),
|
|
18114
|
+
pre_script_output: preScriptOutput,
|
|
18115
|
+
...options.variables ?? {},
|
|
18116
|
+
...beadVariables
|
|
18117
|
+
};
|
|
18010
18118
|
const renderedTask = renderTemplate(prompt.task_template, variables);
|
|
18011
18119
|
const promptHash = createHash("sha256").update(renderedTask).digest("hex").slice(0, 16);
|
|
18012
18120
|
await hooks.emit("post_render", invocationId, metadata.name, metadata.version, {
|
|
@@ -18059,10 +18167,15 @@ You have access via Bash:
|
|
|
18059
18167
|
});
|
|
18060
18168
|
const beadsIntegration = spec.specialist.beads_integration ?? "auto";
|
|
18061
18169
|
let beadId;
|
|
18062
|
-
|
|
18170
|
+
let ownsBead = false;
|
|
18171
|
+
if (options.inputBeadId) {
|
|
18172
|
+
beadId = options.inputBeadId;
|
|
18173
|
+
} else if (beadsClient && shouldCreateBead(beadsIntegration, execution.permission_required)) {
|
|
18063
18174
|
beadId = beadsClient.createBead(metadata.name) ?? undefined;
|
|
18064
|
-
if (beadId)
|
|
18175
|
+
if (beadId) {
|
|
18176
|
+
ownsBead = true;
|
|
18065
18177
|
onBeadCreated?.(beadId);
|
|
18178
|
+
}
|
|
18066
18179
|
}
|
|
18067
18180
|
let output;
|
|
18068
18181
|
let sessionBackend = model;
|
|
@@ -18072,6 +18185,7 @@ You have access via Bash:
|
|
|
18072
18185
|
model,
|
|
18073
18186
|
systemPrompt: agentsMd || undefined,
|
|
18074
18187
|
permissionLevel,
|
|
18188
|
+
cwd: process.cwd(),
|
|
18075
18189
|
onToken: (delta) => onProgress?.(delta),
|
|
18076
18190
|
onThinking: (delta) => onProgress?.(`\uD83D\uDCAD ${delta}`),
|
|
18077
18191
|
onToolStart: (tool) => onProgress?.(`
|
|
@@ -18100,7 +18214,8 @@ You have access via Bash:
|
|
|
18100
18214
|
}
|
|
18101
18215
|
const beadStatus = isCancelled ? "CANCELLED" : "ERROR";
|
|
18102
18216
|
if (beadId) {
|
|
18103
|
-
|
|
18217
|
+
if (ownsBead)
|
|
18218
|
+
beadsClient?.closeBead(beadId, beadStatus, Date.now() - start, model);
|
|
18104
18219
|
beadsClient?.auditBead(beadId, metadata.name, model, 1);
|
|
18105
18220
|
}
|
|
18106
18221
|
await hooks.emit("post_execute", invocationId, metadata.name, metadata.version, {
|
|
@@ -18123,7 +18238,8 @@ You have access via Bash:
|
|
|
18123
18238
|
output_valid: true
|
|
18124
18239
|
});
|
|
18125
18240
|
if (beadId) {
|
|
18126
|
-
|
|
18241
|
+
if (ownsBead)
|
|
18242
|
+
beadsClient?.closeBead(beadId, "COMPLETE", durationMs, model);
|
|
18127
18243
|
beadsClient?.auditBead(beadId, metadata.name, model, 0);
|
|
18128
18244
|
}
|
|
18129
18245
|
return {
|
|
@@ -18132,6 +18248,7 @@ You have access via Bash:
|
|
|
18132
18248
|
model,
|
|
18133
18249
|
durationMs,
|
|
18134
18250
|
specialistVersion: metadata.version,
|
|
18251
|
+
promptHash,
|
|
18135
18252
|
beadId
|
|
18136
18253
|
};
|
|
18137
18254
|
}
|
|
@@ -18234,9 +18351,9 @@ __export(exports_install, {
|
|
|
18234
18351
|
});
|
|
18235
18352
|
import { execFileSync } from "node:child_process";
|
|
18236
18353
|
import { fileURLToPath } from "node:url";
|
|
18237
|
-
import { dirname as dirname2, join as
|
|
18354
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
18238
18355
|
async function run() {
|
|
18239
|
-
const installerPath =
|
|
18356
|
+
const installerPath = join5(dirname2(fileURLToPath(import.meta.url)), "..", "bin", "install.js");
|
|
18240
18357
|
execFileSync(process.execPath, [installerPath], { stdio: "inherit" });
|
|
18241
18358
|
}
|
|
18242
18359
|
var init_install = () => {};
|
|
@@ -18281,6 +18398,10 @@ function parseArgs(argv) {
|
|
|
18281
18398
|
result.scope = value;
|
|
18282
18399
|
continue;
|
|
18283
18400
|
}
|
|
18401
|
+
if (token === "--json") {
|
|
18402
|
+
result.json = true;
|
|
18403
|
+
continue;
|
|
18404
|
+
}
|
|
18284
18405
|
}
|
|
18285
18406
|
return result;
|
|
18286
18407
|
}
|
|
@@ -18300,6 +18421,10 @@ async function run3() {
|
|
|
18300
18421
|
if (args.scope) {
|
|
18301
18422
|
specialists = specialists.filter((s) => s.scope === args.scope);
|
|
18302
18423
|
}
|
|
18424
|
+
if (args.json) {
|
|
18425
|
+
console.log(JSON.stringify(specialists, null, 2));
|
|
18426
|
+
return;
|
|
18427
|
+
}
|
|
18303
18428
|
if (specialists.length === 0) {
|
|
18304
18429
|
console.log("No specialists found.");
|
|
18305
18430
|
return;
|
|
@@ -18383,7 +18508,7 @@ async function run4() {
|
|
|
18383
18508
|
}
|
|
18384
18509
|
const allModels = parsePiModels();
|
|
18385
18510
|
if (!allModels) {
|
|
18386
|
-
console.error("pi not found or failed —
|
|
18511
|
+
console.error("pi not found or failed — install and configure pi first");
|
|
18387
18512
|
process.exit(1);
|
|
18388
18513
|
}
|
|
18389
18514
|
let models = allModels;
|
|
@@ -18439,36 +18564,62 @@ var exports_init = {};
|
|
|
18439
18564
|
__export(exports_init, {
|
|
18440
18565
|
run: () => run5
|
|
18441
18566
|
});
|
|
18442
|
-
import { existsSync as
|
|
18443
|
-
import { join as
|
|
18567
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
18568
|
+
import { join as join6 } from "node:path";
|
|
18444
18569
|
function ok(msg) {
|
|
18445
18570
|
console.log(` ${green2("✓")} ${msg}`);
|
|
18446
18571
|
}
|
|
18447
18572
|
function skip(msg) {
|
|
18448
18573
|
console.log(` ${yellow3("○")} ${msg}`);
|
|
18449
18574
|
}
|
|
18575
|
+
function loadJson(path, fallback) {
|
|
18576
|
+
if (!existsSync4(path))
|
|
18577
|
+
return structuredClone(fallback);
|
|
18578
|
+
try {
|
|
18579
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
18580
|
+
} catch {
|
|
18581
|
+
return structuredClone(fallback);
|
|
18582
|
+
}
|
|
18583
|
+
}
|
|
18584
|
+
function saveJson(path, value) {
|
|
18585
|
+
writeFileSync(path, JSON.stringify(value, null, 2) + `
|
|
18586
|
+
`, "utf-8");
|
|
18587
|
+
}
|
|
18588
|
+
function ensureProjectMcp(cwd) {
|
|
18589
|
+
const mcpPath = join6(cwd, MCP_FILE);
|
|
18590
|
+
const mcp = loadJson(mcpPath, { mcpServers: {} });
|
|
18591
|
+
mcp.mcpServers ??= {};
|
|
18592
|
+
const existing = mcp.mcpServers[MCP_SERVER_NAME];
|
|
18593
|
+
if (existing && existing.command === MCP_SERVER_CONFIG.command && Array.isArray(existing.args) && existing.args.length === MCP_SERVER_CONFIG.args.length) {
|
|
18594
|
+
skip(".mcp.json already registers specialists");
|
|
18595
|
+
return;
|
|
18596
|
+
}
|
|
18597
|
+
mcp.mcpServers[MCP_SERVER_NAME] = MCP_SERVER_CONFIG;
|
|
18598
|
+
saveJson(mcpPath, mcp);
|
|
18599
|
+
ok("registered specialists in project .mcp.json");
|
|
18600
|
+
}
|
|
18450
18601
|
async function run5() {
|
|
18451
18602
|
const cwd = process.cwd();
|
|
18452
18603
|
console.log(`
|
|
18453
18604
|
${bold3("specialists init")}
|
|
18454
18605
|
`);
|
|
18455
|
-
const specialistsDir =
|
|
18456
|
-
if (
|
|
18606
|
+
const specialistsDir = join6(cwd, "specialists");
|
|
18607
|
+
if (existsSync4(specialistsDir)) {
|
|
18457
18608
|
skip("specialists/ already exists");
|
|
18458
18609
|
} else {
|
|
18459
18610
|
mkdirSync(specialistsDir, { recursive: true });
|
|
18460
18611
|
ok("created specialists/");
|
|
18461
18612
|
}
|
|
18462
|
-
const runtimeDir =
|
|
18463
|
-
if (
|
|
18613
|
+
const runtimeDir = join6(cwd, ".specialists");
|
|
18614
|
+
if (existsSync4(runtimeDir)) {
|
|
18464
18615
|
skip(".specialists/ already exists");
|
|
18465
18616
|
} else {
|
|
18466
|
-
mkdirSync(
|
|
18467
|
-
mkdirSync(
|
|
18617
|
+
mkdirSync(join6(runtimeDir, "jobs"), { recursive: true });
|
|
18618
|
+
mkdirSync(join6(runtimeDir, "ready"), { recursive: true });
|
|
18468
18619
|
ok("created .specialists/ (jobs/, ready/)");
|
|
18469
18620
|
}
|
|
18470
|
-
const gitignorePath =
|
|
18471
|
-
if (
|
|
18621
|
+
const gitignorePath = join6(cwd, ".gitignore");
|
|
18622
|
+
if (existsSync4(gitignorePath)) {
|
|
18472
18623
|
const existing = readFileSync(gitignorePath, "utf-8");
|
|
18473
18624
|
if (existing.includes(GITIGNORE_ENTRY)) {
|
|
18474
18625
|
skip(".gitignore already has .specialists/ entry");
|
|
@@ -18485,8 +18636,8 @@ ${bold3("specialists init")}
|
|
|
18485
18636
|
`, "utf-8");
|
|
18486
18637
|
ok("created .gitignore with .specialists/ entry");
|
|
18487
18638
|
}
|
|
18488
|
-
const agentsPath =
|
|
18489
|
-
if (
|
|
18639
|
+
const agentsPath = join6(cwd, "AGENTS.md");
|
|
18640
|
+
if (existsSync4(agentsPath)) {
|
|
18490
18641
|
const existing = readFileSync(agentsPath, "utf-8");
|
|
18491
18642
|
if (existing.includes(AGENTS_MARKER)) {
|
|
18492
18643
|
skip("AGENTS.md already has Specialists section");
|
|
@@ -18500,16 +18651,17 @@ ${bold3("specialists init")}
|
|
|
18500
18651
|
writeFileSync(agentsPath, AGENTS_BLOCK, "utf-8");
|
|
18501
18652
|
ok("created AGENTS.md with Specialists section");
|
|
18502
18653
|
}
|
|
18654
|
+
ensureProjectMcp(cwd);
|
|
18503
18655
|
console.log(`
|
|
18504
18656
|
${bold3("Done!")}
|
|
18505
18657
|
`);
|
|
18506
18658
|
console.log(` ${dim3("Next steps:")}`);
|
|
18507
18659
|
console.log(` 1. Add your specialists to ${yellow3("specialists/")}`);
|
|
18508
18660
|
console.log(` 2. Run ${yellow3("specialists list")} to verify they are discovered`);
|
|
18509
|
-
console.log(` 3. Restart Claude Code to pick up AGENTS.md changes
|
|
18661
|
+
console.log(` 3. Restart Claude Code to pick up AGENTS.md / .mcp.json changes
|
|
18510
18662
|
`);
|
|
18511
18663
|
}
|
|
18512
|
-
var bold3 = (s) => `\x1B[1m${s}\x1B[0m`, green2 = (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_ENTRY = ".specialists/";
|
|
18664
|
+
var bold3 = (s) => `\x1B[1m${s}\x1B[0m`, green2 = (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_ENTRY = ".specialists/", MCP_FILE = ".mcp.json", MCP_SERVER_NAME = "specialists", MCP_SERVER_CONFIG;
|
|
18513
18665
|
var init_init = __esm(() => {
|
|
18514
18666
|
AGENTS_BLOCK = `
|
|
18515
18667
|
## Specialists
|
|
@@ -18519,6 +18671,7 @@ see available specialists. Use \`use_specialist\` or \`start_specialist\` to
|
|
|
18519
18671
|
delegate heavy tasks (code review, bug hunting, deep reasoning) to the right
|
|
18520
18672
|
specialist without user intervention.
|
|
18521
18673
|
`.trimStart();
|
|
18674
|
+
MCP_SERVER_CONFIG = { command: "specialists", args: [] };
|
|
18522
18675
|
});
|
|
18523
18676
|
|
|
18524
18677
|
// src/cli/edit.ts
|
|
@@ -18651,10 +18804,119 @@ var init_edit = __esm(() => {
|
|
|
18651
18804
|
VALID_PERMISSIONS = ["READ_ONLY", "LOW", "MEDIUM", "HIGH"];
|
|
18652
18805
|
});
|
|
18653
18806
|
|
|
18807
|
+
// src/specialist/timeline-events.ts
|
|
18808
|
+
function mapCallbackEventToTimelineEvent(callbackEvent, context) {
|
|
18809
|
+
const t = Date.now();
|
|
18810
|
+
switch (callbackEvent) {
|
|
18811
|
+
case "thinking":
|
|
18812
|
+
return { t, type: TIMELINE_EVENT_TYPES.THINKING };
|
|
18813
|
+
case "toolcall":
|
|
18814
|
+
return {
|
|
18815
|
+
t,
|
|
18816
|
+
type: TIMELINE_EVENT_TYPES.TOOL,
|
|
18817
|
+
tool: context.tool ?? "unknown",
|
|
18818
|
+
phase: "start",
|
|
18819
|
+
tool_call_id: context.toolCallId
|
|
18820
|
+
};
|
|
18821
|
+
case "tool_execution_end":
|
|
18822
|
+
return {
|
|
18823
|
+
t,
|
|
18824
|
+
type: TIMELINE_EVENT_TYPES.TOOL,
|
|
18825
|
+
tool: context.tool ?? "unknown",
|
|
18826
|
+
phase: "end",
|
|
18827
|
+
tool_call_id: context.toolCallId,
|
|
18828
|
+
is_error: context.isError
|
|
18829
|
+
};
|
|
18830
|
+
case "text":
|
|
18831
|
+
return { t, type: TIMELINE_EVENT_TYPES.TEXT };
|
|
18832
|
+
case "agent_end":
|
|
18833
|
+
case "message_done":
|
|
18834
|
+
case "done":
|
|
18835
|
+
return null;
|
|
18836
|
+
default:
|
|
18837
|
+
return null;
|
|
18838
|
+
}
|
|
18839
|
+
}
|
|
18840
|
+
function createRunStartEvent(specialist, beadId) {
|
|
18841
|
+
return {
|
|
18842
|
+
t: Date.now(),
|
|
18843
|
+
type: TIMELINE_EVENT_TYPES.RUN_START,
|
|
18844
|
+
specialist,
|
|
18845
|
+
bead_id: beadId
|
|
18846
|
+
};
|
|
18847
|
+
}
|
|
18848
|
+
function createMetaEvent(model, backend) {
|
|
18849
|
+
return {
|
|
18850
|
+
t: Date.now(),
|
|
18851
|
+
type: TIMELINE_EVENT_TYPES.META,
|
|
18852
|
+
model,
|
|
18853
|
+
backend
|
|
18854
|
+
};
|
|
18855
|
+
}
|
|
18856
|
+
function createRunCompleteEvent(status, elapsed_s, options) {
|
|
18857
|
+
return {
|
|
18858
|
+
t: Date.now(),
|
|
18859
|
+
type: TIMELINE_EVENT_TYPES.RUN_COMPLETE,
|
|
18860
|
+
status,
|
|
18861
|
+
elapsed_s,
|
|
18862
|
+
...options
|
|
18863
|
+
};
|
|
18864
|
+
}
|
|
18865
|
+
function parseTimelineEvent(line) {
|
|
18866
|
+
try {
|
|
18867
|
+
const parsed = JSON.parse(line);
|
|
18868
|
+
if (!parsed || typeof parsed !== "object")
|
|
18869
|
+
return null;
|
|
18870
|
+
if (typeof parsed.t !== "number")
|
|
18871
|
+
return null;
|
|
18872
|
+
if (typeof parsed.type !== "string")
|
|
18873
|
+
return null;
|
|
18874
|
+
if (parsed.type === TIMELINE_EVENT_TYPES.DONE) {
|
|
18875
|
+
return {
|
|
18876
|
+
t: parsed.t,
|
|
18877
|
+
type: TIMELINE_EVENT_TYPES.DONE,
|
|
18878
|
+
elapsed_s: typeof parsed.elapsed_s === "number" ? parsed.elapsed_s : undefined
|
|
18879
|
+
};
|
|
18880
|
+
}
|
|
18881
|
+
if (parsed.type === TIMELINE_EVENT_TYPES.AGENT_END) {
|
|
18882
|
+
return {
|
|
18883
|
+
t: parsed.t,
|
|
18884
|
+
type: TIMELINE_EVENT_TYPES.AGENT_END,
|
|
18885
|
+
elapsed_s: typeof parsed.elapsed_s === "number" ? parsed.elapsed_s : undefined
|
|
18886
|
+
};
|
|
18887
|
+
}
|
|
18888
|
+
const knownTypes = Object.values(TIMELINE_EVENT_TYPES).filter((type) => type !== TIMELINE_EVENT_TYPES.DONE && type !== TIMELINE_EVENT_TYPES.AGENT_END);
|
|
18889
|
+
if (!knownTypes.includes(parsed.type))
|
|
18890
|
+
return null;
|
|
18891
|
+
return parsed;
|
|
18892
|
+
} catch {
|
|
18893
|
+
return null;
|
|
18894
|
+
}
|
|
18895
|
+
}
|
|
18896
|
+
function isRunCompleteEvent(event) {
|
|
18897
|
+
return event.type === TIMELINE_EVENT_TYPES.RUN_COMPLETE;
|
|
18898
|
+
}
|
|
18899
|
+
function compareTimelineEvents(a, b) {
|
|
18900
|
+
return a.t - b.t;
|
|
18901
|
+
}
|
|
18902
|
+
var TIMELINE_EVENT_TYPES;
|
|
18903
|
+
var init_timeline_events = __esm(() => {
|
|
18904
|
+
TIMELINE_EVENT_TYPES = {
|
|
18905
|
+
RUN_START: "run_start",
|
|
18906
|
+
META: "meta",
|
|
18907
|
+
THINKING: "thinking",
|
|
18908
|
+
TOOL: "tool",
|
|
18909
|
+
TEXT: "text",
|
|
18910
|
+
RUN_COMPLETE: "run_complete",
|
|
18911
|
+
DONE: "done",
|
|
18912
|
+
AGENT_END: "agent_end"
|
|
18913
|
+
};
|
|
18914
|
+
});
|
|
18915
|
+
|
|
18654
18916
|
// src/specialist/supervisor.ts
|
|
18655
18917
|
import {
|
|
18656
18918
|
closeSync,
|
|
18657
|
-
existsSync as
|
|
18919
|
+
existsSync as existsSync5,
|
|
18658
18920
|
mkdirSync as mkdirSync2,
|
|
18659
18921
|
openSync,
|
|
18660
18922
|
readdirSync,
|
|
@@ -18665,7 +18927,32 @@ import {
|
|
|
18665
18927
|
writeFileSync as writeFileSync3,
|
|
18666
18928
|
writeSync
|
|
18667
18929
|
} from "node:fs";
|
|
18668
|
-
import { join as
|
|
18930
|
+
import { join as join7 } from "node:path";
|
|
18931
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
18932
|
+
function getCurrentGitSha() {
|
|
18933
|
+
const result = spawnSync4("git", ["rev-parse", "HEAD"], {
|
|
18934
|
+
encoding: "utf-8",
|
|
18935
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
18936
|
+
});
|
|
18937
|
+
if (result.status !== 0)
|
|
18938
|
+
return;
|
|
18939
|
+
const sha = result.stdout?.trim();
|
|
18940
|
+
return sha || undefined;
|
|
18941
|
+
}
|
|
18942
|
+
function formatBeadNotes(result) {
|
|
18943
|
+
const metadata = [
|
|
18944
|
+
`prompt_hash=${result.promptHash}`,
|
|
18945
|
+
`git_sha=${getCurrentGitSha() ?? "unknown"}`,
|
|
18946
|
+
`elapsed_ms=${Math.round(result.durationMs)}`,
|
|
18947
|
+
`model=${result.model}`,
|
|
18948
|
+
`backend=${result.backend}`
|
|
18949
|
+
].join(`
|
|
18950
|
+
`);
|
|
18951
|
+
return `${result.output}
|
|
18952
|
+
|
|
18953
|
+
---
|
|
18954
|
+
${metadata}`;
|
|
18955
|
+
}
|
|
18669
18956
|
|
|
18670
18957
|
class Supervisor {
|
|
18671
18958
|
opts;
|
|
@@ -18673,23 +18960,23 @@ class Supervisor {
|
|
|
18673
18960
|
this.opts = opts;
|
|
18674
18961
|
}
|
|
18675
18962
|
jobDir(id) {
|
|
18676
|
-
return
|
|
18963
|
+
return join7(this.opts.jobsDir, id);
|
|
18677
18964
|
}
|
|
18678
18965
|
statusPath(id) {
|
|
18679
|
-
return
|
|
18966
|
+
return join7(this.jobDir(id), "status.json");
|
|
18680
18967
|
}
|
|
18681
18968
|
resultPath(id) {
|
|
18682
|
-
return
|
|
18969
|
+
return join7(this.jobDir(id), "result.txt");
|
|
18683
18970
|
}
|
|
18684
18971
|
eventsPath(id) {
|
|
18685
|
-
return
|
|
18972
|
+
return join7(this.jobDir(id), "events.jsonl");
|
|
18686
18973
|
}
|
|
18687
18974
|
readyDir() {
|
|
18688
|
-
return
|
|
18975
|
+
return join7(this.opts.jobsDir, "..", "ready");
|
|
18689
18976
|
}
|
|
18690
18977
|
readStatus(id) {
|
|
18691
18978
|
const path = this.statusPath(id);
|
|
18692
|
-
if (!
|
|
18979
|
+
if (!existsSync5(path))
|
|
18693
18980
|
return null;
|
|
18694
18981
|
try {
|
|
18695
18982
|
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
@@ -18698,12 +18985,12 @@ class Supervisor {
|
|
|
18698
18985
|
}
|
|
18699
18986
|
}
|
|
18700
18987
|
listJobs() {
|
|
18701
|
-
if (!
|
|
18988
|
+
if (!existsSync5(this.opts.jobsDir))
|
|
18702
18989
|
return [];
|
|
18703
18990
|
const jobs = [];
|
|
18704
18991
|
for (const entry of readdirSync(this.opts.jobsDir)) {
|
|
18705
|
-
const path =
|
|
18706
|
-
if (!
|
|
18992
|
+
const path = join7(this.opts.jobsDir, entry, "status.json");
|
|
18993
|
+
if (!existsSync5(path))
|
|
18707
18994
|
continue;
|
|
18708
18995
|
try {
|
|
18709
18996
|
jobs.push(JSON.parse(readFileSync3(path, "utf-8")));
|
|
@@ -18724,11 +19011,11 @@ class Supervisor {
|
|
|
18724
19011
|
this.writeStatusFile(id, { ...current, ...updates });
|
|
18725
19012
|
}
|
|
18726
19013
|
gc() {
|
|
18727
|
-
if (!
|
|
19014
|
+
if (!existsSync5(this.opts.jobsDir))
|
|
18728
19015
|
return;
|
|
18729
19016
|
const cutoff = Date.now() - JOB_TTL_DAYS * 86400000;
|
|
18730
19017
|
for (const entry of readdirSync(this.opts.jobsDir)) {
|
|
18731
|
-
const dir =
|
|
19018
|
+
const dir = join7(this.opts.jobsDir, entry);
|
|
18732
19019
|
try {
|
|
18733
19020
|
const stat2 = statSync(dir);
|
|
18734
19021
|
if (!stat2.isDirectory())
|
|
@@ -18739,11 +19026,11 @@ class Supervisor {
|
|
|
18739
19026
|
}
|
|
18740
19027
|
}
|
|
18741
19028
|
crashRecovery() {
|
|
18742
|
-
if (!
|
|
19029
|
+
if (!existsSync5(this.opts.jobsDir))
|
|
18743
19030
|
return;
|
|
18744
19031
|
for (const entry of readdirSync(this.opts.jobsDir)) {
|
|
18745
|
-
const statusPath =
|
|
18746
|
-
if (!
|
|
19032
|
+
const statusPath = join7(this.opts.jobsDir, entry, "status.json");
|
|
19033
|
+
if (!existsSync5(statusPath))
|
|
18747
19034
|
continue;
|
|
18748
19035
|
try {
|
|
18749
19036
|
const s = JSON.parse(readFileSync3(statusPath, "utf-8"));
|
|
@@ -18780,14 +19067,19 @@ class Supervisor {
|
|
|
18780
19067
|
};
|
|
18781
19068
|
this.writeStatusFile(id, initialStatus);
|
|
18782
19069
|
const eventsFd = openSync(this.eventsPath(id), "a");
|
|
18783
|
-
const
|
|
19070
|
+
const appendTimelineEvent = (event) => {
|
|
18784
19071
|
try {
|
|
18785
|
-
writeSync(eventsFd, JSON.stringify(
|
|
19072
|
+
writeSync(eventsFd, JSON.stringify(event) + `
|
|
18786
19073
|
`);
|
|
18787
19074
|
} catch {}
|
|
18788
19075
|
};
|
|
19076
|
+
appendTimelineEvent(createRunStartEvent(runOptions.name));
|
|
18789
19077
|
let textLogged = false;
|
|
18790
19078
|
let currentTool = "";
|
|
19079
|
+
let currentToolCallId = "";
|
|
19080
|
+
let killFn;
|
|
19081
|
+
const sigtermHandler = () => killFn?.();
|
|
19082
|
+
process.once("SIGTERM", sigtermHandler);
|
|
18791
19083
|
try {
|
|
18792
19084
|
const result = await runner.run(runOptions, (delta) => {
|
|
18793
19085
|
const toolMatch = delta.match(/⚙ (.+?)…/);
|
|
@@ -18803,21 +19095,29 @@ class Supervisor {
|
|
|
18803
19095
|
last_event_at_ms: now,
|
|
18804
19096
|
elapsed_s: Math.round((now - startedAtMs) / 1000)
|
|
18805
19097
|
});
|
|
18806
|
-
|
|
18807
|
-
|
|
18808
|
-
|
|
19098
|
+
const timelineEvent = mapCallbackEventToTimelineEvent(eventType, {
|
|
19099
|
+
tool: currentTool,
|
|
19100
|
+
toolCallId: currentToolCallId || undefined
|
|
19101
|
+
});
|
|
19102
|
+
if (timelineEvent) {
|
|
19103
|
+
appendTimelineEvent(timelineEvent);
|
|
18809
19104
|
} else if (eventType === "text" && !textLogged) {
|
|
18810
19105
|
textLogged = true;
|
|
18811
|
-
|
|
19106
|
+
appendTimelineEvent({ t: Date.now(), type: TIMELINE_EVENT_TYPES.TEXT });
|
|
18812
19107
|
}
|
|
18813
19108
|
}, (meta) => {
|
|
18814
19109
|
this.updateStatus(id, { model: meta.model, backend: meta.backend });
|
|
18815
|
-
|
|
18816
|
-
}, (
|
|
19110
|
+
appendTimelineEvent(createMetaEvent(meta.model, meta.backend));
|
|
19111
|
+
}, (fn) => {
|
|
19112
|
+
killFn = fn;
|
|
19113
|
+
}, (beadId) => {
|
|
18817
19114
|
this.updateStatus(id, { bead_id: beadId });
|
|
18818
19115
|
});
|
|
18819
19116
|
const elapsed = Math.round((Date.now() - startedAtMs) / 1000);
|
|
18820
19117
|
writeFileSync3(this.resultPath(id), result.output, "utf-8");
|
|
19118
|
+
if (result.beadId) {
|
|
19119
|
+
this.opts.beadsClient?.updateBeadNotes(result.beadId, formatBeadNotes(result));
|
|
19120
|
+
}
|
|
18821
19121
|
this.updateStatus(id, {
|
|
18822
19122
|
status: "done",
|
|
18823
19123
|
elapsed_s: elapsed,
|
|
@@ -18826,27 +19126,35 @@ class Supervisor {
|
|
|
18826
19126
|
backend: result.backend,
|
|
18827
19127
|
bead_id: result.beadId
|
|
18828
19128
|
});
|
|
18829
|
-
|
|
18830
|
-
|
|
19129
|
+
appendTimelineEvent(createRunCompleteEvent("COMPLETE", elapsed, {
|
|
19130
|
+
model: result.model,
|
|
19131
|
+
backend: result.backend,
|
|
19132
|
+
bead_id: result.beadId
|
|
19133
|
+
}));
|
|
19134
|
+
writeFileSync3(join7(this.readyDir(), id), "", "utf-8");
|
|
18831
19135
|
return id;
|
|
18832
19136
|
} catch (err) {
|
|
18833
19137
|
const elapsed = Math.round((Date.now() - startedAtMs) / 1000);
|
|
19138
|
+
const errorMsg = err?.message ?? String(err);
|
|
18834
19139
|
this.updateStatus(id, {
|
|
18835
19140
|
status: "error",
|
|
18836
19141
|
elapsed_s: elapsed,
|
|
18837
|
-
error:
|
|
19142
|
+
error: errorMsg
|
|
18838
19143
|
});
|
|
18839
|
-
|
|
19144
|
+
appendTimelineEvent(createRunCompleteEvent("ERROR", elapsed, {
|
|
19145
|
+
error: errorMsg
|
|
19146
|
+
}));
|
|
18840
19147
|
throw err;
|
|
18841
19148
|
} finally {
|
|
19149
|
+
process.removeListener("SIGTERM", sigtermHandler);
|
|
18842
19150
|
closeSync(eventsFd);
|
|
18843
19151
|
}
|
|
18844
19152
|
}
|
|
18845
19153
|
}
|
|
18846
|
-
var JOB_TTL_DAYS
|
|
19154
|
+
var JOB_TTL_DAYS;
|
|
18847
19155
|
var init_supervisor = __esm(() => {
|
|
19156
|
+
init_timeline_events();
|
|
18848
19157
|
JOB_TTL_DAYS = Number(process.env.SPECIALISTS_JOB_TTL_DAYS ?? 7);
|
|
18849
|
-
LOGGED_EVENTS = new Set(["thinking", "toolcall", "tool_execution_end", "done"]);
|
|
18850
19158
|
});
|
|
18851
19159
|
|
|
18852
19160
|
// src/cli/run.ts
|
|
@@ -18854,27 +19162,37 @@ var exports_run = {};
|
|
|
18854
19162
|
__export(exports_run, {
|
|
18855
19163
|
run: () => run7
|
|
18856
19164
|
});
|
|
18857
|
-
import { join as
|
|
19165
|
+
import { join as join8 } from "node:path";
|
|
18858
19166
|
async function parseArgs4(argv) {
|
|
18859
19167
|
const name = argv[0];
|
|
18860
19168
|
if (!name || name.startsWith("--")) {
|
|
18861
|
-
console.error('Usage: specialists run <name> [--prompt "..."] [--model <model>] [--no-beads] [--background]');
|
|
19169
|
+
console.error('Usage: specialists run <name> [--prompt "..."] [--bead <id>] [--context-depth <n>] [--model <model>] [--no-beads] [--background]');
|
|
18862
19170
|
process.exit(1);
|
|
18863
19171
|
}
|
|
18864
19172
|
let prompt = "";
|
|
19173
|
+
let beadId;
|
|
18865
19174
|
let model;
|
|
18866
19175
|
let noBeads = false;
|
|
18867
19176
|
let background = false;
|
|
19177
|
+
let contextDepth = 1;
|
|
18868
19178
|
for (let i = 1;i < argv.length; i++) {
|
|
18869
19179
|
const token = argv[i];
|
|
18870
19180
|
if (token === "--prompt" && argv[i + 1]) {
|
|
18871
19181
|
prompt = argv[++i];
|
|
18872
19182
|
continue;
|
|
18873
19183
|
}
|
|
19184
|
+
if (token === "--bead" && argv[i + 1]) {
|
|
19185
|
+
beadId = argv[++i];
|
|
19186
|
+
continue;
|
|
19187
|
+
}
|
|
18874
19188
|
if (token === "--model" && argv[i + 1]) {
|
|
18875
19189
|
model = argv[++i];
|
|
18876
19190
|
continue;
|
|
18877
19191
|
}
|
|
19192
|
+
if (token === "--context-depth" && argv[i + 1]) {
|
|
19193
|
+
contextDepth = parseInt(argv[++i], 10) || 0;
|
|
19194
|
+
continue;
|
|
19195
|
+
}
|
|
18878
19196
|
if (token === "--no-beads") {
|
|
18879
19197
|
noBeads = true;
|
|
18880
19198
|
continue;
|
|
@@ -18884,10 +19202,11 @@ async function parseArgs4(argv) {
|
|
|
18884
19202
|
continue;
|
|
18885
19203
|
}
|
|
18886
19204
|
}
|
|
18887
|
-
if (
|
|
18888
|
-
|
|
18889
|
-
|
|
18890
|
-
|
|
19205
|
+
if (prompt && beadId) {
|
|
19206
|
+
console.error("Error: use either --prompt or --bead, not both.");
|
|
19207
|
+
process.exit(1);
|
|
19208
|
+
}
|
|
19209
|
+
if (!prompt && !beadId && !process.stdin.isTTY) {
|
|
18891
19210
|
prompt = await new Promise((resolve) => {
|
|
18892
19211
|
let buf = "";
|
|
18893
19212
|
process.stdin.setEncoding("utf-8");
|
|
@@ -18897,26 +19216,58 @@ async function parseArgs4(argv) {
|
|
|
18897
19216
|
process.stdin.on("end", () => resolve(buf.trim()));
|
|
18898
19217
|
});
|
|
18899
19218
|
}
|
|
18900
|
-
|
|
19219
|
+
if (!prompt && !beadId) {
|
|
19220
|
+
console.error("Error: provide --prompt, pipe stdin, or use --bead <id>.");
|
|
19221
|
+
process.exit(1);
|
|
19222
|
+
}
|
|
19223
|
+
return { name, prompt, beadId, model, noBeads, background, contextDepth };
|
|
18901
19224
|
}
|
|
18902
19225
|
async function run7() {
|
|
18903
19226
|
const args = await parseArgs4(process.argv.slice(3));
|
|
18904
19227
|
const loader = new SpecialistLoader;
|
|
18905
19228
|
const circuitBreaker = new CircuitBreaker;
|
|
18906
|
-
const hooks = new HookEmitter({ tracePath:
|
|
18907
|
-
const beadsClient = args.noBeads ?
|
|
19229
|
+
const hooks = new HookEmitter({ tracePath: join8(process.cwd(), ".specialists", "trace.jsonl") });
|
|
19230
|
+
const beadsClient = args.noBeads ? undefined : new BeadsClient;
|
|
19231
|
+
const beadReader = beadsClient ?? new BeadsClient;
|
|
19232
|
+
let prompt = args.prompt;
|
|
19233
|
+
let variables;
|
|
19234
|
+
if (args.beadId) {
|
|
19235
|
+
const bead = beadReader.readBead(args.beadId);
|
|
19236
|
+
if (!bead) {
|
|
19237
|
+
throw new Error(`Unable to read bead '${args.beadId}' via bd show --json`);
|
|
19238
|
+
}
|
|
19239
|
+
const blockers = args.contextDepth > 0 ? beadReader.getCompletedBlockers(args.beadId, args.contextDepth) : [];
|
|
19240
|
+
if (blockers.length > 0) {
|
|
19241
|
+
process.stderr.write(dim5(`
|
|
19242
|
+
[context: ${blockers.length} completed dep${blockers.length > 1 ? "s" : ""} injected]
|
|
19243
|
+
`));
|
|
19244
|
+
}
|
|
19245
|
+
const beadContext = buildBeadContext(bead, blockers);
|
|
19246
|
+
prompt = beadContext;
|
|
19247
|
+
variables = {
|
|
19248
|
+
bead_context: beadContext,
|
|
19249
|
+
bead_id: args.beadId
|
|
19250
|
+
};
|
|
19251
|
+
}
|
|
18908
19252
|
const runner = new SpecialistRunner({
|
|
18909
19253
|
loader,
|
|
18910
19254
|
hooks,
|
|
18911
19255
|
circuitBreaker,
|
|
18912
|
-
beadsClient
|
|
19256
|
+
beadsClient
|
|
18913
19257
|
});
|
|
18914
19258
|
if (args.background) {
|
|
18915
|
-
const jobsDir =
|
|
19259
|
+
const jobsDir = join8(process.cwd(), ".specialists", "jobs");
|
|
18916
19260
|
const supervisor = new Supervisor({
|
|
18917
19261
|
runner,
|
|
18918
|
-
runOptions: {
|
|
18919
|
-
|
|
19262
|
+
runOptions: {
|
|
19263
|
+
name: args.name,
|
|
19264
|
+
prompt,
|
|
19265
|
+
variables,
|
|
19266
|
+
backendOverride: args.model,
|
|
19267
|
+
inputBeadId: args.beadId
|
|
19268
|
+
},
|
|
19269
|
+
jobsDir,
|
|
19270
|
+
beadsClient
|
|
18920
19271
|
});
|
|
18921
19272
|
try {
|
|
18922
19273
|
const jobId = await supervisor.run();
|
|
@@ -18933,11 +19284,13 @@ async function run7() {
|
|
|
18933
19284
|
${bold5(`Running ${cyan3(args.name)}`)}
|
|
18934
19285
|
|
|
18935
19286
|
`);
|
|
18936
|
-
let
|
|
19287
|
+
let trackingBeadId;
|
|
18937
19288
|
const result = await runner.run({
|
|
18938
19289
|
name: args.name,
|
|
18939
|
-
prompt
|
|
18940
|
-
|
|
19290
|
+
prompt,
|
|
19291
|
+
variables,
|
|
19292
|
+
backendOverride: args.model,
|
|
19293
|
+
inputBeadId: args.beadId
|
|
18941
19294
|
}, (delta) => process.stdout.write(delta), undefined, (meta) => process.stderr.write(dim5(`
|
|
18942
19295
|
[${meta.backend} / ${meta.model}]
|
|
18943
19296
|
|
|
@@ -18950,10 +19303,10 @@ Interrupted.
|
|
|
18950
19303
|
killFn();
|
|
18951
19304
|
process.exit(130);
|
|
18952
19305
|
});
|
|
18953
|
-
}, (
|
|
18954
|
-
|
|
19306
|
+
}, (beadId) => {
|
|
19307
|
+
trackingBeadId = beadId;
|
|
18955
19308
|
process.stderr.write(dim5(`
|
|
18956
|
-
[bead: ${
|
|
19309
|
+
[bead: ${beadId}]
|
|
18957
19310
|
`));
|
|
18958
19311
|
});
|
|
18959
19312
|
if (result.output && !result.output.endsWith(`
|
|
@@ -18961,8 +19314,9 @@ Interrupted.
|
|
|
18961
19314
|
process.stdout.write(`
|
|
18962
19315
|
`);
|
|
18963
19316
|
const secs = (result.durationMs / 1000).toFixed(1);
|
|
19317
|
+
const effectiveBeadId = args.beadId ?? trackingBeadId;
|
|
18964
19318
|
const footer = [
|
|
18965
|
-
|
|
19319
|
+
effectiveBeadId ? `bead ${effectiveBeadId}` : "",
|
|
18966
19320
|
`${secs}s`,
|
|
18967
19321
|
dim5(result.model)
|
|
18968
19322
|
].filter(Boolean).join(" ");
|
|
@@ -18980,14 +19334,103 @@ var init_run = __esm(() => {
|
|
|
18980
19334
|
init_supervisor();
|
|
18981
19335
|
});
|
|
18982
19336
|
|
|
19337
|
+
// src/cli/format-helpers.ts
|
|
19338
|
+
function formatTime(t) {
|
|
19339
|
+
return new Date(t).toISOString().slice(11, 19);
|
|
19340
|
+
}
|
|
19341
|
+
function formatElapsed(seconds) {
|
|
19342
|
+
if (seconds < 60)
|
|
19343
|
+
return `${seconds}s`;
|
|
19344
|
+
const m = Math.floor(seconds / 60);
|
|
19345
|
+
const s = seconds % 60;
|
|
19346
|
+
return s > 0 ? `${m}m ${s}s` : `${m}m`;
|
|
19347
|
+
}
|
|
19348
|
+
function getEventLabel(type) {
|
|
19349
|
+
return EVENT_LABELS[type] ?? type.slice(0, 5).toUpperCase();
|
|
19350
|
+
}
|
|
19351
|
+
|
|
19352
|
+
class JobColorMap {
|
|
19353
|
+
colors = new Map;
|
|
19354
|
+
nextIdx = 0;
|
|
19355
|
+
getColor(jobId) {
|
|
19356
|
+
let color = this.colors.get(jobId);
|
|
19357
|
+
if (!color) {
|
|
19358
|
+
color = JOB_COLORS[this.nextIdx % JOB_COLORS.length];
|
|
19359
|
+
this.colors.set(jobId, color);
|
|
19360
|
+
this.nextIdx++;
|
|
19361
|
+
}
|
|
19362
|
+
return color;
|
|
19363
|
+
}
|
|
19364
|
+
get(jobId) {
|
|
19365
|
+
return this.getColor(jobId);
|
|
19366
|
+
}
|
|
19367
|
+
has(jobId) {
|
|
19368
|
+
return this.colors.has(jobId);
|
|
19369
|
+
}
|
|
19370
|
+
get size() {
|
|
19371
|
+
return this.colors.size;
|
|
19372
|
+
}
|
|
19373
|
+
}
|
|
19374
|
+
function formatEventLine(event, options) {
|
|
19375
|
+
const ts = dim6(formatTime(event.t));
|
|
19376
|
+
const label = options.colorize(bold6(getEventLabel(event.type).padEnd(5)));
|
|
19377
|
+
const prefix = `${options.colorize(`[${options.jobId}]`)} ${options.specialist}${options.beadId ? ` ${dim6(`[${options.beadId}]`)}` : ""}`;
|
|
19378
|
+
const detailParts = [];
|
|
19379
|
+
if (event.type === "meta") {
|
|
19380
|
+
detailParts.push(`model=${event.model}`);
|
|
19381
|
+
detailParts.push(`backend=${event.backend}`);
|
|
19382
|
+
} else if (event.type === "tool") {
|
|
19383
|
+
detailParts.push(`tool=${event.tool}`);
|
|
19384
|
+
detailParts.push(`phase=${event.phase}`);
|
|
19385
|
+
if (event.phase === "end") {
|
|
19386
|
+
detailParts.push(`ok=${event.is_error ? "false" : "true"}`);
|
|
19387
|
+
}
|
|
19388
|
+
} else if (event.type === "run_complete") {
|
|
19389
|
+
detailParts.push(`status=${event.status}`);
|
|
19390
|
+
detailParts.push(`elapsed=${formatElapsed(event.elapsed_s)}`);
|
|
19391
|
+
if (event.error) {
|
|
19392
|
+
detailParts.push(`error=${event.error}`);
|
|
19393
|
+
}
|
|
19394
|
+
} else if (event.type === "done" || event.type === "agent_end") {
|
|
19395
|
+
detailParts.push("status=COMPLETE");
|
|
19396
|
+
detailParts.push(`elapsed=${formatElapsed(event.elapsed_s ?? 0)}`);
|
|
19397
|
+
} else if (event.type === "run_start") {
|
|
19398
|
+
detailParts.push(`specialist=${event.specialist}`);
|
|
19399
|
+
if (event.bead_id) {
|
|
19400
|
+
detailParts.push(`bead=${event.bead_id}`);
|
|
19401
|
+
}
|
|
19402
|
+
} else if (event.type === "text") {
|
|
19403
|
+
detailParts.push("kind=assistant");
|
|
19404
|
+
} else if (event.type === "thinking") {
|
|
19405
|
+
detailParts.push("kind=model");
|
|
19406
|
+
}
|
|
19407
|
+
const detail = detailParts.length > 0 ? dim6(detailParts.join(" ")) : "";
|
|
19408
|
+
return `${ts} ${prefix} ${label}${detail ? ` ${detail}` : ""}`.trimEnd();
|
|
19409
|
+
}
|
|
19410
|
+
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`, green5 = (s) => `\x1B[32m${s}\x1B[0m`, blue = (s) => `\x1B[34m${s}\x1B[0m`, magenta = (s) => `\x1B[35m${s}\x1B[0m`, JOB_COLORS, EVENT_LABELS;
|
|
19411
|
+
var init_format_helpers = __esm(() => {
|
|
19412
|
+
JOB_COLORS = [cyan4, yellow5, magenta, green5, blue, red];
|
|
19413
|
+
EVENT_LABELS = {
|
|
19414
|
+
run_start: "START",
|
|
19415
|
+
meta: "META",
|
|
19416
|
+
thinking: "THINK",
|
|
19417
|
+
tool: "TOOL",
|
|
19418
|
+
text: "TEXT",
|
|
19419
|
+
run_complete: "DONE",
|
|
19420
|
+
done: "DONE",
|
|
19421
|
+
agent_end: "DONE",
|
|
19422
|
+
error: "ERR"
|
|
19423
|
+
};
|
|
19424
|
+
});
|
|
19425
|
+
|
|
18983
19426
|
// src/cli/status.ts
|
|
18984
19427
|
var exports_status = {};
|
|
18985
19428
|
__export(exports_status, {
|
|
18986
19429
|
run: () => run8
|
|
18987
19430
|
});
|
|
18988
|
-
import { spawnSync as
|
|
18989
|
-
import { existsSync as
|
|
18990
|
-
import { join as
|
|
19431
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
19432
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
19433
|
+
import { join as join9 } from "node:path";
|
|
18991
19434
|
function ok2(msg) {
|
|
18992
19435
|
console.log(` ${green5("✓")} ${msg}`);
|
|
18993
19436
|
}
|
|
@@ -18995,7 +19438,7 @@ function warn(msg) {
|
|
|
18995
19438
|
console.log(` ${yellow5("○")} ${msg}`);
|
|
18996
19439
|
}
|
|
18997
19440
|
function fail(msg) {
|
|
18998
|
-
console.log(` ${
|
|
19441
|
+
console.log(` ${red2("✗")} ${msg}`);
|
|
18999
19442
|
}
|
|
19000
19443
|
function info(msg) {
|
|
19001
19444
|
console.log(` ${dim6(msg)}`);
|
|
@@ -19006,7 +19449,7 @@ function section(label) {
|
|
|
19006
19449
|
${bold6(`── ${label} ${line}`)}`);
|
|
19007
19450
|
}
|
|
19008
19451
|
function cmd(bin, args) {
|
|
19009
|
-
const r =
|
|
19452
|
+
const r = spawnSync5(bin, args, {
|
|
19010
19453
|
encoding: "utf8",
|
|
19011
19454
|
stdio: "pipe",
|
|
19012
19455
|
timeout: 5000
|
|
@@ -19014,9 +19457,9 @@ function cmd(bin, args) {
|
|
|
19014
19457
|
return { ok: r.status === 0 && !r.error, stdout: (r.stdout ?? "").trim() };
|
|
19015
19458
|
}
|
|
19016
19459
|
function isInstalled(bin) {
|
|
19017
|
-
return
|
|
19460
|
+
return spawnSync5("which", [bin], { encoding: "utf8", timeout: 2000 }).status === 0;
|
|
19018
19461
|
}
|
|
19019
|
-
function
|
|
19462
|
+
function formatElapsed2(s) {
|
|
19020
19463
|
if (s.elapsed_s === undefined)
|
|
19021
19464
|
return "...";
|
|
19022
19465
|
const m = Math.floor(s.elapsed_s / 60);
|
|
@@ -19026,11 +19469,11 @@ function formatElapsed(s) {
|
|
|
19026
19469
|
function statusColor(status) {
|
|
19027
19470
|
switch (status) {
|
|
19028
19471
|
case "running":
|
|
19029
|
-
return
|
|
19472
|
+
return cyan5(status);
|
|
19030
19473
|
case "done":
|
|
19031
19474
|
return green5(status);
|
|
19032
19475
|
case "error":
|
|
19033
|
-
return
|
|
19476
|
+
return red2(status);
|
|
19034
19477
|
case "starting":
|
|
19035
19478
|
return yellow5(status);
|
|
19036
19479
|
default:
|
|
@@ -19038,56 +19481,113 @@ function statusColor(status) {
|
|
|
19038
19481
|
}
|
|
19039
19482
|
}
|
|
19040
19483
|
async function run8() {
|
|
19484
|
+
const argv = process.argv.slice(3);
|
|
19485
|
+
const jsonMode = argv.includes("--json");
|
|
19486
|
+
const loader = new SpecialistLoader;
|
|
19487
|
+
const allSpecialists = await loader.list();
|
|
19488
|
+
const piInstalled = isInstalled("pi");
|
|
19489
|
+
const piVersion = piInstalled ? cmd("pi", ["--version"]) : null;
|
|
19490
|
+
const piModels = piInstalled ? cmd("pi", ["--list-models"]) : null;
|
|
19491
|
+
const piProviders = piModels ? new Set(piModels.stdout.split(`
|
|
19492
|
+
`).slice(1).map((line) => line.split(/\s+/)[0]).filter(Boolean)) : new Set;
|
|
19493
|
+
const bdInstalled = isInstalled("bd");
|
|
19494
|
+
const bdVersion = bdInstalled ? cmd("bd", ["--version"]) : null;
|
|
19495
|
+
const beadsPresent = existsSync6(join9(process.cwd(), ".beads"));
|
|
19496
|
+
const specialistsBin = cmd("which", ["specialists"]);
|
|
19497
|
+
const jobsDir = join9(process.cwd(), ".specialists", "jobs");
|
|
19498
|
+
let jobs = [];
|
|
19499
|
+
if (existsSync6(jobsDir)) {
|
|
19500
|
+
const supervisor = new Supervisor({
|
|
19501
|
+
runner: null,
|
|
19502
|
+
runOptions: null,
|
|
19503
|
+
jobsDir
|
|
19504
|
+
});
|
|
19505
|
+
jobs = supervisor.listJobs();
|
|
19506
|
+
}
|
|
19507
|
+
const stalenessMap = {};
|
|
19508
|
+
for (const s of allSpecialists) {
|
|
19509
|
+
stalenessMap[s.name] = await checkStaleness(s);
|
|
19510
|
+
}
|
|
19511
|
+
if (jsonMode) {
|
|
19512
|
+
const output = {
|
|
19513
|
+
specialists: {
|
|
19514
|
+
count: allSpecialists.length,
|
|
19515
|
+
items: allSpecialists.map((s) => ({
|
|
19516
|
+
name: s.name,
|
|
19517
|
+
scope: s.scope,
|
|
19518
|
+
model: s.model,
|
|
19519
|
+
description: s.description,
|
|
19520
|
+
staleness: stalenessMap[s.name]
|
|
19521
|
+
}))
|
|
19522
|
+
},
|
|
19523
|
+
pi: {
|
|
19524
|
+
installed: piInstalled,
|
|
19525
|
+
version: piVersion?.stdout ?? null,
|
|
19526
|
+
providers: [...piProviders]
|
|
19527
|
+
},
|
|
19528
|
+
beads: {
|
|
19529
|
+
installed: bdInstalled,
|
|
19530
|
+
version: bdVersion?.stdout ?? null,
|
|
19531
|
+
initialized: beadsPresent
|
|
19532
|
+
},
|
|
19533
|
+
mcp: {
|
|
19534
|
+
specialists_installed: specialistsBin.ok,
|
|
19535
|
+
binary_path: specialistsBin.ok ? specialistsBin.stdout : null
|
|
19536
|
+
},
|
|
19537
|
+
jobs: jobs.map((j) => ({
|
|
19538
|
+
id: j.id,
|
|
19539
|
+
specialist: j.specialist,
|
|
19540
|
+
status: j.status,
|
|
19541
|
+
elapsed_s: j.elapsed_s,
|
|
19542
|
+
current_tool: j.current_tool ?? null,
|
|
19543
|
+
error: j.error ?? null
|
|
19544
|
+
}))
|
|
19545
|
+
};
|
|
19546
|
+
console.log(JSON.stringify(output, null, 2));
|
|
19547
|
+
return;
|
|
19548
|
+
}
|
|
19041
19549
|
console.log(`
|
|
19042
19550
|
${bold6("specialists status")}
|
|
19043
19551
|
`);
|
|
19044
19552
|
section("Specialists");
|
|
19045
|
-
|
|
19046
|
-
const all = await loader.list();
|
|
19047
|
-
if (all.length === 0) {
|
|
19553
|
+
if (allSpecialists.length === 0) {
|
|
19048
19554
|
warn(`no specialists found — run ${yellow5("specialists init")} to scaffold`);
|
|
19049
19555
|
} else {
|
|
19050
|
-
const byScope =
|
|
19556
|
+
const byScope = allSpecialists.reduce((acc, s) => {
|
|
19051
19557
|
acc[s.scope] = (acc[s.scope] ?? 0) + 1;
|
|
19052
19558
|
return acc;
|
|
19053
19559
|
}, {});
|
|
19054
19560
|
const scopeSummary = Object.entries(byScope).map(([scope, n]) => `${n} ${scope}`).join(", ");
|
|
19055
|
-
ok2(`${
|
|
19056
|
-
for (const s of
|
|
19057
|
-
const staleness =
|
|
19561
|
+
ok2(`${allSpecialists.length} found ${dim6(`(${scopeSummary})`)}`);
|
|
19562
|
+
for (const s of allSpecialists) {
|
|
19563
|
+
const staleness = stalenessMap[s.name];
|
|
19058
19564
|
if (staleness === "AGED") {
|
|
19059
|
-
warn(`${s.name} ${
|
|
19565
|
+
warn(`${s.name} ${red2("AGED")} ${dim6(s.scope)}`);
|
|
19060
19566
|
} else if (staleness === "STALE") {
|
|
19061
19567
|
warn(`${s.name} ${yellow5("STALE")} ${dim6(s.scope)}`);
|
|
19062
19568
|
}
|
|
19063
19569
|
}
|
|
19064
19570
|
}
|
|
19065
19571
|
section("pi (coding agent runtime)");
|
|
19066
|
-
if (!
|
|
19067
|
-
fail(`pi not installed —
|
|
19572
|
+
if (!piInstalled) {
|
|
19573
|
+
fail(`pi not installed — install ${yellow5("pi")} first`);
|
|
19068
19574
|
} else {
|
|
19069
|
-
const
|
|
19070
|
-
const
|
|
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");
|
|
19575
|
+
const vStr = piVersion?.ok ? `v${piVersion.stdout}` : "unknown version";
|
|
19576
|
+
const pStr = piProviders.size > 0 ? `${piProviders.size} provider${piProviders.size > 1 ? "s" : ""} active ${dim6(`(${[...piProviders].join(", ")})`)} ` : yellow5("no providers configured — run pi config");
|
|
19075
19577
|
ok2(`${vStr} — ${pStr}`);
|
|
19076
19578
|
}
|
|
19077
19579
|
section("beads (issue tracker)");
|
|
19078
|
-
if (!
|
|
19079
|
-
fail(`bd not installed —
|
|
19580
|
+
if (!bdInstalled) {
|
|
19581
|
+
fail(`bd not installed — install ${yellow5("bd")} first`);
|
|
19080
19582
|
} else {
|
|
19081
|
-
|
|
19082
|
-
|
|
19083
|
-
if (existsSync5(join8(process.cwd(), ".beads"))) {
|
|
19583
|
+
ok2(`bd installed${bdVersion?.ok ? ` ${dim6(bdVersion.stdout)}` : ""}`);
|
|
19584
|
+
if (beadsPresent) {
|
|
19084
19585
|
ok2(".beads/ present in project");
|
|
19085
19586
|
} else {
|
|
19086
19587
|
warn(`.beads/ not found — run ${yellow5("bd init")} to enable issue tracking`);
|
|
19087
19588
|
}
|
|
19088
19589
|
}
|
|
19089
19590
|
section("MCP");
|
|
19090
|
-
const specialistsBin = cmd("which", ["specialists"]);
|
|
19091
19591
|
if (!specialistsBin.ok) {
|
|
19092
19592
|
fail(`specialists not installed globally — run ${yellow5("npm install -g @jaggerxtrm/specialists")}`);
|
|
19093
19593
|
} else {
|
|
@@ -19095,29 +19595,21 @@ ${bold6("specialists status")}
|
|
|
19095
19595
|
info(`verify registration: claude mcp get specialists`);
|
|
19096
19596
|
info(`re-register: specialists install`);
|
|
19097
19597
|
}
|
|
19098
|
-
|
|
19099
|
-
|
|
19100
|
-
const
|
|
19101
|
-
|
|
19102
|
-
|
|
19103
|
-
|
|
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
|
-
}
|
|
19598
|
+
if (jobs.length > 0) {
|
|
19599
|
+
section("Active Jobs");
|
|
19600
|
+
for (const job of jobs) {
|
|
19601
|
+
const elapsed = formatElapsed2(job);
|
|
19602
|
+
const detail = job.status === "error" ? red2(job.error?.slice(0, 40) ?? "error") : job.current_tool ? dim6(`tool: ${job.current_tool}`) : dim6(job.current_event ?? "");
|
|
19603
|
+
console.log(` ${dim6(job.id)} ${job.specialist.padEnd(20)} ${statusColor(job.status).padEnd(7)} ${elapsed.padStart(6)} ${detail}`);
|
|
19113
19604
|
}
|
|
19114
19605
|
}
|
|
19115
19606
|
console.log();
|
|
19116
19607
|
}
|
|
19117
|
-
var
|
|
19608
|
+
var red2 = (s) => `\x1B[31m${s}\x1B[0m`, cyan5 = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
19118
19609
|
var init_status = __esm(() => {
|
|
19119
19610
|
init_loader();
|
|
19120
19611
|
init_supervisor();
|
|
19612
|
+
init_format_helpers();
|
|
19121
19613
|
});
|
|
19122
19614
|
|
|
19123
19615
|
// src/cli/result.ts
|
|
@@ -19125,15 +19617,15 @@ var exports_result = {};
|
|
|
19125
19617
|
__export(exports_result, {
|
|
19126
19618
|
run: () => run9
|
|
19127
19619
|
});
|
|
19128
|
-
import { existsSync as
|
|
19129
|
-
import { join as
|
|
19620
|
+
import { existsSync as existsSync7, readFileSync as readFileSync4 } from "node:fs";
|
|
19621
|
+
import { join as join10 } from "node:path";
|
|
19130
19622
|
async function run9() {
|
|
19131
19623
|
const jobId = process.argv[3];
|
|
19132
19624
|
if (!jobId) {
|
|
19133
19625
|
console.error("Usage: specialists result <job-id>");
|
|
19134
19626
|
process.exit(1);
|
|
19135
19627
|
}
|
|
19136
|
-
const jobsDir =
|
|
19628
|
+
const jobsDir = join10(process.cwd(), ".specialists", "jobs");
|
|
19137
19629
|
const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
|
|
19138
19630
|
const status = supervisor.readStatus(jobId);
|
|
19139
19631
|
if (!status) {
|
|
@@ -19146,107 +19638,340 @@ async function run9() {
|
|
|
19146
19638
|
process.exit(1);
|
|
19147
19639
|
}
|
|
19148
19640
|
if (status.status === "error") {
|
|
19149
|
-
process.stderr.write(`${
|
|
19641
|
+
process.stderr.write(`${red3(`Job ${jobId} failed:`)} ${status.error ?? "unknown error"}
|
|
19150
19642
|
`);
|
|
19151
19643
|
process.exit(1);
|
|
19152
19644
|
}
|
|
19153
|
-
const resultPath =
|
|
19154
|
-
if (!
|
|
19645
|
+
const resultPath = join10(jobsDir, jobId, "result.txt");
|
|
19646
|
+
if (!existsSync7(resultPath)) {
|
|
19155
19647
|
console.error(`Result file not found for job ${jobId}`);
|
|
19156
19648
|
process.exit(1);
|
|
19157
19649
|
}
|
|
19158
19650
|
process.stdout.write(readFileSync4(resultPath, "utf-8"));
|
|
19159
19651
|
}
|
|
19160
|
-
var dim7 = (s) => `\x1B[2m${s}\x1B[0m`,
|
|
19652
|
+
var dim7 = (s) => `\x1B[2m${s}\x1B[0m`, red3 = (s) => `\x1B[31m${s}\x1B[0m`;
|
|
19161
19653
|
var init_result = __esm(() => {
|
|
19162
19654
|
init_supervisor();
|
|
19163
19655
|
});
|
|
19164
19656
|
|
|
19657
|
+
// src/specialist/timeline-query.ts
|
|
19658
|
+
import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
|
|
19659
|
+
import { join as join11 } from "node:path";
|
|
19660
|
+
function readJobEvents(jobDir) {
|
|
19661
|
+
const eventsPath = join11(jobDir, "events.jsonl");
|
|
19662
|
+
if (!existsSync8(eventsPath))
|
|
19663
|
+
return [];
|
|
19664
|
+
const content = readFileSync5(eventsPath, "utf-8");
|
|
19665
|
+
const lines = content.split(`
|
|
19666
|
+
`).filter(Boolean);
|
|
19667
|
+
const events = [];
|
|
19668
|
+
for (const line of lines) {
|
|
19669
|
+
const event = parseTimelineEvent(line);
|
|
19670
|
+
if (event)
|
|
19671
|
+
events.push(event);
|
|
19672
|
+
}
|
|
19673
|
+
events.sort(compareTimelineEvents);
|
|
19674
|
+
return events;
|
|
19675
|
+
}
|
|
19676
|
+
function readAllJobEvents(jobsDir) {
|
|
19677
|
+
if (!existsSync8(jobsDir))
|
|
19678
|
+
return [];
|
|
19679
|
+
const batches = [];
|
|
19680
|
+
const entries = readdirSync2(jobsDir);
|
|
19681
|
+
for (const entry of entries) {
|
|
19682
|
+
const jobDir = join11(jobsDir, entry);
|
|
19683
|
+
try {
|
|
19684
|
+
const stat2 = __require("node:fs").statSync(jobDir);
|
|
19685
|
+
if (!stat2.isDirectory())
|
|
19686
|
+
continue;
|
|
19687
|
+
} catch {
|
|
19688
|
+
continue;
|
|
19689
|
+
}
|
|
19690
|
+
const jobId = entry;
|
|
19691
|
+
const statusPath = join11(jobDir, "status.json");
|
|
19692
|
+
let specialist = "unknown";
|
|
19693
|
+
let beadId;
|
|
19694
|
+
if (existsSync8(statusPath)) {
|
|
19695
|
+
try {
|
|
19696
|
+
const status = JSON.parse(readFileSync5(statusPath, "utf-8"));
|
|
19697
|
+
specialist = status.specialist ?? "unknown";
|
|
19698
|
+
beadId = status.bead_id;
|
|
19699
|
+
} catch {}
|
|
19700
|
+
}
|
|
19701
|
+
const events = readJobEvents(jobDir);
|
|
19702
|
+
if (events.length > 0) {
|
|
19703
|
+
batches.push({ jobId, specialist, beadId, events });
|
|
19704
|
+
}
|
|
19705
|
+
}
|
|
19706
|
+
return batches;
|
|
19707
|
+
}
|
|
19708
|
+
function mergeTimelineEvents(batches) {
|
|
19709
|
+
const merged = [];
|
|
19710
|
+
for (const batch of batches) {
|
|
19711
|
+
for (const event of batch.events) {
|
|
19712
|
+
merged.push({
|
|
19713
|
+
jobId: batch.jobId,
|
|
19714
|
+
specialist: batch.specialist,
|
|
19715
|
+
beadId: batch.beadId,
|
|
19716
|
+
event
|
|
19717
|
+
});
|
|
19718
|
+
}
|
|
19719
|
+
}
|
|
19720
|
+
merged.sort((a, b) => compareTimelineEvents(a.event, b.event));
|
|
19721
|
+
return merged;
|
|
19722
|
+
}
|
|
19723
|
+
function filterTimelineEvents(merged, filter) {
|
|
19724
|
+
let result = merged;
|
|
19725
|
+
if (filter.since !== undefined) {
|
|
19726
|
+
result = result.filter(({ event }) => event.t >= filter.since);
|
|
19727
|
+
}
|
|
19728
|
+
if (filter.jobId !== undefined) {
|
|
19729
|
+
result = result.filter(({ jobId }) => jobId === filter.jobId);
|
|
19730
|
+
}
|
|
19731
|
+
if (filter.specialist !== undefined) {
|
|
19732
|
+
result = result.filter(({ specialist }) => specialist === filter.specialist);
|
|
19733
|
+
}
|
|
19734
|
+
if (filter.limit !== undefined && filter.limit > 0) {
|
|
19735
|
+
result = result.slice(0, filter.limit);
|
|
19736
|
+
}
|
|
19737
|
+
return result;
|
|
19738
|
+
}
|
|
19739
|
+
function queryTimeline(jobsDir, filter = {}) {
|
|
19740
|
+
let batches = readAllJobEvents(jobsDir);
|
|
19741
|
+
if (filter.jobId !== undefined) {
|
|
19742
|
+
batches = batches.filter((b) => b.jobId === filter.jobId);
|
|
19743
|
+
}
|
|
19744
|
+
if (filter.specialist !== undefined) {
|
|
19745
|
+
batches = batches.filter((b) => b.specialist === filter.specialist);
|
|
19746
|
+
}
|
|
19747
|
+
const merged = mergeTimelineEvents(batches);
|
|
19748
|
+
return filterTimelineEvents(merged, filter);
|
|
19749
|
+
}
|
|
19750
|
+
var init_timeline_query = __esm(() => {
|
|
19751
|
+
init_timeline_events();
|
|
19752
|
+
});
|
|
19753
|
+
|
|
19165
19754
|
// src/cli/feed.ts
|
|
19166
19755
|
var exports_feed = {};
|
|
19167
19756
|
__export(exports_feed, {
|
|
19168
19757
|
run: () => run10
|
|
19169
19758
|
});
|
|
19170
|
-
import { existsSync as
|
|
19171
|
-
import { join as
|
|
19172
|
-
function
|
|
19173
|
-
|
|
19174
|
-
|
|
19175
|
-
|
|
19176
|
-
|
|
19177
|
-
|
|
19178
|
-
|
|
19179
|
-
|
|
19180
|
-
|
|
19759
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
19760
|
+
import { join as join12 } from "node:path";
|
|
19761
|
+
function getHumanEventKey(event) {
|
|
19762
|
+
switch (event.type) {
|
|
19763
|
+
case "meta":
|
|
19764
|
+
return `meta:${event.backend}:${event.model}`;
|
|
19765
|
+
case "tool":
|
|
19766
|
+
return `tool:${event.tool}:${event.phase}:${event.is_error ? "error" : "ok"}`;
|
|
19767
|
+
case "text":
|
|
19768
|
+
return "text";
|
|
19769
|
+
case "thinking":
|
|
19770
|
+
return "thinking";
|
|
19771
|
+
case "run_start":
|
|
19772
|
+
return `run_start:${event.specialist}:${event.bead_id ?? ""}`;
|
|
19773
|
+
case "run_complete":
|
|
19774
|
+
return `run_complete:${event.status}:${event.error ?? ""}`;
|
|
19775
|
+
case "done":
|
|
19776
|
+
case "agent_end":
|
|
19777
|
+
return `complete:${event.type}`;
|
|
19778
|
+
default:
|
|
19779
|
+
return event.type;
|
|
19181
19780
|
}
|
|
19182
19781
|
}
|
|
19183
|
-
function
|
|
19184
|
-
|
|
19185
|
-
|
|
19186
|
-
|
|
19187
|
-
|
|
19782
|
+
function shouldSkipHumanEvent(event, jobId, lastPrintedEventKey, seenMetaKey) {
|
|
19783
|
+
if (event.type === "meta") {
|
|
19784
|
+
const metaKey = `${event.backend}:${event.model}`;
|
|
19785
|
+
if (seenMetaKey.get(jobId) === metaKey)
|
|
19786
|
+
return true;
|
|
19787
|
+
seenMetaKey.set(jobId, metaKey);
|
|
19188
19788
|
}
|
|
19189
|
-
|
|
19789
|
+
const key = getHumanEventKey(event);
|
|
19790
|
+
if (lastPrintedEventKey.get(jobId) === key)
|
|
19791
|
+
return true;
|
|
19792
|
+
lastPrintedEventKey.set(jobId, key);
|
|
19793
|
+
return false;
|
|
19190
19794
|
}
|
|
19191
|
-
|
|
19192
|
-
|
|
19795
|
+
function parseSince(value) {
|
|
19796
|
+
if (value.includes("T") || value.includes("-")) {
|
|
19797
|
+
return new Date(value).getTime();
|
|
19798
|
+
}
|
|
19799
|
+
const match = value.match(/^(\d+)([smhd])$/);
|
|
19800
|
+
if (match) {
|
|
19801
|
+
const num = parseInt(match[1], 10);
|
|
19802
|
+
const unit = match[2];
|
|
19803
|
+
const multipliers = { s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
19804
|
+
return Date.now() - num * multipliers[unit];
|
|
19805
|
+
}
|
|
19806
|
+
return;
|
|
19807
|
+
}
|
|
19808
|
+
function parseArgs5(argv) {
|
|
19193
19809
|
let jobId;
|
|
19810
|
+
let specialist;
|
|
19811
|
+
let since;
|
|
19812
|
+
let limit = 100;
|
|
19194
19813
|
let follow = false;
|
|
19814
|
+
let forever = false;
|
|
19815
|
+
let json = false;
|
|
19195
19816
|
for (let i = 0;i < argv.length; i++) {
|
|
19196
19817
|
if (argv[i] === "--job" && argv[i + 1]) {
|
|
19197
19818
|
jobId = argv[++i];
|
|
19198
19819
|
continue;
|
|
19199
19820
|
}
|
|
19821
|
+
if (argv[i] === "--specialist" && argv[i + 1]) {
|
|
19822
|
+
specialist = argv[++i];
|
|
19823
|
+
continue;
|
|
19824
|
+
}
|
|
19825
|
+
if (argv[i] === "--since" && argv[i + 1]) {
|
|
19826
|
+
since = parseSince(argv[++i]);
|
|
19827
|
+
continue;
|
|
19828
|
+
}
|
|
19829
|
+
if (argv[i] === "--limit" && argv[i + 1]) {
|
|
19830
|
+
limit = parseInt(argv[++i], 10);
|
|
19831
|
+
continue;
|
|
19832
|
+
}
|
|
19200
19833
|
if (argv[i] === "--follow" || argv[i] === "-f") {
|
|
19201
19834
|
follow = true;
|
|
19202
19835
|
continue;
|
|
19203
19836
|
}
|
|
19837
|
+
if (argv[i] === "--forever") {
|
|
19838
|
+
forever = true;
|
|
19839
|
+
continue;
|
|
19840
|
+
}
|
|
19841
|
+
if (argv[i] === "--json") {
|
|
19842
|
+
json = true;
|
|
19843
|
+
continue;
|
|
19844
|
+
}
|
|
19204
19845
|
if (!jobId && !argv[i].startsWith("--"))
|
|
19205
19846
|
jobId = argv[i];
|
|
19206
19847
|
}
|
|
19207
|
-
|
|
19208
|
-
|
|
19209
|
-
|
|
19848
|
+
return { jobId, specialist, since, limit, follow, forever, json };
|
|
19849
|
+
}
|
|
19850
|
+
function printSnapshot(merged, options) {
|
|
19851
|
+
if (merged.length === 0) {
|
|
19852
|
+
if (!options.json)
|
|
19853
|
+
console.log(dim6("No events found."));
|
|
19854
|
+
return;
|
|
19210
19855
|
}
|
|
19211
|
-
const
|
|
19212
|
-
|
|
19213
|
-
|
|
19214
|
-
|
|
19215
|
-
if (!supervisor.readStatus(jobId)) {
|
|
19216
|
-
console.error(`No job found: ${jobId}`);
|
|
19217
|
-
process.exit(1);
|
|
19856
|
+
const colorMap = new JobColorMap;
|
|
19857
|
+
if (options.json) {
|
|
19858
|
+
for (const { jobId, specialist, beadId, event } of merged) {
|
|
19859
|
+
console.log(JSON.stringify({ jobId, specialist, beadId, ...event }));
|
|
19218
19860
|
}
|
|
19219
|
-
console.log(dim8("No events yet."));
|
|
19220
19861
|
return;
|
|
19221
19862
|
}
|
|
19222
|
-
const
|
|
19223
|
-
|
|
19224
|
-
|
|
19225
|
-
|
|
19226
|
-
|
|
19227
|
-
|
|
19863
|
+
const lastPrintedEventKey = new Map;
|
|
19864
|
+
const seenMetaKey = new Map;
|
|
19865
|
+
for (const { jobId, specialist, beadId, event } of merged) {
|
|
19866
|
+
if (shouldSkipHumanEvent(event, jobId, lastPrintedEventKey, seenMetaKey))
|
|
19867
|
+
continue;
|
|
19868
|
+
const colorize = colorMap.get(jobId);
|
|
19869
|
+
console.log(formatEventLine(event, { jobId, specialist, beadId, colorize }));
|
|
19870
|
+
}
|
|
19871
|
+
}
|
|
19872
|
+
function isCompletionEvent(event) {
|
|
19873
|
+
return isRunCompleteEvent(event) || event.type === "done" || event.type === "agent_end";
|
|
19874
|
+
}
|
|
19875
|
+
async function followMerged(jobsDir, options) {
|
|
19876
|
+
const colorMap = new JobColorMap;
|
|
19877
|
+
const lastSeenT = new Map;
|
|
19878
|
+
const completedJobs = new Set;
|
|
19879
|
+
const filteredBatches = () => readAllJobEvents(jobsDir).filter((batch) => !options.jobId || batch.jobId === options.jobId).filter((batch) => !options.specialist || batch.specialist === options.specialist);
|
|
19880
|
+
const initial = queryTimeline(jobsDir, {
|
|
19881
|
+
jobId: options.jobId,
|
|
19882
|
+
specialist: options.specialist,
|
|
19883
|
+
since: options.since,
|
|
19884
|
+
limit: options.limit
|
|
19885
|
+
});
|
|
19886
|
+
printSnapshot(initial, { ...options, json: options.json });
|
|
19887
|
+
for (const batch of filteredBatches()) {
|
|
19888
|
+
if (batch.events.length > 0) {
|
|
19889
|
+
const maxT = Math.max(...batch.events.map((event) => event.t));
|
|
19890
|
+
lastSeenT.set(batch.jobId, maxT);
|
|
19891
|
+
}
|
|
19892
|
+
if (batch.events.some(isCompletionEvent)) {
|
|
19893
|
+
completedJobs.add(batch.jobId);
|
|
19894
|
+
}
|
|
19895
|
+
}
|
|
19896
|
+
const initialBatchCount = filteredBatches().length;
|
|
19897
|
+
if (!options.forever && initialBatchCount > 0 && completedJobs.size === initialBatchCount) {
|
|
19898
|
+
if (!options.json) {
|
|
19899
|
+
process.stderr.write(dim6(`All jobs complete.
|
|
19900
|
+
`));
|
|
19901
|
+
}
|
|
19902
|
+
return;
|
|
19903
|
+
}
|
|
19904
|
+
if (!options.json) {
|
|
19905
|
+
process.stderr.write(dim6(`Following... (Ctrl+C to stop)
|
|
19906
|
+
`));
|
|
19907
|
+
}
|
|
19908
|
+
const lastPrintedEventKey = new Map;
|
|
19909
|
+
const seenMetaKey = new Map;
|
|
19228
19910
|
await new Promise((resolve) => {
|
|
19229
|
-
|
|
19230
|
-
|
|
19231
|
-
|
|
19232
|
-
|
|
19233
|
-
const
|
|
19234
|
-
const
|
|
19235
|
-
|
|
19236
|
-
|
|
19237
|
-
|
|
19238
|
-
|
|
19239
|
-
|
|
19240
|
-
|
|
19241
|
-
|
|
19911
|
+
const interval = setInterval(() => {
|
|
19912
|
+
const batches = filteredBatches();
|
|
19913
|
+
const newEvents = [];
|
|
19914
|
+
for (const batch of batches) {
|
|
19915
|
+
const lastT = lastSeenT.get(batch.jobId) ?? 0;
|
|
19916
|
+
for (const event of batch.events) {
|
|
19917
|
+
if (event.t > lastT) {
|
|
19918
|
+
newEvents.push({
|
|
19919
|
+
jobId: batch.jobId,
|
|
19920
|
+
specialist: batch.specialist,
|
|
19921
|
+
beadId: batch.beadId,
|
|
19922
|
+
event
|
|
19923
|
+
});
|
|
19924
|
+
}
|
|
19242
19925
|
}
|
|
19243
|
-
|
|
19244
|
-
|
|
19926
|
+
if (batch.events.length > 0) {
|
|
19927
|
+
const maxT = Math.max(...batch.events.map((e) => e.t));
|
|
19928
|
+
lastSeenT.set(batch.jobId, maxT);
|
|
19929
|
+
}
|
|
19930
|
+
if (batch.events.some(isCompletionEvent)) {
|
|
19931
|
+
completedJobs.add(batch.jobId);
|
|
19932
|
+
}
|
|
19933
|
+
}
|
|
19934
|
+
newEvents.sort((a, b) => a.event.t - b.event.t);
|
|
19935
|
+
for (const { jobId, specialist, beadId, event } of newEvents) {
|
|
19936
|
+
if (options.json) {
|
|
19937
|
+
console.log(JSON.stringify({ jobId, specialist, beadId, ...event }));
|
|
19938
|
+
} else {
|
|
19939
|
+
if (shouldSkipHumanEvent(event, jobId, lastPrintedEventKey, seenMetaKey))
|
|
19940
|
+
continue;
|
|
19941
|
+
const colorize = colorMap.get(jobId);
|
|
19942
|
+
console.log(formatEventLine(event, { jobId, specialist, beadId, colorize }));
|
|
19943
|
+
}
|
|
19944
|
+
}
|
|
19945
|
+
if (!options.forever && batches.length > 0 && completedJobs.size === batches.length) {
|
|
19946
|
+
clearInterval(interval);
|
|
19947
|
+
resolve();
|
|
19948
|
+
}
|
|
19949
|
+
}, 500);
|
|
19245
19950
|
});
|
|
19246
19951
|
}
|
|
19247
|
-
|
|
19952
|
+
async function run10() {
|
|
19953
|
+
const options = parseArgs5(process.argv.slice(3));
|
|
19954
|
+
const jobsDir = join12(process.cwd(), ".specialists", "jobs");
|
|
19955
|
+
if (!existsSync9(jobsDir)) {
|
|
19956
|
+
console.log(dim6("No jobs directory found."));
|
|
19957
|
+
return;
|
|
19958
|
+
}
|
|
19959
|
+
if (options.follow) {
|
|
19960
|
+
await followMerged(jobsDir, options);
|
|
19961
|
+
return;
|
|
19962
|
+
}
|
|
19963
|
+
const merged = queryTimeline(jobsDir, {
|
|
19964
|
+
jobId: options.jobId,
|
|
19965
|
+
specialist: options.specialist,
|
|
19966
|
+
since: options.since,
|
|
19967
|
+
limit: options.limit
|
|
19968
|
+
});
|
|
19969
|
+
printSnapshot(merged, options);
|
|
19970
|
+
}
|
|
19248
19971
|
var init_feed = __esm(() => {
|
|
19249
|
-
|
|
19972
|
+
init_timeline_events();
|
|
19973
|
+
init_timeline_query();
|
|
19974
|
+
init_format_helpers();
|
|
19250
19975
|
});
|
|
19251
19976
|
|
|
19252
19977
|
// src/cli/stop.ts
|
|
@@ -19254,14 +19979,14 @@ var exports_stop = {};
|
|
|
19254
19979
|
__export(exports_stop, {
|
|
19255
19980
|
run: () => run11
|
|
19256
19981
|
});
|
|
19257
|
-
import { join as
|
|
19982
|
+
import { join as join13 } from "node:path";
|
|
19258
19983
|
async function run11() {
|
|
19259
19984
|
const jobId = process.argv[3];
|
|
19260
19985
|
if (!jobId) {
|
|
19261
19986
|
console.error("Usage: specialists stop <job-id>");
|
|
19262
19987
|
process.exit(1);
|
|
19263
19988
|
}
|
|
19264
|
-
const jobsDir =
|
|
19989
|
+
const jobsDir = join13(process.cwd(), ".specialists", "jobs");
|
|
19265
19990
|
const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
|
|
19266
19991
|
const status = supervisor.readStatus(jobId);
|
|
19267
19992
|
if (!status) {
|
|
@@ -19269,71 +19994,742 @@ async function run11() {
|
|
|
19269
19994
|
process.exit(1);
|
|
19270
19995
|
}
|
|
19271
19996
|
if (status.status === "done" || status.status === "error") {
|
|
19272
|
-
process.stderr.write(`${
|
|
19997
|
+
process.stderr.write(`${dim8(`Job ${jobId} is already ${status.status}.`)}
|
|
19273
19998
|
`);
|
|
19274
19999
|
return;
|
|
19275
20000
|
}
|
|
19276
20001
|
if (!status.pid) {
|
|
19277
|
-
process.stderr.write(`${
|
|
20002
|
+
process.stderr.write(`${red5(`No PID recorded for job ${jobId}.`)}
|
|
19278
20003
|
`);
|
|
19279
20004
|
process.exit(1);
|
|
19280
20005
|
}
|
|
19281
20006
|
try {
|
|
19282
20007
|
process.kill(status.pid, "SIGTERM");
|
|
19283
|
-
process.stdout.write(`${
|
|
20008
|
+
process.stdout.write(`${green7("✓")} Sent SIGTERM to PID ${status.pid} (job ${jobId})
|
|
19284
20009
|
`);
|
|
19285
20010
|
} catch (err) {
|
|
19286
20011
|
if (err.code === "ESRCH") {
|
|
19287
|
-
process.stderr.write(`${
|
|
20012
|
+
process.stderr.write(`${red5(`Process ${status.pid} not found.`)} Job may have already completed.
|
|
19288
20013
|
`);
|
|
19289
20014
|
} else {
|
|
19290
|
-
process.stderr.write(`${
|
|
20015
|
+
process.stderr.write(`${red5("Error:")} ${err.message}
|
|
19291
20016
|
`);
|
|
19292
20017
|
process.exit(1);
|
|
19293
20018
|
}
|
|
19294
20019
|
}
|
|
19295
20020
|
}
|
|
19296
|
-
var
|
|
20021
|
+
var green7 = (s) => `\x1B[32m${s}\x1B[0m`, red5 = (s) => `\x1B[31m${s}\x1B[0m`, dim8 = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
19297
20022
|
var init_stop = __esm(() => {
|
|
19298
20023
|
init_supervisor();
|
|
19299
20024
|
});
|
|
19300
20025
|
|
|
20026
|
+
// src/cli/quickstart.ts
|
|
20027
|
+
var exports_quickstart = {};
|
|
20028
|
+
__export(exports_quickstart, {
|
|
20029
|
+
run: () => run12
|
|
20030
|
+
});
|
|
20031
|
+
function section2(title) {
|
|
20032
|
+
const bar = "─".repeat(60);
|
|
20033
|
+
return `
|
|
20034
|
+
${bold7(cyan7(title))}
|
|
20035
|
+
${dim9(bar)}`;
|
|
20036
|
+
}
|
|
20037
|
+
function cmd2(s) {
|
|
20038
|
+
return yellow6(s);
|
|
20039
|
+
}
|
|
20040
|
+
function flag(s) {
|
|
20041
|
+
return green8(s);
|
|
20042
|
+
}
|
|
20043
|
+
async function run12() {
|
|
20044
|
+
const lines = [
|
|
20045
|
+
"",
|
|
20046
|
+
bold7("specialists · Quick Start Guide"),
|
|
20047
|
+
dim9("One MCP server. Multiple AI backends. Intelligent orchestration."),
|
|
20048
|
+
""
|
|
20049
|
+
];
|
|
20050
|
+
lines.push(section2("1. Installation"));
|
|
20051
|
+
lines.push("");
|
|
20052
|
+
lines.push(` ${cmd2("npm install -g @jaggerxtrm/specialists")} # install globally`);
|
|
20053
|
+
lines.push(` ${cmd2("specialists install")} # project setup:`);
|
|
20054
|
+
lines.push(` ${dim9(" # checks pi · bd · xt, then wires MCP + hooks")}`);
|
|
20055
|
+
lines.push("");
|
|
20056
|
+
lines.push(` Verify everything is healthy:`);
|
|
20057
|
+
lines.push(` ${cmd2("specialists status")} # shows pi, beads, MCP, active jobs`);
|
|
20058
|
+
lines.push("");
|
|
20059
|
+
lines.push(section2("2. Initialize a Project"));
|
|
20060
|
+
lines.push("");
|
|
20061
|
+
lines.push(` Run once per project root:`);
|
|
20062
|
+
lines.push(` ${cmd2("specialists init")} # creates specialists/, .specialists/, AGENTS.md`);
|
|
20063
|
+
lines.push("");
|
|
20064
|
+
lines.push(` What this creates:`);
|
|
20065
|
+
lines.push(` ${dim9("specialists/")} — put your .specialist.yaml files here`);
|
|
20066
|
+
lines.push(` ${dim9(".specialists/")} — runtime data (jobs/, ready/) — gitignored`);
|
|
20067
|
+
lines.push(` ${dim9("AGENTS.md")} — context block injected into Claude sessions`);
|
|
20068
|
+
lines.push("");
|
|
20069
|
+
lines.push(section2("3. Discover Specialists"));
|
|
20070
|
+
lines.push("");
|
|
20071
|
+
lines.push(` ${cmd2("specialists list")} # all specialists (project + user)`);
|
|
20072
|
+
lines.push(` ${cmd2("specialists list")} ${flag("--scope project")} # project-scoped only`);
|
|
20073
|
+
lines.push(` ${cmd2("specialists list")} ${flag("--scope user")} # user-scoped (~/.specialists/)`);
|
|
20074
|
+
lines.push(` ${cmd2("specialists list")} ${flag("--category analysis")} # filter by category`);
|
|
20075
|
+
lines.push(` ${cmd2("specialists list")} ${flag("--json")} # machine-readable JSON`);
|
|
20076
|
+
lines.push("");
|
|
20077
|
+
lines.push(` Scopes (searched in order):`);
|
|
20078
|
+
lines.push(` ${blue2("project")} ./specialists/*.specialist.yaml`);
|
|
20079
|
+
lines.push(` ${blue2("user")} ~/.specialists/*.specialist.yaml`);
|
|
20080
|
+
lines.push(` ${blue2("system")} bundled specialists (shipped with the package)`);
|
|
20081
|
+
lines.push("");
|
|
20082
|
+
lines.push(section2("4. Running a Specialist"));
|
|
20083
|
+
lines.push("");
|
|
20084
|
+
lines.push(` ${bold7("Foreground")} (streams output to stdout):`);
|
|
20085
|
+
lines.push(` ${cmd2("specialists run code-review")} ${flag("--prompt")} ${dim9('"Review src/api.ts for security issues"')}`);
|
|
20086
|
+
lines.push("");
|
|
20087
|
+
lines.push(` ${bold7("Background")} (returns a job ID immediately):`);
|
|
20088
|
+
lines.push(` ${cmd2("specialists run code-review")} ${flag("--prompt")} ${dim9('"..."')} ${flag("--background")}`);
|
|
20089
|
+
lines.push(` ${dim9(" # → Job started: job_a1b2c3d4")}`);
|
|
20090
|
+
lines.push("");
|
|
20091
|
+
lines.push(` Override model for one run:`);
|
|
20092
|
+
lines.push(` ${cmd2("specialists run code-review")} ${flag("--model")} ${dim9("anthropic/claude-opus-4-6")} ${flag("--prompt")} ${dim9('"..."')}`);
|
|
20093
|
+
lines.push("");
|
|
20094
|
+
lines.push(` Run without beads issue tracking:`);
|
|
20095
|
+
lines.push(` ${cmd2("specialists run code-review")} ${flag("--no-beads")} ${flag("--prompt")} ${dim9('"..."')}`);
|
|
20096
|
+
lines.push("");
|
|
20097
|
+
lines.push(` Pipe a prompt from stdin:`);
|
|
20098
|
+
lines.push(` ${cmd2("cat my-brief.md | specialists run code-review")}`);
|
|
20099
|
+
lines.push("");
|
|
20100
|
+
lines.push(section2("5. Background Job Lifecycle"));
|
|
20101
|
+
lines.push("");
|
|
20102
|
+
lines.push(` ${bold7("Watch progress")} — stream events as they arrive:`);
|
|
20103
|
+
lines.push(` ${cmd2("specialists feed job_a1b2c3d4")} # print events so far`);
|
|
20104
|
+
lines.push(` ${cmd2("specialists feed job_a1b2c3d4")} ${flag("--follow")} # tail and stream live updates`);
|
|
20105
|
+
lines.push("");
|
|
20106
|
+
lines.push(` ${bold7("Read results")} — print the final output:`);
|
|
20107
|
+
lines.push(` ${cmd2("specialists result job_a1b2c3d4")} # exits 1 if still running`);
|
|
20108
|
+
lines.push("");
|
|
20109
|
+
lines.push(` ${bold7("Cancel a job")}:`);
|
|
20110
|
+
lines.push(` ${cmd2("specialists stop job_a1b2c3d4")} # sends SIGTERM to the agent process`);
|
|
20111
|
+
lines.push("");
|
|
20112
|
+
lines.push(` ${bold7("Job files")} in ${dim9(".specialists/jobs/<job-id>/")}:`);
|
|
20113
|
+
lines.push(` ${dim9("status.json")} — id, specialist, status, pid, started_at, elapsed_s, current_tool`);
|
|
20114
|
+
lines.push(` ${dim9("events.jsonl")} — one JSON event per line (tool_use, text, agent_end, error …)`);
|
|
20115
|
+
lines.push(` ${dim9("result.txt")} — final output (written when status=done)`);
|
|
20116
|
+
lines.push("");
|
|
20117
|
+
lines.push(section2("6. Editing Specialists"));
|
|
20118
|
+
lines.push("");
|
|
20119
|
+
lines.push(` Change a field without opening the YAML manually:`);
|
|
20120
|
+
lines.push(` ${cmd2("specialists edit code-review")} ${flag("--model")} ${dim9("anthropic/claude-sonnet-4-6")}`);
|
|
20121
|
+
lines.push(` ${cmd2("specialists edit code-review")} ${flag("--description")} ${dim9('"Updated description"')}`);
|
|
20122
|
+
lines.push(` ${cmd2("specialists edit code-review")} ${flag("--timeout")} ${dim9("120000")}`);
|
|
20123
|
+
lines.push(` ${cmd2("specialists edit code-review")} ${flag("--permission")} ${dim9("HIGH")}`);
|
|
20124
|
+
lines.push(` ${cmd2("specialists edit code-review")} ${flag("--tags")} ${dim9("analysis,security,review")}`);
|
|
20125
|
+
lines.push("");
|
|
20126
|
+
lines.push(` Preview without writing:`);
|
|
20127
|
+
lines.push(` ${cmd2("specialists edit code-review")} ${flag("--model")} ${dim9("...")} ${flag("--dry-run")}`);
|
|
20128
|
+
lines.push("");
|
|
20129
|
+
lines.push(section2("7. .specialist.yaml Schema"));
|
|
20130
|
+
lines.push("");
|
|
20131
|
+
lines.push(` Full annotated example:`);
|
|
20132
|
+
lines.push("");
|
|
20133
|
+
const schemaLines = [
|
|
20134
|
+
"specialist:",
|
|
20135
|
+
" metadata:",
|
|
20136
|
+
' name: my-specialist # required · used in "specialists run <name>"',
|
|
20137
|
+
" version: 1.0.0 # semver, for staleness detection",
|
|
20138
|
+
' description: "What it does" # shown in specialists list',
|
|
20139
|
+
" category: analysis # free-form tag for --category filter",
|
|
20140
|
+
" tags: [review, security] # array of labels",
|
|
20141
|
+
' updated: "2026-03-11" # ISO date — used for staleness check',
|
|
20142
|
+
"",
|
|
20143
|
+
" execution:",
|
|
20144
|
+
" mode: tool # tool (default) | chat",
|
|
20145
|
+
" model: anthropic/claude-sonnet-4-6 # primary model",
|
|
20146
|
+
" fallback_model: qwen-cli/qwen3-coder # if primary circuit-breaks",
|
|
20147
|
+
" timeout_ms: 120000 # ms before job is killed (default: 120000)",
|
|
20148
|
+
" stall_timeout_ms: 30000 # ms of silence before stall-detection fires",
|
|
20149
|
+
" response_format: markdown # markdown | json | text",
|
|
20150
|
+
" permission_required: MEDIUM # READ_ONLY | LOW | MEDIUM | HIGH",
|
|
20151
|
+
"",
|
|
20152
|
+
" prompt:",
|
|
20153
|
+
" system: | # system prompt (multiline YAML literal block)",
|
|
20154
|
+
" You are …",
|
|
20155
|
+
" user_template: | # optional; $prompt and $context are substituted",
|
|
20156
|
+
" Task: $prompt",
|
|
20157
|
+
" Context: $context",
|
|
20158
|
+
"",
|
|
20159
|
+
" skills:",
|
|
20160
|
+
" paths: # extra skill dirs searched at runtime",
|
|
20161
|
+
" - ./specialists/skills",
|
|
20162
|
+
" - ~/.specialists/skills",
|
|
20163
|
+
"",
|
|
20164
|
+
" capabilities:",
|
|
20165
|
+
" web_search: false # allow web search tool",
|
|
20166
|
+
" file_write: true # allow file writes",
|
|
20167
|
+
"",
|
|
20168
|
+
" beads_integration:",
|
|
20169
|
+
" auto_create: true # create a beads issue per run",
|
|
20170
|
+
" issue_type: task # task | bug | feature",
|
|
20171
|
+
" priority: 2 # 0=critical … 4=backlog"
|
|
20172
|
+
];
|
|
20173
|
+
for (const l of schemaLines) {
|
|
20174
|
+
lines.push(` ${dim9(l)}`);
|
|
20175
|
+
}
|
|
20176
|
+
lines.push("");
|
|
20177
|
+
lines.push(section2("8. Hook System"));
|
|
20178
|
+
lines.push("");
|
|
20179
|
+
lines.push(` Specialists emits lifecycle events to ${dim9(".specialists/trace.jsonl")}:`);
|
|
20180
|
+
lines.push("");
|
|
20181
|
+
lines.push(` ${bold7("Hook point")} ${bold7("When fired")}`);
|
|
20182
|
+
lines.push(` ${yellow6("specialist:start")} before the agent session begins`);
|
|
20183
|
+
lines.push(` ${yellow6("specialist:token")} on each streamed token (delta)`);
|
|
20184
|
+
lines.push(` ${yellow6("specialist:done")} after successful completion`);
|
|
20185
|
+
lines.push(` ${yellow6("specialist:error")} on failure or timeout`);
|
|
20186
|
+
lines.push("");
|
|
20187
|
+
lines.push(` Each event line in trace.jsonl:`);
|
|
20188
|
+
lines.push(` ${dim9('{"t":"<ISO>","hook":"specialist:done","specialist":"code-review","durationMs":4120}')}`);
|
|
20189
|
+
lines.push("");
|
|
20190
|
+
lines.push(` Tail the trace file to observe all activity:`);
|
|
20191
|
+
lines.push(` ${cmd2("tail -f .specialists/trace.jsonl | jq .")}`);
|
|
20192
|
+
lines.push("");
|
|
20193
|
+
lines.push(section2("9. MCP Integration (Claude Code)"));
|
|
20194
|
+
lines.push("");
|
|
20195
|
+
lines.push(` After ${cmd2("specialists install")}, these MCP tools are available to Claude:`);
|
|
20196
|
+
lines.push("");
|
|
20197
|
+
lines.push(` ${bold7("specialist_init")} — bootstrap: bd init + list specialists`);
|
|
20198
|
+
lines.push(` ${bold7("list_specialists")} — discover specialists (project/user/system)`);
|
|
20199
|
+
lines.push(` ${bold7("use_specialist")} — full lifecycle: load → agents.md → run → output`);
|
|
20200
|
+
lines.push(` ${bold7("run_parallel")} — concurrent or pipeline execution`);
|
|
20201
|
+
lines.push(` ${bold7("start_specialist")} — async job start, returns job ID`);
|
|
20202
|
+
lines.push(` ${bold7("poll_specialist")} — poll job status/output by ID`);
|
|
20203
|
+
lines.push(` ${bold7("stop_specialist")} — cancel a running job by ID`);
|
|
20204
|
+
lines.push(` ${bold7("specialist_status")} — circuit breaker health + staleness`);
|
|
20205
|
+
lines.push("");
|
|
20206
|
+
lines.push(section2("10. Common Workflows"));
|
|
20207
|
+
lines.push("");
|
|
20208
|
+
lines.push(` ${bold7("Foreground review, save to file:")}`);
|
|
20209
|
+
lines.push(` ${cmd2('specialists run code-review --prompt "Audit src/" > review.md')}`);
|
|
20210
|
+
lines.push("");
|
|
20211
|
+
lines.push(` ${bold7("Fire-and-forget, check later:")}`);
|
|
20212
|
+
lines.push(` ${cmd2('specialists run deep-analysis --prompt "..." --background')}`);
|
|
20213
|
+
lines.push(` ${cmd2("specialists feed <job-id> --follow")}`);
|
|
20214
|
+
lines.push(` ${cmd2("specialists result <job-id> > analysis.md")}`);
|
|
20215
|
+
lines.push("");
|
|
20216
|
+
lines.push(` ${bold7("Override model for a single run:")}`);
|
|
20217
|
+
lines.push(` ${cmd2('specialists run code-review --model anthropic/claude-opus-4-6 --prompt "..."')}`);
|
|
20218
|
+
lines.push("");
|
|
20219
|
+
lines.push(dim9("─".repeat(62)));
|
|
20220
|
+
lines.push(` ${dim9("specialists help")} command list ${dim9("specialists <cmd> --help")} per-command flags`);
|
|
20221
|
+
lines.push(` ${dim9("specialists status")} health check ${dim9("specialists models")} available models`);
|
|
20222
|
+
lines.push("");
|
|
20223
|
+
console.log(lines.join(`
|
|
20224
|
+
`));
|
|
20225
|
+
}
|
|
20226
|
+
var bold7 = (s) => `\x1B[1m${s}\x1B[0m`, dim9 = (s) => `\x1B[2m${s}\x1B[0m`, yellow6 = (s) => `\x1B[33m${s}\x1B[0m`, cyan7 = (s) => `\x1B[36m${s}\x1B[0m`, blue2 = (s) => `\x1B[34m${s}\x1B[0m`, green8 = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
20227
|
+
|
|
20228
|
+
// src/cli/doctor.ts
|
|
20229
|
+
var exports_doctor = {};
|
|
20230
|
+
__export(exports_doctor, {
|
|
20231
|
+
run: () => run13
|
|
20232
|
+
});
|
|
20233
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
20234
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync as readdirSync3 } from "node:fs";
|
|
20235
|
+
import { join as join14 } from "node:path";
|
|
20236
|
+
function ok3(msg) {
|
|
20237
|
+
console.log(` ${green9("✓")} ${msg}`);
|
|
20238
|
+
}
|
|
20239
|
+
function warn2(msg) {
|
|
20240
|
+
console.log(` ${yellow7("○")} ${msg}`);
|
|
20241
|
+
}
|
|
20242
|
+
function fail2(msg) {
|
|
20243
|
+
console.log(` ${red6("✗")} ${msg}`);
|
|
20244
|
+
}
|
|
20245
|
+
function fix(msg) {
|
|
20246
|
+
console.log(` ${dim10("→ fix:")} ${yellow7(msg)}`);
|
|
20247
|
+
}
|
|
20248
|
+
function hint(msg) {
|
|
20249
|
+
console.log(` ${dim10(msg)}`);
|
|
20250
|
+
}
|
|
20251
|
+
function section3(label) {
|
|
20252
|
+
const line = "─".repeat(Math.max(0, 38 - label.length));
|
|
20253
|
+
console.log(`
|
|
20254
|
+
${bold8(`── ${label} ${line}`)}`);
|
|
20255
|
+
}
|
|
20256
|
+
function sp(bin, args) {
|
|
20257
|
+
const r = spawnSync6(bin, args, { encoding: "utf8", stdio: "pipe", timeout: 5000 });
|
|
20258
|
+
return { ok: r.status === 0 && !r.error, stdout: (r.stdout ?? "").trim() };
|
|
20259
|
+
}
|
|
20260
|
+
function isInstalled2(bin) {
|
|
20261
|
+
return spawnSync6("which", [bin], { encoding: "utf8", timeout: 2000 }).status === 0;
|
|
20262
|
+
}
|
|
20263
|
+
function loadJson2(path) {
|
|
20264
|
+
if (!existsSync10(path))
|
|
20265
|
+
return null;
|
|
20266
|
+
try {
|
|
20267
|
+
return JSON.parse(readFileSync6(path, "utf8"));
|
|
20268
|
+
} catch {
|
|
20269
|
+
return null;
|
|
20270
|
+
}
|
|
20271
|
+
}
|
|
20272
|
+
function checkPi() {
|
|
20273
|
+
section3("pi (coding agent runtime)");
|
|
20274
|
+
if (!isInstalled2("pi")) {
|
|
20275
|
+
fail2("pi not installed");
|
|
20276
|
+
fix("install pi first");
|
|
20277
|
+
return false;
|
|
20278
|
+
}
|
|
20279
|
+
const version2 = sp("pi", ["--version"]);
|
|
20280
|
+
const models = sp("pi", ["--list-models"]);
|
|
20281
|
+
const providers = models.ok ? new Set(models.stdout.split(`
|
|
20282
|
+
`).slice(1).map((line) => line.split(/\s+/)[0]).filter(Boolean)) : new Set;
|
|
20283
|
+
const vStr = version2.ok ? `v${version2.stdout}` : "unknown version";
|
|
20284
|
+
if (providers.size === 0) {
|
|
20285
|
+
warn2(`pi ${vStr} installed but no active providers`);
|
|
20286
|
+
fix("pi config (add at least one API key)");
|
|
20287
|
+
return false;
|
|
20288
|
+
}
|
|
20289
|
+
ok3(`pi ${vStr} — ${providers.size} provider${providers.size > 1 ? "s" : ""} active ${dim10(`(${[...providers].join(", ")})`)}`);
|
|
20290
|
+
return true;
|
|
20291
|
+
}
|
|
20292
|
+
function checkBd() {
|
|
20293
|
+
section3("beads (issue tracker)");
|
|
20294
|
+
if (!isInstalled2("bd")) {
|
|
20295
|
+
fail2("bd not installed");
|
|
20296
|
+
fix("install beads (bd) first");
|
|
20297
|
+
return false;
|
|
20298
|
+
}
|
|
20299
|
+
ok3(`bd installed ${dim10(sp("bd", ["--version"]).stdout || "")}`);
|
|
20300
|
+
if (existsSync10(join14(CWD, ".beads")))
|
|
20301
|
+
ok3(".beads/ present in project");
|
|
20302
|
+
else
|
|
20303
|
+
warn2(".beads/ not found in project");
|
|
20304
|
+
return true;
|
|
20305
|
+
}
|
|
20306
|
+
function checkXt() {
|
|
20307
|
+
section3("xtrm-tools");
|
|
20308
|
+
if (!isInstalled2("xt")) {
|
|
20309
|
+
fail2("xt not installed");
|
|
20310
|
+
fix("install xtrm-tools first");
|
|
20311
|
+
return false;
|
|
20312
|
+
}
|
|
20313
|
+
ok3(`xt installed ${dim10(sp("xt", ["--version"]).stdout || "")}`);
|
|
20314
|
+
return true;
|
|
20315
|
+
}
|
|
20316
|
+
function checkHooks() {
|
|
20317
|
+
section3("Claude Code hooks (2 expected)");
|
|
20318
|
+
let allPresent = true;
|
|
20319
|
+
for (const name of HOOK_NAMES) {
|
|
20320
|
+
const dest = join14(HOOKS_DIR, name);
|
|
20321
|
+
if (!existsSync10(dest)) {
|
|
20322
|
+
fail2(`${name} ${red6("missing")}`);
|
|
20323
|
+
fix("specialists install");
|
|
20324
|
+
allPresent = false;
|
|
20325
|
+
} else {
|
|
20326
|
+
ok3(name);
|
|
20327
|
+
}
|
|
20328
|
+
}
|
|
20329
|
+
const settings = loadJson2(SETTINGS_FILE);
|
|
20330
|
+
if (!settings) {
|
|
20331
|
+
warn2(`Could not read ${SETTINGS_FILE}`);
|
|
20332
|
+
fix("specialists install");
|
|
20333
|
+
return false;
|
|
20334
|
+
}
|
|
20335
|
+
const hooks = settings.hooks ?? {};
|
|
20336
|
+
const wiredCommands = new Set([
|
|
20337
|
+
...hooks.UserPromptSubmit ?? [],
|
|
20338
|
+
...hooks.SessionStart ?? []
|
|
20339
|
+
].flatMap((entry) => (entry.hooks ?? []).map((h) => h.command ?? "")));
|
|
20340
|
+
for (const name of HOOK_NAMES) {
|
|
20341
|
+
const expected = join14(HOOKS_DIR, name);
|
|
20342
|
+
if (!wiredCommands.has(expected)) {
|
|
20343
|
+
warn2(`${name} not wired in settings.json`);
|
|
20344
|
+
fix("specialists install");
|
|
20345
|
+
allPresent = false;
|
|
20346
|
+
}
|
|
20347
|
+
}
|
|
20348
|
+
if (allPresent)
|
|
20349
|
+
hint(`Hooks wired in ${SETTINGS_FILE}`);
|
|
20350
|
+
return allPresent;
|
|
20351
|
+
}
|
|
20352
|
+
function checkMCP() {
|
|
20353
|
+
section3("MCP registration");
|
|
20354
|
+
const mcp = loadJson2(MCP_FILE2);
|
|
20355
|
+
const spec = mcp?.mcpServers?.specialists;
|
|
20356
|
+
if (!spec || spec.command !== "specialists") {
|
|
20357
|
+
fail2(`MCP server 'specialists' not registered in .mcp.json`);
|
|
20358
|
+
fix("specialists install");
|
|
20359
|
+
return false;
|
|
20360
|
+
}
|
|
20361
|
+
ok3(`MCP server 'specialists' registered in ${MCP_FILE2}`);
|
|
20362
|
+
return true;
|
|
20363
|
+
}
|
|
20364
|
+
function checkRuntimeDirs() {
|
|
20365
|
+
section3(".specialists/ runtime directories");
|
|
20366
|
+
const rootDir = join14(CWD, ".specialists");
|
|
20367
|
+
const jobsDir = join14(rootDir, "jobs");
|
|
20368
|
+
const readyDir = join14(rootDir, "ready");
|
|
20369
|
+
let allOk = true;
|
|
20370
|
+
if (!existsSync10(rootDir)) {
|
|
20371
|
+
warn2(".specialists/ not found in current project");
|
|
20372
|
+
fix("specialists init");
|
|
20373
|
+
allOk = false;
|
|
20374
|
+
} else {
|
|
20375
|
+
ok3(".specialists/ present");
|
|
20376
|
+
for (const [subDir, label] of [[jobsDir, "jobs"], [readyDir, "ready"]]) {
|
|
20377
|
+
if (!existsSync10(subDir)) {
|
|
20378
|
+
warn2(`.specialists/${label}/ missing — auto-creating`);
|
|
20379
|
+
mkdirSync3(subDir, { recursive: true });
|
|
20380
|
+
ok3(`.specialists/${label}/ created`);
|
|
20381
|
+
} else {
|
|
20382
|
+
ok3(`.specialists/${label}/ present`);
|
|
20383
|
+
}
|
|
20384
|
+
}
|
|
20385
|
+
}
|
|
20386
|
+
return allOk;
|
|
20387
|
+
}
|
|
20388
|
+
function checkZombieJobs() {
|
|
20389
|
+
section3("Background jobs");
|
|
20390
|
+
const jobsDir = join14(CWD, ".specialists", "jobs");
|
|
20391
|
+
if (!existsSync10(jobsDir)) {
|
|
20392
|
+
hint("No .specialists/jobs/ — skipping");
|
|
20393
|
+
return true;
|
|
20394
|
+
}
|
|
20395
|
+
let entries;
|
|
20396
|
+
try {
|
|
20397
|
+
entries = readdirSync3(jobsDir);
|
|
20398
|
+
} catch {
|
|
20399
|
+
entries = [];
|
|
20400
|
+
}
|
|
20401
|
+
if (entries.length === 0) {
|
|
20402
|
+
ok3("No jobs found");
|
|
20403
|
+
return true;
|
|
20404
|
+
}
|
|
20405
|
+
let zombies = 0;
|
|
20406
|
+
let total = 0;
|
|
20407
|
+
let running = 0;
|
|
20408
|
+
for (const jobId of entries) {
|
|
20409
|
+
const statusPath = join14(jobsDir, jobId, "status.json");
|
|
20410
|
+
if (!existsSync10(statusPath))
|
|
20411
|
+
continue;
|
|
20412
|
+
try {
|
|
20413
|
+
const status = JSON.parse(readFileSync6(statusPath, "utf8"));
|
|
20414
|
+
total++;
|
|
20415
|
+
if (status.status === "running" || status.status === "starting") {
|
|
20416
|
+
const pid = status.pid;
|
|
20417
|
+
if (pid) {
|
|
20418
|
+
let alive = false;
|
|
20419
|
+
try {
|
|
20420
|
+
process.kill(pid, 0);
|
|
20421
|
+
alive = true;
|
|
20422
|
+
} catch {}
|
|
20423
|
+
if (alive)
|
|
20424
|
+
running++;
|
|
20425
|
+
else {
|
|
20426
|
+
zombies++;
|
|
20427
|
+
warn2(`${jobId} ${yellow7("ZOMBIE")} ${dim10(`pid ${pid} not found, status=${status.status}`)}`);
|
|
20428
|
+
fix(`Edit .specialists/jobs/${jobId}/status.json → set "status": "error"`);
|
|
20429
|
+
}
|
|
20430
|
+
}
|
|
20431
|
+
}
|
|
20432
|
+
} catch {}
|
|
20433
|
+
}
|
|
20434
|
+
if (zombies === 0) {
|
|
20435
|
+
const detail = running > 0 ? `, ${running} currently running` : ", none currently running";
|
|
20436
|
+
ok3(`${total} job${total !== 1 ? "s" : ""} checked${detail}`);
|
|
20437
|
+
}
|
|
20438
|
+
return zombies === 0;
|
|
20439
|
+
}
|
|
20440
|
+
async function run13() {
|
|
20441
|
+
console.log(`
|
|
20442
|
+
${bold8("specialists doctor")}
|
|
20443
|
+
`);
|
|
20444
|
+
const piOk = checkPi();
|
|
20445
|
+
const bdOk = checkBd();
|
|
20446
|
+
const xtOk = checkXt();
|
|
20447
|
+
const hooksOk = checkHooks();
|
|
20448
|
+
const mcpOk = checkMCP();
|
|
20449
|
+
const dirsOk = checkRuntimeDirs();
|
|
20450
|
+
const jobsOk = checkZombieJobs();
|
|
20451
|
+
const allOk = piOk && bdOk && xtOk && hooksOk && mcpOk && dirsOk && jobsOk;
|
|
20452
|
+
console.log("");
|
|
20453
|
+
if (allOk) {
|
|
20454
|
+
console.log(` ${green9("✓")} ${bold8("All checks passed")} — specialists is healthy`);
|
|
20455
|
+
} else {
|
|
20456
|
+
console.log(` ${yellow7("○")} ${bold8("Some checks failed")} — follow the fix hints above`);
|
|
20457
|
+
console.log(` ${dim10("specialists install fixes hook + MCP registration; pi, bd, and xt must be installed separately.")}`);
|
|
20458
|
+
}
|
|
20459
|
+
console.log("");
|
|
20460
|
+
}
|
|
20461
|
+
var bold8 = (s) => `\x1B[1m${s}\x1B[0m`, dim10 = (s) => `\x1B[2m${s}\x1B[0m`, green9 = (s) => `\x1B[32m${s}\x1B[0m`, yellow7 = (s) => `\x1B[33m${s}\x1B[0m`, red6 = (s) => `\x1B[31m${s}\x1B[0m`, CWD, CLAUDE_DIR, HOOKS_DIR, SETTINGS_FILE, MCP_FILE2, HOOK_NAMES;
|
|
20462
|
+
var init_doctor = __esm(() => {
|
|
20463
|
+
CWD = process.cwd();
|
|
20464
|
+
CLAUDE_DIR = join14(CWD, ".claude");
|
|
20465
|
+
HOOKS_DIR = join14(CLAUDE_DIR, "hooks");
|
|
20466
|
+
SETTINGS_FILE = join14(CLAUDE_DIR, "settings.json");
|
|
20467
|
+
MCP_FILE2 = join14(CWD, ".mcp.json");
|
|
20468
|
+
HOOK_NAMES = [
|
|
20469
|
+
"specialists-complete.mjs",
|
|
20470
|
+
"specialists-session-start.mjs"
|
|
20471
|
+
];
|
|
20472
|
+
});
|
|
20473
|
+
|
|
20474
|
+
// src/cli/setup.ts
|
|
20475
|
+
var exports_setup = {};
|
|
20476
|
+
__export(exports_setup, {
|
|
20477
|
+
run: () => run14
|
|
20478
|
+
});
|
|
20479
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
20480
|
+
import { homedir as homedir3 } from "node:os";
|
|
20481
|
+
import { join as join15, resolve } from "node:path";
|
|
20482
|
+
function ok4(msg) {
|
|
20483
|
+
console.log(` ${green10("✓")} ${msg}`);
|
|
20484
|
+
}
|
|
20485
|
+
function skip2(msg) {
|
|
20486
|
+
console.log(` ${yellow8("○")} ${msg}`);
|
|
20487
|
+
}
|
|
20488
|
+
function resolveTarget(target) {
|
|
20489
|
+
switch (target) {
|
|
20490
|
+
case "global":
|
|
20491
|
+
return join15(homedir3(), ".claude", "CLAUDE.md");
|
|
20492
|
+
case "agents":
|
|
20493
|
+
return join15(process.cwd(), "AGENTS.md");
|
|
20494
|
+
case "project":
|
|
20495
|
+
default:
|
|
20496
|
+
return join15(process.cwd(), "CLAUDE.md");
|
|
20497
|
+
}
|
|
20498
|
+
}
|
|
20499
|
+
function parseArgs6() {
|
|
20500
|
+
const argv = process.argv.slice(3);
|
|
20501
|
+
let target = "project";
|
|
20502
|
+
let dryRun = false;
|
|
20503
|
+
for (let i = 0;i < argv.length; i++) {
|
|
20504
|
+
const token = argv[i];
|
|
20505
|
+
if (token === "--global" || token === "-g") {
|
|
20506
|
+
target = "global";
|
|
20507
|
+
continue;
|
|
20508
|
+
}
|
|
20509
|
+
if (token === "--agents" || token === "-a") {
|
|
20510
|
+
target = "agents";
|
|
20511
|
+
continue;
|
|
20512
|
+
}
|
|
20513
|
+
if (token === "--project" || token === "-p") {
|
|
20514
|
+
target = "project";
|
|
20515
|
+
continue;
|
|
20516
|
+
}
|
|
20517
|
+
if (token === "--dry-run") {
|
|
20518
|
+
dryRun = true;
|
|
20519
|
+
continue;
|
|
20520
|
+
}
|
|
20521
|
+
}
|
|
20522
|
+
return { target, dryRun };
|
|
20523
|
+
}
|
|
20524
|
+
async function run14() {
|
|
20525
|
+
const { target, dryRun } = parseArgs6();
|
|
20526
|
+
const filePath = resolve(resolveTarget(target));
|
|
20527
|
+
const label = target === "global" ? "~/.claude/CLAUDE.md" : filePath.replace(process.cwd() + "/", "");
|
|
20528
|
+
console.log(`
|
|
20529
|
+
${bold9("specialists setup")}
|
|
20530
|
+
`);
|
|
20531
|
+
console.log(` Target: ${yellow8(label)}${dryRun ? dim11(" (dry-run)") : ""}
|
|
20532
|
+
`);
|
|
20533
|
+
if (existsSync11(filePath)) {
|
|
20534
|
+
const existing = readFileSync7(filePath, "utf8");
|
|
20535
|
+
if (existing.includes(MARKER)) {
|
|
20536
|
+
skip2(`${label} already contains Specialists Workflow section`);
|
|
20537
|
+
console.log(`
|
|
20538
|
+
${dim11("To force-update, remove the ## Specialists Workflow section and re-run.")}
|
|
20539
|
+
`);
|
|
20540
|
+
return;
|
|
20541
|
+
}
|
|
20542
|
+
if (dryRun) {
|
|
20543
|
+
console.log(dim11("─".repeat(60)));
|
|
20544
|
+
console.log(dim11("Would append to existing file:"));
|
|
20545
|
+
console.log("");
|
|
20546
|
+
console.log(WORKFLOW_BLOCK);
|
|
20547
|
+
console.log(dim11("─".repeat(60)));
|
|
20548
|
+
return;
|
|
20549
|
+
}
|
|
20550
|
+
const separator = existing.trimEnd().endsWith(`
|
|
20551
|
+
`) ? `
|
|
20552
|
+
` : `
|
|
20553
|
+
|
|
20554
|
+
`;
|
|
20555
|
+
writeFileSync4(filePath, existing.trimEnd() + separator + WORKFLOW_BLOCK, "utf8");
|
|
20556
|
+
ok4(`Appended Specialists Workflow section to ${label}`);
|
|
20557
|
+
} else {
|
|
20558
|
+
if (dryRun) {
|
|
20559
|
+
console.log(dim11("─".repeat(60)));
|
|
20560
|
+
console.log(dim11(`Would create ${label}:`));
|
|
20561
|
+
console.log("");
|
|
20562
|
+
console.log(WORKFLOW_BLOCK);
|
|
20563
|
+
console.log(dim11("─".repeat(60)));
|
|
20564
|
+
return;
|
|
20565
|
+
}
|
|
20566
|
+
writeFileSync4(filePath, WORKFLOW_BLOCK, "utf8");
|
|
20567
|
+
ok4(`Created ${label} with Specialists Workflow section`);
|
|
20568
|
+
}
|
|
20569
|
+
console.log("");
|
|
20570
|
+
console.log(` ${dim11("Next steps:")}`);
|
|
20571
|
+
console.log(` • Restart Claude Code to pick up the new context`);
|
|
20572
|
+
console.log(` • Run ${yellow8("specialists list")} to see available specialists`);
|
|
20573
|
+
console.log(` • Run ${yellow8("specialist_init")} in a new session to bootstrap context`);
|
|
20574
|
+
console.log("");
|
|
20575
|
+
}
|
|
20576
|
+
var bold9 = (s) => `\x1B[1m${s}\x1B[0m`, dim11 = (s) => `\x1B[2m${s}\x1B[0m`, green10 = (s) => `\x1B[32m${s}\x1B[0m`, yellow8 = (s) => `\x1B[33m${s}\x1B[0m`, MARKER = "## Specialists Workflow", WORKFLOW_BLOCK = `## Specialists Workflow
|
|
20577
|
+
|
|
20578
|
+
> Injected by \`specialists setup\`. Keep this section — agents use it for context.
|
|
20579
|
+
|
|
20580
|
+
### When to use specialists
|
|
20581
|
+
|
|
20582
|
+
Specialists are autonomous AI agents (running via the \`specialists\` MCP server)
|
|
20583
|
+
optimised for heavy tasks: code review, deep bug analysis, test generation,
|
|
20584
|
+
architecture design. Use them instead of doing the work yourself when the task
|
|
20585
|
+
would benefit from a fresh perspective, a second opinion, or a different model.
|
|
20586
|
+
|
|
20587
|
+
### Quick reference
|
|
20588
|
+
|
|
20589
|
+
\`\`\`
|
|
20590
|
+
# List available specialists
|
|
20591
|
+
specialists list # all scopes
|
|
20592
|
+
specialists list --scope project # this project only
|
|
20593
|
+
|
|
20594
|
+
# Run a specialist (foreground — streams output)
|
|
20595
|
+
specialists run <name> --prompt "..."
|
|
20596
|
+
|
|
20597
|
+
# Run async (background — immediate job ID)
|
|
20598
|
+
specialists run <name> --prompt "..." --background
|
|
20599
|
+
→ Job started: job_a1b2c3d4
|
|
20600
|
+
|
|
20601
|
+
# Watch / get results
|
|
20602
|
+
specialists feed job_a1b2c3d4 --follow # tail live events
|
|
20603
|
+
specialists result job_a1b2c3d4 # read final output
|
|
20604
|
+
specialists stop job_a1b2c3d4 # cancel if needed
|
|
20605
|
+
\`\`\`
|
|
20606
|
+
|
|
20607
|
+
### MCP tools (available in this session)
|
|
20608
|
+
|
|
20609
|
+
| Tool | Purpose |
|
|
20610
|
+
|------|---------|
|
|
20611
|
+
| \`specialist_init\` | Bootstrap: bd init + list specialists |
|
|
20612
|
+
| \`list_specialists\` | Discover specialists across scopes |
|
|
20613
|
+
| \`use_specialist\` | Run foreground: load → inject context → execute → output |
|
|
20614
|
+
| \`start_specialist\` | Start async: returns job ID immediately |
|
|
20615
|
+
| \`poll_specialist\` | Poll job status + delta output by ID |
|
|
20616
|
+
| \`stop_specialist\` | Cancel a running job |
|
|
20617
|
+
| \`run_parallel\` | Run multiple specialists concurrently or as a pipeline |
|
|
20618
|
+
| \`specialist_status\` | Circuit breaker health + staleness |
|
|
20619
|
+
|
|
20620
|
+
### Completion banner format
|
|
20621
|
+
|
|
20622
|
+
When a specialist finishes, you may see:
|
|
20623
|
+
|
|
20624
|
+
\`\`\`
|
|
20625
|
+
✓ bead unitAI-xxx 4.1s anthropic/claude-sonnet-4-6
|
|
20626
|
+
\`\`\`
|
|
20627
|
+
|
|
20628
|
+
This means:
|
|
20629
|
+
- The specialist completed successfully
|
|
20630
|
+
- A beads issue (\`unitAI-xxx\`) was created to track the run
|
|
20631
|
+
- The result can be fetched with \`specialists result <job-id>\`
|
|
20632
|
+
|
|
20633
|
+
### When NOT to use specialists
|
|
20634
|
+
|
|
20635
|
+
- Simple single-file edits — just do it directly
|
|
20636
|
+
- Tasks that need interactive back-and-forth — use foreground mode or work yourself
|
|
20637
|
+
- Short read-only queries — faster to answer directly
|
|
20638
|
+
`;
|
|
20639
|
+
var init_setup = () => {};
|
|
20640
|
+
|
|
19301
20641
|
// src/cli/help.ts
|
|
19302
20642
|
var exports_help = {};
|
|
19303
20643
|
__export(exports_help, {
|
|
19304
|
-
run: () =>
|
|
20644
|
+
run: () => run15
|
|
19305
20645
|
});
|
|
19306
|
-
|
|
20646
|
+
function formatCommands(entries) {
|
|
20647
|
+
const width = Math.max(...entries.map(([cmd3]) => cmd3.length));
|
|
20648
|
+
return entries.map(([cmd3, desc]) => ` ${cmd3.padEnd(width)} ${desc}`);
|
|
20649
|
+
}
|
|
20650
|
+
async function run15() {
|
|
19307
20651
|
const lines = [
|
|
19308
20652
|
"",
|
|
19309
|
-
|
|
20653
|
+
"Specialists lets you run project-scoped specialist agents with a bead-first workflow.",
|
|
20654
|
+
"",
|
|
20655
|
+
bold10("Usage:"),
|
|
20656
|
+
" specialists [command]",
|
|
20657
|
+
" specialists [command] --help",
|
|
20658
|
+
"",
|
|
20659
|
+
bold10("Common flows:"),
|
|
20660
|
+
"",
|
|
20661
|
+
" Tracked work (primary)",
|
|
20662
|
+
' bd create "Task title" -t task -p 1 --json',
|
|
20663
|
+
" specialists run <name> --bead <id> [--context-depth N] [--background]",
|
|
20664
|
+
" specialists feed -f",
|
|
20665
|
+
' bd close <id> --reason "Done"',
|
|
20666
|
+
"",
|
|
20667
|
+
" Ad-hoc work",
|
|
20668
|
+
' specialists run <name> --prompt "..."',
|
|
20669
|
+
"",
|
|
20670
|
+
" Rules",
|
|
20671
|
+
" --bead is for tracked work",
|
|
20672
|
+
" --prompt is for quick untracked work",
|
|
20673
|
+
" --context-depth defaults to 1 with --bead",
|
|
20674
|
+
" --no-beads does not disable bead reading",
|
|
20675
|
+
"",
|
|
20676
|
+
bold10("Core commands:"),
|
|
20677
|
+
...formatCommands(CORE_COMMANDS),
|
|
20678
|
+
"",
|
|
20679
|
+
bold10("Extended commands:"),
|
|
20680
|
+
...formatCommands(EXTENDED_COMMANDS),
|
|
20681
|
+
"",
|
|
20682
|
+
bold10("xtrm worktree commands:"),
|
|
20683
|
+
...formatCommands(WORKTREE_COMMANDS),
|
|
19310
20684
|
"",
|
|
19311
|
-
"
|
|
19312
|
-
|
|
20685
|
+
bold10("Examples:"),
|
|
20686
|
+
" specialists init",
|
|
20687
|
+
" specialists list",
|
|
20688
|
+
" specialists run bug-hunt --bead unitAI-123 --background",
|
|
20689
|
+
' specialists run codebase-explorer --prompt "Map the CLI architecture"',
|
|
20690
|
+
" specialists feed -f",
|
|
20691
|
+
" specialists result <job-id>",
|
|
19313
20692
|
"",
|
|
19314
|
-
|
|
20693
|
+
bold10("More help:"),
|
|
20694
|
+
" specialists quickstart Full guide and workflow reference",
|
|
20695
|
+
" specialists run --help Run command details and flags",
|
|
20696
|
+
" specialists init --help Bootstrap behavior and workflow injection",
|
|
20697
|
+
" specialists feed --help Background job monitoring details",
|
|
20698
|
+
"",
|
|
20699
|
+
dim12("Project model: specialists are project-only; user-scope discovery is deprecated."),
|
|
19315
20700
|
""
|
|
19316
20701
|
];
|
|
19317
20702
|
console.log(lines.join(`
|
|
19318
20703
|
`));
|
|
19319
20704
|
}
|
|
19320
|
-
var
|
|
20705
|
+
var bold10 = (s) => `\x1B[1m${s}\x1B[0m`, dim12 = (s) => `\x1B[2m${s}\x1B[0m`, CORE_COMMANDS, EXTENDED_COMMANDS, WORKTREE_COMMANDS;
|
|
19321
20706
|
var init_help = __esm(() => {
|
|
19322
|
-
|
|
19323
|
-
["
|
|
19324
|
-
["list", "List
|
|
19325
|
-
["
|
|
20707
|
+
CORE_COMMANDS = [
|
|
20708
|
+
["init", "Bootstrap a project: dirs, workflow injection, project MCP registration"],
|
|
20709
|
+
["list", "List specialists in this project"],
|
|
20710
|
+
["run", "Run a specialist with --bead for tracked work or --prompt for ad-hoc work"],
|
|
20711
|
+
["feed", "Tail job events; use -f to follow all jobs"],
|
|
20712
|
+
["result", "Print final output of a completed background job"],
|
|
20713
|
+
["stop", "Stop a running background job"],
|
|
20714
|
+
["status", "Show health, MCP state, and active jobs"],
|
|
20715
|
+
["doctor", "Diagnose installation/runtime problems"],
|
|
20716
|
+
["quickstart", "Full getting-started guide"],
|
|
20717
|
+
["help", "Show this help"]
|
|
20718
|
+
];
|
|
20719
|
+
EXTENDED_COMMANDS = [
|
|
20720
|
+
["edit", "Edit a specialist field such as model or description"],
|
|
20721
|
+
["models", "List models available on pi"],
|
|
19326
20722
|
["version", "Print installed version"],
|
|
19327
|
-
["
|
|
19328
|
-
["
|
|
19329
|
-
|
|
19330
|
-
|
|
19331
|
-
["
|
|
19332
|
-
["
|
|
19333
|
-
["
|
|
19334
|
-
["
|
|
20723
|
+
["setup", "[deprecated] Use specialists init instead"],
|
|
20724
|
+
["install", "[deprecated] Use specialists init instead"]
|
|
20725
|
+
];
|
|
20726
|
+
WORKTREE_COMMANDS = [
|
|
20727
|
+
["xt pi [name]", "Start a Pi session in a sandboxed xt worktree"],
|
|
20728
|
+
["xt claude [name]", "Start a Claude session in a sandboxed xt worktree"],
|
|
20729
|
+
["xt attach [slug]", "Resume an existing xt worktree session"],
|
|
20730
|
+
["xt worktree list", "List worktrees with runtime and activity"],
|
|
20731
|
+
["xt end", "Close session, push, PR, cleanup"]
|
|
19335
20732
|
];
|
|
19336
|
-
COL_WIDTH = Math.max(...COMMANDS.map(([cmd2]) => cmd2.length));
|
|
19337
20733
|
});
|
|
19338
20734
|
|
|
19339
20735
|
// node_modules/zod/v4/core/core.js
|
|
@@ -25362,6 +26758,9 @@ class Protocol {
|
|
|
25362
26758
|
}
|
|
25363
26759
|
}
|
|
25364
26760
|
async connect(transport) {
|
|
26761
|
+
if (this._transport) {
|
|
26762
|
+
throw new Error("Already connected to a transport. Call close() before connecting to a new transport, or use a separate Protocol instance per connection.");
|
|
26763
|
+
}
|
|
25365
26764
|
this._transport = transport;
|
|
25366
26765
|
const _onclose = this.transport?.onclose;
|
|
25367
26766
|
this._transport.onclose = () => {
|
|
@@ -25394,6 +26793,10 @@ class Protocol {
|
|
|
25394
26793
|
this._progressHandlers.clear();
|
|
25395
26794
|
this._taskProgressTokens.clear();
|
|
25396
26795
|
this._pendingDebouncedNotifications.clear();
|
|
26796
|
+
for (const controller of this._requestHandlerAbortControllers.values()) {
|
|
26797
|
+
controller.abort();
|
|
26798
|
+
}
|
|
26799
|
+
this._requestHandlerAbortControllers.clear();
|
|
25397
26800
|
const error2 = McpError.fromError(ErrorCode.ConnectionClosed, "Connection closed");
|
|
25398
26801
|
this._transport = undefined;
|
|
25399
26802
|
this.onclose?.();
|
|
@@ -25444,6 +26847,8 @@ class Protocol {
|
|
|
25444
26847
|
sessionId: capturedTransport?.sessionId,
|
|
25445
26848
|
_meta: request.params?._meta,
|
|
25446
26849
|
sendNotification: async (notification) => {
|
|
26850
|
+
if (abortController.signal.aborted)
|
|
26851
|
+
return;
|
|
25447
26852
|
const notificationOptions = { relatedRequestId: request.id };
|
|
25448
26853
|
if (relatedTaskId) {
|
|
25449
26854
|
notificationOptions.relatedTask = { taskId: relatedTaskId };
|
|
@@ -25451,6 +26856,9 @@ class Protocol {
|
|
|
25451
26856
|
await this.notification(notification, notificationOptions);
|
|
25452
26857
|
},
|
|
25453
26858
|
sendRequest: async (r, resultSchema, options) => {
|
|
26859
|
+
if (abortController.signal.aborted) {
|
|
26860
|
+
throw new McpError(ErrorCode.ConnectionClosed, "Request was cancelled");
|
|
26861
|
+
}
|
|
25454
26862
|
const requestOptions = { ...options, relatedRequestId: request.id };
|
|
25455
26863
|
if (relatedTaskId && !requestOptions.relatedTask) {
|
|
25456
26864
|
requestOptions.relatedTask = { taskId: relatedTaskId };
|
|
@@ -26064,6 +27472,62 @@ class ExperimentalServerTasks {
|
|
|
26064
27472
|
requestStream(request, resultSchema, options) {
|
|
26065
27473
|
return this._server.requestStream(request, resultSchema, options);
|
|
26066
27474
|
}
|
|
27475
|
+
createMessageStream(params, options) {
|
|
27476
|
+
const clientCapabilities = this._server.getClientCapabilities();
|
|
27477
|
+
if ((params.tools || params.toolChoice) && !clientCapabilities?.sampling?.tools) {
|
|
27478
|
+
throw new Error("Client does not support sampling tools capability.");
|
|
27479
|
+
}
|
|
27480
|
+
if (params.messages.length > 0) {
|
|
27481
|
+
const lastMessage = params.messages[params.messages.length - 1];
|
|
27482
|
+
const lastContent = Array.isArray(lastMessage.content) ? lastMessage.content : [lastMessage.content];
|
|
27483
|
+
const hasToolResults = lastContent.some((c) => c.type === "tool_result");
|
|
27484
|
+
const previousMessage = params.messages.length > 1 ? params.messages[params.messages.length - 2] : undefined;
|
|
27485
|
+
const previousContent = previousMessage ? Array.isArray(previousMessage.content) ? previousMessage.content : [previousMessage.content] : [];
|
|
27486
|
+
const hasPreviousToolUse = previousContent.some((c) => c.type === "tool_use");
|
|
27487
|
+
if (hasToolResults) {
|
|
27488
|
+
if (lastContent.some((c) => c.type !== "tool_result")) {
|
|
27489
|
+
throw new Error("The last message must contain only tool_result content if any is present");
|
|
27490
|
+
}
|
|
27491
|
+
if (!hasPreviousToolUse) {
|
|
27492
|
+
throw new Error("tool_result blocks are not matching any tool_use from the previous message");
|
|
27493
|
+
}
|
|
27494
|
+
}
|
|
27495
|
+
if (hasPreviousToolUse) {
|
|
27496
|
+
const toolUseIds = new Set(previousContent.filter((c) => c.type === "tool_use").map((c) => c.id));
|
|
27497
|
+
const toolResultIds = new Set(lastContent.filter((c) => c.type === "tool_result").map((c) => c.toolUseId));
|
|
27498
|
+
if (toolUseIds.size !== toolResultIds.size || ![...toolUseIds].every((id) => toolResultIds.has(id))) {
|
|
27499
|
+
throw new Error("ids of tool_result blocks and tool_use blocks from previous message do not match");
|
|
27500
|
+
}
|
|
27501
|
+
}
|
|
27502
|
+
}
|
|
27503
|
+
return this.requestStream({
|
|
27504
|
+
method: "sampling/createMessage",
|
|
27505
|
+
params
|
|
27506
|
+
}, CreateMessageResultSchema, options);
|
|
27507
|
+
}
|
|
27508
|
+
elicitInputStream(params, options) {
|
|
27509
|
+
const clientCapabilities = this._server.getClientCapabilities();
|
|
27510
|
+
const mode = params.mode ?? "form";
|
|
27511
|
+
switch (mode) {
|
|
27512
|
+
case "url": {
|
|
27513
|
+
if (!clientCapabilities?.elicitation?.url) {
|
|
27514
|
+
throw new Error("Client does not support url elicitation.");
|
|
27515
|
+
}
|
|
27516
|
+
break;
|
|
27517
|
+
}
|
|
27518
|
+
case "form": {
|
|
27519
|
+
if (!clientCapabilities?.elicitation?.form) {
|
|
27520
|
+
throw new Error("Client does not support form elicitation.");
|
|
27521
|
+
}
|
|
27522
|
+
break;
|
|
27523
|
+
}
|
|
27524
|
+
}
|
|
27525
|
+
const normalizedParams = mode === "form" && params.mode === undefined ? { ...params, mode: "form" } : params;
|
|
27526
|
+
return this.requestStream({
|
|
27527
|
+
method: "elicitation/create",
|
|
27528
|
+
params: normalizedParams
|
|
27529
|
+
}, ElicitResultSchema, options);
|
|
27530
|
+
}
|
|
26067
27531
|
async getTask(taskId, options) {
|
|
26068
27532
|
return this._server.getTask({ taskId }, options);
|
|
26069
27533
|
}
|
|
@@ -26538,7 +28002,7 @@ class StdioServerTransport {
|
|
|
26538
28002
|
}
|
|
26539
28003
|
|
|
26540
28004
|
// src/server.ts
|
|
26541
|
-
import { join as
|
|
28005
|
+
import { join as join4 } from "node:path";
|
|
26542
28006
|
|
|
26543
28007
|
// src/constants.ts
|
|
26544
28008
|
var LOG_PREFIX = "[specialists]";
|
|
@@ -26627,25 +28091,49 @@ function createListSpecialistsTool(loader) {
|
|
|
26627
28091
|
|
|
26628
28092
|
// src/tools/specialist/use_specialist.tool.ts
|
|
26629
28093
|
init_zod();
|
|
28094
|
+
init_beads();
|
|
26630
28095
|
var useSpecialistSchema = exports_external.object({
|
|
26631
28096
|
name: exports_external.string().describe("Specialist identifier (e.g. codebase-explorer)"),
|
|
26632
|
-
prompt: exports_external.string().describe("The task or question for the specialist"),
|
|
28097
|
+
prompt: exports_external.string().optional().describe("The task or question for the specialist"),
|
|
28098
|
+
bead_id: exports_external.string().optional().describe("Use an existing bead as the specialist prompt"),
|
|
26633
28099
|
variables: exports_external.record(exports_external.string()).optional().describe("Additional $variable substitutions"),
|
|
26634
28100
|
backend_override: exports_external.string().optional().describe("Force a specific backend (gemini, qwen, anthropic)"),
|
|
26635
|
-
autonomy_level: exports_external.string().optional().describe("Override permission level for this invocation")
|
|
28101
|
+
autonomy_level: exports_external.string().optional().describe("Override permission level for this invocation"),
|
|
28102
|
+
context_depth: exports_external.number().optional().describe("Depth of blocker context injection (0 = none, 1 = immediate blockers, etc.)")
|
|
28103
|
+
}).refine((input) => Boolean(input.prompt?.trim() || input.bead_id), {
|
|
28104
|
+
message: "Either prompt or bead_id is required",
|
|
28105
|
+
path: ["prompt"]
|
|
26636
28106
|
});
|
|
26637
28107
|
function createUseSpecialistTool(runner) {
|
|
26638
28108
|
return {
|
|
26639
28109
|
name: "use_specialist",
|
|
26640
|
-
description: "Run a specialist synchronously and wait for the result. " + "Full lifecycle: load → agents.md → pi session → output. " + "Response includes output, model, durationMs, and beadId (string | undefined). " + "beadId is set when the specialist's beads_integration policy triggered bead creation " + "(default: auto — creates for LOW/MEDIUM/HIGH permission, skips for READ_ONLY). " + "If beadId is present, use `bd update <beadId> --notes` to attach findings or " + "`bd remember` to persist key discoveries for future sessions.",
|
|
28110
|
+
description: "Run a specialist synchronously and wait for the result. " + "Full lifecycle: load → agents.md → pi session → output. " + "Response includes output, model, durationMs, and beadId (string | undefined). " + "beadId is set when the specialist's beads_integration policy triggered bead creation " + "(default: auto — creates for LOW/MEDIUM/HIGH permission, skips for READ_ONLY). " + "If beadId is present, use `bd update <beadId> --notes` to attach findings or " + "`bd remember` to persist key discoveries for future sessions. " + "When bead_id is provided, the source bead becomes the specialist prompt and the tracking bead links back to it. " + "Use context_depth to inject outputs from completed blocking dependencies (depth 1 = immediate blockers, 2 = include their blockers too).",
|
|
26641
28111
|
inputSchema: useSpecialistSchema,
|
|
26642
28112
|
async execute(input, onProgress) {
|
|
28113
|
+
let prompt = input.prompt?.trim() ?? "";
|
|
28114
|
+
let variables = input.variables;
|
|
28115
|
+
if (input.bead_id) {
|
|
28116
|
+
const beadsClient = new BeadsClient;
|
|
28117
|
+
const bead = beadsClient.readBead(input.bead_id);
|
|
28118
|
+
if (!bead) {
|
|
28119
|
+
throw new Error(`Unable to read bead '${input.bead_id}' via bd show --json`);
|
|
28120
|
+
}
|
|
28121
|
+
const blockers = input.context_depth && input.context_depth > 0 ? beadsClient.getBlockers(input.bead_id, input.context_depth) : [];
|
|
28122
|
+
const beadContext = buildBeadContext(bead, { blockers, depth: input.context_depth ?? 0 });
|
|
28123
|
+
prompt = beadContext;
|
|
28124
|
+
variables = {
|
|
28125
|
+
...input.variables ?? {},
|
|
28126
|
+
bead_context: beadContext,
|
|
28127
|
+
bead_id: input.bead_id
|
|
28128
|
+
};
|
|
28129
|
+
}
|
|
26643
28130
|
return runner.run({
|
|
26644
28131
|
name: input.name,
|
|
26645
|
-
prompt
|
|
26646
|
-
variables
|
|
28132
|
+
prompt,
|
|
28133
|
+
variables,
|
|
26647
28134
|
backendOverride: input.backend_override,
|
|
26648
|
-
autonomyLevel: input.autonomy_level
|
|
28135
|
+
autonomyLevel: input.autonomy_level,
|
|
28136
|
+
inputBeadId: input.bead_id
|
|
26649
28137
|
}, onProgress);
|
|
26650
28138
|
}
|
|
26651
28139
|
};
|
|
@@ -26751,14 +28239,14 @@ function createSpecialistStatusTool(loader, circuitBreaker) {
|
|
|
26751
28239
|
async execute(_) {
|
|
26752
28240
|
const list = await loader.list();
|
|
26753
28241
|
const stalenessResults = await Promise.all(list.map((s) => checkStaleness(s)));
|
|
26754
|
-
const { existsSync:
|
|
26755
|
-
const { join:
|
|
26756
|
-
const jobsDir =
|
|
28242
|
+
const { existsSync: existsSync3, readdirSync, readFileSync } = await import("node:fs");
|
|
28243
|
+
const { join: join3 } = await import("node:path");
|
|
28244
|
+
const jobsDir = join3(process.cwd(), ".specialists", "jobs");
|
|
26757
28245
|
const jobs = [];
|
|
26758
|
-
if (
|
|
28246
|
+
if (existsSync3(jobsDir)) {
|
|
26759
28247
|
for (const entry of readdirSync(jobsDir)) {
|
|
26760
|
-
const statusPath =
|
|
26761
|
-
if (!
|
|
28248
|
+
const statusPath = join3(jobsDir, entry, "status.json");
|
|
28249
|
+
if (!existsSync3(statusPath))
|
|
26762
28250
|
continue;
|
|
26763
28251
|
try {
|
|
26764
28252
|
jobs.push(JSON.parse(readFileSync(statusPath, "utf-8")));
|
|
@@ -26970,13 +28458,13 @@ init_zod();
|
|
|
26970
28458
|
// src/tools/specialist/specialist_init.tool.ts
|
|
26971
28459
|
init_zod();
|
|
26972
28460
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
26973
|
-
import { existsSync as
|
|
26974
|
-
import { join as
|
|
28461
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
28462
|
+
import { join as join3 } from "node:path";
|
|
26975
28463
|
var specialistInitSchema = objectType({});
|
|
26976
28464
|
function createSpecialistInitTool(loader, deps) {
|
|
26977
28465
|
const resolved = deps ?? {
|
|
26978
28466
|
bdAvailable: () => spawnSync2("bd", ["--version"], { stdio: "ignore" }).status === 0,
|
|
26979
|
-
beadsExists: () =>
|
|
28467
|
+
beadsExists: () => existsSync3(join3(process.cwd(), ".beads")),
|
|
26980
28468
|
bdInit: () => spawnSync2("bd", ["init"], { stdio: "ignore" })
|
|
26981
28469
|
};
|
|
26982
28470
|
return {
|
|
@@ -27011,7 +28499,7 @@ class SpecialistsServer {
|
|
|
27011
28499
|
const circuitBreaker = new CircuitBreaker;
|
|
27012
28500
|
const loader = new SpecialistLoader;
|
|
27013
28501
|
const hooks = new HookEmitter({
|
|
27014
|
-
tracePath:
|
|
28502
|
+
tracePath: join4(process.cwd(), ".specialists", "trace.jsonl")
|
|
27015
28503
|
});
|
|
27016
28504
|
const beadsClient = new BeadsClient;
|
|
27017
28505
|
const runner = new SpecialistRunner({ loader, hooks, circuitBreaker, beadsClient });
|
|
@@ -27107,8 +28595,26 @@ class SpecialistsServer {
|
|
|
27107
28595
|
|
|
27108
28596
|
// src/index.ts
|
|
27109
28597
|
var sub = process.argv[2];
|
|
27110
|
-
|
|
28598
|
+
var next = process.argv[3];
|
|
28599
|
+
function wantsHelp() {
|
|
28600
|
+
return next === "--help" || next === "-h";
|
|
28601
|
+
}
|
|
28602
|
+
async function run16() {
|
|
27111
28603
|
if (sub === "install") {
|
|
28604
|
+
if (wantsHelp()) {
|
|
28605
|
+
console.log([
|
|
28606
|
+
"",
|
|
28607
|
+
"Usage: specialists install",
|
|
28608
|
+
"",
|
|
28609
|
+
"Project setup: checks pi/bd/xt prerequisites, registers the MCP server,",
|
|
28610
|
+
"and installs specialists-specific project hooks.",
|
|
28611
|
+
"",
|
|
28612
|
+
"No flags — just run it.",
|
|
28613
|
+
""
|
|
28614
|
+
].join(`
|
|
28615
|
+
`));
|
|
28616
|
+
return;
|
|
28617
|
+
}
|
|
27112
28618
|
const { run: handler } = await Promise.resolve().then(() => (init_install(), exports_install));
|
|
27113
28619
|
return handler();
|
|
27114
28620
|
}
|
|
@@ -27117,41 +28623,322 @@ async function run13() {
|
|
|
27117
28623
|
return handler();
|
|
27118
28624
|
}
|
|
27119
28625
|
if (sub === "list") {
|
|
28626
|
+
if (wantsHelp()) {
|
|
28627
|
+
console.log([
|
|
28628
|
+
"",
|
|
28629
|
+
"Usage: specialists list [options]",
|
|
28630
|
+
"",
|
|
28631
|
+
"List specialists in the current project.",
|
|
28632
|
+
"",
|
|
28633
|
+
"What it shows:",
|
|
28634
|
+
" - specialist name",
|
|
28635
|
+
" - model",
|
|
28636
|
+
" - short description",
|
|
28637
|
+
"",
|
|
28638
|
+
"Options:",
|
|
28639
|
+
" --category <name> Filter by category tag",
|
|
28640
|
+
" --json Output as JSON array",
|
|
28641
|
+
"",
|
|
28642
|
+
"Examples:",
|
|
28643
|
+
" specialists list",
|
|
28644
|
+
" specialists list --category analysis",
|
|
28645
|
+
" specialists list --json",
|
|
28646
|
+
"",
|
|
28647
|
+
"Project model:",
|
|
28648
|
+
" Specialists are project-only. User-scope discovery is deprecated.",
|
|
28649
|
+
""
|
|
28650
|
+
].join(`
|
|
28651
|
+
`));
|
|
28652
|
+
return;
|
|
28653
|
+
}
|
|
27120
28654
|
const { run: handler } = await Promise.resolve().then(() => (init_list(), exports_list));
|
|
27121
28655
|
return handler();
|
|
27122
28656
|
}
|
|
27123
28657
|
if (sub === "models") {
|
|
28658
|
+
if (wantsHelp()) {
|
|
28659
|
+
console.log([
|
|
28660
|
+
"",
|
|
28661
|
+
"Usage: specialists models",
|
|
28662
|
+
"",
|
|
28663
|
+
"List all models available on pi, with thinking and image support flags.",
|
|
28664
|
+
"",
|
|
28665
|
+
"No flags.",
|
|
28666
|
+
""
|
|
28667
|
+
].join(`
|
|
28668
|
+
`));
|
|
28669
|
+
return;
|
|
28670
|
+
}
|
|
27124
28671
|
const { run: handler } = await Promise.resolve().then(() => (init_models(), exports_models));
|
|
27125
28672
|
return handler();
|
|
27126
28673
|
}
|
|
27127
28674
|
if (sub === "init") {
|
|
28675
|
+
if (wantsHelp()) {
|
|
28676
|
+
console.log([
|
|
28677
|
+
"",
|
|
28678
|
+
"Usage: specialists init [--force-workflow]",
|
|
28679
|
+
"",
|
|
28680
|
+
"Bootstrap a project for specialists. This is the sole onboarding command.",
|
|
28681
|
+
"",
|
|
28682
|
+
"What it does:",
|
|
28683
|
+
" • creates specialists/ for project .specialist.yaml files",
|
|
28684
|
+
" • creates .specialists/ runtime dirs (jobs/, ready/)",
|
|
28685
|
+
" • adds .specialists/ to .gitignore",
|
|
28686
|
+
" • injects the managed workflow block into AGENTS.md and CLAUDE.md",
|
|
28687
|
+
" • registers the Specialists MCP server at project scope",
|
|
28688
|
+
"",
|
|
28689
|
+
"Options:",
|
|
28690
|
+
" --force-workflow Overwrite existing managed workflow blocks",
|
|
28691
|
+
"",
|
|
28692
|
+
"Examples:",
|
|
28693
|
+
" specialists init",
|
|
28694
|
+
" specialists init --force-workflow",
|
|
28695
|
+
"",
|
|
28696
|
+
"Notes:",
|
|
28697
|
+
" setup and install are deprecated; use specialists init.",
|
|
28698
|
+
" Safe to run again; existing project state is preserved where possible.",
|
|
28699
|
+
""
|
|
28700
|
+
].join(`
|
|
28701
|
+
`));
|
|
28702
|
+
return;
|
|
28703
|
+
}
|
|
27128
28704
|
const { run: handler } = await Promise.resolve().then(() => (init_init(), exports_init));
|
|
27129
28705
|
return handler();
|
|
27130
28706
|
}
|
|
27131
28707
|
if (sub === "edit") {
|
|
28708
|
+
if (wantsHelp()) {
|
|
28709
|
+
console.log([
|
|
28710
|
+
"",
|
|
28711
|
+
"Usage: specialists edit <name> --<field> <value> [options]",
|
|
28712
|
+
"",
|
|
28713
|
+
"Edit a field in a .specialist.yaml without opening the file.",
|
|
28714
|
+
"",
|
|
28715
|
+
"Editable fields:",
|
|
28716
|
+
" --model <value> Primary execution model",
|
|
28717
|
+
" --fallback-model <value> Fallback model (used on circuit-break)",
|
|
28718
|
+
" --description <value> One-line description",
|
|
28719
|
+
" --permission <value> READ_ONLY | LOW | MEDIUM | HIGH",
|
|
28720
|
+
" --timeout <ms> Timeout in milliseconds",
|
|
28721
|
+
" --tags <a,b,c> Comma-separated list of tags",
|
|
28722
|
+
"",
|
|
28723
|
+
"Options:",
|
|
28724
|
+
" --dry-run Preview the change without writing",
|
|
28725
|
+
" --scope <project|user> Disambiguate if same name exists in multiple scopes",
|
|
28726
|
+
"",
|
|
28727
|
+
"Examples:",
|
|
28728
|
+
" specialists edit code-review --model anthropic/claude-opus-4-6",
|
|
28729
|
+
" specialists edit code-review --permission HIGH --dry-run",
|
|
28730
|
+
" specialists edit code-review --tags analysis,security",
|
|
28731
|
+
""
|
|
28732
|
+
].join(`
|
|
28733
|
+
`));
|
|
28734
|
+
return;
|
|
28735
|
+
}
|
|
27132
28736
|
const { run: handler } = await Promise.resolve().then(() => (init_edit(), exports_edit));
|
|
27133
28737
|
return handler();
|
|
27134
28738
|
}
|
|
27135
28739
|
if (sub === "run") {
|
|
28740
|
+
if (wantsHelp()) {
|
|
28741
|
+
console.log([
|
|
28742
|
+
"",
|
|
28743
|
+
"Usage: specialists run <name> [options]",
|
|
28744
|
+
"",
|
|
28745
|
+
"Run a specialist in foreground or background.",
|
|
28746
|
+
"",
|
|
28747
|
+
"Primary modes:",
|
|
28748
|
+
" tracked: specialists run <name> --bead <id>",
|
|
28749
|
+
' ad-hoc: specialists run <name> --prompt "..."',
|
|
28750
|
+
"",
|
|
28751
|
+
"Options:",
|
|
28752
|
+
" --bead <id> Use an existing bead as the prompt source",
|
|
28753
|
+
" --prompt <text> Ad-hoc prompt for untracked work",
|
|
28754
|
+
" --context-depth <n> Dependency context depth when using --bead (default: 1)",
|
|
28755
|
+
" --no-beads Do not create a new tracking bead (does not disable bead reading)",
|
|
28756
|
+
" --background Start async and return a job id",
|
|
28757
|
+
" --model <model> Override the configured model for this run",
|
|
28758
|
+
"",
|
|
28759
|
+
"Examples:",
|
|
28760
|
+
" specialists run bug-hunt --bead unitAI-55d",
|
|
28761
|
+
" specialists run bug-hunt --bead unitAI-55d --context-depth 2 --background",
|
|
28762
|
+
' specialists run code-review --prompt "Audit src/api.ts"',
|
|
28763
|
+
" cat brief.md | specialists run report-generator",
|
|
28764
|
+
"",
|
|
28765
|
+
"Rules:",
|
|
28766
|
+
" Use --bead for tracked work.",
|
|
28767
|
+
" Use --prompt for quick ad-hoc work.",
|
|
28768
|
+
""
|
|
28769
|
+
].join(`
|
|
28770
|
+
`));
|
|
28771
|
+
return;
|
|
28772
|
+
}
|
|
27136
28773
|
const { run: handler } = await Promise.resolve().then(() => (init_run(), exports_run));
|
|
27137
28774
|
return handler();
|
|
27138
28775
|
}
|
|
27139
28776
|
if (sub === "status") {
|
|
28777
|
+
if (wantsHelp()) {
|
|
28778
|
+
console.log([
|
|
28779
|
+
"",
|
|
28780
|
+
"Usage: specialists status [options]",
|
|
28781
|
+
"",
|
|
28782
|
+
"Show current runtime state.",
|
|
28783
|
+
"",
|
|
28784
|
+
"Sections include:",
|
|
28785
|
+
" - discovered specialists",
|
|
28786
|
+
" - pi provider/runtime health",
|
|
28787
|
+
" - beads availability",
|
|
28788
|
+
" - MCP registration hints",
|
|
28789
|
+
" - active background jobs",
|
|
28790
|
+
"",
|
|
28791
|
+
"Options:",
|
|
28792
|
+
" --json Output machine-readable JSON",
|
|
28793
|
+
"",
|
|
28794
|
+
"Examples:",
|
|
28795
|
+
" specialists status",
|
|
28796
|
+
" specialists status --json",
|
|
28797
|
+
""
|
|
28798
|
+
].join(`
|
|
28799
|
+
`));
|
|
28800
|
+
return;
|
|
28801
|
+
}
|
|
27140
28802
|
const { run: handler } = await Promise.resolve().then(() => (init_status(), exports_status));
|
|
27141
28803
|
return handler();
|
|
27142
28804
|
}
|
|
27143
28805
|
if (sub === "result") {
|
|
28806
|
+
if (wantsHelp()) {
|
|
28807
|
+
console.log([
|
|
28808
|
+
"",
|
|
28809
|
+
"Usage: specialists result <job-id>",
|
|
28810
|
+
"",
|
|
28811
|
+
"Print the final output of a completed background job.",
|
|
28812
|
+
"Exits with code 1 if the job is still running or failed.",
|
|
28813
|
+
"",
|
|
28814
|
+
"Examples:",
|
|
28815
|
+
" specialists result job_a1b2c3d4",
|
|
28816
|
+
" specialists result job_a1b2c3d4 > output.md",
|
|
28817
|
+
"",
|
|
28818
|
+
"See also:",
|
|
28819
|
+
" specialists feed <job-id> --follow (stream live events)",
|
|
28820
|
+
" specialists status (list all active jobs)",
|
|
28821
|
+
""
|
|
28822
|
+
].join(`
|
|
28823
|
+
`));
|
|
28824
|
+
return;
|
|
28825
|
+
}
|
|
27144
28826
|
const { run: handler } = await Promise.resolve().then(() => (init_result(), exports_result));
|
|
27145
28827
|
return handler();
|
|
27146
28828
|
}
|
|
27147
28829
|
if (sub === "feed") {
|
|
28830
|
+
if (wantsHelp()) {
|
|
28831
|
+
console.log([
|
|
28832
|
+
"",
|
|
28833
|
+
"Usage: specialists feed <job-id> [options]",
|
|
28834
|
+
" specialists feed -f [--forever]",
|
|
28835
|
+
"",
|
|
28836
|
+
"Read background job events.",
|
|
28837
|
+
"",
|
|
28838
|
+
"Modes:",
|
|
28839
|
+
" specialists feed <job-id> Replay events for one job",
|
|
28840
|
+
" specialists feed <job-id> -f Follow one job until completion",
|
|
28841
|
+
" specialists feed -f Follow all jobs globally",
|
|
28842
|
+
"",
|
|
28843
|
+
"Options:",
|
|
28844
|
+
" -f, --follow Follow live updates",
|
|
28845
|
+
" --forever Keep following in global mode even when all jobs complete",
|
|
28846
|
+
"",
|
|
28847
|
+
"Examples:",
|
|
28848
|
+
" specialists feed 49adda",
|
|
28849
|
+
" specialists feed 49adda --follow",
|
|
28850
|
+
" specialists feed -f",
|
|
28851
|
+
" specialists feed -f --forever",
|
|
28852
|
+
""
|
|
28853
|
+
].join(`
|
|
28854
|
+
`));
|
|
28855
|
+
return;
|
|
28856
|
+
}
|
|
27148
28857
|
const { run: handler } = await Promise.resolve().then(() => (init_feed(), exports_feed));
|
|
27149
28858
|
return handler();
|
|
27150
28859
|
}
|
|
27151
28860
|
if (sub === "stop") {
|
|
28861
|
+
if (wantsHelp()) {
|
|
28862
|
+
console.log([
|
|
28863
|
+
"",
|
|
28864
|
+
"Usage: specialists stop <job-id>",
|
|
28865
|
+
"",
|
|
28866
|
+
"Send SIGTERM to the agent process for a running background job.",
|
|
28867
|
+
"Has no effect if the job is already done or errored.",
|
|
28868
|
+
"",
|
|
28869
|
+
"Examples:",
|
|
28870
|
+
" specialists stop job_a1b2c3d4",
|
|
28871
|
+
""
|
|
28872
|
+
].join(`
|
|
28873
|
+
`));
|
|
28874
|
+
return;
|
|
28875
|
+
}
|
|
27152
28876
|
const { run: handler } = await Promise.resolve().then(() => (init_stop(), exports_stop));
|
|
27153
28877
|
return handler();
|
|
27154
28878
|
}
|
|
28879
|
+
if (sub === "quickstart") {
|
|
28880
|
+
const { run: handler } = await Promise.resolve().then(() => exports_quickstart);
|
|
28881
|
+
return handler();
|
|
28882
|
+
}
|
|
28883
|
+
if (sub === "doctor") {
|
|
28884
|
+
if (wantsHelp()) {
|
|
28885
|
+
console.log([
|
|
28886
|
+
"",
|
|
28887
|
+
"Usage: specialists doctor",
|
|
28888
|
+
"",
|
|
28889
|
+
"Diagnose bootstrap and runtime problems.",
|
|
28890
|
+
"",
|
|
28891
|
+
"Checks:",
|
|
28892
|
+
" 1. pi installed and has active providers",
|
|
28893
|
+
" 2. beads installed and .beads/ present",
|
|
28894
|
+
" 3. xtrm-tools availability",
|
|
28895
|
+
" 4. Specialists MCP registration in .mcp.json",
|
|
28896
|
+
" 5. .specialists/ runtime directories",
|
|
28897
|
+
" 6. hook wiring expectations",
|
|
28898
|
+
" 7. zombie job detection",
|
|
28899
|
+
"",
|
|
28900
|
+
"Behavior:",
|
|
28901
|
+
" - prints fix hints for failing checks",
|
|
28902
|
+
" - auto-creates missing runtime directories when possible",
|
|
28903
|
+
"",
|
|
28904
|
+
"Examples:",
|
|
28905
|
+
" specialists doctor",
|
|
28906
|
+
""
|
|
28907
|
+
].join(`
|
|
28908
|
+
`));
|
|
28909
|
+
return;
|
|
28910
|
+
}
|
|
28911
|
+
const { run: handler } = await Promise.resolve().then(() => (init_doctor(), exports_doctor));
|
|
28912
|
+
return handler();
|
|
28913
|
+
}
|
|
28914
|
+
if (sub === "setup") {
|
|
28915
|
+
if (wantsHelp()) {
|
|
28916
|
+
console.log([
|
|
28917
|
+
"",
|
|
28918
|
+
"Usage: specialists setup [options]",
|
|
28919
|
+
"",
|
|
28920
|
+
"Inject the Specialists Workflow context block into AGENTS.md or CLAUDE.md.",
|
|
28921
|
+
"This teaches agents in that project how to use specialists.",
|
|
28922
|
+
"",
|
|
28923
|
+
"Options:",
|
|
28924
|
+
" --project, -p Write to ./CLAUDE.md (default)",
|
|
28925
|
+
" --agents, -a Write to ./AGENTS.md",
|
|
28926
|
+
" --global, -g Write to ~/.claude/CLAUDE.md",
|
|
28927
|
+
" --dry-run Preview the block without writing",
|
|
28928
|
+
"",
|
|
28929
|
+
"Examples:",
|
|
28930
|
+
" specialists setup # → ./CLAUDE.md",
|
|
28931
|
+
" specialists setup --agents # → ./AGENTS.md",
|
|
28932
|
+
" specialists setup --global # → ~/.claude/CLAUDE.md",
|
|
28933
|
+
" specialists setup --dry-run # preview only",
|
|
28934
|
+
""
|
|
28935
|
+
].join(`
|
|
28936
|
+
`));
|
|
28937
|
+
return;
|
|
28938
|
+
}
|
|
28939
|
+
const { run: handler } = await Promise.resolve().then(() => (init_setup(), exports_setup));
|
|
28940
|
+
return handler();
|
|
28941
|
+
}
|
|
27155
28942
|
if (sub === "help" || sub === "--help" || sub === "-h") {
|
|
27156
28943
|
const { run: handler } = await Promise.resolve().then(() => (init_help(), exports_help));
|
|
27157
28944
|
return handler();
|
|
@@ -27165,7 +28952,7 @@ Run 'specialists help' to see available commands.`);
|
|
|
27165
28952
|
const server = new SpecialistsServer;
|
|
27166
28953
|
await server.start();
|
|
27167
28954
|
}
|
|
27168
|
-
|
|
28955
|
+
run16().catch((error2) => {
|
|
27169
28956
|
logger.error(`Fatal error: ${error2}`);
|
|
27170
28957
|
process.exit(1);
|
|
27171
28958
|
});
|