@jaggerxtrm/specialists 2.1.0 → 2.1.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.
Files changed (3) hide show
  1. package/README.md +10 -3
  2. package/dist/index.js +76 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -86,15 +86,22 @@ The orchestrating agent can retrieve the `beadId` from `poll_specialist` output
86
86
 
87
87
  ## Installation
88
88
 
89
- ### One-line installer (recommended)
89
+ ### Recommended
90
90
 
91
91
  ```bash
92
- npx --package=@jaggerxtrm/specialists install
92
+ npm install -g @jaggerxtrm/specialists
93
+ specialists install
93
94
  ```
94
95
 
95
96
  Installs: **pi** (`@mariozechner/pi-coding-agent`), **beads** (`@beads/bd`), **dolt** (interactive sudo on Linux / brew on macOS), registers the `specialists` MCP at user scope, scaffolds `~/.agents/specialists/`.
96
97
 
97
- After running, **restart Claude Code** to load the MCP.
98
+ After running, **restart Claude Code** to load the MCP. Re-run `specialists install` at any time to update or repair the installation.
99
+
100
+ ### One-time (no global install)
101
+
102
+ ```bash
103
+ npx --package=@jaggerxtrm/specialists install
104
+ ```
98
105
 
99
106
  ---
100
107
 
package/dist/index.js CHANGED
@@ -13393,6 +13393,11 @@ var require_public_api = __commonJS((exports) => {
13393
13393
  exports.stringify = stringify;
13394
13394
  });
13395
13395
 
13396
+ // src/index.ts
13397
+ import { execFileSync } from "node:child_process";
13398
+ import { fileURLToPath } from "node:url";
13399
+ import { dirname as dirname2, join as join4 } from "node:path";
13400
+
13396
13401
  // node_modules/zod/v3/external.js
13397
13402
  var exports_external = {};
13398
13403
  __export(exports_external, {
@@ -25080,6 +25085,57 @@ class PiAgentSession {
25080
25085
  }
25081
25086
 
25082
25087
  // src/specialist/beads.ts
25088
+ import { spawnSync } from "node:child_process";
25089
+
25090
+ class BeadsClient {
25091
+ available;
25092
+ constructor() {
25093
+ this.available = BeadsClient.checkAvailable();
25094
+ if (!this.available) {
25095
+ console.warn("[specialists] bd CLI not found — beads tracking disabled");
25096
+ }
25097
+ }
25098
+ static checkAvailable() {
25099
+ const result = spawnSync("bd", ["--version"], { stdio: "ignore" });
25100
+ return result.status === 0;
25101
+ }
25102
+ isAvailable() {
25103
+ return this.available;
25104
+ }
25105
+ createBead(specialistName) {
25106
+ if (!this.available)
25107
+ return null;
25108
+ const result = spawnSync("bd", ["q", `specialist:${specialistName}`, "--type", "task", "--labels", "specialist"], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
25109
+ if (result.status !== 0)
25110
+ return null;
25111
+ const id = result.stdout?.trim();
25112
+ return id || null;
25113
+ }
25114
+ closeBead(id, status, durationMs, model) {
25115
+ if (!this.available || !id)
25116
+ return;
25117
+ const reason = `${status}, ${Math.round(durationMs)}ms, ${model}`;
25118
+ spawnSync("bd", ["close", id, "-r", reason], { stdio: "ignore" });
25119
+ }
25120
+ auditBead(id, toolName, model, exitCode) {
25121
+ if (!this.available || !id)
25122
+ return;
25123
+ spawnSync("bd", [
25124
+ "audit",
25125
+ "record",
25126
+ "--kind",
25127
+ "tool_call",
25128
+ "--tool-name",
25129
+ toolName,
25130
+ "--model",
25131
+ model,
25132
+ "--issue-id",
25133
+ id,
25134
+ "--exit-code",
25135
+ String(exitCode)
25136
+ ], { stdio: "ignore" });
25137
+ }
25138
+ }
25083
25139
  function shouldCreateBead(beadsIntegration, permissionRequired) {
25084
25140
  if (beadsIntegration === "never")
25085
25141
  return false;
@@ -25096,7 +25152,7 @@ class SpecialistRunner {
25096
25152
  this.deps = deps;
25097
25153
  this.sessionFactory = deps.sessionFactory ?? PiAgentSession.create.bind(PiAgentSession);
25098
25154
  }
25099
- async run(options, onProgress, onEvent, onMeta, onKillRegistered) {
25155
+ async run(options, onProgress, onEvent, onMeta, onKillRegistered, onBeadCreated) {
25100
25156
  const { loader, hooks, circuitBreaker, beadsClient } = this.deps;
25101
25157
  const invocationId = crypto.randomUUID();
25102
25158
  const start = Date.now();
@@ -25156,6 +25212,8 @@ You have access via Bash:
25156
25212
  let beadId;
25157
25213
  if (beadsClient && shouldCreateBead(beadsIntegration, execution.permission_required)) {
25158
25214
  beadId = beadsClient.createBead(metadata.name) ?? undefined;
25215
+ if (beadId)
25216
+ onBeadCreated?.(beadId);
25159
25217
  }
25160
25218
  let output;
25161
25219
  let session;
@@ -25240,7 +25298,7 @@ You have access via Bash:
25240
25298
  model: "?",
25241
25299
  specialistVersion
25242
25300
  });
25243
- this.run(options, (text) => registry2.appendOutput(jobId, text), (eventType) => registry2.setCurrentEvent(jobId, eventType), (meta) => registry2.setMeta(jobId, meta), (killFn) => registry2.setKillFn(jobId, killFn)).then((result) => registry2.complete(jobId, result)).catch((err) => registry2.fail(jobId, err));
25301
+ this.run(options, (text) => registry2.appendOutput(jobId, text), (eventType) => registry2.setCurrentEvent(jobId, eventType), (meta) => registry2.setMeta(jobId, meta), (killFn) => registry2.setKillFn(jobId, killFn), (beadId) => registry2.setBeadId(jobId, beadId)).then((result) => registry2.complete(jobId, result)).catch((err) => registry2.fail(jobId, err));
25244
25302
  return jobId;
25245
25303
  }
25246
25304
  }
@@ -25343,7 +25401,7 @@ var useSpecialistSchema = exports_external.object({
25343
25401
  function createUseSpecialistTool(runner) {
25344
25402
  return {
25345
25403
  name: "use_specialist",
25346
- description: "Execute a specialist. Full lifecycle: load → agents.md → pi → validate output.",
25404
+ description: "Run a specialist synchronously and wait for the result. " + "Full lifecycle: load → agents.md → pi session output. " + "Response includes output, model, durationMs, and beadId (string | undefined). " + "beadId is set when the specialist's beads_integration policy triggered bead creation " + "(default: auto — creates for LOW/MEDIUM/HIGH permission, skips for READ_ONLY). " + "If beadId is present, use `bd update <beadId> --notes` to attach findings or " + "`bd remember` to persist key discoveries for future sessions.",
25347
25405
  inputSchema: useSpecialistSchema,
25348
25406
  async execute(input, onProgress) {
25349
25407
  return runner.run({
@@ -25584,7 +25642,7 @@ var startSpecialistSchema = exports_external.object({
25584
25642
  function createStartSpecialistTool(runner, registry2) {
25585
25643
  return {
25586
25644
  name: "start_specialist",
25587
- description: "Start a specialist asynchronously. Returns job_id immediately use poll_specialist to track progress and get output. Enables true parallel execution of multiple specialists.",
25645
+ description: "Start a specialist asynchronously. Returns job_id immediately. " + "Use poll_specialist to track progress, receive output delta, and retrieve beadId " + "(the beads issue auto-created for this run, if beads_integration policy applies). " + "Use stop_specialist to cancel. Enables true parallel execution of multiple specialists.",
25588
25646
  inputSchema: startSpecialistSchema,
25589
25647
  async execute(input) {
25590
25648
  const jobId = await runner.startAsync({
@@ -25606,7 +25664,7 @@ var pollSpecialistSchema = exports_external.object({
25606
25664
  function createPollSpecialistTool(registry2) {
25607
25665
  return {
25608
25666
  name: "poll_specialist",
25609
- description: "Poll a running specialist job. Returns status (running|done|error), delta (new content since cursor), next_cursor, and full output only when done. Pass next_cursor from each response as cursor on the next poll to receive only new tokens.",
25667
+ description: "Poll a running specialist job. Returns status (running|done|error|cancelled), " + "delta (new tokens since cursor), next_cursor, and full output when done. " + "Pass next_cursor back as cursor on each subsequent poll to receive only new content. " + "Response also includes beadId (string | undefined) once the specialist has started — " + "this is the beads issue tracking this run. If present after status=done, consider: " + '`bd update <beadId> --notes "<key finding>"` to attach results, or ' + '`bd remember "<insight>"` to persist discoveries across sessions.',
25610
25668
  inputSchema: pollSpecialistSchema,
25611
25669
  async execute(input) {
25612
25670
  const snapshot = registry2.snapshot(input.job_id, input.cursor ?? 0);
@@ -25638,19 +25696,19 @@ function createStopSpecialistTool(registry2) {
25638
25696
  }
25639
25697
 
25640
25698
  // src/tools/specialist/specialist_init.tool.ts
25641
- import { spawnSync } from "node:child_process";
25699
+ import { spawnSync as spawnSync2 } from "node:child_process";
25642
25700
  import { existsSync as existsSync2 } from "node:fs";
25643
25701
  import { join as join2 } from "node:path";
25644
- var specialistInitSchema = exports_external.object({});
25702
+ var specialistInitSchema = objectType({});
25645
25703
  function createSpecialistInitTool(loader, deps) {
25646
25704
  const resolved = deps ?? {
25647
- bdAvailable: () => spawnSync("bd", ["--version"], { stdio: "ignore" }).status === 0,
25705
+ bdAvailable: () => spawnSync2("bd", ["--version"], { stdio: "ignore" }).status === 0,
25648
25706
  beadsExists: () => existsSync2(join2(process.cwd(), ".beads")),
25649
- bdInit: () => spawnSync("bd", ["init"], { stdio: "ignore" })
25707
+ bdInit: () => spawnSync2("bd", ["init"], { stdio: "ignore" })
25650
25708
  };
25651
25709
  return {
25652
25710
  name: "specialist_init",
25653
- description: "Session bootstrap: initializes beads in the project if not already set up, " + "then returns available specialists. Call at session start for orientation.",
25711
+ description: "Call this first at session start. Returns available specialists and initializes beads " + "tracking (runs `bd init` if not already set up). " + "Response includes: specialists[] (use with use_specialist/start_specialist), " + "beads.available (bool), beads.initialized (bool). " + "If beads.available is true, specialists with permission LOW/MEDIUM/HIGH will auto-create " + "a beads issue when they run — no action needed from you.",
25654
25712
  inputSchema: specialistInitSchema,
25655
25713
  async execute(_input) {
25656
25714
  const available = resolved.bdAvailable();
@@ -25682,7 +25740,8 @@ class SpecialistsServer {
25682
25740
  const hooks = new HookEmitter({
25683
25741
  tracePath: join3(process.cwd(), ".specialists", "trace.jsonl")
25684
25742
  });
25685
- const runner = new SpecialistRunner({ loader, hooks, circuitBreaker });
25743
+ const beadsClient = new BeadsClient;
25744
+ const runner = new SpecialistRunner({ loader, hooks, circuitBreaker, beadsClient });
25686
25745
  const registry2 = new JobRegistry;
25687
25746
  this.tools = [
25688
25747
  createListSpecialistsTool(loader),
@@ -25769,6 +25828,12 @@ class SpecialistsServer {
25769
25828
  }
25770
25829
 
25771
25830
  // src/index.ts
25831
+ if (process.argv[2] === "install") {
25832
+ const __dirname2 = dirname2(fileURLToPath(import.meta.url));
25833
+ const installerPath = join4(__dirname2, "..", "bin", "install.js");
25834
+ execFileSync(process.execPath, [installerPath], { stdio: "inherit" });
25835
+ process.exit(0);
25836
+ }
25772
25837
  async function main() {
25773
25838
  logger.info("Starting Specialists MCP Server...");
25774
25839
  const server = new SpecialistsServer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jaggerxtrm/specialists",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "OmniSpecialist — 7-tool MCP orchestration layer powered by the Specialist System. Discover and execute .specialist.yaml files across project/user/system scopes via pi.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",