@jaggerxtrm/specialists 2.1.2 → 2.1.4

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 (2) hide show
  1. package/bin/install.js +105 -7
  2. package/package.json +1 -1
package/bin/install.js CHANGED
@@ -1,14 +1,18 @@
1
1
  #!/usr/bin/env node
2
- // OmniSpecialist Installer
3
- // Usage: npx --package=github:Jaggerxtrm/specialists install
2
+ // Specialists Installer
3
+ // Usage: npx --package=@jaggerxtrm/specialists install
4
4
 
5
5
  import { spawnSync } from 'node:child_process';
6
- import { existsSync, mkdirSync } from 'node:fs';
6
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, chmodSync } from 'node:fs';
7
7
  import { homedir } from 'node:os';
8
8
  import { join } from 'node:path';
9
9
 
10
10
  const HOME = homedir();
11
11
  const SPECIALISTS_DIR = join(HOME, '.agents', 'specialists');
12
+ const CLAUDE_DIR = join(HOME, '.claude');
13
+ const HOOKS_DIR = join(CLAUDE_DIR, 'hooks');
14
+ const SETTINGS_FILE = join(CLAUDE_DIR, 'settings.json');
15
+ const HOOK_FILE = join(HOOKS_DIR, 'specialists-main-guard.mjs');
12
16
  const MCP_NAME = 'specialists';
13
17
  const GITHUB_PKG = '@jaggerxtrm/specialists';
14
18
 
@@ -72,8 +76,93 @@ function registerMCP() {
72
76
  return true;
73
77
  }
74
78
 
79
+ // ── Hook installation ─────────────────────────────────────────────────────────
80
+
81
+ const HOOK_SCRIPT = `#!/usr/bin/env node
82
+ // specialists — Claude Code PreToolUse hook
83
+ // Blocks writes and git commit/push on main/master branch.
84
+ // Exit 0: allow | Exit 2: block (message shown to user)
85
+ //
86
+ // Installed by: npx --package=@jaggerxtrm/specialists install
87
+
88
+ import { execSync } from 'node:child_process';
89
+ import { readFileSync } from 'node:fs';
90
+
91
+ let branch = '';
92
+ try {
93
+ branch = execSync('git branch --show-current', {
94
+ encoding: 'utf8',
95
+ stdio: ['pipe', 'pipe', 'pipe'],
96
+ }).trim();
97
+ } catch {}
98
+
99
+ if (!branch || (branch !== 'main' && branch !== 'master')) {
100
+ process.exit(0);
101
+ }
102
+
103
+ let input;
104
+ try {
105
+ input = JSON.parse(readFileSync(0, 'utf8'));
106
+ } catch {
107
+ process.exit(0);
108
+ }
109
+
110
+ const tool = input.tool_name ?? '';
111
+ const blockMsg =
112
+ \`⛔ Direct edits on '\${branch}' are not allowed.\\n\` +
113
+ \`Create a feature branch first: git checkout -b feature/<name>\`;
114
+
115
+ const WRITE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
116
+
117
+ if (WRITE_TOOLS.has(tool)) {
118
+ process.stderr.write(blockMsg + '\\n');
119
+ process.exit(2);
120
+ }
121
+
122
+ if (tool === 'Bash') {
123
+ const cmd = input.tool_input?.command ?? '';
124
+ if (/^git (commit|push)/.test(cmd)) {
125
+ process.stderr.write(blockMsg + '\\n');
126
+ process.exit(2);
127
+ }
128
+ }
129
+
130
+ process.exit(0);
131
+ `;
132
+
133
+ const HOOK_ENTRY = {
134
+ matcher: 'Edit|Write|MultiEdit|NotebookEdit|Bash',
135
+ hooks: [{ type: 'command', command: HOOK_FILE }],
136
+ };
137
+
138
+ function installHook() {
139
+ // 1. Write hook script
140
+ mkdirSync(HOOKS_DIR, { recursive: true });
141
+ writeFileSync(HOOK_FILE, HOOK_SCRIPT, 'utf8');
142
+ chmodSync(HOOK_FILE, 0o755);
143
+
144
+ // 2. Merge into ~/.claude/settings.json
145
+ let settings = {};
146
+ if (existsSync(SETTINGS_FILE)) {
147
+ try { settings = JSON.parse(readFileSync(SETTINGS_FILE, 'utf8')); } catch { /* malformed, overwrite */ }
148
+ }
149
+
150
+ if (!Array.isArray(settings.hooks?.PreToolUse)) {
151
+ settings.hooks = settings.hooks ?? {};
152
+ settings.hooks.PreToolUse = [];
153
+ }
154
+
155
+ // Idempotent: remove any previous specialists-main-guard entry, re-add
156
+ settings.hooks.PreToolUse = settings.hooks.PreToolUse
157
+ .filter(e => !e.hooks?.some(h => h.command?.includes('specialists-main-guard')));
158
+ settings.hooks.PreToolUse.push(HOOK_ENTRY);
159
+
160
+ mkdirSync(CLAUDE_DIR, { recursive: true });
161
+ writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + '\n', 'utf8');
162
+ }
163
+
75
164
  // ── Main ──────────────────────────────────────────────────────────────────────
76
- console.log('\n' + bold(' OmniSpecialist — full-stack installer'));
165
+ console.log('\n' + bold(' Specialists — full-stack installer'));
77
166
 
78
167
  // 1. pi
79
168
  section('pi (coding agent runtime)');
@@ -119,7 +208,16 @@ if (!existsSync(SPECIALISTS_DIR)) {
119
208
  skip('~/.agents/specialists/ already exists');
120
209
  }
121
210
 
122
- // 6. Health check
211
+ // 6. Claude Code hooks
212
+ section('Claude Code hooks');
213
+ const hookExisted = existsSync(HOOK_FILE);
214
+ installHook();
215
+ hookExisted
216
+ ? 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)');
219
+
220
+ // 7. Health check
123
221
  section('Health check');
124
222
  if (isInstalled('pi')) {
125
223
  const r = spawnSync('pi', ['--list-models'], { encoding: 'utf8' });
@@ -128,9 +226,9 @@ if (isInstalled('pi')) {
128
226
  : skip('No active provider — run pi config to set one up');
129
227
  }
130
228
 
131
- // 7. Done
229
+ // 8. Done
132
230
  console.log('\n' + bold(green(' Done!')));
133
231
  console.log('\n' + bold(' Next steps:'));
134
232
  console.log(` 1. ${bold('Configure pi:')} run ${yellow('pi')} then ${yellow('pi config')} to enable model providers`);
135
- console.log(` 2. ${bold('Restart Claude Code')} to load the MCP`);
233
+ console.log(` 2. ${bold('Restart Claude Code')} to load the MCP and hooks`);
136
234
  console.log(` 3. ${bold('Update later:')} re-run this installer\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jaggerxtrm/specialists",
3
- "version": "2.1.2",
3
+ "version": "2.1.4",
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",