@jaggerxtrm/specialists 3.4.1 → 3.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +463 -189
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19111,7 +19111,8 @@ class Supervisor {
|
|
|
19111
19111
|
status: "starting",
|
|
19112
19112
|
started_at_ms: startedAtMs,
|
|
19113
19113
|
pid: process.pid,
|
|
19114
|
-
...runOptions.inputBeadId ? { bead_id: runOptions.inputBeadId } : {}
|
|
19114
|
+
...runOptions.inputBeadId ? { bead_id: runOptions.inputBeadId } : {},
|
|
19115
|
+
...process.env.SPECIALISTS_TMUX_SESSION ? { tmux_session: process.env.SPECIALISTS_TMUX_SESSION } : {}
|
|
19115
19116
|
};
|
|
19116
19117
|
this.writeStatusFile(id, initialStatus);
|
|
19117
19118
|
writeFileSync(join3(this.opts.jobsDir, "latest"), `${id}
|
|
@@ -19381,6 +19382,9 @@ class Supervisor {
|
|
|
19381
19382
|
if (existsSync4(fifoPath))
|
|
19382
19383
|
rmSync(fifoPath);
|
|
19383
19384
|
} catch {}
|
|
19385
|
+
if (statusSnapshot.tmux_session) {
|
|
19386
|
+
spawnSync3("tmux", ["kill-session", "-t", statusSnapshot.tmux_session], { stdio: "ignore" });
|
|
19387
|
+
}
|
|
19384
19388
|
}
|
|
19385
19389
|
}
|
|
19386
19390
|
}
|
|
@@ -19577,6 +19581,132 @@ __export(exports_list, {
|
|
|
19577
19581
|
parseArgs: () => parseArgs,
|
|
19578
19582
|
ArgParseError: () => ArgParseError
|
|
19579
19583
|
});
|
|
19584
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
19585
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync5 } from "node:fs";
|
|
19586
|
+
import { join as join13 } from "node:path";
|
|
19587
|
+
import readline from "node:readline";
|
|
19588
|
+
function toLiveJob(status) {
|
|
19589
|
+
if (!status)
|
|
19590
|
+
return null;
|
|
19591
|
+
if (status.status !== "running" && status.status !== "waiting" || !status.tmux_session) {
|
|
19592
|
+
return null;
|
|
19593
|
+
}
|
|
19594
|
+
const elapsedS = status.elapsed_s ?? Math.max(0, Math.floor((Date.now() - status.started_at_ms) / 1000));
|
|
19595
|
+
return {
|
|
19596
|
+
id: status.id,
|
|
19597
|
+
specialist: status.specialist,
|
|
19598
|
+
status: status.status,
|
|
19599
|
+
tmuxSession: status.tmux_session,
|
|
19600
|
+
elapsedS,
|
|
19601
|
+
startedAtMs: status.started_at_ms
|
|
19602
|
+
};
|
|
19603
|
+
}
|
|
19604
|
+
function readJobStatus(statusPath) {
|
|
19605
|
+
try {
|
|
19606
|
+
return JSON.parse(readFileSync5(statusPath, "utf-8"));
|
|
19607
|
+
} catch {
|
|
19608
|
+
return null;
|
|
19609
|
+
}
|
|
19610
|
+
}
|
|
19611
|
+
function listLiveJobs() {
|
|
19612
|
+
const jobsDir = join13(process.cwd(), ".specialists", "jobs");
|
|
19613
|
+
if (!existsSync9(jobsDir))
|
|
19614
|
+
return [];
|
|
19615
|
+
const jobs = readdirSync3(jobsDir).map((entry) => toLiveJob(readJobStatus(join13(jobsDir, entry, "status.json")))).filter((job) => job !== null).sort((a, b) => b.startedAtMs - a.startedAtMs);
|
|
19616
|
+
return jobs;
|
|
19617
|
+
}
|
|
19618
|
+
function formatLiveChoice(job) {
|
|
19619
|
+
return `${job.tmuxSession} ${job.specialist} ${job.elapsedS}s ${job.status}`;
|
|
19620
|
+
}
|
|
19621
|
+
function renderLiveSelector(jobs, selectedIndex) {
|
|
19622
|
+
return [
|
|
19623
|
+
"",
|
|
19624
|
+
bold2("Select tmux session (↑/↓, Enter to attach, Ctrl+C to cancel)"),
|
|
19625
|
+
"",
|
|
19626
|
+
...jobs.map((job, index) => `${index === selectedIndex ? cyan("❯") : " "} ${formatLiveChoice(job)}`),
|
|
19627
|
+
""
|
|
19628
|
+
];
|
|
19629
|
+
}
|
|
19630
|
+
function selectLiveJob(jobs) {
|
|
19631
|
+
return new Promise((resolve2) => {
|
|
19632
|
+
const input = process.stdin;
|
|
19633
|
+
const output = process.stdout;
|
|
19634
|
+
const wasRawMode = input.isTTY ? input.isRaw : false;
|
|
19635
|
+
let selectedIndex = 0;
|
|
19636
|
+
let renderedLineCount = 0;
|
|
19637
|
+
const cleanup = (selected) => {
|
|
19638
|
+
input.off("keypress", onKeypress);
|
|
19639
|
+
if (input.isTTY && !wasRawMode) {
|
|
19640
|
+
input.setRawMode(false);
|
|
19641
|
+
}
|
|
19642
|
+
output.write("\x1B[?25h");
|
|
19643
|
+
if (renderedLineCount > 0) {
|
|
19644
|
+
readline.moveCursor(output, 0, -renderedLineCount);
|
|
19645
|
+
readline.clearScreenDown(output);
|
|
19646
|
+
}
|
|
19647
|
+
resolve2(selected);
|
|
19648
|
+
};
|
|
19649
|
+
const render = () => {
|
|
19650
|
+
if (renderedLineCount > 0) {
|
|
19651
|
+
readline.moveCursor(output, 0, -renderedLineCount);
|
|
19652
|
+
readline.clearScreenDown(output);
|
|
19653
|
+
}
|
|
19654
|
+
const lines = renderLiveSelector(jobs, selectedIndex);
|
|
19655
|
+
output.write(lines.join(`
|
|
19656
|
+
`));
|
|
19657
|
+
renderedLineCount = lines.length;
|
|
19658
|
+
};
|
|
19659
|
+
const onKeypress = (_, key) => {
|
|
19660
|
+
if (key.ctrl && key.name === "c") {
|
|
19661
|
+
cleanup(null);
|
|
19662
|
+
return;
|
|
19663
|
+
}
|
|
19664
|
+
if (key.name === "up") {
|
|
19665
|
+
selectedIndex = (selectedIndex - 1 + jobs.length) % jobs.length;
|
|
19666
|
+
render();
|
|
19667
|
+
return;
|
|
19668
|
+
}
|
|
19669
|
+
if (key.name === "down") {
|
|
19670
|
+
selectedIndex = (selectedIndex + 1) % jobs.length;
|
|
19671
|
+
render();
|
|
19672
|
+
return;
|
|
19673
|
+
}
|
|
19674
|
+
if (key.name === "return") {
|
|
19675
|
+
cleanup(jobs[selectedIndex]);
|
|
19676
|
+
}
|
|
19677
|
+
};
|
|
19678
|
+
readline.emitKeypressEvents(input);
|
|
19679
|
+
if (input.isTTY && !wasRawMode) {
|
|
19680
|
+
input.setRawMode(true);
|
|
19681
|
+
}
|
|
19682
|
+
output.write("\x1B[?25l");
|
|
19683
|
+
input.on("keypress", onKeypress);
|
|
19684
|
+
render();
|
|
19685
|
+
});
|
|
19686
|
+
}
|
|
19687
|
+
async function runLiveMode() {
|
|
19688
|
+
const jobs = listLiveJobs();
|
|
19689
|
+
if (jobs.length === 0) {
|
|
19690
|
+
console.log("No running tmux sessions found.");
|
|
19691
|
+
return;
|
|
19692
|
+
}
|
|
19693
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
19694
|
+
for (const job of jobs) {
|
|
19695
|
+
console.log(`${job.id} ${job.tmuxSession} ${job.status}`);
|
|
19696
|
+
}
|
|
19697
|
+
return;
|
|
19698
|
+
}
|
|
19699
|
+
const selected = await selectLiveJob(jobs);
|
|
19700
|
+
if (!selected)
|
|
19701
|
+
return;
|
|
19702
|
+
const attach = spawnSync5("tmux", ["attach-session", "-t", selected.tmuxSession], {
|
|
19703
|
+
stdio: "inherit"
|
|
19704
|
+
});
|
|
19705
|
+
if (attach.error) {
|
|
19706
|
+
console.error(`Failed to attach tmux session ${selected.tmuxSession}: ${attach.error.message}`);
|
|
19707
|
+
process.exit(1);
|
|
19708
|
+
}
|
|
19709
|
+
}
|
|
19580
19710
|
function parseArgs(argv) {
|
|
19581
19711
|
const result = {};
|
|
19582
19712
|
for (let i = 0;i < argv.length; i++) {
|
|
@@ -19601,6 +19731,10 @@ function parseArgs(argv) {
|
|
|
19601
19731
|
result.json = true;
|
|
19602
19732
|
continue;
|
|
19603
19733
|
}
|
|
19734
|
+
if (token === "--live") {
|
|
19735
|
+
result.live = true;
|
|
19736
|
+
continue;
|
|
19737
|
+
}
|
|
19604
19738
|
}
|
|
19605
19739
|
return result;
|
|
19606
19740
|
}
|
|
@@ -19615,6 +19749,10 @@ async function run3() {
|
|
|
19615
19749
|
}
|
|
19616
19750
|
throw err;
|
|
19617
19751
|
}
|
|
19752
|
+
if (args.live) {
|
|
19753
|
+
await runLiveMode();
|
|
19754
|
+
return;
|
|
19755
|
+
}
|
|
19618
19756
|
const loader = new SpecialistLoader;
|
|
19619
19757
|
let specialists = await loader.list(args.category);
|
|
19620
19758
|
if (args.scope) {
|
|
@@ -19658,9 +19796,9 @@ var exports_models = {};
|
|
|
19658
19796
|
__export(exports_models, {
|
|
19659
19797
|
run: () => run4
|
|
19660
19798
|
});
|
|
19661
|
-
import { spawnSync as
|
|
19799
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
19662
19800
|
function parsePiModels() {
|
|
19663
|
-
const r =
|
|
19801
|
+
const r = spawnSync6("pi", ["--list-models"], {
|
|
19664
19802
|
encoding: "utf8",
|
|
19665
19803
|
stdio: "pipe",
|
|
19666
19804
|
timeout: 8000
|
|
@@ -19763,8 +19901,8 @@ var exports_init = {};
|
|
|
19763
19901
|
__export(exports_init, {
|
|
19764
19902
|
run: () => run5
|
|
19765
19903
|
});
|
|
19766
|
-
import { copyFileSync, cpSync, existsSync as
|
|
19767
|
-
import { join as
|
|
19904
|
+
import { copyFileSync, cpSync, existsSync as existsSync10, mkdirSync as mkdirSync2, readdirSync as readdirSync4, readFileSync as readFileSync6, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
19905
|
+
import { join as join14 } from "node:path";
|
|
19768
19906
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
19769
19907
|
function ok(msg) {
|
|
19770
19908
|
console.log(` ${green3("✓")} ${msg}`);
|
|
@@ -19773,10 +19911,10 @@ function skip(msg) {
|
|
|
19773
19911
|
console.log(` ${yellow4("○")} ${msg}`);
|
|
19774
19912
|
}
|
|
19775
19913
|
function loadJson(path, fallback) {
|
|
19776
|
-
if (!
|
|
19914
|
+
if (!existsSync10(path))
|
|
19777
19915
|
return structuredClone(fallback);
|
|
19778
19916
|
try {
|
|
19779
|
-
return JSON.parse(
|
|
19917
|
+
return JSON.parse(readFileSync6(path, "utf-8"));
|
|
19780
19918
|
} catch {
|
|
19781
19919
|
return structuredClone(fallback);
|
|
19782
19920
|
}
|
|
@@ -19788,30 +19926,30 @@ function saveJson(path, value) {
|
|
|
19788
19926
|
function resolvePackagePath(relativePath) {
|
|
19789
19927
|
const configPath = `config/${relativePath}`;
|
|
19790
19928
|
let resolved = fileURLToPath2(new URL(`../${configPath}`, import.meta.url));
|
|
19791
|
-
if (
|
|
19929
|
+
if (existsSync10(resolved))
|
|
19792
19930
|
return resolved;
|
|
19793
19931
|
resolved = fileURLToPath2(new URL(`../../${configPath}`, import.meta.url));
|
|
19794
|
-
if (
|
|
19932
|
+
if (existsSync10(resolved))
|
|
19795
19933
|
return resolved;
|
|
19796
19934
|
return null;
|
|
19797
19935
|
}
|
|
19798
19936
|
function migrateLegacySpecialists(cwd, scope) {
|
|
19799
|
-
const sourceDir =
|
|
19800
|
-
if (!
|
|
19937
|
+
const sourceDir = join14(cwd, ".specialists", scope, "specialists");
|
|
19938
|
+
if (!existsSync10(sourceDir))
|
|
19801
19939
|
return;
|
|
19802
|
-
const targetDir =
|
|
19803
|
-
if (!
|
|
19940
|
+
const targetDir = join14(cwd, ".specialists", scope);
|
|
19941
|
+
if (!existsSync10(targetDir)) {
|
|
19804
19942
|
mkdirSync2(targetDir, { recursive: true });
|
|
19805
19943
|
}
|
|
19806
|
-
const files =
|
|
19944
|
+
const files = readdirSync4(sourceDir).filter((f) => f.endsWith(".specialist.yaml"));
|
|
19807
19945
|
if (files.length === 0)
|
|
19808
19946
|
return;
|
|
19809
19947
|
let moved = 0;
|
|
19810
19948
|
let skipped = 0;
|
|
19811
19949
|
for (const file of files) {
|
|
19812
|
-
const src =
|
|
19813
|
-
const dest =
|
|
19814
|
-
if (
|
|
19950
|
+
const src = join14(sourceDir, file);
|
|
19951
|
+
const dest = join14(targetDir, file);
|
|
19952
|
+
if (existsSync10(dest)) {
|
|
19815
19953
|
skipped++;
|
|
19816
19954
|
continue;
|
|
19817
19955
|
}
|
|
@@ -19831,21 +19969,21 @@ function copyCanonicalSpecialists(cwd) {
|
|
|
19831
19969
|
skip("no canonical specialists found in package");
|
|
19832
19970
|
return;
|
|
19833
19971
|
}
|
|
19834
|
-
const targetDir =
|
|
19835
|
-
const files =
|
|
19972
|
+
const targetDir = join14(cwd, ".specialists", "default");
|
|
19973
|
+
const files = readdirSync4(sourceDir).filter((f) => f.endsWith(".specialist.yaml"));
|
|
19836
19974
|
if (files.length === 0) {
|
|
19837
19975
|
skip("no specialist files found in package");
|
|
19838
19976
|
return;
|
|
19839
19977
|
}
|
|
19840
|
-
if (!
|
|
19978
|
+
if (!existsSync10(targetDir)) {
|
|
19841
19979
|
mkdirSync2(targetDir, { recursive: true });
|
|
19842
19980
|
}
|
|
19843
19981
|
let copied = 0;
|
|
19844
19982
|
let skipped = 0;
|
|
19845
19983
|
for (const file of files) {
|
|
19846
|
-
const src =
|
|
19847
|
-
const dest =
|
|
19848
|
-
if (
|
|
19984
|
+
const src = join14(sourceDir, file);
|
|
19985
|
+
const dest = join14(targetDir, file);
|
|
19986
|
+
if (existsSync10(dest)) {
|
|
19849
19987
|
skipped++;
|
|
19850
19988
|
} else {
|
|
19851
19989
|
copyFileSync(src, dest);
|
|
@@ -19865,21 +20003,21 @@ function installProjectHooks(cwd) {
|
|
|
19865
20003
|
skip("no canonical hooks found in package");
|
|
19866
20004
|
return;
|
|
19867
20005
|
}
|
|
19868
|
-
const targetDir =
|
|
19869
|
-
const hooks =
|
|
20006
|
+
const targetDir = join14(cwd, ".claude", "hooks");
|
|
20007
|
+
const hooks = readdirSync4(sourceDir).filter((f) => f.endsWith(".mjs"));
|
|
19870
20008
|
if (hooks.length === 0) {
|
|
19871
20009
|
skip("no hook files found in package");
|
|
19872
20010
|
return;
|
|
19873
20011
|
}
|
|
19874
|
-
if (!
|
|
20012
|
+
if (!existsSync10(targetDir)) {
|
|
19875
20013
|
mkdirSync2(targetDir, { recursive: true });
|
|
19876
20014
|
}
|
|
19877
20015
|
let copied = 0;
|
|
19878
20016
|
let skipped = 0;
|
|
19879
20017
|
for (const file of hooks) {
|
|
19880
|
-
const src =
|
|
19881
|
-
const dest =
|
|
19882
|
-
if (
|
|
20018
|
+
const src = join14(sourceDir, file);
|
|
20019
|
+
const dest = join14(targetDir, file);
|
|
20020
|
+
if (existsSync10(dest)) {
|
|
19883
20021
|
skipped++;
|
|
19884
20022
|
} else {
|
|
19885
20023
|
copyFileSync(src, dest);
|
|
@@ -19894,9 +20032,9 @@ function installProjectHooks(cwd) {
|
|
|
19894
20032
|
}
|
|
19895
20033
|
}
|
|
19896
20034
|
function ensureProjectHookWiring(cwd) {
|
|
19897
|
-
const settingsPath =
|
|
19898
|
-
const settingsDir =
|
|
19899
|
-
if (!
|
|
20035
|
+
const settingsPath = join14(cwd, ".claude", "settings.json");
|
|
20036
|
+
const settingsDir = join14(cwd, ".claude");
|
|
20037
|
+
if (!existsSync10(settingsDir)) {
|
|
19900
20038
|
mkdirSync2(settingsDir, { recursive: true });
|
|
19901
20039
|
}
|
|
19902
20040
|
const settings = loadJson(settingsPath, {});
|
|
@@ -19925,25 +20063,25 @@ function installProjectSkills(cwd) {
|
|
|
19925
20063
|
skip("no canonical skills found in package");
|
|
19926
20064
|
return;
|
|
19927
20065
|
}
|
|
19928
|
-
const skills =
|
|
20066
|
+
const skills = readdirSync4(sourceDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
19929
20067
|
if (skills.length === 0) {
|
|
19930
20068
|
skip("no skill directories found in package");
|
|
19931
20069
|
return;
|
|
19932
20070
|
}
|
|
19933
20071
|
const targetDirs = [
|
|
19934
|
-
|
|
19935
|
-
|
|
20072
|
+
join14(cwd, ".claude", "skills"),
|
|
20073
|
+
join14(cwd, ".pi", "skills")
|
|
19936
20074
|
];
|
|
19937
20075
|
let totalCopied = 0;
|
|
19938
20076
|
let totalSkipped = 0;
|
|
19939
20077
|
for (const targetDir of targetDirs) {
|
|
19940
|
-
if (!
|
|
20078
|
+
if (!existsSync10(targetDir)) {
|
|
19941
20079
|
mkdirSync2(targetDir, { recursive: true });
|
|
19942
20080
|
}
|
|
19943
20081
|
for (const skill of skills) {
|
|
19944
|
-
const src =
|
|
19945
|
-
const dest =
|
|
19946
|
-
if (
|
|
20082
|
+
const src = join14(sourceDir, skill);
|
|
20083
|
+
const dest = join14(targetDir, skill);
|
|
20084
|
+
if (existsSync10(dest)) {
|
|
19947
20085
|
totalSkipped++;
|
|
19948
20086
|
} else {
|
|
19949
20087
|
cpSync(src, dest, { recursive: true });
|
|
@@ -19959,20 +20097,20 @@ function installProjectSkills(cwd) {
|
|
|
19959
20097
|
}
|
|
19960
20098
|
}
|
|
19961
20099
|
function createUserDirs(cwd) {
|
|
19962
|
-
const userDir =
|
|
19963
|
-
if (!
|
|
20100
|
+
const userDir = join14(cwd, ".specialists", "user");
|
|
20101
|
+
if (!existsSync10(userDir)) {
|
|
19964
20102
|
mkdirSync2(userDir, { recursive: true });
|
|
19965
20103
|
ok("created .specialists/user/ for custom specialists");
|
|
19966
20104
|
}
|
|
19967
20105
|
}
|
|
19968
20106
|
function createRuntimeDirs(cwd) {
|
|
19969
20107
|
const runtimeDirs = [
|
|
19970
|
-
|
|
19971
|
-
|
|
20108
|
+
join14(cwd, ".specialists", "jobs"),
|
|
20109
|
+
join14(cwd, ".specialists", "ready")
|
|
19972
20110
|
];
|
|
19973
20111
|
let created = 0;
|
|
19974
20112
|
for (const dir of runtimeDirs) {
|
|
19975
|
-
if (!
|
|
20113
|
+
if (!existsSync10(dir)) {
|
|
19976
20114
|
mkdirSync2(dir, { recursive: true });
|
|
19977
20115
|
created++;
|
|
19978
20116
|
}
|
|
@@ -19982,7 +20120,7 @@ function createRuntimeDirs(cwd) {
|
|
|
19982
20120
|
}
|
|
19983
20121
|
}
|
|
19984
20122
|
function ensureProjectMcp(cwd) {
|
|
19985
|
-
const mcpPath =
|
|
20123
|
+
const mcpPath = join14(cwd, MCP_FILE);
|
|
19986
20124
|
const mcp = loadJson(mcpPath, { mcpServers: {} });
|
|
19987
20125
|
mcp.mcpServers ??= {};
|
|
19988
20126
|
const existing = mcp.mcpServers[MCP_SERVER_NAME];
|
|
@@ -19995,8 +20133,8 @@ function ensureProjectMcp(cwd) {
|
|
|
19995
20133
|
ok("registered specialists in project .mcp.json");
|
|
19996
20134
|
}
|
|
19997
20135
|
function ensureGitignore(cwd) {
|
|
19998
|
-
const gitignorePath =
|
|
19999
|
-
const existing =
|
|
20136
|
+
const gitignorePath = join14(cwd, ".gitignore");
|
|
20137
|
+
const existing = existsSync10(gitignorePath) ? readFileSync6(gitignorePath, "utf-8") : "";
|
|
20000
20138
|
let added = 0;
|
|
20001
20139
|
const lines = existing.split(`
|
|
20002
20140
|
`);
|
|
@@ -20016,9 +20154,9 @@ function ensureGitignore(cwd) {
|
|
|
20016
20154
|
}
|
|
20017
20155
|
}
|
|
20018
20156
|
function ensureAgentsMd(cwd) {
|
|
20019
|
-
const agentsPath =
|
|
20020
|
-
if (
|
|
20021
|
-
const existing =
|
|
20157
|
+
const agentsPath = join14(cwd, "AGENTS.md");
|
|
20158
|
+
if (existsSync10(agentsPath)) {
|
|
20159
|
+
const existing = readFileSync6(agentsPath, "utf-8");
|
|
20022
20160
|
if (existing.includes(AGENTS_MARKER)) {
|
|
20023
20161
|
skip("AGENTS.md already has Specialists section");
|
|
20024
20162
|
} else {
|
|
@@ -20111,8 +20249,8 @@ __export(exports_validate, {
|
|
|
20111
20249
|
ArgParseError: () => ArgParseError2
|
|
20112
20250
|
});
|
|
20113
20251
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
20114
|
-
import { existsSync as
|
|
20115
|
-
import { join as
|
|
20252
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
20253
|
+
import { join as join15 } from "node:path";
|
|
20116
20254
|
function parseArgs3(argv) {
|
|
20117
20255
|
const name = argv[0];
|
|
20118
20256
|
if (!name || name.startsWith("--")) {
|
|
@@ -20123,15 +20261,15 @@ function parseArgs3(argv) {
|
|
|
20123
20261
|
}
|
|
20124
20262
|
function findSpecialistFile(name) {
|
|
20125
20263
|
const scanDirs = [
|
|
20126
|
-
|
|
20127
|
-
|
|
20128
|
-
|
|
20129
|
-
|
|
20130
|
-
|
|
20264
|
+
join15(process.cwd(), ".specialists", "user"),
|
|
20265
|
+
join15(process.cwd(), ".specialists", "user", "specialists"),
|
|
20266
|
+
join15(process.cwd(), ".specialists", "default"),
|
|
20267
|
+
join15(process.cwd(), ".specialists", "default", "specialists"),
|
|
20268
|
+
join15(process.cwd(), "specialists")
|
|
20131
20269
|
];
|
|
20132
20270
|
for (const dir of scanDirs) {
|
|
20133
|
-
const candidate =
|
|
20134
|
-
if (
|
|
20271
|
+
const candidate = join15(dir, `${name}.specialist.yaml`);
|
|
20272
|
+
if (existsSync11(candidate)) {
|
|
20135
20273
|
return candidate;
|
|
20136
20274
|
}
|
|
20137
20275
|
}
|
|
@@ -20223,9 +20361,9 @@ var exports_edit = {};
|
|
|
20223
20361
|
__export(exports_edit, {
|
|
20224
20362
|
run: () => run7
|
|
20225
20363
|
});
|
|
20226
|
-
import { spawnSync as
|
|
20227
|
-
import { existsSync as
|
|
20228
|
-
import { join as
|
|
20364
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
20365
|
+
import { existsSync as existsSync12, readdirSync as readdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
20366
|
+
import { join as join16 } from "node:path";
|
|
20229
20367
|
function parseArgs4(argv) {
|
|
20230
20368
|
const name = argv[0];
|
|
20231
20369
|
if (!name || name.startsWith("--")) {
|
|
@@ -20289,18 +20427,18 @@ function setIn(doc2, path, value) {
|
|
|
20289
20427
|
}
|
|
20290
20428
|
}
|
|
20291
20429
|
function openAllConfigSpecialistsInEditor() {
|
|
20292
|
-
const configDir =
|
|
20293
|
-
if (!
|
|
20430
|
+
const configDir = join16(process.cwd(), "config", "specialists");
|
|
20431
|
+
if (!existsSync12(configDir)) {
|
|
20294
20432
|
console.error(`Error: missing directory: ${configDir}`);
|
|
20295
20433
|
process.exit(1);
|
|
20296
20434
|
}
|
|
20297
|
-
const files =
|
|
20435
|
+
const files = readdirSync5(configDir).filter((file) => file.endsWith(".specialist.yaml")).sort().map((file) => join16(configDir, file));
|
|
20298
20436
|
if (files.length === 0) {
|
|
20299
20437
|
console.error("Error: no specialist YAML files found in config/specialists/");
|
|
20300
20438
|
process.exit(1);
|
|
20301
20439
|
}
|
|
20302
20440
|
const editor = process.env.VISUAL ?? process.env.EDITOR ?? "vi";
|
|
20303
|
-
const result =
|
|
20441
|
+
const result = spawnSync7(editor, files, { stdio: "inherit", shell: true });
|
|
20304
20442
|
if (result.status !== 0) {
|
|
20305
20443
|
process.exit(result.status ?? 1);
|
|
20306
20444
|
}
|
|
@@ -20322,7 +20460,7 @@ async function run7() {
|
|
|
20322
20460
|
console.error(` Run ${yellow6("specialists list")} to see available specialists`);
|
|
20323
20461
|
process.exit(1);
|
|
20324
20462
|
}
|
|
20325
|
-
const raw =
|
|
20463
|
+
const raw = readFileSync7(match.filePath, "utf-8");
|
|
20326
20464
|
const doc2 = $parseDocument(raw);
|
|
20327
20465
|
const yamlPath = FIELD_MAP[field];
|
|
20328
20466
|
let typedValue = value;
|
|
@@ -20377,9 +20515,9 @@ var exports_config = {};
|
|
|
20377
20515
|
__export(exports_config, {
|
|
20378
20516
|
run: () => run8
|
|
20379
20517
|
});
|
|
20380
|
-
import { existsSync as
|
|
20518
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
20381
20519
|
import { readdir as readdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
20382
|
-
import { basename as basename2, join as
|
|
20520
|
+
import { basename as basename2, join as join17 } from "node:path";
|
|
20383
20521
|
function usage() {
|
|
20384
20522
|
return [
|
|
20385
20523
|
"Usage:",
|
|
@@ -20449,22 +20587,22 @@ function splitKeyPath(key) {
|
|
|
20449
20587
|
return path;
|
|
20450
20588
|
}
|
|
20451
20589
|
function getSpecialistDir(projectDir) {
|
|
20452
|
-
return
|
|
20590
|
+
return join17(projectDir, "config", "specialists");
|
|
20453
20591
|
}
|
|
20454
20592
|
function getSpecialistNameFromPath(path) {
|
|
20455
20593
|
return path.replace(/\.specialist\.yaml$/, "");
|
|
20456
20594
|
}
|
|
20457
20595
|
async function listSpecialistFiles(projectDir) {
|
|
20458
20596
|
const specialistDir = getSpecialistDir(projectDir);
|
|
20459
|
-
if (!
|
|
20597
|
+
if (!existsSync13(specialistDir)) {
|
|
20460
20598
|
throw new Error(`Missing directory: ${specialistDir}`);
|
|
20461
20599
|
}
|
|
20462
20600
|
const entries = await readdir2(specialistDir);
|
|
20463
|
-
return entries.filter((entry) => entry.endsWith(".specialist.yaml")).sort((a, b) => a.localeCompare(b)).map((entry) =>
|
|
20601
|
+
return entries.filter((entry) => entry.endsWith(".specialist.yaml")).sort((a, b) => a.localeCompare(b)).map((entry) => join17(specialistDir, entry));
|
|
20464
20602
|
}
|
|
20465
20603
|
async function findNamedSpecialistFile(projectDir, name) {
|
|
20466
|
-
const path =
|
|
20467
|
-
if (!
|
|
20604
|
+
const path = join17(getSpecialistDir(projectDir), `${name}.specialist.yaml`);
|
|
20605
|
+
if (!existsSync13(path)) {
|
|
20468
20606
|
throw new Error(`Specialist not found in config/specialists/: ${name}`);
|
|
20469
20607
|
}
|
|
20470
20608
|
return path;
|
|
@@ -20693,13 +20831,47 @@ var init_format_helpers = __esm(() => {
|
|
|
20693
20831
|
};
|
|
20694
20832
|
});
|
|
20695
20833
|
|
|
20834
|
+
// src/cli/tmux-utils.ts
|
|
20835
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
20836
|
+
function escapeForSingleQuotedBash(script) {
|
|
20837
|
+
return script.replace(/'/g, "'\\''");
|
|
20838
|
+
}
|
|
20839
|
+
function quoteShellValue(value) {
|
|
20840
|
+
return `'${escapeForSingleQuotedBash(value)}'`;
|
|
20841
|
+
}
|
|
20842
|
+
function isTmuxAvailable() {
|
|
20843
|
+
return spawnSync8("which", ["tmux"], { encoding: "utf8", timeout: 2000 }).status === 0;
|
|
20844
|
+
}
|
|
20845
|
+
function buildSessionName(specialist, suffix) {
|
|
20846
|
+
return `${TMUX_SESSION_PREFIX}-${specialist}-${suffix}`;
|
|
20847
|
+
}
|
|
20848
|
+
function createTmuxSession(name, cwd, cmd, extraEnv = {}) {
|
|
20849
|
+
const exports = [
|
|
20850
|
+
"unset CLAUDECODE CLAUDE_CODE_SSE_PORT CLAUDE_CODE_ENTRYPOINT",
|
|
20851
|
+
`export SPECIALISTS_TMUX_SESSION=${quoteShellValue(name)}`
|
|
20852
|
+
];
|
|
20853
|
+
for (const [key, value] of Object.entries(extraEnv)) {
|
|
20854
|
+
exports.push(`export ${key}=${quoteShellValue(value)}`);
|
|
20855
|
+
}
|
|
20856
|
+
const startupScript = `${exports.join("; ")}; exec ${cmd}`;
|
|
20857
|
+
const wrappedCommand = `/bin/bash -c '${escapeForSingleQuotedBash(startupScript)}'`;
|
|
20858
|
+
const result = spawnSync8("tmux", ["new-session", "-d", "-s", name, "-c", cwd, wrappedCommand], { encoding: "utf8", stdio: "pipe" });
|
|
20859
|
+
if (result.status !== 0) {
|
|
20860
|
+
const errorOutput = (result.stderr ?? "").trim() || (result.error?.message ?? "unknown error");
|
|
20861
|
+
throw new Error(`Failed to create tmux session "${name}": ${errorOutput}`);
|
|
20862
|
+
}
|
|
20863
|
+
}
|
|
20864
|
+
var TMUX_SESSION_PREFIX = "sp";
|
|
20865
|
+
var init_tmux_utils = () => {};
|
|
20866
|
+
|
|
20696
20867
|
// src/cli/run.ts
|
|
20697
20868
|
var exports_run = {};
|
|
20698
20869
|
__export(exports_run, {
|
|
20699
20870
|
run: () => run9
|
|
20700
20871
|
});
|
|
20701
|
-
import { join as
|
|
20702
|
-
import { readFileSync as
|
|
20872
|
+
import { join as join18 } from "node:path";
|
|
20873
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
20874
|
+
import { randomBytes } from "node:crypto";
|
|
20703
20875
|
import { spawn as cpSpawn } from "node:child_process";
|
|
20704
20876
|
async function parseArgs6(argv) {
|
|
20705
20877
|
const name = argv[0];
|
|
@@ -20787,13 +20959,13 @@ async function parseArgs6(argv) {
|
|
|
20787
20959
|
return { name, prompt, beadId, model, noBeads, noBeadNotes, keepAlive, noKeepAlive, background, contextDepth, outputMode };
|
|
20788
20960
|
}
|
|
20789
20961
|
function startEventTailer(jobId, jobsDir, mode, specialist, beadId) {
|
|
20790
|
-
const eventsPath =
|
|
20962
|
+
const eventsPath = join18(jobsDir, jobId, "events.jsonl");
|
|
20791
20963
|
let linesRead = 0;
|
|
20792
20964
|
let activeInlinePhase = null;
|
|
20793
20965
|
const drain = () => {
|
|
20794
20966
|
let content;
|
|
20795
20967
|
try {
|
|
20796
|
-
content =
|
|
20968
|
+
content = readFileSync8(eventsPath, "utf-8");
|
|
20797
20969
|
} catch {
|
|
20798
20970
|
return;
|
|
20799
20971
|
}
|
|
@@ -20849,31 +21021,44 @@ function formatFooterModel(backend, model) {
|
|
|
20849
21021
|
return model;
|
|
20850
21022
|
return model.startsWith(`${backend}/`) ? model : `${backend}/${model}`;
|
|
20851
21023
|
}
|
|
21024
|
+
function shellQuote(value) {
|
|
21025
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
21026
|
+
}
|
|
20852
21027
|
async function run9() {
|
|
20853
21028
|
const args = await parseArgs6(process.argv.slice(3));
|
|
20854
21029
|
if (args.background) {
|
|
20855
|
-
const latestPath =
|
|
21030
|
+
const latestPath = join18(process.cwd(), ".specialists", "jobs", "latest");
|
|
20856
21031
|
const oldLatest = (() => {
|
|
20857
21032
|
try {
|
|
20858
|
-
return
|
|
21033
|
+
return readFileSync8(latestPath, "utf-8").trim();
|
|
20859
21034
|
} catch {
|
|
20860
21035
|
return "";
|
|
20861
21036
|
}
|
|
20862
21037
|
})();
|
|
20863
|
-
const
|
|
20864
|
-
const
|
|
20865
|
-
|
|
20866
|
-
|
|
20867
|
-
|
|
20868
|
-
|
|
20869
|
-
|
|
20870
|
-
|
|
21038
|
+
const cwd = process.cwd();
|
|
21039
|
+
const innerArgs = process.argv.slice(2).filter((a) => a !== "--background");
|
|
21040
|
+
const cmd = `${process.execPath} ${process.argv[1]} ${innerArgs.map(shellQuote).join(" ")}`;
|
|
21041
|
+
let childPid;
|
|
21042
|
+
if (isTmuxAvailable()) {
|
|
21043
|
+
const suffix = randomBytes(3).toString("hex");
|
|
21044
|
+
const sessionName = buildSessionName(args.name, suffix);
|
|
21045
|
+
createTmuxSession(sessionName, cwd, cmd);
|
|
21046
|
+
} else {
|
|
21047
|
+
const child = cpSpawn(process.execPath, [process.argv[1], ...innerArgs], {
|
|
21048
|
+
detached: true,
|
|
21049
|
+
stdio: "ignore",
|
|
21050
|
+
cwd,
|
|
21051
|
+
env: process.env
|
|
21052
|
+
});
|
|
21053
|
+
child.unref();
|
|
21054
|
+
childPid = child.pid;
|
|
21055
|
+
}
|
|
20871
21056
|
const deadline = Date.now() + 5000;
|
|
20872
21057
|
let jobId2 = "";
|
|
20873
21058
|
while (Date.now() < deadline) {
|
|
20874
21059
|
await new Promise((r) => setTimeout(r, 100));
|
|
20875
21060
|
try {
|
|
20876
|
-
const current =
|
|
21061
|
+
const current = readFileSync8(latestPath, "utf-8").trim();
|
|
20877
21062
|
if (current && current !== oldLatest) {
|
|
20878
21063
|
jobId2 = current;
|
|
20879
21064
|
break;
|
|
@@ -20886,14 +21071,14 @@ async function run9() {
|
|
|
20886
21071
|
} else {
|
|
20887
21072
|
process.stderr.write(`Warning: job started but ID not yet available. Check specialists status.
|
|
20888
21073
|
`);
|
|
20889
|
-
process.stdout.write(`${
|
|
21074
|
+
process.stdout.write(`${childPid ?? ""}
|
|
20890
21075
|
`);
|
|
20891
21076
|
}
|
|
20892
21077
|
process.exit(0);
|
|
20893
21078
|
}
|
|
20894
21079
|
const loader = new SpecialistLoader;
|
|
20895
21080
|
const circuitBreaker = new CircuitBreaker;
|
|
20896
|
-
const hooks = new HookEmitter({ tracePath:
|
|
21081
|
+
const hooks = new HookEmitter({ tracePath: join18(process.cwd(), ".specialists", "trace.jsonl") });
|
|
20897
21082
|
const beadsClient = args.noBeads ? undefined : new BeadsClient;
|
|
20898
21083
|
const beadReader = beadsClient ?? new BeadsClient;
|
|
20899
21084
|
let prompt = args.prompt;
|
|
@@ -20928,7 +21113,7 @@ async function run9() {
|
|
|
20928
21113
|
beadsClient
|
|
20929
21114
|
});
|
|
20930
21115
|
const beadsWriteNotes = args.noBeadNotes ? false : specialist.specialist.beads_write_notes ?? true;
|
|
20931
|
-
const jobsDir =
|
|
21116
|
+
const jobsDir = join18(process.cwd(), ".specialists", "jobs");
|
|
20932
21117
|
let stopTailer;
|
|
20933
21118
|
const supervisor = new Supervisor({
|
|
20934
21119
|
runner,
|
|
@@ -20999,6 +21184,7 @@ var init_run = __esm(() => {
|
|
|
20999
21184
|
init_beads();
|
|
21000
21185
|
init_supervisor();
|
|
21001
21186
|
init_format_helpers();
|
|
21187
|
+
init_tmux_utils();
|
|
21002
21188
|
});
|
|
21003
21189
|
|
|
21004
21190
|
// src/cli/status.ts
|
|
@@ -21006,9 +21192,9 @@ var exports_status = {};
|
|
|
21006
21192
|
__export(exports_status, {
|
|
21007
21193
|
run: () => run10
|
|
21008
21194
|
});
|
|
21009
|
-
import { spawnSync as
|
|
21010
|
-
import { existsSync as
|
|
21011
|
-
import { join as
|
|
21195
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
21196
|
+
import { existsSync as existsSync14, readFileSync as readFileSync9 } from "node:fs";
|
|
21197
|
+
import { join as join19 } from "node:path";
|
|
21012
21198
|
function ok2(msg) {
|
|
21013
21199
|
console.log(` ${green7("✓")} ${msg}`);
|
|
21014
21200
|
}
|
|
@@ -21027,7 +21213,7 @@ function section(label) {
|
|
|
21027
21213
|
${bold7(`── ${label} ${line}`)}`);
|
|
21028
21214
|
}
|
|
21029
21215
|
function cmd(bin, args) {
|
|
21030
|
-
const r =
|
|
21216
|
+
const r = spawnSync9(bin, args, {
|
|
21031
21217
|
encoding: "utf8",
|
|
21032
21218
|
stdio: "pipe",
|
|
21033
21219
|
timeout: 5000
|
|
@@ -21035,7 +21221,7 @@ function cmd(bin, args) {
|
|
|
21035
21221
|
return { ok: r.status === 0 && !r.error, stdout: (r.stdout ?? "").trim() };
|
|
21036
21222
|
}
|
|
21037
21223
|
function isInstalled(bin) {
|
|
21038
|
-
return
|
|
21224
|
+
return spawnSync9("which", [bin], { encoding: "utf8", timeout: 2000 }).status === 0;
|
|
21039
21225
|
}
|
|
21040
21226
|
function formatElapsed2(s) {
|
|
21041
21227
|
if (s.elapsed_s === undefined)
|
|
@@ -21087,10 +21273,10 @@ function parseStatusArgs(argv) {
|
|
|
21087
21273
|
return { jsonMode, jobId };
|
|
21088
21274
|
}
|
|
21089
21275
|
function countJobEvents(jobsDir, jobId) {
|
|
21090
|
-
const eventsFile =
|
|
21091
|
-
if (!
|
|
21276
|
+
const eventsFile = join19(jobsDir, jobId, "events.jsonl");
|
|
21277
|
+
if (!existsSync14(eventsFile))
|
|
21092
21278
|
return 0;
|
|
21093
|
-
const raw =
|
|
21279
|
+
const raw = readFileSync9(eventsFile, "utf-8").trim();
|
|
21094
21280
|
if (!raw)
|
|
21095
21281
|
return 0;
|
|
21096
21282
|
return raw.split(`
|
|
@@ -21133,12 +21319,12 @@ async function run10() {
|
|
|
21133
21319
|
`).slice(1).map((line) => line.split(/\s+/)[0]).filter(Boolean)) : new Set;
|
|
21134
21320
|
const bdInstalled = isInstalled("bd");
|
|
21135
21321
|
const bdVersion = bdInstalled ? cmd("bd", ["--version"]) : null;
|
|
21136
|
-
const beadsPresent =
|
|
21322
|
+
const beadsPresent = existsSync14(join19(process.cwd(), ".beads"));
|
|
21137
21323
|
const specialistsBin = cmd("which", ["specialists"]);
|
|
21138
|
-
const jobsDir =
|
|
21324
|
+
const jobsDir = join19(process.cwd(), ".specialists", "jobs");
|
|
21139
21325
|
let jobs = [];
|
|
21140
21326
|
let supervisor = null;
|
|
21141
|
-
if (
|
|
21327
|
+
if (existsSync14(jobsDir)) {
|
|
21142
21328
|
supervisor = new Supervisor({
|
|
21143
21329
|
runner: null,
|
|
21144
21330
|
runOptions: null,
|
|
@@ -21283,8 +21469,8 @@ var exports_result = {};
|
|
|
21283
21469
|
__export(exports_result, {
|
|
21284
21470
|
run: () => run11
|
|
21285
21471
|
});
|
|
21286
|
-
import { existsSync as
|
|
21287
|
-
import { join as
|
|
21472
|
+
import { existsSync as existsSync15, readFileSync as readFileSync10 } from "node:fs";
|
|
21473
|
+
import { join as join20 } from "node:path";
|
|
21288
21474
|
function parseArgs7(argv) {
|
|
21289
21475
|
const jobId = argv[0];
|
|
21290
21476
|
if (!jobId || jobId.startsWith("--")) {
|
|
@@ -21314,9 +21500,9 @@ function parseArgs7(argv) {
|
|
|
21314
21500
|
async function run11() {
|
|
21315
21501
|
const args = parseArgs7(process.argv.slice(3));
|
|
21316
21502
|
const { jobId } = args;
|
|
21317
|
-
const jobsDir =
|
|
21503
|
+
const jobsDir = join20(process.cwd(), ".specialists", "jobs");
|
|
21318
21504
|
const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
|
|
21319
|
-
const resultPath =
|
|
21505
|
+
const resultPath = join20(jobsDir, jobId, "result.txt");
|
|
21320
21506
|
if (args.wait) {
|
|
21321
21507
|
const startMs = Date.now();
|
|
21322
21508
|
while (true) {
|
|
@@ -21326,11 +21512,11 @@ async function run11() {
|
|
|
21326
21512
|
process.exit(1);
|
|
21327
21513
|
}
|
|
21328
21514
|
if (status2.status === "done") {
|
|
21329
|
-
if (!
|
|
21515
|
+
if (!existsSync15(resultPath)) {
|
|
21330
21516
|
console.error(`Result file not found for job ${jobId}`);
|
|
21331
21517
|
process.exit(1);
|
|
21332
21518
|
}
|
|
21333
|
-
process.stdout.write(
|
|
21519
|
+
process.stdout.write(readFileSync10(resultPath, "utf-8"));
|
|
21334
21520
|
return;
|
|
21335
21521
|
}
|
|
21336
21522
|
if (status2.status === "error") {
|
|
@@ -21355,14 +21541,14 @@ async function run11() {
|
|
|
21355
21541
|
process.exit(1);
|
|
21356
21542
|
}
|
|
21357
21543
|
if (status.status === "running" || status.status === "starting") {
|
|
21358
|
-
if (!
|
|
21544
|
+
if (!existsSync15(resultPath)) {
|
|
21359
21545
|
process.stderr.write(`${dim9(`Job ${jobId} is still ${status.status}. Use 'specialists feed --job ${jobId}' to follow.`)}
|
|
21360
21546
|
`);
|
|
21361
21547
|
process.exit(1);
|
|
21362
21548
|
}
|
|
21363
21549
|
process.stderr.write(`${dim9(`Job ${jobId} is currently ${status.status}. Showing last completed output while it continues.`)}
|
|
21364
21550
|
`);
|
|
21365
|
-
process.stdout.write(
|
|
21551
|
+
process.stdout.write(readFileSync10(resultPath, "utf-8"));
|
|
21366
21552
|
return;
|
|
21367
21553
|
}
|
|
21368
21554
|
if (status.status === "error") {
|
|
@@ -21370,11 +21556,11 @@ async function run11() {
|
|
|
21370
21556
|
`);
|
|
21371
21557
|
process.exit(1);
|
|
21372
21558
|
}
|
|
21373
|
-
if (!
|
|
21559
|
+
if (!existsSync15(resultPath)) {
|
|
21374
21560
|
console.error(`Result file not found for job ${jobId}`);
|
|
21375
21561
|
process.exit(1);
|
|
21376
21562
|
}
|
|
21377
|
-
process.stdout.write(
|
|
21563
|
+
process.stdout.write(readFileSync10(resultPath, "utf-8"));
|
|
21378
21564
|
}
|
|
21379
21565
|
var dim9 = (s) => `\x1B[2m${s}\x1B[0m`, red3 = (s) => `\x1B[31m${s}\x1B[0m`;
|
|
21380
21566
|
var init_result = __esm(() => {
|
|
@@ -21386,8 +21572,8 @@ var exports_feed = {};
|
|
|
21386
21572
|
__export(exports_feed, {
|
|
21387
21573
|
run: () => run12
|
|
21388
21574
|
});
|
|
21389
|
-
import { existsSync as
|
|
21390
|
-
import { join as
|
|
21575
|
+
import { existsSync as existsSync16, readFileSync as readFileSync11 } from "node:fs";
|
|
21576
|
+
import { join as join21 } from "node:path";
|
|
21391
21577
|
function getHumanEventKey(event) {
|
|
21392
21578
|
switch (event.type) {
|
|
21393
21579
|
case "meta":
|
|
@@ -21451,9 +21637,9 @@ function parseSince(value) {
|
|
|
21451
21637
|
return;
|
|
21452
21638
|
}
|
|
21453
21639
|
function isTerminalJobStatus(jobsDir, jobId) {
|
|
21454
|
-
const statusPath =
|
|
21640
|
+
const statusPath = join21(jobsDir, jobId, "status.json");
|
|
21455
21641
|
try {
|
|
21456
|
-
const status = JSON.parse(
|
|
21642
|
+
const status = JSON.parse(readFileSync11(statusPath, "utf-8"));
|
|
21457
21643
|
return status.status === "done" || status.status === "error";
|
|
21458
21644
|
} catch {
|
|
21459
21645
|
return false;
|
|
@@ -21464,10 +21650,10 @@ function makeJobMetaReader(jobsDir) {
|
|
|
21464
21650
|
return (jobId) => {
|
|
21465
21651
|
if (cache.has(jobId))
|
|
21466
21652
|
return cache.get(jobId);
|
|
21467
|
-
const statusPath =
|
|
21653
|
+
const statusPath = join21(jobsDir, jobId, "status.json");
|
|
21468
21654
|
let meta = { startedAtMs: Date.now() };
|
|
21469
21655
|
try {
|
|
21470
|
-
const status = JSON.parse(
|
|
21656
|
+
const status = JSON.parse(readFileSync11(statusPath, "utf-8"));
|
|
21471
21657
|
meta = {
|
|
21472
21658
|
model: status.model,
|
|
21473
21659
|
backend: status.backend,
|
|
@@ -21659,8 +21845,8 @@ async function followMerged(jobsDir, options) {
|
|
|
21659
21845
|
}
|
|
21660
21846
|
async function run12() {
|
|
21661
21847
|
const options = parseArgs8(process.argv.slice(3));
|
|
21662
|
-
const jobsDir =
|
|
21663
|
-
if (!
|
|
21848
|
+
const jobsDir = join21(process.cwd(), ".specialists", "jobs");
|
|
21849
|
+
if (!existsSync16(jobsDir)) {
|
|
21664
21850
|
console.log(dim7("No jobs directory found."));
|
|
21665
21851
|
return;
|
|
21666
21852
|
}
|
|
@@ -21687,8 +21873,8 @@ var exports_poll = {};
|
|
|
21687
21873
|
__export(exports_poll, {
|
|
21688
21874
|
run: () => run13
|
|
21689
21875
|
});
|
|
21690
|
-
import { existsSync as
|
|
21691
|
-
import { join as
|
|
21876
|
+
import { existsSync as existsSync17, readFileSync as readFileSync12 } from "node:fs";
|
|
21877
|
+
import { join as join22 } from "node:path";
|
|
21692
21878
|
function parseArgs9(argv) {
|
|
21693
21879
|
let jobId;
|
|
21694
21880
|
let cursor = 0;
|
|
@@ -21725,19 +21911,19 @@ function parseArgs9(argv) {
|
|
|
21725
21911
|
return { jobId, cursor, outputCursor };
|
|
21726
21912
|
}
|
|
21727
21913
|
function readJobState(jobsDir, jobId, cursor, outputCursor) {
|
|
21728
|
-
const jobDir =
|
|
21729
|
-
const statusPath =
|
|
21914
|
+
const jobDir = join22(jobsDir, jobId);
|
|
21915
|
+
const statusPath = join22(jobDir, "status.json");
|
|
21730
21916
|
let status = null;
|
|
21731
|
-
if (
|
|
21917
|
+
if (existsSync17(statusPath)) {
|
|
21732
21918
|
try {
|
|
21733
|
-
status = JSON.parse(
|
|
21919
|
+
status = JSON.parse(readFileSync12(statusPath, "utf-8"));
|
|
21734
21920
|
} catch {}
|
|
21735
21921
|
}
|
|
21736
|
-
const resultPath =
|
|
21922
|
+
const resultPath = join22(jobDir, "result.txt");
|
|
21737
21923
|
let fullOutput = "";
|
|
21738
|
-
if (
|
|
21924
|
+
if (existsSync17(resultPath)) {
|
|
21739
21925
|
try {
|
|
21740
|
-
fullOutput =
|
|
21926
|
+
fullOutput = readFileSync12(resultPath, "utf-8");
|
|
21741
21927
|
} catch {}
|
|
21742
21928
|
}
|
|
21743
21929
|
const events = readJobEventsById(jobsDir, jobId);
|
|
@@ -21769,9 +21955,9 @@ function readJobState(jobsDir, jobId, cursor, outputCursor) {
|
|
|
21769
21955
|
}
|
|
21770
21956
|
async function run13() {
|
|
21771
21957
|
const { jobId, cursor, outputCursor } = parseArgs9(process.argv.slice(3));
|
|
21772
|
-
const jobsDir =
|
|
21773
|
-
const jobDir =
|
|
21774
|
-
if (!
|
|
21958
|
+
const jobsDir = join22(process.cwd(), ".specialists", "jobs");
|
|
21959
|
+
const jobDir = join22(jobsDir, jobId);
|
|
21960
|
+
if (!existsSync17(jobDir)) {
|
|
21775
21961
|
const result2 = {
|
|
21776
21962
|
job_id: jobId,
|
|
21777
21963
|
status: "error",
|
|
@@ -21802,7 +21988,7 @@ var exports_steer = {};
|
|
|
21802
21988
|
__export(exports_steer, {
|
|
21803
21989
|
run: () => run14
|
|
21804
21990
|
});
|
|
21805
|
-
import { join as
|
|
21991
|
+
import { join as join23 } from "node:path";
|
|
21806
21992
|
import { writeFileSync as writeFileSync6 } from "node:fs";
|
|
21807
21993
|
async function run14() {
|
|
21808
21994
|
const jobId = process.argv[3];
|
|
@@ -21811,7 +21997,7 @@ async function run14() {
|
|
|
21811
21997
|
console.error('Usage: specialists|sp steer <job-id> "<message>"');
|
|
21812
21998
|
process.exit(1);
|
|
21813
21999
|
}
|
|
21814
|
-
const jobsDir =
|
|
22000
|
+
const jobsDir = join23(process.cwd(), ".specialists", "jobs");
|
|
21815
22001
|
const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
|
|
21816
22002
|
const status = supervisor.readStatus(jobId);
|
|
21817
22003
|
if (!status) {
|
|
@@ -21852,7 +22038,7 @@ var exports_resume = {};
|
|
|
21852
22038
|
__export(exports_resume, {
|
|
21853
22039
|
run: () => run15
|
|
21854
22040
|
});
|
|
21855
|
-
import { join as
|
|
22041
|
+
import { join as join24 } from "node:path";
|
|
21856
22042
|
import { writeFileSync as writeFileSync7 } from "node:fs";
|
|
21857
22043
|
async function run15() {
|
|
21858
22044
|
const jobId = process.argv[3];
|
|
@@ -21861,7 +22047,7 @@ async function run15() {
|
|
|
21861
22047
|
console.error('Usage: specialists|sp resume <job-id> "<task>"');
|
|
21862
22048
|
process.exit(1);
|
|
21863
22049
|
}
|
|
21864
|
-
const jobsDir =
|
|
22050
|
+
const jobsDir = join24(process.cwd(), ".specialists", "jobs");
|
|
21865
22051
|
const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
|
|
21866
22052
|
const status = supervisor.readStatus(jobId);
|
|
21867
22053
|
if (!status) {
|
|
@@ -21916,13 +22102,13 @@ __export(exports_clean, {
|
|
|
21916
22102
|
run: () => run17
|
|
21917
22103
|
});
|
|
21918
22104
|
import {
|
|
21919
|
-
existsSync as
|
|
21920
|
-
readdirSync as
|
|
21921
|
-
readFileSync as
|
|
22105
|
+
existsSync as existsSync18,
|
|
22106
|
+
readdirSync as readdirSync6,
|
|
22107
|
+
readFileSync as readFileSync13,
|
|
21922
22108
|
rmSync as rmSync2,
|
|
21923
22109
|
statSync as statSync2
|
|
21924
22110
|
} from "node:fs";
|
|
21925
|
-
import { join as
|
|
22111
|
+
import { join as join25 } from "node:path";
|
|
21926
22112
|
function parseTtlDaysFromEnvironment() {
|
|
21927
22113
|
const rawValue = process.env.SPECIALISTS_JOB_TTL_DAYS ?? process.env.JOB_TTL_DAYS;
|
|
21928
22114
|
if (!rawValue)
|
|
@@ -21976,9 +22162,9 @@ function parseOptions(argv) {
|
|
|
21976
22162
|
}
|
|
21977
22163
|
function readDirectorySizeBytes(directoryPath) {
|
|
21978
22164
|
let totalBytes = 0;
|
|
21979
|
-
const entries =
|
|
22165
|
+
const entries = readdirSync6(directoryPath, { withFileTypes: true });
|
|
21980
22166
|
for (const entry of entries) {
|
|
21981
|
-
const entryPath =
|
|
22167
|
+
const entryPath = join25(directoryPath, entry.name);
|
|
21982
22168
|
const stats = statSync2(entryPath);
|
|
21983
22169
|
if (stats.isDirectory()) {
|
|
21984
22170
|
totalBytes += readDirectorySizeBytes(entryPath);
|
|
@@ -21991,13 +22177,13 @@ function readDirectorySizeBytes(directoryPath) {
|
|
|
21991
22177
|
function readCompletedJobDirectory(baseDirectory, entry) {
|
|
21992
22178
|
if (!entry.isDirectory())
|
|
21993
22179
|
return null;
|
|
21994
|
-
const directoryPath =
|
|
21995
|
-
const statusFilePath =
|
|
21996
|
-
if (!
|
|
22180
|
+
const directoryPath = join25(baseDirectory, entry.name);
|
|
22181
|
+
const statusFilePath = join25(directoryPath, "status.json");
|
|
22182
|
+
if (!existsSync18(statusFilePath))
|
|
21997
22183
|
return null;
|
|
21998
22184
|
let statusData;
|
|
21999
22185
|
try {
|
|
22000
|
-
statusData = JSON.parse(
|
|
22186
|
+
statusData = JSON.parse(readFileSync13(statusFilePath, "utf-8"));
|
|
22001
22187
|
} catch {
|
|
22002
22188
|
return null;
|
|
22003
22189
|
}
|
|
@@ -22013,7 +22199,7 @@ function readCompletedJobDirectory(baseDirectory, entry) {
|
|
|
22013
22199
|
};
|
|
22014
22200
|
}
|
|
22015
22201
|
function collectCompletedJobDirectories(jobsDirectoryPath) {
|
|
22016
|
-
const entries =
|
|
22202
|
+
const entries = readdirSync6(jobsDirectoryPath, { withFileTypes: true });
|
|
22017
22203
|
const completedJobs = [];
|
|
22018
22204
|
for (const entry of entries) {
|
|
22019
22205
|
const completedJob = readCompletedJobDirectory(jobsDirectoryPath, entry);
|
|
@@ -22078,8 +22264,8 @@ async function run17() {
|
|
|
22078
22264
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
22079
22265
|
printUsageAndExit(message);
|
|
22080
22266
|
}
|
|
22081
|
-
const jobsDirectoryPath =
|
|
22082
|
-
if (!
|
|
22267
|
+
const jobsDirectoryPath = join25(process.cwd(), ".specialists", "jobs");
|
|
22268
|
+
if (!existsSync18(jobsDirectoryPath)) {
|
|
22083
22269
|
console.log("No jobs directory found.");
|
|
22084
22270
|
return;
|
|
22085
22271
|
}
|
|
@@ -22106,14 +22292,14 @@ var exports_stop = {};
|
|
|
22106
22292
|
__export(exports_stop, {
|
|
22107
22293
|
run: () => run18
|
|
22108
22294
|
});
|
|
22109
|
-
import { join as
|
|
22295
|
+
import { join as join26 } from "node:path";
|
|
22110
22296
|
async function run18() {
|
|
22111
22297
|
const jobId = process.argv[3];
|
|
22112
22298
|
if (!jobId) {
|
|
22113
22299
|
console.error("Usage: specialists|sp stop <job-id>");
|
|
22114
22300
|
process.exit(1);
|
|
22115
22301
|
}
|
|
22116
|
-
const jobsDir =
|
|
22302
|
+
const jobsDir = join26(process.cwd(), ".specialists", "jobs");
|
|
22117
22303
|
const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
|
|
22118
22304
|
const status = supervisor.readStatus(jobId);
|
|
22119
22305
|
if (!status) {
|
|
@@ -22150,10 +22336,60 @@ var init_stop = __esm(() => {
|
|
|
22150
22336
|
init_supervisor();
|
|
22151
22337
|
});
|
|
22152
22338
|
|
|
22339
|
+
// src/cli/attach.ts
|
|
22340
|
+
var exports_attach = {};
|
|
22341
|
+
__export(exports_attach, {
|
|
22342
|
+
run: () => run19
|
|
22343
|
+
});
|
|
22344
|
+
import { execFileSync as execFileSync2, spawnSync as spawnSync10 } from "node:child_process";
|
|
22345
|
+
import { readFileSync as readFileSync14 } from "node:fs";
|
|
22346
|
+
import { join as join27 } from "node:path";
|
|
22347
|
+
function exitWithError(message) {
|
|
22348
|
+
console.error(message);
|
|
22349
|
+
process.exit(1);
|
|
22350
|
+
}
|
|
22351
|
+
function readStatus(statusPath, jobId) {
|
|
22352
|
+
try {
|
|
22353
|
+
return JSON.parse(readFileSync14(statusPath, "utf-8"));
|
|
22354
|
+
} catch (error2) {
|
|
22355
|
+
if (error2 && typeof error2 === "object" && "code" in error2 && error2.code === "ENOENT") {
|
|
22356
|
+
exitWithError(`Job \`${jobId}\` not found. Run \`specialists status\` to see active jobs.`);
|
|
22357
|
+
}
|
|
22358
|
+
const details = error2 instanceof Error ? error2.message : String(error2);
|
|
22359
|
+
exitWithError(`Failed to read status for job \`${jobId}\`: ${details}`);
|
|
22360
|
+
}
|
|
22361
|
+
}
|
|
22362
|
+
async function run19() {
|
|
22363
|
+
const [jobId] = process.argv.slice(3);
|
|
22364
|
+
if (!jobId) {
|
|
22365
|
+
exitWithError("Usage: specialists attach <job-id>");
|
|
22366
|
+
}
|
|
22367
|
+
const jobsDir = join27(process.cwd(), ".specialists", "jobs");
|
|
22368
|
+
const statusPath = join27(jobsDir, jobId, "status.json");
|
|
22369
|
+
const status = readStatus(statusPath, jobId);
|
|
22370
|
+
if (status.status === "done" || status.status === "error") {
|
|
22371
|
+
exitWithError(`Job \`${jobId}\` has already completed (status: ${status.status}). Use \`specialists result ${jobId}\` to read output.`);
|
|
22372
|
+
}
|
|
22373
|
+
const sessionName = status.tmux_session?.trim();
|
|
22374
|
+
if (!sessionName) {
|
|
22375
|
+
exitWithError("Job `" + jobId + "` has no tmux session. It may have been started without tmux or tmux was not installed.");
|
|
22376
|
+
}
|
|
22377
|
+
const whichTmux = spawnSync10("which", ["tmux"], { stdio: "ignore" });
|
|
22378
|
+
if (whichTmux.status !== 0) {
|
|
22379
|
+
exitWithError("tmux is not installed. Install tmux to use `specialists attach`.");
|
|
22380
|
+
}
|
|
22381
|
+
try {
|
|
22382
|
+
execFileSync2("tmux", ["attach-session", "-t", sessionName], { stdio: "inherit" });
|
|
22383
|
+
} catch {
|
|
22384
|
+
process.exit(1);
|
|
22385
|
+
}
|
|
22386
|
+
}
|
|
22387
|
+
var init_attach = () => {};
|
|
22388
|
+
|
|
22153
22389
|
// src/cli/quickstart.ts
|
|
22154
22390
|
var exports_quickstart = {};
|
|
22155
22391
|
__export(exports_quickstart, {
|
|
22156
|
-
run: () =>
|
|
22392
|
+
run: () => run20
|
|
22157
22393
|
});
|
|
22158
22394
|
function section2(title) {
|
|
22159
22395
|
const bar = "─".repeat(60);
|
|
@@ -22167,7 +22403,7 @@ function cmd2(s) {
|
|
|
22167
22403
|
function flag(s) {
|
|
22168
22404
|
return green12(s);
|
|
22169
22405
|
}
|
|
22170
|
-
async function
|
|
22406
|
+
async function run20() {
|
|
22171
22407
|
const lines = [
|
|
22172
22408
|
"",
|
|
22173
22409
|
bold9("specialists · Quick Start Guide"),
|
|
@@ -22383,11 +22619,11 @@ var bold9 = (s) => `\x1B[1m${s}\x1B[0m`, dim11 = (s) => `\x1B[2m${s}\x1B[0m`, ye
|
|
|
22383
22619
|
// src/cli/doctor.ts
|
|
22384
22620
|
var exports_doctor = {};
|
|
22385
22621
|
__export(exports_doctor, {
|
|
22386
|
-
run: () =>
|
|
22622
|
+
run: () => run21
|
|
22387
22623
|
});
|
|
22388
|
-
import { spawnSync as
|
|
22389
|
-
import { existsSync as
|
|
22390
|
-
import { join as
|
|
22624
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
22625
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync3, readFileSync as readFileSync15, readdirSync as readdirSync7 } from "node:fs";
|
|
22626
|
+
import { join as join28 } from "node:path";
|
|
22391
22627
|
function ok3(msg) {
|
|
22392
22628
|
console.log(` ${green13("✓")} ${msg}`);
|
|
22393
22629
|
}
|
|
@@ -22409,17 +22645,17 @@ function section3(label) {
|
|
|
22409
22645
|
${bold10(`── ${label} ${line}`)}`);
|
|
22410
22646
|
}
|
|
22411
22647
|
function sp(bin, args) {
|
|
22412
|
-
const r =
|
|
22648
|
+
const r = spawnSync11(bin, args, { encoding: "utf8", stdio: "pipe", timeout: 5000 });
|
|
22413
22649
|
return { ok: r.status === 0 && !r.error, stdout: (r.stdout ?? "").trim() };
|
|
22414
22650
|
}
|
|
22415
22651
|
function isInstalled2(bin) {
|
|
22416
|
-
return
|
|
22652
|
+
return spawnSync11("which", [bin], { encoding: "utf8", timeout: 2000 }).status === 0;
|
|
22417
22653
|
}
|
|
22418
22654
|
function loadJson2(path) {
|
|
22419
|
-
if (!
|
|
22655
|
+
if (!existsSync19(path))
|
|
22420
22656
|
return null;
|
|
22421
22657
|
try {
|
|
22422
|
-
return JSON.parse(
|
|
22658
|
+
return JSON.parse(readFileSync15(path, "utf8"));
|
|
22423
22659
|
} catch {
|
|
22424
22660
|
return null;
|
|
22425
22661
|
}
|
|
@@ -22462,7 +22698,7 @@ function checkBd() {
|
|
|
22462
22698
|
return false;
|
|
22463
22699
|
}
|
|
22464
22700
|
ok3(`bd installed ${dim12(sp("bd", ["--version"]).stdout || "")}`);
|
|
22465
|
-
if (
|
|
22701
|
+
if (existsSync19(join28(CWD, ".beads")))
|
|
22466
22702
|
ok3(".beads/ present in project");
|
|
22467
22703
|
else
|
|
22468
22704
|
warn2(".beads/ not found in project");
|
|
@@ -22482,8 +22718,8 @@ function checkHooks() {
|
|
|
22482
22718
|
section3("Claude Code hooks (2 expected)");
|
|
22483
22719
|
let allPresent = true;
|
|
22484
22720
|
for (const name of HOOK_NAMES) {
|
|
22485
|
-
const dest =
|
|
22486
|
-
if (!
|
|
22721
|
+
const dest = join28(HOOKS_DIR, name);
|
|
22722
|
+
if (!existsSync19(dest)) {
|
|
22487
22723
|
fail2(`${name} ${red7("missing")}`);
|
|
22488
22724
|
fix("specialists install");
|
|
22489
22725
|
allPresent = false;
|
|
@@ -22527,18 +22763,18 @@ function checkMCP() {
|
|
|
22527
22763
|
}
|
|
22528
22764
|
function checkRuntimeDirs() {
|
|
22529
22765
|
section3(".specialists/ runtime directories");
|
|
22530
|
-
const rootDir =
|
|
22531
|
-
const jobsDir =
|
|
22532
|
-
const readyDir =
|
|
22766
|
+
const rootDir = join28(CWD, ".specialists");
|
|
22767
|
+
const jobsDir = join28(rootDir, "jobs");
|
|
22768
|
+
const readyDir = join28(rootDir, "ready");
|
|
22533
22769
|
let allOk = true;
|
|
22534
|
-
if (!
|
|
22770
|
+
if (!existsSync19(rootDir)) {
|
|
22535
22771
|
warn2(".specialists/ not found in current project");
|
|
22536
22772
|
fix("specialists init");
|
|
22537
22773
|
allOk = false;
|
|
22538
22774
|
} else {
|
|
22539
22775
|
ok3(".specialists/ present");
|
|
22540
22776
|
for (const [subDir, label] of [[jobsDir, "jobs"], [readyDir, "ready"]]) {
|
|
22541
|
-
if (!
|
|
22777
|
+
if (!existsSync19(subDir)) {
|
|
22542
22778
|
warn2(`.specialists/${label}/ missing — auto-creating`);
|
|
22543
22779
|
mkdirSync3(subDir, { recursive: true });
|
|
22544
22780
|
ok3(`.specialists/${label}/ created`);
|
|
@@ -22551,14 +22787,14 @@ function checkRuntimeDirs() {
|
|
|
22551
22787
|
}
|
|
22552
22788
|
function checkZombieJobs() {
|
|
22553
22789
|
section3("Background jobs");
|
|
22554
|
-
const jobsDir =
|
|
22555
|
-
if (!
|
|
22790
|
+
const jobsDir = join28(CWD, ".specialists", "jobs");
|
|
22791
|
+
if (!existsSync19(jobsDir)) {
|
|
22556
22792
|
hint("No .specialists/jobs/ — skipping");
|
|
22557
22793
|
return true;
|
|
22558
22794
|
}
|
|
22559
22795
|
let entries;
|
|
22560
22796
|
try {
|
|
22561
|
-
entries =
|
|
22797
|
+
entries = readdirSync7(jobsDir);
|
|
22562
22798
|
} catch {
|
|
22563
22799
|
entries = [];
|
|
22564
22800
|
}
|
|
@@ -22570,11 +22806,11 @@ function checkZombieJobs() {
|
|
|
22570
22806
|
let total = 0;
|
|
22571
22807
|
let running = 0;
|
|
22572
22808
|
for (const jobId of entries) {
|
|
22573
|
-
const statusPath =
|
|
22574
|
-
if (!
|
|
22809
|
+
const statusPath = join28(jobsDir, jobId, "status.json");
|
|
22810
|
+
if (!existsSync19(statusPath))
|
|
22575
22811
|
continue;
|
|
22576
22812
|
try {
|
|
22577
|
-
const status = JSON.parse(
|
|
22813
|
+
const status = JSON.parse(readFileSync15(statusPath, "utf8"));
|
|
22578
22814
|
total++;
|
|
22579
22815
|
if (status.status === "running" || status.status === "starting") {
|
|
22580
22816
|
const pid = status.pid;
|
|
@@ -22601,7 +22837,7 @@ function checkZombieJobs() {
|
|
|
22601
22837
|
}
|
|
22602
22838
|
return zombies === 0;
|
|
22603
22839
|
}
|
|
22604
|
-
async function
|
|
22840
|
+
async function run21() {
|
|
22605
22841
|
console.log(`
|
|
22606
22842
|
${bold10("specialists doctor")}
|
|
22607
22843
|
`);
|
|
@@ -22626,11 +22862,11 @@ ${bold10("specialists doctor")}
|
|
|
22626
22862
|
var bold10 = (s) => `\x1B[1m${s}\x1B[0m`, dim12 = (s) => `\x1B[2m${s}\x1B[0m`, green13 = (s) => `\x1B[32m${s}\x1B[0m`, yellow10 = (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;
|
|
22627
22863
|
var init_doctor = __esm(() => {
|
|
22628
22864
|
CWD = process.cwd();
|
|
22629
|
-
CLAUDE_DIR =
|
|
22630
|
-
SPECIALISTS_DIR =
|
|
22631
|
-
HOOKS_DIR =
|
|
22632
|
-
SETTINGS_FILE =
|
|
22633
|
-
MCP_FILE2 =
|
|
22865
|
+
CLAUDE_DIR = join28(CWD, ".claude");
|
|
22866
|
+
SPECIALISTS_DIR = join28(CWD, ".specialists");
|
|
22867
|
+
HOOKS_DIR = join28(SPECIALISTS_DIR, "default", "hooks");
|
|
22868
|
+
SETTINGS_FILE = join28(CLAUDE_DIR, "settings.json");
|
|
22869
|
+
MCP_FILE2 = join28(CWD, ".mcp.json");
|
|
22634
22870
|
HOOK_NAMES = [
|
|
22635
22871
|
"specialists-complete.mjs",
|
|
22636
22872
|
"specialists-session-start.mjs"
|
|
@@ -22640,9 +22876,9 @@ var init_doctor = __esm(() => {
|
|
|
22640
22876
|
// src/cli/setup.ts
|
|
22641
22877
|
var exports_setup = {};
|
|
22642
22878
|
__export(exports_setup, {
|
|
22643
|
-
run: () =>
|
|
22879
|
+
run: () => run22
|
|
22644
22880
|
});
|
|
22645
|
-
async function
|
|
22881
|
+
async function run22() {
|
|
22646
22882
|
console.log("");
|
|
22647
22883
|
console.log(yellow11("⚠ DEPRECATED: `specialists setup` is deprecated"));
|
|
22648
22884
|
console.log("");
|
|
@@ -22665,13 +22901,13 @@ var bold11 = (s) => `\x1B[1m${s}\x1B[0m`, yellow11 = (s) => `\x1B[33m${s}\x1B[0m
|
|
|
22665
22901
|
// src/cli/help.ts
|
|
22666
22902
|
var exports_help = {};
|
|
22667
22903
|
__export(exports_help, {
|
|
22668
|
-
run: () =>
|
|
22904
|
+
run: () => run23
|
|
22669
22905
|
});
|
|
22670
22906
|
function formatCommands(entries) {
|
|
22671
22907
|
const width = Math.max(...entries.map(([cmd3]) => cmd3.length));
|
|
22672
22908
|
return entries.map(([cmd3, desc]) => ` ${cmd3.padEnd(width)} ${desc}`);
|
|
22673
22909
|
}
|
|
22674
|
-
async function
|
|
22910
|
+
async function run23() {
|
|
22675
22911
|
const lines = [
|
|
22676
22912
|
"",
|
|
22677
22913
|
"Specialists lets you run project-scoped specialist agents with a bead-first workflow.",
|
|
@@ -22710,6 +22946,12 @@ async function run22() {
|
|
|
22710
22946
|
" specialists feed|poll|result <job-id> # observe/progress/final output",
|
|
22711
22947
|
' Shell: specialists run <name> --prompt "..." & # native shell backgrounding',
|
|
22712
22948
|
"",
|
|
22949
|
+
" Background workflow",
|
|
22950
|
+
' specialists run executor --background --prompt "..." # → prints job-id',
|
|
22951
|
+
" specialists list --live # → interactive session picker",
|
|
22952
|
+
" specialists attach <job-id> # → attach directly",
|
|
22953
|
+
" specialists feed <job-id> --follow # → structured event stream",
|
|
22954
|
+
"",
|
|
22713
22955
|
bold12("Core commands:"),
|
|
22714
22956
|
...formatCommands(CORE_COMMANDS),
|
|
22715
22957
|
"",
|
|
@@ -22729,6 +22971,7 @@ async function run22() {
|
|
|
22729
22971
|
" specialists feed -f # stream all job events",
|
|
22730
22972
|
' specialists steer <job-id> "focus only on supervisor.ts"',
|
|
22731
22973
|
' specialists resume <job-id> "now write the fix"',
|
|
22974
|
+
" specialists attach <job-id> # open raw tmux session output",
|
|
22732
22975
|
' specialists run debugger --prompt "why does auth fail"',
|
|
22733
22976
|
" specialists report list",
|
|
22734
22977
|
" specialists report show --specialists",
|
|
@@ -22753,7 +22996,7 @@ var bold12 = (s) => `\x1B[1m${s}\x1B[0m`, dim14 = (s) => `\x1B[2m${s}\x1B[0m`, C
|
|
|
22753
22996
|
var init_help = __esm(() => {
|
|
22754
22997
|
CORE_COMMANDS = [
|
|
22755
22998
|
["init", "Bootstrap a project: dirs, workflow injection, project MCP registration"],
|
|
22756
|
-
["list", "List specialists
|
|
22999
|
+
["list", "List specialists; --live for interactive tmux session picker"],
|
|
22757
23000
|
["validate", "Validate a specialist YAML against the schema"],
|
|
22758
23001
|
["config", "Batch get/set specialist YAML keys in config/specialists/"],
|
|
22759
23002
|
["run", "Run a specialist; --json for NDJSON event stream, --raw for legacy text"],
|
|
@@ -22765,6 +23008,7 @@ var init_help = __esm(() => {
|
|
|
22765
23008
|
["resume", "Resume a waiting keep-alive session with a next-turn prompt (retains full context)"],
|
|
22766
23009
|
["follow-up", "[deprecated] Use resume instead"],
|
|
22767
23010
|
["stop", "Stop a running job"],
|
|
23011
|
+
["attach", "Attach terminal to a running background job tmux session"],
|
|
22768
23012
|
["report", "Generate/show/list/diff session reports in .xtrm/reports/"],
|
|
22769
23013
|
["status", "Show health, MCP state, and active jobs"],
|
|
22770
23014
|
["doctor", "Diagnose installation/runtime problems"],
|
|
@@ -30868,7 +31112,7 @@ var next = process.argv[3];
|
|
|
30868
31112
|
function wantsHelp() {
|
|
30869
31113
|
return next === "--help" || next === "-h";
|
|
30870
31114
|
}
|
|
30871
|
-
async function
|
|
31115
|
+
async function run24() {
|
|
30872
31116
|
if (sub === "install") {
|
|
30873
31117
|
if (wantsHelp()) {
|
|
30874
31118
|
console.log([
|
|
@@ -30904,11 +31148,13 @@ async function run23() {
|
|
|
30904
31148
|
"Options:",
|
|
30905
31149
|
" --category <name> Filter by category tag",
|
|
30906
31150
|
" --json Output as JSON array",
|
|
31151
|
+
" --live List running tmux-backed jobs and attach interactively",
|
|
30907
31152
|
"",
|
|
30908
31153
|
"Examples:",
|
|
30909
31154
|
" specialists list",
|
|
30910
31155
|
" specialists list --category analysis",
|
|
30911
31156
|
" specialists list --json",
|
|
31157
|
+
" specialists list --live",
|
|
30912
31158
|
"",
|
|
30913
31159
|
"Project model:",
|
|
30914
31160
|
" Specialists are project-only. User-scope discovery is deprecated.",
|
|
@@ -31360,6 +31606,34 @@ async function run23() {
|
|
|
31360
31606
|
const { run: handler } = await Promise.resolve().then(() => (init_stop(), exports_stop));
|
|
31361
31607
|
return handler();
|
|
31362
31608
|
}
|
|
31609
|
+
if (sub === "attach") {
|
|
31610
|
+
if (wantsHelp()) {
|
|
31611
|
+
process.stdout.write([
|
|
31612
|
+
"Usage: specialists attach <job-id>",
|
|
31613
|
+
"",
|
|
31614
|
+
"Attach your terminal to the tmux session of a running background specialist job.",
|
|
31615
|
+
"The job must have been started with --background and tmux must be installed.",
|
|
31616
|
+
"",
|
|
31617
|
+
"Arguments:",
|
|
31618
|
+
" <job-id> The job ID returned by specialists run --background",
|
|
31619
|
+
"",
|
|
31620
|
+
"Exit codes:",
|
|
31621
|
+
" 0 — session attached and exited normally",
|
|
31622
|
+
" 1 — job not found, already done, or no tmux session",
|
|
31623
|
+
"",
|
|
31624
|
+
"Examples:",
|
|
31625
|
+
" specialists attach job_a1b2c3d4",
|
|
31626
|
+
' specialists attach $(specialists run executor --background --prompt "...")',
|
|
31627
|
+
"",
|
|
31628
|
+
"See also: specialists list --live (interactive session picker)"
|
|
31629
|
+
].join(`
|
|
31630
|
+
`) + `
|
|
31631
|
+
`);
|
|
31632
|
+
process.exit(0);
|
|
31633
|
+
}
|
|
31634
|
+
const { run: handler } = await Promise.resolve().then(() => (init_attach(), exports_attach));
|
|
31635
|
+
return handler();
|
|
31636
|
+
}
|
|
31363
31637
|
if (sub === "quickstart") {
|
|
31364
31638
|
const { run: handler } = await Promise.resolve().then(() => exports_quickstart);
|
|
31365
31639
|
return handler();
|
|
@@ -31423,7 +31697,7 @@ Run 'specialists help' to see available commands.`);
|
|
|
31423
31697
|
const server = new SpecialistsServer;
|
|
31424
31698
|
await server.start();
|
|
31425
31699
|
}
|
|
31426
|
-
|
|
31700
|
+
run24().catch((error2) => {
|
|
31427
31701
|
logger.error(`Fatal error: ${error2}`);
|
|
31428
31702
|
process.exit(1);
|
|
31429
31703
|
});
|