@jaggerxtrm/specialists 3.0.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/install.js CHANGED
@@ -112,7 +112,13 @@ const BEADS_CLOSE_MEMORY_PROMPT_ENTRY = {
112
112
  const SPECIALISTS_COMPLETE_FILE = join(HOOKS_DIR, 'specialists-complete.mjs');
113
113
  const SPECIALISTS_COMPLETE_ENTRY = {
114
114
  hooks: [{ type: 'command', command: SPECIALISTS_COMPLETE_FILE, timeout: 5000 }],
115
+ }
116
+ const SPECIALISTS_SESSION_START_FILE = join(HOOKS_DIR, 'specialists-session-start.mjs');
117
+ const SPECIALISTS_SESSION_START_ENTRY = {
118
+ hooks: [{ type: 'command', command: SPECIALISTS_SESSION_START_FILE, timeout: 8000 }],
115
119
  };
120
+ const BUNDLED_SKILLS_DIR = new URL('../skills', import.meta.url).pathname;
121
+ const CLAUDE_SKILLS_DIR = join(CLAUDE_DIR, 'skills');;
116
122
 
117
123
  function promptYN(question) {
118
124
  if (!process.stdin.isTTY) return true; // non-interactive: default yes
@@ -132,7 +138,8 @@ function getHookDrift() {
132
138
  ['beads-commit-gate.mjs', BEADS_COMMIT_GATE_FILE],
133
139
  ['beads-stop-gate.mjs', BEADS_STOP_GATE_FILE],
134
140
  ['beads-close-memory-prompt.mjs', BEADS_CLOSE_MEMORY_PROMPT_FILE],
135
- ['specialists-complete.mjs', SPECIALISTS_COMPLETE_FILE],
141
+ ['specialists-complete.mjs', SPECIALISTS_COMPLETE_FILE],
142
+ ['specialists-session-start.mjs', SPECIALISTS_SESSION_START_FILE],
136
143
  ];
137
144
  return pairs
138
145
  .map(([bundled, dest]) => ({
@@ -162,6 +169,8 @@ function installHook() {
162
169
  chmodSync(BEADS_CLOSE_MEMORY_PROMPT_FILE, 0o755);
163
170
  copyFileSync(join(BUNDLED_HOOKS_DIR, 'specialists-complete.mjs'), SPECIALISTS_COMPLETE_FILE);
164
171
  chmodSync(SPECIALISTS_COMPLETE_FILE, 0o755);
172
+ copyFileSync(join(BUNDLED_HOOKS_DIR, 'specialists-session-start.mjs'), SPECIALISTS_SESSION_START_FILE);
173
+ chmodSync(SPECIALISTS_SESSION_START_FILE, 0o755);
165
174
 
166
175
  let settings = {};
167
176
  if (existsSync(SETTINGS_FILE)) {
@@ -204,10 +213,59 @@ function installHook() {
204
213
  );
205
214
  settings.hooks.UserPromptSubmit.push(SPECIALISTS_COMPLETE_ENTRY);
206
215
 
216
+ // SessionStart — replace any existing specialists-session-start entry
217
+ if (!Array.isArray(settings.hooks.SessionStart)) settings.hooks.SessionStart = [];
218
+ settings.hooks.SessionStart = settings.hooks.SessionStart.filter(e =>
219
+ !e.hooks?.some(h => h.command?.includes('specialists-session-start'))
220
+ );
221
+ settings.hooks.SessionStart.push(SPECIALISTS_SESSION_START_ENTRY);
222
+
207
223
  mkdirSync(CLAUDE_DIR, { recursive: true });
208
224
  writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + '\n', 'utf8');
209
225
  }
210
226
 
227
+
228
+ function installSkills() {
229
+ if (!existsSync(BUNDLED_SKILLS_DIR)) return { installed: 0, skipped: 0 };
230
+ mkdirSync(CLAUDE_SKILLS_DIR, { recursive: true });
231
+
232
+ let installed = 0;
233
+ let skippedCount = 0;
234
+ let skillNames;
235
+ try {
236
+ skillNames = readdirSync(BUNDLED_SKILLS_DIR, { withFileTypes: true })
237
+ .filter(d => d.isDirectory())
238
+ .map(d => d.name);
239
+ } catch {
240
+ return { installed: 0, skipped: 0 };
241
+ }
242
+
243
+ for (const skillName of skillNames) {
244
+ const srcDir = join(BUNDLED_SKILLS_DIR, skillName);
245
+ const destDir = join(CLAUDE_SKILLS_DIR, skillName);
246
+ const skillFile = join(srcDir, 'SKILL.md');
247
+ const destSkillFile = join(destDir, 'SKILL.md');
248
+
249
+ if (!existsSync(skillFile)) continue;
250
+
251
+ if (existsSync(destSkillFile)) {
252
+ // Check if content matches bundled version
253
+ try {
254
+ if (readFileSync(skillFile, 'utf8') === readFileSync(destSkillFile, 'utf8')) {
255
+ skippedCount++;
256
+ continue;
257
+ }
258
+ } catch { /* fall through to copy */ }
259
+ }
260
+
261
+ mkdirSync(destDir, { recursive: true });
262
+ copyFileSync(skillFile, destSkillFile);
263
+ installed++;
264
+ }
265
+
266
+ return { installed, skipped: skippedCount };
267
+ }
268
+
211
269
  // ── Main ──────────────────────────────────────────────────────────────────────
212
270
  console.log('\n' + bold(' Specialists — full-stack installer'));
213
271
 
@@ -283,7 +341,7 @@ if (!hooksExist) {
283
341
  skip('hooks up to date');
284
342
  } else {
285
343
  const label = (h) => h.missing ? red('missing') : yellow('updated');
286
- console.log(` ${yellow('○')} ${drift.length} of 6 hook(s) have changes:`);
344
+ console.log(` ${yellow('○')} ${drift.length} of 7 hook(s) have changes:`);
287
345
  for (const h of drift) info(` ${h.name} ${label(h)}`);
288
346
  console.log();
289
347
  const confirmed = promptYN(' Update hooks?');
@@ -300,8 +358,17 @@ info('beads-commit-gate: requires issues closed before git commit');
300
358
  info('beads-stop-gate: requires issues closed before session end');
301
359
  info('beads-close-memory-prompt: nudges knowledge capture after bd close');
302
360
  info('specialists-complete: injects completion banners for background jobs');
361
+ info('specialists-session-start: injects context (jobs, specialists, commands) at session start');
362
+
363
+ // 7. Skills
364
+ section('Skills');
365
+ const skillResult = installSkills();
366
+ if (skillResult.installed > 0) ok(`${skillResult.installed} skill(s) installed → ~/.claude/skills/`);
367
+ if (skillResult.skipped > 0) skip(`${skillResult.skipped} skill(s) already up to date`);
368
+ if (skillResult.installed === 0 && skillResult.skipped === 0) skip('No bundled skills found');
369
+ info("specialists-usage: teaches agents when/how to use specialists CLI and MCP tools");
303
370
 
304
- // 7. Health check
371
+ // 8. Health check
305
372
  section('Health check');
306
373
  if (isInstalled('pi')) {
307
374
  const r = spawnSync('pi', ['--list-models'], { encoding: 'utf8' });
@@ -310,7 +377,7 @@ if (isInstalled('pi')) {
310
377
  : skip('No active provider — run pi config to set one up');
311
378
  }
312
379
 
313
- // 8. Done
380
+ // 9. Done
314
381
  console.log('\n' + bold(green(' Done!')));
315
382
  console.log('\n' + bold(' Next steps:'));
316
383
  console.log(` 1. ${bold('Configure pi:')} run ${yellow('pi')} then ${yellow('pi config')} to enable model providers`);