@jaggerxtrm/specialists 2.1.5 → 2.1.7

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 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 three scopes:
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
- | **user** | `~/.agents/specialists/` | Personal specialists |
23
- | **system** | bundled with the package | Built-in defaults |
22
+ | **user** | `~/.agents/specialists/` | Built-in defaults (copied on install) + your own |
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
 
@@ -170,8 +169,19 @@ specialist:
170
169
 
171
170
  communication:
172
171
  publishes: [result]
172
+
173
+ # Optional: run scripts before/after the specialist
174
+ skills:
175
+ scripts:
176
+ - path: ./scripts/health-check.sh
177
+ phase: pre # runs before the task prompt
178
+ inject_output: true # output injected as $pre_script_output
179
+ - path: ./scripts/cleanup.sh
180
+ phase: post # runs after the specialist completes
173
181
  ```
174
182
 
183
+ Pre-script output is formatted as `<pre_flight_context>` XML and available in `task_template` via `$pre_script_output`. Scripts run locally via the host shell — not inside the pi agent. Failed scripts include their exit code so the specialist can reason about failures.
184
+
175
185
  **Model IDs** use the full provider/model format: `anthropic/claude-sonnet-4-6`, `google-gemini-cli/gemini-3-flash-preview`, `anthropic/claude-haiku-4-5`.
176
186
 
177
187
  ---
@@ -187,7 +197,7 @@ bun test
187
197
  ```
188
198
 
189
199
  - **Build**: `bun build src/index.ts --target=node --outfile=dist/index.js`
190
- - **Test**: `bun --bun vitest run` (67 unit tests)
200
+ - **Test**: `bun --bun vitest run` (68 unit tests)
191
201
  - **Lint**: `tsc --noEmit`
192
202
 
193
203
  See [CLAUDE.md](CLAUDE.md) for the full architecture guide and [ROADMAP.md](ROADMAP.md) for planned features.
package/bin/install.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // Usage: npx --package=@jaggerxtrm/specialists install
4
4
 
5
5
  import { spawnSync } from 'node:child_process';
6
- import { existsSync, mkdirSync, writeFileSync, readFileSync, chmodSync } from 'node:fs';
6
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, copyFileSync, readdirSync, chmodSync } from 'node:fs';
7
7
  import { homedir } from 'node:os';
8
8
  import { join } from 'node:path';
9
9
 
@@ -16,6 +16,9 @@ const HOOK_FILE = join(HOOKS_DIR, 'specialists-main-guard.mjs');
16
16
  const MCP_NAME = 'specialists';
17
17
  const GITHUB_PKG = '@jaggerxtrm/specialists';
18
18
 
19
+ // Bundled specialists dir — resolved relative to this file (bin/../specialists/)
20
+ const BUNDLED_SPECIALISTS_DIR = new URL('../specialists', import.meta.url).pathname;
21
+
19
22
  // ── ANSI helpers ──────────────────────────────────────────────────────────────
20
23
  const dim = (s) => `\x1b[2m${s}\x1b[0m`;
21
24
  const green = (s) => `\x1b[32m${s}\x1b[0m`;
@@ -136,15 +139,13 @@ const HOOK_ENTRY = {
136
139
  };
137
140
 
138
141
  function installHook() {
139
- // 1. Write hook script
140
142
  mkdirSync(HOOKS_DIR, { recursive: true });
141
143
  writeFileSync(HOOK_FILE, HOOK_SCRIPT, 'utf8');
142
144
  chmodSync(HOOK_FILE, 0o755);
143
145
 
144
- // 2. Merge into ~/.claude/settings.json
145
146
  let settings = {};
146
147
  if (existsSync(SETTINGS_FILE)) {
147
- try { settings = JSON.parse(readFileSync(SETTINGS_FILE, 'utf8')); } catch { /* malformed, overwrite */ }
148
+ try { settings = JSON.parse(readFileSync(SETTINGS_FILE, 'utf8')); } catch {}
148
149
  }
149
150
 
150
151
  if (!Array.isArray(settings.hooks?.PreToolUse)) {
@@ -152,7 +153,6 @@ function installHook() {
152
153
  settings.hooks.PreToolUse = [];
153
154
  }
154
155
 
155
- // Idempotent: remove any previous specialists-main-guard entry, re-add
156
156
  settings.hooks.PreToolUse = settings.hooks.PreToolUse
157
157
  .filter(e => !e.hooks?.some(h => h.command?.includes('specialists-main-guard')));
158
158
  settings.hooks.PreToolUse.push(HOOK_ENTRY);
@@ -199,23 +199,39 @@ registered
199
199
  ? ok(`MCP '${MCP_NAME}' registered at user scope`)
200
200
  : skip(`MCP '${MCP_NAME}' already registered`);
201
201
 
202
- // 5. Scaffold user specialists directory
203
- section('Scaffold');
204
- if (!existsSync(SPECIALISTS_DIR)) {
205
- mkdirSync(SPECIALISTS_DIR, { recursive: true });
206
- ok('~/.agents/specialists/ created');
207
- } else {
208
- skip('~/.agents/specialists/ already exists');
202
+ // 5. Scaffold + copy built-in specialists
203
+ section('Specialists');
204
+ mkdirSync(SPECIALISTS_DIR, { recursive: true });
205
+
206
+ const yamlFiles = existsSync(BUNDLED_SPECIALISTS_DIR)
207
+ ? readdirSync(BUNDLED_SPECIALISTS_DIR).filter(f => f.endsWith('.specialist.yaml'))
208
+ : [];
209
+
210
+ let installed = 0;
211
+ let skipped = 0;
212
+ for (const file of yamlFiles) {
213
+ const dest = join(SPECIALISTS_DIR, file);
214
+ if (existsSync(dest)) {
215
+ skipped++;
216
+ } else {
217
+ copyFileSync(join(BUNDLED_SPECIALISTS_DIR, file), dest);
218
+ installed++;
219
+ }
209
220
  }
210
221
 
222
+ if (installed > 0) ok(`${installed} specialist(s) installed → ~/.agents/specialists/`);
223
+ if (skipped > 0) skip(`${skipped} specialist(s) already exist (user-modified, keeping)`);
224
+ if (installed === 0 && skipped === 0) skip('No built-in specialists found');
225
+ info('Edit any .specialist.yaml in ~/.agents/specialists/ to customise models, prompts, permissions');
226
+
211
227
  // 6. Claude Code hooks
212
228
  section('Claude Code hooks');
213
229
  const hookExisted = existsSync(HOOK_FILE);
214
230
  installHook();
215
231
  hookExisted
216
232
  ? ok('main-guard hook updated')
217
- : ok('main-guard hook installed → ~/.claude/hooks/specialists-main-guard.sh');
218
- info('Blocks Edit/Write/git commit/push on main or master branch (JS, no jq needed)');
233
+ : ok('main-guard hook installed → ~/.claude/hooks/specialists-main-guard.mjs');
234
+ info('Blocks Edit/Write/git commit/push on main or master branch');
219
235
 
220
236
  // 7. Health check
221
237
  section('Health check');
@@ -231,4 +247,5 @@ console.log('\n' + bold(green(' Done!')));
231
247
  console.log('\n' + bold(' Next steps:'));
232
248
  console.log(` 1. ${bold('Configure pi:')} run ${yellow('pi')} then ${yellow('pi config')} to enable model providers`);
233
249
  console.log(` 2. ${bold('Restart Claude Code')} to load the MCP and hooks`);
234
- console.log(` 3. ${bold('Update later:')} re-run this installer\n`);
250
+ console.log(` 3. ${bold('Customise specialists:')} edit files in ${yellow('~/.agents/specialists/')}`);
251
+ console.log(` 4. ${bold('Update later:')} re-run this installer (existing specialists preserved)\n`);
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jaggerxtrm/specialists",
3
- "version": "2.1.5",
3
+ "version": "2.1.7",
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",