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