@jaggerxtrm/specialists 2.1.2 → 2.1.3

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 +92 -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.sh');
12
16
  const MCP_NAME = 'specialists';
13
17
  const GITHUB_PKG = '@jaggerxtrm/specialists';
14
18
 
@@ -72,8 +76,80 @@ function registerMCP() {
72
76
  return true;
73
77
  }
74
78
 
79
+ // ── Hook installation ─────────────────────────────────────────────────────────
80
+
81
+ const HOOK_SCRIPT = `#!/usr/bin/env bash
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
+ BRANCH=$(git branch --show-current 2>/dev/null)
89
+
90
+ # Not in a git repo or not on a protected branch — allow
91
+ if [ -z "$BRANCH" ] || { [ "$BRANCH" != "main" ] && [ "$BRANCH" != "master" ]; }; then
92
+ exit 0
93
+ fi
94
+
95
+ INPUT=$(cat)
96
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name' 2>/dev/null)
97
+
98
+ BLOCK_MSG="⛔ Direct edits on '$BRANCH' are not allowed.
99
+ Create a feature branch first: git checkout -b feature/<name>"
100
+
101
+ case "$TOOL" in
102
+ Edit|Write|MultiEdit|NotebookEdit)
103
+ echo "$BLOCK_MSG" >&2
104
+ exit 2
105
+ ;;
106
+ Bash)
107
+ CMD=$(echo "$INPUT" | jq -r '.tool_input.command' 2>/dev/null)
108
+ if echo "$CMD" | grep -qE '^git (commit|push)'; then
109
+ echo "$BLOCK_MSG" >&2
110
+ exit 2
111
+ fi
112
+ exit 0
113
+ ;;
114
+ *)
115
+ exit 0
116
+ ;;
117
+ esac
118
+ `;
119
+
120
+ const HOOK_ENTRY = {
121
+ matcher: 'Edit|Write|MultiEdit|NotebookEdit|Bash',
122
+ hooks: [{ type: 'command', command: HOOK_FILE }],
123
+ };
124
+
125
+ function installHook() {
126
+ // 1. Write hook script
127
+ mkdirSync(HOOKS_DIR, { recursive: true });
128
+ writeFileSync(HOOK_FILE, HOOK_SCRIPT, 'utf8');
129
+ chmodSync(HOOK_FILE, 0o755);
130
+
131
+ // 2. Merge into ~/.claude/settings.json
132
+ let settings = {};
133
+ if (existsSync(SETTINGS_FILE)) {
134
+ try { settings = JSON.parse(readFileSync(SETTINGS_FILE, 'utf8')); } catch { /* malformed, overwrite */ }
135
+ }
136
+
137
+ if (!Array.isArray(settings.hooks?.PreToolUse)) {
138
+ settings.hooks = settings.hooks ?? {};
139
+ settings.hooks.PreToolUse = [];
140
+ }
141
+
142
+ // Idempotent: remove any previous specialists-main-guard entry, re-add
143
+ settings.hooks.PreToolUse = settings.hooks.PreToolUse
144
+ .filter(e => !e.hooks?.some(h => h.command?.includes('specialists-main-guard')));
145
+ settings.hooks.PreToolUse.push(HOOK_ENTRY);
146
+
147
+ mkdirSync(CLAUDE_DIR, { recursive: true });
148
+ writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + '\n', 'utf8');
149
+ }
150
+
75
151
  // ── Main ──────────────────────────────────────────────────────────────────────
76
- console.log('\n' + bold(' OmniSpecialist — full-stack installer'));
152
+ console.log('\n' + bold(' Specialists — full-stack installer'));
77
153
 
78
154
  // 1. pi
79
155
  section('pi (coding agent runtime)');
@@ -119,7 +195,16 @@ if (!existsSync(SPECIALISTS_DIR)) {
119
195
  skip('~/.agents/specialists/ already exists');
120
196
  }
121
197
 
122
- // 6. Health check
198
+ // 6. Claude Code hooks
199
+ section('Claude Code hooks');
200
+ const hookExisted = existsSync(HOOK_FILE);
201
+ installHook();
202
+ hookExisted
203
+ ? ok('main-guard hook updated')
204
+ : ok('main-guard hook installed → ~/.claude/hooks/specialists-main-guard.sh');
205
+ info('Blocks Edit/Write/git commit/push on main or master branch');
206
+
207
+ // 7. Health check
123
208
  section('Health check');
124
209
  if (isInstalled('pi')) {
125
210
  const r = spawnSync('pi', ['--list-models'], { encoding: 'utf8' });
@@ -128,9 +213,9 @@ if (isInstalled('pi')) {
128
213
  : skip('No active provider — run pi config to set one up');
129
214
  }
130
215
 
131
- // 7. Done
216
+ // 8. Done
132
217
  console.log('\n' + bold(green(' Done!')));
133
218
  console.log('\n' + bold(' Next steps:'));
134
219
  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`);
220
+ console.log(` 2. ${bold('Restart Claude Code')} to load the MCP and hooks`);
136
221
  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.3",
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",