@hailer/mcp 0.0.6 → 0.1.1

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 (122) hide show
  1. package/.claude/agents/ada.md +127 -0
  2. package/.claude/agents/agent-builder.md +151 -0
  3. package/.claude/agents/alejandro.md +66 -0
  4. package/.claude/agents/bjorn.md +305 -0
  5. package/.claude/agents/dmitri.md +61 -0
  6. package/.claude/agents/giuseppe.md +66 -0
  7. package/.claude/agents/gunther.md +355 -0
  8. package/.claude/agents/helga.md +68 -0
  9. package/.claude/agents/ingrid.md +108 -0
  10. package/.claude/agents/kenji.md +58 -0
  11. package/.claude/agents/svetlana.md +394 -0
  12. package/.claude/agents/viktor.md +63 -0
  13. package/.claude/agents/yevgeni.md +60 -0
  14. package/.claude/hooks/agent-failure-detector.cjs +286 -0
  15. package/.claude/hooks/app-edit-guard.cjs +462 -0
  16. package/.claude/hooks/interactive-mode.cjs +59 -0
  17. package/.claude/hooks/mcp-server-guard.cjs +92 -0
  18. package/.claude/hooks/post-scaffold-hook.cjs +31 -0
  19. package/.claude/hooks/sdk-delete-guard.cjs +2 -0
  20. package/.claude/hooks/src-edit-guard.cjs +208 -0
  21. package/.claude/settings.json +47 -2
  22. package/.claude/skills/insight-join-patterns/SKILL.md +209 -0
  23. package/.env.example +13 -1
  24. package/CLAUDE.md +135 -0
  25. package/dist/app.js +4 -3
  26. package/dist/cli.js +0 -0
  27. package/dist/client/adaptive-documentation-bot.d.ts +0 -2
  28. package/dist/client/adaptive-documentation-bot.js +5 -16
  29. package/dist/client/message-processor.js +5 -0
  30. package/dist/client/providers/anthropic-provider.js +21 -7
  31. package/dist/mcp/UserContextCache.d.ts +14 -0
  32. package/dist/mcp/UserContextCache.js +49 -24
  33. package/dist/mcp/auth.d.ts +7 -0
  34. package/dist/mcp/auth.js +13 -5
  35. package/dist/mcp/hailer-clients.d.ts +5 -2
  36. package/dist/mcp/signal-handler.d.ts +28 -2
  37. package/dist/mcp/signal-handler.js +4 -2
  38. package/dist/mcp/tool-registry.d.ts +55 -2
  39. package/dist/mcp/tool-registry.js +197 -2
  40. package/dist/mcp/tools/app-core.d.ts +15 -0
  41. package/dist/mcp/tools/app-core.js +609 -0
  42. package/dist/mcp/tools/app-marketplace.d.ts +21 -0
  43. package/dist/mcp/tools/app-marketplace.js +1284 -0
  44. package/dist/mcp/tools/app-member.d.ts +11 -0
  45. package/dist/mcp/tools/app-member.js +258 -0
  46. package/dist/mcp/tools/app-scaffold.d.ts +11 -0
  47. package/dist/mcp/tools/app-scaffold.js +743 -0
  48. package/dist/mcp/tools/app.d.ts +13 -22
  49. package/dist/mcp/tools/app.js +17 -2466
  50. package/dist/mcp/tools/file.js +6 -6
  51. package/dist/mcp/tools/insight.d.ts +1 -0
  52. package/dist/mcp/tools/insight.js +203 -64
  53. package/dist/mcp/tools/user.js +3 -9
  54. package/dist/mcp/tools/workflow.js +49 -38
  55. package/dist/mcp/utils/hailer-api-client.js +4 -13
  56. package/dist/mcp/utils/tool-helpers.d.ts +102 -0
  57. package/dist/mcp/utils/tool-helpers.js +179 -0
  58. package/dist/mcp/utils/types.d.ts +6 -0
  59. package/dist/mcp/workspace-cache.d.ts +5 -5
  60. package/dist/mcp/workspace-cache.js +4 -3
  61. package/package.json +1 -1
  62. package/.claude/hooks/PreToolUse.sh +0 -52
  63. package/.claude/hooks/prompt-skill-loader.cjs +0 -553
  64. package/.claude/hooks/skill-loader.cjs +0 -142
  65. package/.claude/settings.local.json +0 -49
  66. package/.claude/skills/MCP-add-app-member-skill/SKILL.md +0 -977
  67. package/.claude/skills/MCP-build-data-app-skill/SKILL.md +0 -372
  68. package/.claude/skills/MCP-create-app-skill/SKILL.md +0 -1101
  69. package/.claude/skills/MCP-create-insight-skill/SKILL.md +0 -1317
  70. package/.claude/skills/MCP-get-insight-data-skill/SKILL.md +0 -1053
  71. package/.claude/skills/MCP-insight-api/SKILL.md +0 -185
  72. package/.claude/skills/MCP-insight-api/references/insight-endpoints.md +0 -514
  73. package/.claude/skills/MCP-install-workflow-skill/SKILL.md +0 -1056
  74. package/.claude/skills/MCP-list-apps-skill/SKILL.md +0 -1010
  75. package/.claude/skills/MCP-list-workflows-minimal-skill/SKILL.md +0 -992
  76. package/.claude/skills/MCP-local-first-skill/SKILL.md +0 -570
  77. package/.claude/skills/MCP-populate-workflow-data-skill/SKILL.md +0 -395
  78. package/.claude/skills/MCP-preview-insight-skill/SKILL.md +0 -1290
  79. package/.claude/skills/MCP-publish-hailer-app-skill/SKILL.md +0 -453
  80. package/.claude/skills/MCP-publish-template-skill/SKILL.md +0 -278
  81. package/.claude/skills/MCP-remove-app-member-skill/SKILL.md +0 -671
  82. package/.claude/skills/MCP-remove-app-skill/SKILL.md +0 -985
  83. package/.claude/skills/MCP-remove-insight-skill/SKILL.md +0 -1011
  84. package/.claude/skills/MCP-remove-workflow-skill/SKILL.md +0 -920
  85. package/.claude/skills/MCP-scaffold-hailer-app-skill/SKILL.md +0 -1314
  86. package/.claude/skills/MCP-update-app-skill/SKILL.md +0 -970
  87. package/.claude/skills/MCP-update-workflow-field-skill/SKILL.md +0 -1098
  88. package/.claude/skills/SDK-create-function-field-skill/SKILL.md +0 -313
  89. package/.claude/skills/SDK-generate-skill/SKILL.md +0 -223
  90. package/.claude/skills/SDK-init-skill/SKILL.md +0 -177
  91. package/.claude/skills/SDK-workspace-setup-skill/SKILL.md +0 -605
  92. package/.claude/skills/SDK-ws-config-skill/SKILL.md +0 -435
  93. package/.claude/skills/activity-api/SKILL.md +0 -96
  94. package/.claude/skills/activity-api/references/activity-endpoints.md +0 -845
  95. package/.claude/skills/agent-building/SKILL.md +0 -243
  96. package/.claude/skills/agent-building/references/architecture-patterns.md +0 -446
  97. package/.claude/skills/agent-building/references/code-examples.md +0 -587
  98. package/.claude/skills/agent-building/references/implementation-guide.md +0 -619
  99. package/.claude/skills/app-api/SKILL.md +0 -219
  100. package/.claude/skills/app-api/references/app-endpoints.md +0 -759
  101. package/.claude/skills/building-hailer-apps-skill/SKILL.md +0 -813
  102. package/.claude/skills/hailer-api/SKILL.md +0 -283
  103. package/.claude/skills/hailer-api/references/activities.md +0 -620
  104. package/.claude/skills/hailer-api/references/authentication.md +0 -216
  105. package/.claude/skills/hailer-api/references/datasets.md +0 -437
  106. package/.claude/skills/hailer-api/references/files.md +0 -301
  107. package/.claude/skills/hailer-api/references/insights.md +0 -469
  108. package/.claude/skills/hailer-api/references/workflows.md +0 -720
  109. package/.claude/skills/hailer-api/references/workspaces-users.md +0 -445
  110. package/.claude/skills/hailer-app-builder/SKILL.md +0 -340
  111. package/.claude/skills/mcp-tools/SKILL.md +0 -419
  112. package/.claude/skills/mcp-tools/references/api-endpoints.md +0 -499
  113. package/.claude/skills/mcp-tools/references/data-structures.md +0 -554
  114. package/.claude/skills/mcp-tools/references/implementation-patterns.md +0 -717
  115. package/.claude/skills/skill-testing/README.md +0 -137
  116. package/.claude/skills/skill-testing/SKILL.md +0 -348
  117. package/.claude/skills/skill-testing/references/test-patterns.md +0 -705
  118. package/.claude/skills/skill-testing/references/testing-guide.md +0 -603
  119. package/.claude/skills/skill-testing/references/validation-checklist.md +0 -537
  120. package/.claude/skills/spawn-app-builder/SKILL.md +0 -366
  121. package/.claude/skills/tool-builder/SKILL.md +0 -328
  122. package/tsconfig.json +0 -23
@@ -0,0 +1,462 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Claude Code PreToolUse Hook - BULLETPROOF Hailer App Guard
4
+ *
5
+ * Blocks ALL direct edits to Hailer app directories.
6
+ * Detection is pattern-based - no registration required.
7
+ *
8
+ * A directory is considered a Hailer app if it contains:
9
+ * - public/manifest.json with "appId" field, OR
10
+ * - package.json with @hailer/app-sdk dependency
11
+ *
12
+ * Edits are ONLY allowed when:
13
+ * 1. Running inside a subagent (Task tool with CLAUDE_CODE_ENTRYPOINT=task)
14
+ * 2. The app directory has been explicitly released for manual editing
15
+ *
16
+ * To release an app for manual editing:
17
+ * node app-edit-guard.cjs --release /path/to/app
18
+ */
19
+
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+
23
+ const RELEASE_TRACKER = '/tmp/.claude-released-apps.json';
24
+ const BUILDER_MODE_DIR = '/tmp/.claude-builder-mode';
25
+ const BUILDER_AGENT_ACTIVE = '/tmp/.claude-builder-agent-active';
26
+
27
+ // Read hook input from stdin
28
+ let input = '';
29
+ process.stdin.setEncoding('utf8');
30
+ process.stdin.on('data', chunk => input += chunk);
31
+ process.stdin.on('end', () => {
32
+ try {
33
+ const data = JSON.parse(input);
34
+ processHook(data);
35
+ } catch (e) {
36
+ // Invalid JSON - fail safe by BLOCKING
37
+ outputBlock('Hook received invalid input - blocking for safety');
38
+ }
39
+ });
40
+
41
+ function outputAllow() {
42
+ console.log(JSON.stringify({ decision: 'allow' }));
43
+ process.exit(0);
44
+ }
45
+
46
+ function outputBlock(message) {
47
+ console.log(JSON.stringify({
48
+ decision: 'block',
49
+ reason: message
50
+ }));
51
+ process.exit(0);
52
+ }
53
+
54
+ /**
55
+ * Check if a directory is a Hailer app by examining its contents
56
+ */
57
+ function isHailerAppDirectory(dirPath) {
58
+ try {
59
+ // Check 1: public/manifest.json with appId
60
+ const manifestPath = path.join(dirPath, 'public', 'manifest.json');
61
+ if (fs.existsSync(manifestPath)) {
62
+ try {
63
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
64
+ if (manifest.appId || manifest.name) {
65
+ return { isApp: true, name: manifest.name || path.basename(dirPath), reason: 'manifest.json' };
66
+ }
67
+ } catch {
68
+ // Invalid JSON in manifest, still might be a Hailer app structure
69
+ }
70
+ }
71
+
72
+ // Check 2: package.json with @hailer/app-sdk
73
+ const packagePath = path.join(dirPath, 'package.json');
74
+ if (fs.existsSync(packagePath)) {
75
+ try {
76
+ const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
77
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
78
+ if (deps['@hailer/app-sdk']) {
79
+ return { isApp: true, name: pkg.name || path.basename(dirPath), reason: '@hailer/app-sdk dependency' };
80
+ }
81
+ } catch {
82
+ // Invalid package.json
83
+ }
84
+ }
85
+
86
+ // Check 3: vite.config with hailer in comments or specific CORS config
87
+ const viteConfigPath = path.join(dirPath, 'vite.config.ts');
88
+ if (fs.existsSync(viteConfigPath)) {
89
+ try {
90
+ const viteConfig = fs.readFileSync(viteConfigPath, 'utf8');
91
+ if (viteConfig.includes('hailer') || viteConfig.includes('app.hailer.com')) {
92
+ return { isApp: true, name: path.basename(dirPath), reason: 'vite.config.ts hailer reference' };
93
+ }
94
+ } catch {
95
+ // Can't read vite config
96
+ }
97
+ }
98
+
99
+ return { isApp: false };
100
+ } catch {
101
+ return { isApp: false };
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Walk up the directory tree to find if file is inside a Hailer app
107
+ */
108
+ function findHailerAppRoot(filePath) {
109
+ let currentDir = path.dirname(filePath);
110
+ const root = path.parse(currentDir).root;
111
+
112
+ // Walk up max 10 levels
113
+ for (let i = 0; i < 10 && currentDir !== root; i++) {
114
+ const result = isHailerAppDirectory(currentDir);
115
+ if (result.isApp) {
116
+ return { ...result, path: currentDir };
117
+ }
118
+ currentDir = path.dirname(currentDir);
119
+ }
120
+
121
+ return null;
122
+ }
123
+
124
+ /**
125
+ * Check if an app directory has been released for manual editing
126
+ */
127
+ function isAppReleased(appPath) {
128
+ try {
129
+ if (!fs.existsSync(RELEASE_TRACKER)) {
130
+ return false;
131
+ }
132
+ const released = JSON.parse(fs.readFileSync(RELEASE_TRACKER, 'utf8'));
133
+ return released.includes(path.resolve(appPath));
134
+ } catch {
135
+ return false;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Check if a builder agent is currently active (global flag)
141
+ * Main Claude enables this before spawning builder agents
142
+ */
143
+ function isBuilderAgentActive() {
144
+ return fs.existsSync(BUILDER_AGENT_ACTIVE);
145
+ }
146
+
147
+ /**
148
+ * Check if builder mode is active for an app directory
149
+ * Builder mode is enabled by creating a marker file in BUILDER_MODE_DIR
150
+ */
151
+ function isBuilderModeActive(appPath) {
152
+ try {
153
+ if (!fs.existsSync(BUILDER_MODE_DIR)) {
154
+ return false;
155
+ }
156
+ // Hash the app path to create a unique marker filename
157
+ const normalizedPath = path.resolve(appPath);
158
+ const markerFile = path.join(BUILDER_MODE_DIR, Buffer.from(normalizedPath).toString('base64').replace(/[/+=]/g, '_'));
159
+ return fs.existsSync(markerFile);
160
+ } catch {
161
+ return false;
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Enable builder mode for an app directory
167
+ */
168
+ function enableBuilderMode(appPath) {
169
+ const normalizedPath = path.resolve(appPath);
170
+ if (!fs.existsSync(BUILDER_MODE_DIR)) {
171
+ fs.mkdirSync(BUILDER_MODE_DIR, { recursive: true });
172
+ }
173
+ const markerFile = path.join(BUILDER_MODE_DIR, Buffer.from(normalizedPath).toString('base64').replace(/[/+=]/g, '_'));
174
+ fs.writeFileSync(markerFile, JSON.stringify({ appPath: normalizedPath, enabledAt: new Date().toISOString() }));
175
+ return markerFile;
176
+ }
177
+
178
+ /**
179
+ * Disable builder mode for an app directory
180
+ */
181
+ function disableBuilderMode(appPath) {
182
+ const normalizedPath = path.resolve(appPath);
183
+ const markerFile = path.join(BUILDER_MODE_DIR, Buffer.from(normalizedPath).toString('base64').replace(/[/+=]/g, '_'));
184
+ if (fs.existsSync(markerFile)) {
185
+ fs.unlinkSync(markerFile);
186
+ return true;
187
+ }
188
+ return false;
189
+ }
190
+
191
+ function processHook(data) {
192
+ const { tool_name, tool_input } = data;
193
+
194
+ // Only guard Write and Edit tools
195
+ if (tool_name !== 'Write' && tool_name !== 'Edit') {
196
+ outputAllow();
197
+ return;
198
+ }
199
+
200
+ const filePath = tool_input?.file_path;
201
+ if (!filePath) {
202
+ outputAllow();
203
+ return;
204
+ }
205
+
206
+ // Normalize path
207
+ const normalizedPath = path.resolve(filePath);
208
+
209
+ // Find if this file is inside a Hailer app
210
+ const appInfo = findHailerAppRoot(normalizedPath);
211
+
212
+ if (!appInfo) {
213
+ // Not in a Hailer app directory
214
+ outputAllow();
215
+ return;
216
+ }
217
+
218
+ // Check if a builder agent is globally active - ALLOW
219
+ // Main Claude enables this before spawning builder agents
220
+ if (isBuilderAgentActive()) {
221
+ outputAllow();
222
+ return;
223
+ }
224
+
225
+ // Check if builder mode is active for this app - ALLOW
226
+ // This is the primary mechanism for spawned builder agents
227
+ if (isBuilderModeActive(appInfo.path)) {
228
+ outputAllow();
229
+ return;
230
+ }
231
+
232
+ // Check if this app has been released for manual editing - ALLOW
233
+ if (isAppReleased(appInfo.path)) {
234
+ outputAllow();
235
+ return;
236
+ }
237
+
238
+ // BLOCK with helpful message
239
+ outputBlock(`
240
+ 🚫 BLOCKED: Direct edit to Hailer app "${appInfo.name}"
241
+
242
+ Detected as Hailer app via: ${appInfo.reason}
243
+ App directory: ${appInfo.path}
244
+
245
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
246
+ WHY: Hailer apps must be built by a specialized builder agent
247
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
248
+
249
+ The builder agent has:
250
+ • Access to @hailer/app-sdk documentation
251
+ • Live dev server feedback for iteration
252
+ • Proper TypeScript patterns for Hailer apps
253
+
254
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
255
+ TO PROCEED: Spawn a builder agent
256
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
257
+
258
+ 1. Load the spawn skill:
259
+ Skill("spawn-app-builder")
260
+
261
+ 2. Follow the skill instructions to spawn the agent
262
+
263
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
264
+ OR: Release for manual editing (if user explicitly requests)
265
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
266
+
267
+ Ask the user first! If they confirm manual editing, run:
268
+ Bash: node .claude/hooks/app-edit-guard.cjs --release "${appInfo.path}"
269
+
270
+ Then retry your edit.
271
+ `);
272
+ }
273
+
274
+ // CLI: Release an app for manual editing
275
+ if (process.argv[2] === '--release' && process.argv[3]) {
276
+ const appPath = path.resolve(process.argv[3]);
277
+
278
+ // Verify it's actually a Hailer app
279
+ const appInfo = isHailerAppDirectory(appPath);
280
+ if (!appInfo.isApp) {
281
+ console.error(`Error: ${appPath} is not a Hailer app directory`);
282
+ process.exit(1);
283
+ }
284
+
285
+ // Load existing releases
286
+ let released = [];
287
+ try {
288
+ if (fs.existsSync(RELEASE_TRACKER)) {
289
+ released = JSON.parse(fs.readFileSync(RELEASE_TRACKER, 'utf8'));
290
+ }
291
+ } catch {
292
+ released = [];
293
+ }
294
+
295
+ // Add this app if not already released
296
+ if (!released.includes(appPath)) {
297
+ released.push(appPath);
298
+ fs.writeFileSync(RELEASE_TRACKER, JSON.stringify(released, null, 2));
299
+ console.log(`✅ Released "${appInfo.name}" for manual editing`);
300
+ console.log(` Path: ${appPath}`);
301
+ } else {
302
+ console.log(`ℹ️ "${appInfo.name}" was already released`);
303
+ }
304
+ process.exit(0);
305
+ }
306
+
307
+ // CLI: List released apps
308
+ if (process.argv[2] === '--list') {
309
+ try {
310
+ if (fs.existsSync(RELEASE_TRACKER)) {
311
+ const released = JSON.parse(fs.readFileSync(RELEASE_TRACKER, 'utf8'));
312
+ if (released.length === 0) {
313
+ console.log('No apps released for manual editing');
314
+ } else {
315
+ console.log('Released apps:');
316
+ released.forEach(p => console.log(` - ${p}`));
317
+ }
318
+ } else {
319
+ console.log('No apps released for manual editing');
320
+ }
321
+ } catch {
322
+ console.log('No apps released for manual editing');
323
+ }
324
+ process.exit(0);
325
+ }
326
+
327
+ // CLI: Enable builder mode for an app
328
+ if (process.argv[2] === '--builder-on' && process.argv[3]) {
329
+ const appPath = path.resolve(process.argv[3]);
330
+
331
+ // Verify it's actually a Hailer app
332
+ const appInfo = isHailerAppDirectory(appPath);
333
+ if (!appInfo.isApp) {
334
+ console.error(`Error: ${appPath} is not a Hailer app directory`);
335
+ process.exit(1);
336
+ }
337
+
338
+ enableBuilderMode(appPath);
339
+ console.log(`🔧 Builder mode ENABLED for "${appInfo.name}"`);
340
+ console.log(` Path: ${appPath}`);
341
+ console.log(` Spawned agents can now edit this app`);
342
+ process.exit(0);
343
+ }
344
+
345
+ // CLI: Disable builder mode for an app
346
+ if (process.argv[2] === '--builder-off' && process.argv[3]) {
347
+ const appPath = path.resolve(process.argv[3]);
348
+
349
+ if (disableBuilderMode(appPath)) {
350
+ console.log(`🔒 Builder mode DISABLED for: ${appPath}`);
351
+ } else {
352
+ console.log(`ℹ️ Builder mode was not active for: ${appPath}`);
353
+ }
354
+ process.exit(0);
355
+ }
356
+
357
+ // CLI: Revoke release
358
+ if (process.argv[2] === '--revoke' && process.argv[3]) {
359
+ const appPath = path.resolve(process.argv[3]);
360
+
361
+ try {
362
+ if (fs.existsSync(RELEASE_TRACKER)) {
363
+ let released = JSON.parse(fs.readFileSync(RELEASE_TRACKER, 'utf8'));
364
+ const before = released.length;
365
+ released = released.filter(p => p !== appPath);
366
+ if (released.length < before) {
367
+ fs.writeFileSync(RELEASE_TRACKER, JSON.stringify(released, null, 2));
368
+ console.log(`✅ Revoked manual editing for: ${appPath}`);
369
+ } else {
370
+ console.log(`ℹ️ App was not in released list: ${appPath}`);
371
+ }
372
+ }
373
+ } catch {
374
+ console.error('Error reading release tracker');
375
+ }
376
+ process.exit(0);
377
+ }
378
+
379
+ // CLI: Check if a path is a Hailer app
380
+ if (process.argv[2] === '--check' && process.argv[3]) {
381
+ const checkPath = path.resolve(process.argv[3]);
382
+ const result = isHailerAppDirectory(checkPath);
383
+ if (result.isApp) {
384
+ console.log(`✅ Hailer app detected: ${result.name}`);
385
+ console.log(` Detected via: ${result.reason}`);
386
+ console.log(` Builder mode: ${isBuilderModeActive(checkPath) ? 'ACTIVE' : 'Off'}`);
387
+ console.log(` Manual release: ${isAppReleased(checkPath) ? 'Yes' : 'No'}`);
388
+ } else {
389
+ console.log(`❌ Not a Hailer app: ${checkPath}`);
390
+ }
391
+ process.exit(0);
392
+ }
393
+
394
+ // CLI: Enable global builder agent mode
395
+ if (process.argv[2] === '--agent-on') {
396
+ fs.writeFileSync(BUILDER_AGENT_ACTIVE, JSON.stringify({ enabledAt: new Date().toISOString() }));
397
+ console.log('🔧 Builder agent mode ENABLED globally');
398
+ console.log(' All Hailer app edits are now allowed');
399
+ console.log(' Run --agent-off when done');
400
+ process.exit(0);
401
+ }
402
+
403
+ // CLI: Disable global builder agent mode
404
+ if (process.argv[2] === '--agent-off') {
405
+ if (fs.existsSync(BUILDER_AGENT_ACTIVE)) {
406
+ fs.unlinkSync(BUILDER_AGENT_ACTIVE);
407
+ console.log('🔒 Builder agent mode DISABLED');
408
+ } else {
409
+ console.log('ℹ️ Builder agent mode was not active');
410
+ }
411
+ process.exit(0);
412
+ }
413
+
414
+ // CLI: Check global builder agent status
415
+ if (process.argv[2] === '--agent-status') {
416
+ if (fs.existsSync(BUILDER_AGENT_ACTIVE)) {
417
+ console.log('🔧 Builder agent mode is ACTIVE');
418
+ } else {
419
+ console.log('🔒 Builder agent mode is OFF');
420
+ }
421
+ process.exit(0);
422
+ }
423
+
424
+ // CLI: Help
425
+ if (process.argv[2] === '--help' || process.argv[2] === '-h') {
426
+ console.log(`
427
+ Hailer App Edit Guard - Bulletproof protection for Hailer apps
428
+
429
+ UNIFIED SYSTEM: --agent-on/--agent-off controls BOTH this hook AND src-edit-guard.cjs.
430
+ One command enables builder mode for Hailer apps AND src/ directory edits.
431
+
432
+ Usage:
433
+ Global Builder Agent Mode (RECOMMENDED for spawning agents):
434
+ node app-edit-guard.cjs --agent-on Enable global builder mode
435
+ node app-edit-guard.cjs --agent-off Disable global builder mode
436
+ node app-edit-guard.cjs --agent-status Check if builder mode is active
437
+
438
+ Per-App Builder Mode:
439
+ node app-edit-guard.cjs --builder-on <path> Enable builder mode for an app
440
+ node app-edit-guard.cjs --builder-off <path> Disable builder mode for an app
441
+
442
+ Manual Editing (for direct edits by main agent):
443
+ node app-edit-guard.cjs --release <path> Release an app for manual editing
444
+ node app-edit-guard.cjs --revoke <path> Revoke manual editing permission
445
+
446
+ Utilities:
447
+ node app-edit-guard.cjs --check <path> Check if path is a Hailer app
448
+ node app-edit-guard.cjs --list List all released apps
449
+ node app-edit-guard.cjs --help Show this help
450
+
451
+ Workflow for spawning builder agents (RECOMMENDED):
452
+ 1. Main agent: node app-edit-guard.cjs --agent-on
453
+ 2. Main agent spawns builder agent via Task tool
454
+ 3. Builder agent can freely edit ANY Hailer app AND src/ files
455
+ 4. Main agent: node app-edit-guard.cjs --agent-off
456
+
457
+ As a hook:
458
+ Reads JSON from stdin with tool_name and tool_input
459
+ Outputs JSON with decision: "allow" or "block"
460
+ `);
461
+ process.exit(0);
462
+ }
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Claude Code UserPromptSubmit Hook - Interactive Question Mode
4
+ *
5
+ * Injects a reminder to ask clarifying questions before starting complex tasks.
6
+ * Triggers on every user prompt to encourage interactive behavior.
7
+ */
8
+
9
+ // Read hook input from stdin
10
+ let input = '';
11
+ process.stdin.setEncoding('utf8');
12
+ process.stdin.on('data', chunk => input += chunk);
13
+ process.stdin.on('end', () => {
14
+ try {
15
+ const data = JSON.parse(input);
16
+ processHook(data);
17
+ } catch {
18
+ process.exit(0);
19
+ }
20
+ });
21
+
22
+ function processHook(data) {
23
+ const { prompt } = data;
24
+
25
+ if (!prompt) {
26
+ process.exit(0);
27
+ }
28
+
29
+ const lowerPrompt = prompt.toLowerCase();
30
+
31
+ // Detect task types that benefit from questions
32
+ const taskPatterns = [
33
+ { pattern: /build|create|make.*app/i, type: 'app', questions: ['What data to display?', 'What layout/components?', 'What user actions needed?'] },
34
+ { pattern: /create|add.*insight|report/i, type: 'insight', questions: ['What metrics/aggregations?', 'Which workflows to query?', 'Any filters needed?'] },
35
+ { pattern: /import|create.*activit|bulk/i, type: 'data', questions: ['Which workflow?', 'What field values?', 'How many records?'] },
36
+ { pattern: /add|create.*field|workflow|phase/i, type: 'schema', questions: ['Field type?', 'Required or optional?', 'Default values?'] },
37
+ { pattern: /update|change|modify/i, type: 'update', questions: ['Which records affected?', 'What new values?', 'Confirm before applying?'] },
38
+ ];
39
+
40
+ const matched = taskPatterns.find(p => p.pattern.test(prompt));
41
+
42
+ if (matched) {
43
+ const output = `
44
+ <interactive-mode>
45
+ BEFORE STARTING: Consider asking clarifying questions.
46
+
47
+ Task type detected: ${matched.type}
48
+ Suggested questions to ask user:
49
+ ${matched.questions.map(q => `- ${q}`).join('\n')}
50
+
51
+ Use AskUserQuestion tool if requirements are unclear.
52
+ Gather specifics before spawning agents or making changes.
53
+ </interactive-mode>
54
+ `;
55
+ console.log(output);
56
+ }
57
+
58
+ process.exit(0);
59
+ }
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Server Guard Hook
4
+ *
5
+ * PreToolUse hook that prevents Claude from starting the MCP server.
6
+ * When blocked, instructs Claude to provide manual instructions to the user.
7
+ *
8
+ * Blocked commands: npm run dev, npm start, tsx src/app.ts, etc.
9
+ */
10
+
11
+ // Commands that would start the MCP server
12
+ const SERVER_START_PATTERNS = [
13
+ /npm run dev\b/,
14
+ /npm run start\b/,
15
+ /npm start\b/,
16
+ /tsx\s+.*src\/app\.ts/,
17
+ /tsx\s+watch\s+.*src\/app\.ts/,
18
+ /node\s+.*src\/app\.ts/,
19
+ /node\s+.*dist\/app\.js/,
20
+ /npx\s+tsx\s+.*src\/app/,
21
+ ];
22
+
23
+ // Read stdin
24
+ async function readStdin() {
25
+ return new Promise((resolve) => {
26
+ let data = '';
27
+ process.stdin.setEncoding('utf8');
28
+ process.stdin.on('data', chunk => data += chunk);
29
+ process.stdin.on('end', () => resolve(data));
30
+ setTimeout(() => resolve(data), 100);
31
+ });
32
+ }
33
+
34
+ async function main() {
35
+ try {
36
+ const input = await readStdin();
37
+
38
+ if (!input.trim()) {
39
+ process.exit(0);
40
+ }
41
+
42
+ const hookData = JSON.parse(input);
43
+ const command = hookData.tool_input?.command || '';
44
+
45
+ // Check if this is a server start command
46
+ const isServerStart = SERVER_START_PATTERNS.some(pattern => pattern.test(command));
47
+
48
+ if (!isServerStart) {
49
+ process.exit(0);
50
+ }
51
+
52
+ // Output instructions to stderr (appears as system reminder)
53
+ const instructions = `============================================================
54
+ 🚫 MCP SERVER COMMAND BLOCKED
55
+ ============================================================
56
+
57
+ You CANNOT start the MCP server. The user runs it manually.
58
+
59
+ INSTEAD, tell the user to run this command in their terminal:
60
+
61
+ cd ${process.env.CLAUDE_PROJECT_DIR || '/home/brodolf/Desktop/hailer-mcp/hailer-mcp'}
62
+ npm run dev
63
+
64
+ Or if they want to run it in the background:
65
+
66
+ npm run dev &
67
+
68
+ The MCP server must be running BEFORE starting Claude Code with MCP.
69
+
70
+ ============================================================
71
+ DO NOT attempt to run server commands. Give manual instructions only.
72
+ ============================================================`;
73
+
74
+ // Output to stderr so it appears as system reminder
75
+ console.error(instructions);
76
+
77
+ // Block the command
78
+ const response = {
79
+ permissionDecision: "deny",
80
+ permissionDecisionReason: "MCP server commands are blocked. Provide manual instructions to the user instead."
81
+ };
82
+
83
+ console.log(JSON.stringify(response));
84
+ process.exit(0);
85
+
86
+ } catch (error) {
87
+ console.error(`[mcp-server-guard] Error: ${error.message}`);
88
+ process.exit(0);
89
+ }
90
+ }
91
+
92
+ main();
@@ -4,9 +4,20 @@
4
4
  *
5
5
  * This hook triggers after scaffold_hailer_app completes successfully
6
6
  * and ASKS the user if they want to spawn the app builder agent.
7
+ *
8
+ * Also registers the app with app-edit-guard to block direct edits.
7
9
  */
8
10
 
9
11
  const path = require('path');
12
+ const fs = require('fs');
13
+ const { execSync } = require('child_process');
14
+
15
+ const TRACKER_DIR = '/tmp/.claude-scaffolded-apps';
16
+
17
+ // Ensure tracker directory exists
18
+ if (!fs.existsSync(TRACKER_DIR)) {
19
+ fs.mkdirSync(TRACKER_DIR, { recursive: true });
20
+ }
10
21
 
11
22
  // Read hook input from stdin
12
23
  let input = '';
@@ -45,6 +56,9 @@ function processHook(data) {
45
56
  ? path.join(tool_input.targetDirectory, projectName)
46
57
  : path.join(cwd, projectName);
47
58
 
59
+ // Register app with edit guard to block direct edits
60
+ registerScaffoldedApp(projectName, projectPath);
61
+
48
62
  // Build the AskUserQuestion instruction
49
63
  const output = `
50
64
  ============================================================
@@ -123,3 +137,20 @@ ASK THE USER NOW - Do not skip this question!
123
137
  console.error(output);
124
138
  process.exit(0);
125
139
  }
140
+
141
+ /**
142
+ * Register scaffolded app in tracker so app-edit-guard can block direct edits
143
+ */
144
+ function registerScaffoldedApp(name, appPath) {
145
+ try {
146
+ const trackerFile = path.join(TRACKER_DIR, `${name}.json`);
147
+ fs.writeFileSync(trackerFile, JSON.stringify({
148
+ name,
149
+ path: path.resolve(appPath),
150
+ scaffoldedAt: new Date().toISOString()
151
+ }));
152
+ console.error(`📝 Registered app "${name}" for edit protection`);
153
+ } catch (err) {
154
+ console.error(`Warning: Could not register app for edit protection: ${err.message}`);
155
+ }
156
+ }
@@ -18,6 +18,8 @@ const DELETE_RISK_PATTERNS = [
18
18
  /npm run groups-push\b/, // Push groups - can delete groups
19
19
  /npm run teams-push\b/, // Push teams - can delete teams
20
20
  /npm run insights-push\b/, // Push insights - can delete insights
21
+ /npm run templates-sync\b/, // Sync templates - can delete templates
22
+ /npm run templates-push\b/, // Push templates - can modify/delete templates
21
23
  /hailer-sdk ws-config push\b/,
22
24
  /hailer-sdk ws-config.*sync\b/,
23
25
  ];