@jaggerxtrm/specialists 3.0.1 → 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 4 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?');
@@ -298,8 +356,19 @@ info('main-guard: blocks file edits and direct master pushes (enforces PR workfl
298
356
  info('beads-edit-gate: requires in_progress bead before editing files');
299
357
  info('beads-commit-gate: requires issues closed before git commit');
300
358
  info('beads-stop-gate: requires issues closed before session end');
301
-
302
- // 7. Health check
359
+ info('beads-close-memory-prompt: nudges knowledge capture after bd close');
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");
370
+
371
+ // 8. Health check
303
372
  section('Health check');
304
373
  if (isInstalled('pi')) {
305
374
  const r = spawnSync('pi', ['--list-models'], { encoding: 'utf8' });
@@ -308,7 +377,7 @@ if (isInstalled('pi')) {
308
377
  : skip('No active provider — run pi config to set one up');
309
378
  }
310
379
 
311
- // 8. Done
380
+ // 9. Done
312
381
  console.log('\n' + bold(green(' Done!')));
313
382
  console.log('\n' + bold(' Next steps:'));
314
383
  console.log(` 1. ${bold('Configure pi:')} run ${yellow('pi')} then ${yellow('pi config')} to enable model providers`);