@jaggerxtrm/specialists 2.1.6 → 2.1.8
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 +1 -2
- package/bin/install.js +209 -10
- package/dist/index.js +36 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,13 +14,12 @@
|
|
|
14
14
|
|
|
15
15
|
## How it works
|
|
16
16
|
|
|
17
|
-
Specialists are `.specialist.yaml` files that define an autonomous agent: its model, system prompt, task template, and permission tier. The server discovers them across
|
|
17
|
+
Specialists are `.specialist.yaml` files that define an autonomous agent: its model, system prompt, task template, and permission tier. The server discovers them across two scopes:
|
|
18
18
|
|
|
19
19
|
| Scope | Location | Purpose |
|
|
20
20
|
|-------|----------|---------|
|
|
21
21
|
| **project** | `./specialists/` | Per-project specialists |
|
|
22
22
|
| **user** | `~/.agents/specialists/` | Built-in defaults (copied on install) + your own |
|
|
23
|
-
| **system** | bundled with the package | Fallback if user scope is empty |
|
|
24
23
|
|
|
25
24
|
When a specialist runs, the server spawns a `pi` subprocess with the right model, tools, and system prompt injected. Output streams back in real time via cursor-based polling.
|
|
26
25
|
|
package/bin/install.js
CHANGED
|
@@ -138,24 +138,220 @@ const HOOK_ENTRY = {
|
|
|
138
138
|
hooks: [{ type: 'command', command: HOOK_FILE }],
|
|
139
139
|
};
|
|
140
140
|
|
|
141
|
+
|
|
142
|
+
const BEADS_EDIT_GATE_FILE = join(HOOKS_DIR, 'beads-edit-gate.mjs');
|
|
143
|
+
const BEADS_COMMIT_GATE_FILE = join(HOOKS_DIR, 'beads-commit-gate.mjs');
|
|
144
|
+
const BEADS_STOP_GATE_FILE = join(HOOKS_DIR, 'beads-stop-gate.mjs');
|
|
145
|
+
|
|
146
|
+
const BEADS_EDIT_GATE_SCRIPT = `#!/usr/bin/env node
|
|
147
|
+
// beads-edit-gate — Claude Code PreToolUse hook
|
|
148
|
+
// Blocks file edits when no beads issue is in_progress.
|
|
149
|
+
// Only active in projects with a .beads/ directory.
|
|
150
|
+
// Exit 0: allow | Exit 2: block (stderr shown to Claude)
|
|
151
|
+
//
|
|
152
|
+
// Installed by: npx --package=@jaggerxtrm/specialists install
|
|
153
|
+
|
|
154
|
+
import { execSync } from 'node:child_process';
|
|
155
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
156
|
+
import { join } from 'node:path';
|
|
157
|
+
|
|
158
|
+
let input;
|
|
159
|
+
try {
|
|
160
|
+
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
161
|
+
} catch {
|
|
162
|
+
process.exit(0);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const cwd = input.cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
166
|
+
if (!existsSync(join(cwd, '.beads'))) process.exit(0);
|
|
167
|
+
|
|
168
|
+
let inProgress = 0;
|
|
169
|
+
try {
|
|
170
|
+
const output = execSync('bd list --status=in_progress', {
|
|
171
|
+
encoding: 'utf8',
|
|
172
|
+
cwd,
|
|
173
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
174
|
+
timeout: 8000,
|
|
175
|
+
});
|
|
176
|
+
inProgress = (output.match(/in_progress/g) ?? []).length;
|
|
177
|
+
} catch {
|
|
178
|
+
process.exit(0);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (inProgress === 0) {
|
|
182
|
+
process.stderr.write(
|
|
183
|
+
'\\u{1F6AB} BEADS GATE: No in_progress issue tracked.\\n' +
|
|
184
|
+
'You MUST create and claim a beads issue BEFORE editing any file:\\n\\n' +
|
|
185
|
+
' bd create --title="<task summary>" --type=task --priority=2\\n' +
|
|
186
|
+
' bd update <id> --status=in_progress\\n\\n' +
|
|
187
|
+
'No exceptions. Momentum is not an excuse.\\n'
|
|
188
|
+
);
|
|
189
|
+
process.exit(2);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
process.exit(0);
|
|
193
|
+
`;
|
|
194
|
+
|
|
195
|
+
const BEADS_COMMIT_GATE_SCRIPT = `#!/usr/bin/env node
|
|
196
|
+
// beads-commit-gate — Claude Code PreToolUse hook
|
|
197
|
+
// Blocks \`git commit\` when in_progress beads issues still exist.
|
|
198
|
+
// Forces: close issues first, THEN commit.
|
|
199
|
+
// Exit 0: allow | Exit 2: block (stderr shown to Claude)
|
|
200
|
+
//
|
|
201
|
+
// Installed by: npx --package=@jaggerxtrm/specialists install
|
|
202
|
+
|
|
203
|
+
import { execSync } from 'node:child_process';
|
|
204
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
205
|
+
import { join } from 'node:path';
|
|
206
|
+
|
|
207
|
+
let input;
|
|
208
|
+
try {
|
|
209
|
+
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
210
|
+
} catch {
|
|
211
|
+
process.exit(0);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const tool = input.tool_name ?? '';
|
|
215
|
+
if (tool !== 'Bash') process.exit(0);
|
|
216
|
+
|
|
217
|
+
const cmd = input.tool_input?.command ?? '';
|
|
218
|
+
if (!/\\bgit\\s+commit\\b/.test(cmd)) process.exit(0);
|
|
219
|
+
|
|
220
|
+
const cwd = input.cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
221
|
+
if (!existsSync(join(cwd, '.beads'))) process.exit(0);
|
|
222
|
+
|
|
223
|
+
let inProgress = 0;
|
|
224
|
+
let summary = '';
|
|
225
|
+
try {
|
|
226
|
+
const output = execSync('bd list --status=in_progress', {
|
|
227
|
+
encoding: 'utf8',
|
|
228
|
+
cwd,
|
|
229
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
230
|
+
timeout: 8000,
|
|
231
|
+
});
|
|
232
|
+
inProgress = (output.match(/in_progress/g) ?? []).length;
|
|
233
|
+
summary = output.trim();
|
|
234
|
+
} catch {
|
|
235
|
+
process.exit(0);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (inProgress > 0) {
|
|
239
|
+
process.stderr.write(
|
|
240
|
+
'\\u{1F6AB} BEADS GATE: Cannot commit with open in_progress issues.\\n' +
|
|
241
|
+
'Close them first, THEN commit:\\n\\n' +
|
|
242
|
+
' bd close <id1> <id2> ...\\n' +
|
|
243
|
+
' git add <files> && git commit -m "..."\\n\\n' +
|
|
244
|
+
\`Open issues:\\n\${summary}\\n\`
|
|
245
|
+
);
|
|
246
|
+
process.exit(2);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
process.exit(0);
|
|
250
|
+
`;
|
|
251
|
+
|
|
252
|
+
const BEADS_STOP_GATE_SCRIPT = `#!/usr/bin/env node
|
|
253
|
+
// beads-stop-gate — Claude Code Stop hook
|
|
254
|
+
// Blocks the agent from stopping when in_progress beads issues remain.
|
|
255
|
+
// Forces the session close protocol before declaring done.
|
|
256
|
+
// Exit 0: allow stop | Exit 2: block stop (stderr shown to Claude)
|
|
257
|
+
//
|
|
258
|
+
// Installed by: npx --package=@jaggerxtrm/specialists install
|
|
259
|
+
|
|
260
|
+
import { execSync } from 'node:child_process';
|
|
261
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
262
|
+
import { join } from 'node:path';
|
|
263
|
+
|
|
264
|
+
let input;
|
|
265
|
+
try {
|
|
266
|
+
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
267
|
+
} catch {
|
|
268
|
+
process.exit(0);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const cwd = input.cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
272
|
+
if (!existsSync(join(cwd, '.beads'))) process.exit(0);
|
|
273
|
+
|
|
274
|
+
let inProgress = 0;
|
|
275
|
+
let summary = '';
|
|
276
|
+
try {
|
|
277
|
+
const output = execSync('bd list --status=in_progress', {
|
|
278
|
+
encoding: 'utf8',
|
|
279
|
+
cwd,
|
|
280
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
281
|
+
timeout: 8000,
|
|
282
|
+
});
|
|
283
|
+
inProgress = (output.match(/in_progress/g) ?? []).length;
|
|
284
|
+
summary = output.trim();
|
|
285
|
+
} catch {
|
|
286
|
+
process.exit(0);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (inProgress > 0) {
|
|
290
|
+
process.stderr.write(
|
|
291
|
+
'\\u{1F6AB} BEADS STOP GATE: Cannot stop with unresolved in_progress issues.\\n' +
|
|
292
|
+
'Complete the session close protocol:\\n\\n' +
|
|
293
|
+
' bd close <id1> <id2> ...\\n' +
|
|
294
|
+
' git add <files> && git commit -m "..."\\n' +
|
|
295
|
+
' git push\\n\\n' +
|
|
296
|
+
\`Open issues:\\n\${summary}\\n\`
|
|
297
|
+
);
|
|
298
|
+
process.exit(2);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
process.exit(0);
|
|
302
|
+
`;
|
|
303
|
+
|
|
304
|
+
const BEADS_EDIT_GATE_ENTRY = {
|
|
305
|
+
matcher: 'Edit|Write|MultiEdit|NotebookEdit|mcp__serena__replace_symbol_body|mcp__serena__insert_after_symbol|mcp__serena__insert_before_symbol',
|
|
306
|
+
hooks: [{ type: 'command', command: BEADS_EDIT_GATE_FILE, timeout: 10 }],
|
|
307
|
+
};
|
|
308
|
+
const BEADS_COMMIT_GATE_ENTRY = {
|
|
309
|
+
matcher: 'Bash',
|
|
310
|
+
hooks: [{ type: 'command', command: BEADS_COMMIT_GATE_FILE, timeout: 10 }],
|
|
311
|
+
};
|
|
312
|
+
const BEADS_STOP_GATE_ENTRY = {
|
|
313
|
+
hooks: [{ type: 'command', command: BEADS_STOP_GATE_FILE, timeout: 10 }],
|
|
314
|
+
};
|
|
315
|
+
|
|
141
316
|
function installHook() {
|
|
142
317
|
mkdirSync(HOOKS_DIR, { recursive: true });
|
|
318
|
+
|
|
319
|
+
// Write all hook files
|
|
143
320
|
writeFileSync(HOOK_FILE, HOOK_SCRIPT, 'utf8');
|
|
144
321
|
chmodSync(HOOK_FILE, 0o755);
|
|
322
|
+
writeFileSync(BEADS_EDIT_GATE_FILE, BEADS_EDIT_GATE_SCRIPT, 'utf8');
|
|
323
|
+
chmodSync(BEADS_EDIT_GATE_FILE, 0o755);
|
|
324
|
+
writeFileSync(BEADS_COMMIT_GATE_FILE, BEADS_COMMIT_GATE_SCRIPT, 'utf8');
|
|
325
|
+
chmodSync(BEADS_COMMIT_GATE_FILE, 0o755);
|
|
326
|
+
writeFileSync(BEADS_STOP_GATE_FILE, BEADS_STOP_GATE_SCRIPT, 'utf8');
|
|
327
|
+
chmodSync(BEADS_STOP_GATE_FILE, 0o755);
|
|
145
328
|
|
|
146
329
|
let settings = {};
|
|
147
330
|
if (existsSync(SETTINGS_FILE)) {
|
|
148
331
|
try { settings = JSON.parse(readFileSync(SETTINGS_FILE, 'utf8')); } catch {}
|
|
149
332
|
}
|
|
150
333
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
334
|
+
settings.hooks = settings.hooks ?? {};
|
|
335
|
+
|
|
336
|
+
// PreToolUse — replace any existing specialists-managed entries
|
|
337
|
+
if (!Array.isArray(settings.hooks.PreToolUse)) settings.hooks.PreToolUse = [];
|
|
338
|
+
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(e =>
|
|
339
|
+
!e.hooks?.some(h =>
|
|
340
|
+
h.command?.includes('specialists-main-guard') ||
|
|
341
|
+
h.command?.includes('beads-edit-gate') ||
|
|
342
|
+
h.command?.includes('beads-commit-gate')
|
|
343
|
+
)
|
|
344
|
+
);
|
|
158
345
|
settings.hooks.PreToolUse.push(HOOK_ENTRY);
|
|
346
|
+
settings.hooks.PreToolUse.push(BEADS_EDIT_GATE_ENTRY);
|
|
347
|
+
settings.hooks.PreToolUse.push(BEADS_COMMIT_GATE_ENTRY);
|
|
348
|
+
|
|
349
|
+
// Stop — replace any existing beads-stop-gate entry
|
|
350
|
+
if (!Array.isArray(settings.hooks.Stop)) settings.hooks.Stop = [];
|
|
351
|
+
settings.hooks.Stop = settings.hooks.Stop.filter(e =>
|
|
352
|
+
!e.hooks?.some(h => h.command?.includes('beads-stop-gate'))
|
|
353
|
+
);
|
|
354
|
+
settings.hooks.Stop.push(BEADS_STOP_GATE_ENTRY);
|
|
159
355
|
|
|
160
356
|
mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
161
357
|
writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
@@ -229,9 +425,12 @@ section('Claude Code hooks');
|
|
|
229
425
|
const hookExisted = existsSync(HOOK_FILE);
|
|
230
426
|
installHook();
|
|
231
427
|
hookExisted
|
|
232
|
-
? ok('main-guard
|
|
233
|
-
: ok('
|
|
234
|
-
info('
|
|
428
|
+
? ok('hooks updated (main-guard + beads gates)')
|
|
429
|
+
: ok('hooks installed → ~/.claude/hooks/');
|
|
430
|
+
info('main-guard: blocks Edit/Write/git commit/push on main or master branch');
|
|
431
|
+
info('beads-edit-gate: requires in_progress bead before editing files');
|
|
432
|
+
info('beads-commit-gate: requires issues closed before git commit');
|
|
433
|
+
info('beads-stop-gate: requires issues closed before session end');
|
|
235
434
|
|
|
236
435
|
// 7. Health check
|
|
237
436
|
section('Health check');
|
package/dist/index.js
CHANGED
|
@@ -24786,19 +24786,16 @@ class SpecialistLoader {
|
|
|
24786
24786
|
cache = new Map;
|
|
24787
24787
|
projectDir;
|
|
24788
24788
|
userDir;
|
|
24789
|
-
systemDir;
|
|
24790
24789
|
constructor(options = {}) {
|
|
24791
24790
|
this.projectDir = options.projectDir ?? process.cwd();
|
|
24792
24791
|
this.userDir = options.userDir ?? join(homedir(), ".agents", "specialists");
|
|
24793
|
-
this.systemDir = options.systemDir ?? join(new URL(import.meta.url).pathname, "..", "..", "specialists");
|
|
24794
24792
|
}
|
|
24795
24793
|
getScanDirs() {
|
|
24796
24794
|
return [
|
|
24797
24795
|
{ path: join(this.projectDir, "specialists"), scope: "project" },
|
|
24798
24796
|
{ path: join(this.projectDir, ".claude", "specialists"), scope: "project" },
|
|
24799
24797
|
{ path: join(this.projectDir, ".agent-forge", "specialists"), scope: "project" },
|
|
24800
|
-
{ path: this.userDir, scope: "user" }
|
|
24801
|
-
{ path: this.systemDir, scope: "system" }
|
|
24798
|
+
{ path: this.userDir, scope: "user" }
|
|
24802
24799
|
].filter((d) => existsSync(d.path));
|
|
24803
24800
|
}
|
|
24804
24801
|
async list(category) {
|
|
@@ -24897,6 +24894,12 @@ function getProviderArgs(model) {
|
|
|
24897
24894
|
}
|
|
24898
24895
|
|
|
24899
24896
|
// src/pi/session.ts
|
|
24897
|
+
class SessionKilledError extends Error {
|
|
24898
|
+
constructor() {
|
|
24899
|
+
super("Session was killed");
|
|
24900
|
+
this.name = "SessionKilledError";
|
|
24901
|
+
}
|
|
24902
|
+
}
|
|
24900
24903
|
function mapPermissionToTools(level) {
|
|
24901
24904
|
switch (level?.toUpperCase()) {
|
|
24902
24905
|
case "READ_ONLY":
|
|
@@ -24955,6 +24958,7 @@ class PiAgentSession {
|
|
|
24955
24958
|
this._doneResolve = resolve;
|
|
24956
24959
|
this._doneReject = reject;
|
|
24957
24960
|
});
|
|
24961
|
+
donePromise.catch(() => {});
|
|
24958
24962
|
this._donePromise = donePromise;
|
|
24959
24963
|
this.proc.stdout?.on("data", (chunk) => {
|
|
24960
24964
|
this._lineBuffer += chunk.toString();
|
|
@@ -25067,10 +25071,12 @@ class PiAgentSession {
|
|
|
25067
25071
|
return this._lastOutput;
|
|
25068
25072
|
}
|
|
25069
25073
|
kill() {
|
|
25074
|
+
if (this._killed)
|
|
25075
|
+
return;
|
|
25070
25076
|
this._killed = true;
|
|
25071
25077
|
this.proc?.kill();
|
|
25072
25078
|
this.proc = undefined;
|
|
25073
|
-
this.
|
|
25079
|
+
this._doneReject?.(new SessionKilledError);
|
|
25074
25080
|
}
|
|
25075
25081
|
}
|
|
25076
25082
|
|
|
@@ -25235,6 +25241,7 @@ You have access via Bash:
|
|
|
25235
25241
|
onBeadCreated?.(beadId);
|
|
25236
25242
|
}
|
|
25237
25243
|
let output;
|
|
25244
|
+
let sessionBackend = model;
|
|
25238
25245
|
let session;
|
|
25239
25246
|
try {
|
|
25240
25247
|
session = await this.sessionFactory({
|
|
@@ -25254,20 +25261,28 @@ You have access via Bash:
|
|
|
25254
25261
|
onKillRegistered?.(session.kill.bind(session));
|
|
25255
25262
|
await session.prompt(renderedTask);
|
|
25256
25263
|
await session.waitForDone();
|
|
25264
|
+
sessionBackend = session.meta.backend;
|
|
25257
25265
|
output = await session.getLastOutput();
|
|
25266
|
+
sessionBackend = session.meta.backend;
|
|
25258
25267
|
const postScripts = spec.specialist.skills?.scripts?.filter((s) => s.phase === "post") ?? [];
|
|
25259
25268
|
for (const script of postScripts)
|
|
25260
25269
|
runScript(script.path);
|
|
25261
25270
|
circuitBreaker.recordSuccess(model);
|
|
25262
25271
|
} catch (err) {
|
|
25263
|
-
|
|
25264
|
-
if (
|
|
25265
|
-
|
|
25272
|
+
const isCancelled = err instanceof SessionKilledError;
|
|
25273
|
+
if (!isCancelled) {
|
|
25274
|
+
circuitBreaker.recordFailure(model);
|
|
25275
|
+
}
|
|
25276
|
+
const beadStatus = isCancelled ? "CANCELLED" : "ERROR";
|
|
25277
|
+
if (beadId) {
|
|
25278
|
+
beadsClient?.closeBead(beadId, beadStatus, Date.now() - start, model);
|
|
25279
|
+
beadsClient?.auditBead(beadId, metadata.name, model, 1);
|
|
25280
|
+
}
|
|
25266
25281
|
await hooks.emit("post_execute", invocationId, metadata.name, metadata.version, {
|
|
25267
|
-
status: "ERROR",
|
|
25282
|
+
status: isCancelled ? "CANCELLED" : "ERROR",
|
|
25268
25283
|
duration_ms: Date.now() - start,
|
|
25269
25284
|
output_valid: false,
|
|
25270
|
-
error: { type: "backend_error", message: err.message }
|
|
25285
|
+
error: { type: isCancelled ? "cancelled" : "backend_error", message: err.message }
|
|
25271
25286
|
});
|
|
25272
25287
|
throw err;
|
|
25273
25288
|
} finally {
|
|
@@ -25288,7 +25303,7 @@ You have access via Bash:
|
|
|
25288
25303
|
}
|
|
25289
25304
|
return {
|
|
25290
25305
|
output,
|
|
25291
|
-
backend:
|
|
25306
|
+
backend: sessionBackend,
|
|
25292
25307
|
model,
|
|
25293
25308
|
durationMs,
|
|
25294
25309
|
specialistVersion: metadata.version,
|
|
@@ -25463,16 +25478,16 @@ async function runPipeline(steps, runner, onProgress) {
|
|
|
25463
25478
|
}
|
|
25464
25479
|
|
|
25465
25480
|
// src/tools/specialist/run_parallel.tool.ts
|
|
25466
|
-
var InvocationSchema =
|
|
25467
|
-
name:
|
|
25468
|
-
prompt:
|
|
25469
|
-
variables:
|
|
25470
|
-
backend_override:
|
|
25481
|
+
var InvocationSchema = objectType({
|
|
25482
|
+
name: stringType(),
|
|
25483
|
+
prompt: stringType(),
|
|
25484
|
+
variables: recordType(stringType()).optional(),
|
|
25485
|
+
backend_override: stringType().optional()
|
|
25471
25486
|
});
|
|
25472
|
-
var runParallelSchema =
|
|
25473
|
-
specialists:
|
|
25474
|
-
merge_strategy:
|
|
25475
|
-
timeout_ms:
|
|
25487
|
+
var runParallelSchema = objectType({
|
|
25488
|
+
specialists: arrayType(InvocationSchema).min(1),
|
|
25489
|
+
merge_strategy: enumType(["collect", "synthesize", "vote", "pipeline"]).default("collect"),
|
|
25490
|
+
timeout_ms: numberType().default(120000)
|
|
25476
25491
|
});
|
|
25477
25492
|
function createRunParallelTool(runner) {
|
|
25478
25493
|
return {
|
|
@@ -25502,6 +25517,7 @@ function createRunParallelTool(runner) {
|
|
|
25502
25517
|
status: r.status,
|
|
25503
25518
|
output: r.status === "fulfilled" ? r.value.output : null,
|
|
25504
25519
|
durationMs: r.status === "fulfilled" ? r.value.durationMs : null,
|
|
25520
|
+
beadId: r.status === "fulfilled" ? r.value.beadId : undefined,
|
|
25505
25521
|
error: r.status === "rejected" ? String(r.reason?.message) : null
|
|
25506
25522
|
}));
|
|
25507
25523
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jaggerxtrm/specialists",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.8",
|
|
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",
|