create-merlin-brain 3.4.7 → 3.4.8
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.cjs +113 -76
- package/package.json +1 -1
package/bin/install.cjs
CHANGED
|
@@ -176,14 +176,18 @@ function ensureDir(dir) {
|
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
async function promptApiKey() {
|
|
179
|
+
async function promptApiKey(hasExisting) {
|
|
180
180
|
const rl = readline.createInterface({
|
|
181
181
|
input: process.stdin,
|
|
182
182
|
output: process.stdout,
|
|
183
183
|
});
|
|
184
184
|
|
|
185
|
+
const prompt = hasExisting
|
|
186
|
+
? `\n${colors.cyan}?${colors.reset} Merlin Sights API key (Enter to keep existing, or paste new): `
|
|
187
|
+
: `\n${colors.cyan}?${colors.reset} Merlin Sights API key (get one at ${colors.cyan}merlin.build${colors.reset}, Enter to skip): `;
|
|
188
|
+
|
|
185
189
|
return new Promise((resolve) => {
|
|
186
|
-
rl.question(
|
|
190
|
+
rl.question(prompt, (answer) => {
|
|
187
191
|
rl.close();
|
|
188
192
|
resolve(answer.trim());
|
|
189
193
|
});
|
|
@@ -272,9 +276,9 @@ merlin-loop() {
|
|
|
272
276
|
if (rcContent.includes('# Merlin Brain - The AI Brain') ||
|
|
273
277
|
rcContent.includes('# Merlin Brain - Makes Claude Code') ||
|
|
274
278
|
rcContent.includes('# Merlin Brain -')) {
|
|
275
|
-
// Remove old blocks
|
|
276
|
-
rcContent = rcContent.replace(/\n# ═+\n# Merlin Brain[\s\S]*?alias cc="claude"\n?/g, '');
|
|
277
|
-
rcContent = rcContent.replace(/\n# Merlin Brain - Makes Claude Code[\s\S]*?alias cc='claude'\n?/g, '');
|
|
279
|
+
// Remove old blocks (capture full block including trailing merlin-loop if present)
|
|
280
|
+
rcContent = rcContent.replace(/\n# ═+\n# Merlin Brain[\s\S]*?alias cc="claude"\n?([\s\S]*?merlin-loop\(\) \{[\s\S]*?\n\}\n)?/g, '');
|
|
281
|
+
rcContent = rcContent.replace(/\n# Merlin Brain - Makes Claude Code[\s\S]*?alias cc='claude'\n?([\s\S]*?merlin-loop\(\) \{[\s\S]*?\n\}\n)?/g, '');
|
|
278
282
|
rcContent = rcContent.replace(/\n# Merlin - Claude Code with[\s\S]*?alias cc=.*\n?/g, '');
|
|
279
283
|
fs.writeFileSync(rcFile, rcContent);
|
|
280
284
|
logSuccess('Updating existing Merlin shell integration');
|
|
@@ -302,7 +306,7 @@ function cleanupLegacy() {
|
|
|
302
306
|
const content = fs.readFileSync(claudeMdPath, 'utf8');
|
|
303
307
|
if (content.includes('ccwiki_') || content.includes('briefed')) {
|
|
304
308
|
fs.unlinkSync(claudeMdPath);
|
|
305
|
-
cleaned.push('~/.claude/CLAUDE.md (had old naming)');
|
|
309
|
+
cleaned.push('Removed ~/.claude/CLAUDE.md (had old naming)');
|
|
306
310
|
}
|
|
307
311
|
}
|
|
308
312
|
|
|
@@ -324,20 +328,27 @@ function cleanupLegacy() {
|
|
|
324
328
|
let settings = JSON.parse(content);
|
|
325
329
|
|
|
326
330
|
// Remove broken hooks (contain $HOME, gsd-, or old paths)
|
|
331
|
+
// Iterate ALL hook types, handle both old and new format
|
|
327
332
|
if (settings.hooks) {
|
|
328
|
-
const
|
|
329
|
-
for (const hookType of hookTypes) {
|
|
333
|
+
for (const hookType of Object.keys(settings.hooks)) {
|
|
330
334
|
if (Array.isArray(settings.hooks[hookType])) {
|
|
331
335
|
const originalLength = settings.hooks[hookType].length;
|
|
332
|
-
settings.hooks[hookType] = settings.hooks[hookType].filter(
|
|
333
|
-
//
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
336
|
+
settings.hooks[hookType] = settings.hooks[hookType].filter(entry => {
|
|
337
|
+
// Extract command paths from both old and new format
|
|
338
|
+
let hookPaths = '';
|
|
339
|
+
if (entry.command) {
|
|
340
|
+
// Old format: { type, command, ... }
|
|
341
|
+
hookPaths = entry.command;
|
|
342
|
+
} else if (entry.hooks && Array.isArray(entry.hooks)) {
|
|
343
|
+
// New format: { matcher?, hooks: [{ type, command }] }
|
|
344
|
+
hookPaths = entry.hooks.map(h => h.command || h.path || '').join(' ');
|
|
345
|
+
} else if (typeof entry === 'string') {
|
|
346
|
+
hookPaths = entry;
|
|
347
|
+
}
|
|
348
|
+
if (hookPaths.includes('$HOME') ||
|
|
349
|
+
hookPaths.includes('gsd-') ||
|
|
350
|
+
hookPaths.includes('ccwiki') ||
|
|
351
|
+
hookPaths.includes('briefed')) {
|
|
341
352
|
return false;
|
|
342
353
|
}
|
|
343
354
|
return true;
|
|
@@ -359,13 +370,13 @@ function cleanupLegacy() {
|
|
|
359
370
|
|
|
360
371
|
if (modified) {
|
|
361
372
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
362
|
-
cleaned.push('~/.claude/settings.local.json (
|
|
373
|
+
cleaned.push('Cleaned ~/.claude/settings.local.json (old tools and broken hooks)');
|
|
363
374
|
}
|
|
364
375
|
} catch (jsonErr) {
|
|
365
376
|
// If JSON parsing fails, just do string replacement
|
|
366
377
|
if (modified) {
|
|
367
378
|
fs.writeFileSync(settingsPath, content);
|
|
368
|
-
cleaned.push('~/.claude/settings.local.json (
|
|
379
|
+
cleaned.push('Cleaned ~/.claude/settings.local.json (old tools)');
|
|
369
380
|
}
|
|
370
381
|
}
|
|
371
382
|
} catch (e) { /* ignore */ }
|
|
@@ -379,7 +390,7 @@ function cleanupLegacy() {
|
|
|
379
390
|
// Remove old gsd-* hook files
|
|
380
391
|
if (file.startsWith('gsd-') || file.includes('ccwiki') || file.includes('briefed')) {
|
|
381
392
|
removeFile(path.join(hooksDir, file));
|
|
382
|
-
cleaned.push(
|
|
393
|
+
cleaned.push(`Removed ~/.claude/hooks/${file}`);
|
|
383
394
|
}
|
|
384
395
|
}
|
|
385
396
|
}
|
|
@@ -424,7 +435,7 @@ function cleanupLegacy() {
|
|
|
424
435
|
|
|
425
436
|
if (modified) {
|
|
426
437
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
427
|
-
cleaned.push('~/.claude/config.json (
|
|
438
|
+
cleaned.push('Migrated ~/.claude/config.json (to global binary)');
|
|
428
439
|
}
|
|
429
440
|
} catch (e) { /* ignore */ }
|
|
430
441
|
}
|
|
@@ -470,7 +481,7 @@ function cleanupLegacy() {
|
|
|
470
481
|
|
|
471
482
|
if (modified) {
|
|
472
483
|
fs.writeFileSync(desktopConfigPath, JSON.stringify(config, null, 2));
|
|
473
|
-
cleaned.push('~/.claude/claude_desktop_config.json (
|
|
484
|
+
cleaned.push('Migrated ~/.claude/claude_desktop_config.json (to global binary)');
|
|
474
485
|
}
|
|
475
486
|
} catch (e) { /* ignore */ }
|
|
476
487
|
}
|
|
@@ -513,7 +524,7 @@ function cleanupLegacy() {
|
|
|
513
524
|
|
|
514
525
|
for (const dir of dirsToRemove) {
|
|
515
526
|
if (removeDirRecursive(dir)) {
|
|
516
|
-
cleaned.push(dir.replace(os.homedir(), '~'));
|
|
527
|
+
cleaned.push('Removed ' + dir.replace(os.homedir(), '~'));
|
|
517
528
|
}
|
|
518
529
|
}
|
|
519
530
|
|
|
@@ -543,7 +554,7 @@ function cleanupLegacy() {
|
|
|
543
554
|
|
|
544
555
|
for (const file of filesToRemove) {
|
|
545
556
|
if (removeFile(file)) {
|
|
546
|
-
cleaned.push(file.replace(os.homedir(), '~'));
|
|
557
|
+
cleaned.push('Removed ' + file.replace(os.homedir(), '~'));
|
|
547
558
|
}
|
|
548
559
|
}
|
|
549
560
|
|
|
@@ -565,7 +576,7 @@ function cleanupLegacy() {
|
|
|
565
576
|
try {
|
|
566
577
|
// Reset to empty object so Merlin auto-detects from git remote
|
|
567
578
|
fs.writeFileSync(merlinConfigPath, '{}');
|
|
568
|
-
cleaned.push('~/.merlin/config.json (
|
|
579
|
+
cleaned.push('Reset ~/.merlin/config.json (for auto-detect)');
|
|
569
580
|
} catch (e) { /* ignore */ }
|
|
570
581
|
}
|
|
571
582
|
|
|
@@ -586,7 +597,7 @@ function cleanupLegacy() {
|
|
|
586
597
|
claudeJson.mcpServers.merlin.command = 'merlin-brain';
|
|
587
598
|
delete claudeJson.mcpServers.merlin.args;
|
|
588
599
|
modified = true;
|
|
589
|
-
cleaned.push('~/.claude.json (
|
|
600
|
+
cleaned.push('Migrated ~/.claude.json (to global binary)');
|
|
590
601
|
}
|
|
591
602
|
}
|
|
592
603
|
|
|
@@ -594,7 +605,7 @@ function cleanupLegacy() {
|
|
|
594
605
|
if (claudeJson.mcpServers?.briefed) {
|
|
595
606
|
delete claudeJson.mcpServers.briefed;
|
|
596
607
|
modified = true;
|
|
597
|
-
cleaned.push('
|
|
608
|
+
cleaned.push('Removed deprecated briefed server from ~/.claude.json');
|
|
598
609
|
}
|
|
599
610
|
|
|
600
611
|
if (modified) {
|
|
@@ -622,7 +633,7 @@ function cleanupLegacy() {
|
|
|
622
633
|
const pkgJsonPath2 = path.join(entryPath, 'node_modules', 'create-merlin-pro', 'package.json');
|
|
623
634
|
if (fs.existsSync(pkgJsonPath) || fs.existsSync(pkgJsonPath2)) {
|
|
624
635
|
removeDirRecursive(entryPath);
|
|
625
|
-
cleaned.push('~/.npm/_npx/' + entry.name + ' (old npx cache)');
|
|
636
|
+
cleaned.push('Removed ~/.npm/_npx/' + entry.name + ' (old npx cache)');
|
|
626
637
|
}
|
|
627
638
|
}
|
|
628
639
|
}
|
|
@@ -639,12 +650,12 @@ function cleanupLegacy() {
|
|
|
639
650
|
// Remove old gsd-*.md and ccwiki-*.md agents
|
|
640
651
|
if ((file.startsWith('gsd-') || file.startsWith('ccwiki-')) && file.endsWith('.md')) {
|
|
641
652
|
removeFile(path.join(agentsDir, file));
|
|
642
|
-
cleaned.push(
|
|
653
|
+
cleaned.push(`Removed ~/.claude/agents/${file}`);
|
|
643
654
|
}
|
|
644
655
|
// Remove backup files in agents
|
|
645
656
|
if (file.endsWith('.backup') || file.endsWith('.bak') || file.endsWith('.old') || file.endsWith('~')) {
|
|
646
657
|
removeFile(path.join(agentsDir, file));
|
|
647
|
-
cleaned.push(
|
|
658
|
+
cleaned.push(`Removed ~/.claude/agents/${file}`);
|
|
648
659
|
}
|
|
649
660
|
}
|
|
650
661
|
}
|
|
@@ -709,7 +720,7 @@ function cleanupLegacy() {
|
|
|
709
720
|
|
|
710
721
|
if (modified) {
|
|
711
722
|
fs.writeFileSync(rcFile, content);
|
|
712
|
-
cleaned.push(rcFile.replace(os.homedir(), '~') + ' (removed old integrations)');
|
|
723
|
+
cleaned.push('Updated ' + rcFile.replace(os.homedir(), '~') + ' (removed old integrations)');
|
|
713
724
|
}
|
|
714
725
|
}
|
|
715
726
|
}
|
|
@@ -734,7 +745,7 @@ async function install() {
|
|
|
734
745
|
const cleaned = cleanupLegacy();
|
|
735
746
|
if (cleaned.length > 0) {
|
|
736
747
|
for (const item of cleaned) {
|
|
737
|
-
logSuccess(
|
|
748
|
+
logSuccess(item);
|
|
738
749
|
}
|
|
739
750
|
} else {
|
|
740
751
|
logSuccess('No legacy artifacts found');
|
|
@@ -890,60 +901,83 @@ async function install() {
|
|
|
890
901
|
}
|
|
891
902
|
settings.hooks = settings.hooks || {};
|
|
892
903
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
904
|
+
// ═══════════════════════════════════════════════════════════════
|
|
905
|
+
// MIGRATE old-format hooks to new format (matcher + hooks array)
|
|
906
|
+
// Old: { type, command, matcher?, prompt? }
|
|
907
|
+
// New: { matcher?, hooks: [{ type, command, prompt? }] }
|
|
908
|
+
// ═══════════════════════════════════════════════════════════════
|
|
909
|
+
for (const [eventName, entries] of Object.entries(settings.hooks)) {
|
|
910
|
+
if (!Array.isArray(entries)) continue;
|
|
911
|
+
settings.hooks[eventName] = entries.map(entry => {
|
|
912
|
+
// Already in new format (has hooks array)
|
|
913
|
+
if (entry.hooks && Array.isArray(entry.hooks)) return entry;
|
|
914
|
+
// Old format → convert to new format
|
|
915
|
+
const handler = {};
|
|
916
|
+
if (entry.type) handler.type = entry.type;
|
|
917
|
+
if (entry.command) handler.command = entry.command;
|
|
918
|
+
if (entry.prompt) handler.prompt = entry.prompt;
|
|
919
|
+
if (entry.timeout !== undefined) handler.timeout = entry.timeout;
|
|
920
|
+
if (entry.async !== undefined) handler.async = entry.async;
|
|
921
|
+
if (entry.statusMessage) handler.statusMessage = entry.statusMessage;
|
|
922
|
+
const newEntry = {};
|
|
923
|
+
if (entry.matcher) newEntry.matcher = entry.matcher;
|
|
924
|
+
newEntry.hooks = [handler];
|
|
925
|
+
return newEntry;
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// Helper: check if a command hook already exists in an array (new format)
|
|
930
|
+
const addHookIfMissing = (hookArray, matcherGroup) => {
|
|
931
|
+
const cmd = matcherGroup.hooks?.[0]?.command;
|
|
932
|
+
if (!cmd) { hookArray.push(matcherGroup); return hookArray; }
|
|
933
|
+
const exists = hookArray.some(entry =>
|
|
934
|
+
entry.hooks?.some(h => h.command === cmd)
|
|
935
|
+
);
|
|
936
|
+
if (!exists) hookArray.push(matcherGroup);
|
|
896
937
|
return hookArray;
|
|
897
938
|
};
|
|
898
939
|
|
|
899
|
-
// SessionStart
|
|
940
|
+
// SessionStart hooks
|
|
900
941
|
settings.hooks.SessionStart = settings.hooks.SessionStart || [];
|
|
901
942
|
addHookIfMissing(settings.hooks.SessionStart, {
|
|
902
|
-
type: 'command',
|
|
903
|
-
command: 'bash ~/.claude/hooks/session-start.sh'
|
|
943
|
+
hooks: [{ type: 'command', command: 'bash ~/.claude/hooks/session-start.sh' }]
|
|
904
944
|
});
|
|
905
945
|
|
|
906
946
|
// SessionStart hook: Agent sync (background, runs at most once/hour)
|
|
907
947
|
addHookIfMissing(settings.hooks.SessionStart, {
|
|
908
|
-
type: 'command',
|
|
909
|
-
command: 'bash ~/.claude/hooks/agent-sync.sh'
|
|
948
|
+
hooks: [{ type: 'command', command: 'bash ~/.claude/hooks/agent-sync.sh' }]
|
|
910
949
|
});
|
|
911
950
|
|
|
912
951
|
// PreToolUse hook (Edit/Write only)
|
|
913
952
|
settings.hooks.PreToolUse = settings.hooks.PreToolUse || [];
|
|
914
953
|
addHookIfMissing(settings.hooks.PreToolUse, {
|
|
915
|
-
|
|
916
|
-
command: 'bash ~/.claude/hooks/pre-edit-sights-check.sh'
|
|
917
|
-
matcher: 'Edit|Write'
|
|
954
|
+
matcher: 'Edit|Write',
|
|
955
|
+
hooks: [{ type: 'command', command: 'bash ~/.claude/hooks/pre-edit-sights-check.sh' }]
|
|
918
956
|
});
|
|
919
957
|
|
|
920
958
|
// PostToolUse hook (Edit/Write only)
|
|
921
959
|
settings.hooks.PostToolUse = settings.hooks.PostToolUse || [];
|
|
922
960
|
addHookIfMissing(settings.hooks.PostToolUse, {
|
|
923
|
-
|
|
924
|
-
command: 'bash ~/.claude/hooks/post-edit-logger.sh'
|
|
925
|
-
matcher: 'Edit|Write'
|
|
961
|
+
matcher: 'Edit|Write',
|
|
962
|
+
hooks: [{ type: 'command', command: 'bash ~/.claude/hooks/post-edit-logger.sh' }]
|
|
926
963
|
});
|
|
927
964
|
|
|
928
965
|
// Stop hook (session end)
|
|
929
966
|
settings.hooks.Stop = settings.hooks.Stop || [];
|
|
930
967
|
addHookIfMissing(settings.hooks.Stop, {
|
|
931
|
-
type: 'command',
|
|
932
|
-
command: 'bash ~/.claude/hooks/session-end.sh'
|
|
968
|
+
hooks: [{ type: 'command', command: 'bash ~/.claude/hooks/session-end.sh' }]
|
|
933
969
|
});
|
|
934
970
|
|
|
935
971
|
// TeammateIdle hook (Agent Teams quality gate — only fires when Teams active)
|
|
936
972
|
settings.hooks.TeammateIdle = settings.hooks.TeammateIdle || [];
|
|
937
973
|
addHookIfMissing(settings.hooks.TeammateIdle, {
|
|
938
|
-
type: 'command',
|
|
939
|
-
command: 'bash ~/.claude/hooks/teammate-idle-verify.sh'
|
|
974
|
+
hooks: [{ type: 'command', command: 'bash ~/.claude/hooks/teammate-idle-verify.sh' }]
|
|
940
975
|
});
|
|
941
976
|
|
|
942
977
|
// SubagentStart hook (Agent Teams context injection — only fires when Teams active)
|
|
943
978
|
settings.hooks.SubagentStart = settings.hooks.SubagentStart || [];
|
|
944
979
|
addHookIfMissing(settings.hooks.SubagentStart, {
|
|
945
|
-
type: 'command',
|
|
946
|
-
command: 'bash ~/.claude/hooks/subagent-context.sh'
|
|
980
|
+
hooks: [{ type: 'command', command: 'bash ~/.claude/hooks/subagent-context.sh' }]
|
|
947
981
|
});
|
|
948
982
|
|
|
949
983
|
// --- Prompt-based hooks (LLM evaluates .md content) ---
|
|
@@ -951,11 +985,15 @@ async function install() {
|
|
|
951
985
|
// first (identified by containing 'merlin' or 'Merlin' in their prompt text),
|
|
952
986
|
// then re-add fresh ones. This prevents duplicate hooks across version upgrades.
|
|
953
987
|
const removeMerlinPromptHooks = (hookArray) => {
|
|
954
|
-
return hookArray.filter(
|
|
955
|
-
if (
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
988
|
+
return hookArray.filter(entry => {
|
|
989
|
+
if (!entry.hooks || !Array.isArray(entry.hooks)) return true;
|
|
990
|
+
// Remove entry if ALL its hooks are Merlin prompt hooks
|
|
991
|
+
const allMerlinPrompts = entry.hooks.every(h => {
|
|
992
|
+
if (h.type !== 'prompt') return false;
|
|
993
|
+
const prompt = (h.prompt || '').toLowerCase();
|
|
994
|
+
return prompt.includes('merlin') || prompt.includes('sights');
|
|
995
|
+
});
|
|
996
|
+
return !allMerlinPrompts;
|
|
959
997
|
});
|
|
960
998
|
};
|
|
961
999
|
|
|
@@ -964,34 +1002,29 @@ async function install() {
|
|
|
964
1002
|
settings.hooks.Stop = removeMerlinPromptHooks(settings.hooks.Stop);
|
|
965
1003
|
settings.hooks.Notification = settings.hooks.Notification || [];
|
|
966
1004
|
settings.hooks.Notification = removeMerlinPromptHooks(settings.hooks.Notification);
|
|
1005
|
+
settings.hooks.TaskCompleted = settings.hooks.TaskCompleted || [];
|
|
1006
|
+
settings.hooks.TaskCompleted = removeMerlinPromptHooks(settings.hooks.TaskCompleted);
|
|
967
1007
|
|
|
968
|
-
// Now add fresh prompt hooks
|
|
1008
|
+
// Now add fresh prompt hooks in new format
|
|
969
1009
|
// Prompt-based SessionStart hook: FULL Merlin boot sequence
|
|
970
|
-
// This is the key hook that makes every session feel like Loop
|
|
971
1010
|
settings.hooks.SessionStart.push({
|
|
972
|
-
type: 'prompt',
|
|
973
|
-
prompt: fs.readFileSync(path.join(HOOKS_DIR, 'session-start-boot.md'), 'utf8')
|
|
1011
|
+
hooks: [{ type: 'prompt', prompt: fs.readFileSync(path.join(HOOKS_DIR, 'session-start-boot.md'), 'utf8') }]
|
|
974
1012
|
});
|
|
975
1013
|
|
|
976
1014
|
// Prompt-based PreToolUse hook: enforce Sights check before edits
|
|
977
|
-
// This is the Sights guarantee — no edit without context
|
|
978
1015
|
settings.hooks.PreToolUse.push({
|
|
979
|
-
|
|
980
|
-
prompt: fs.readFileSync(path.join(HOOKS_DIR, 'pre-edit-sights-enforce.md'), 'utf8')
|
|
981
|
-
matcher: 'Edit|Write'
|
|
1016
|
+
matcher: 'Edit|Write',
|
|
1017
|
+
hooks: [{ type: 'prompt', prompt: fs.readFileSync(path.join(HOOKS_DIR, 'pre-edit-sights-enforce.md'), 'utf8') }]
|
|
982
1018
|
});
|
|
983
1019
|
|
|
984
1020
|
// Prompt-based Stop hook: quality gate before session ends
|
|
985
1021
|
settings.hooks.Stop.push({
|
|
986
|
-
type: 'prompt',
|
|
987
|
-
prompt: fs.readFileSync(path.join(HOOKS_DIR, 'stop-check.md'), 'utf8')
|
|
1022
|
+
hooks: [{ type: 'prompt', prompt: fs.readFileSync(path.join(HOOKS_DIR, 'stop-check.md'), 'utf8') }]
|
|
988
1023
|
});
|
|
989
1024
|
|
|
990
|
-
//
|
|
991
|
-
settings.hooks.
|
|
992
|
-
type: 'prompt',
|
|
993
|
-
prompt: fs.readFileSync(path.join(HOOKS_DIR, 'task-completed-verify.md'), 'utf8'),
|
|
994
|
-
matcher: 'task_completed'
|
|
1025
|
+
// Task completion verification hook (using dedicated TaskCompleted event)
|
|
1026
|
+
settings.hooks.TaskCompleted.push({
|
|
1027
|
+
hooks: [{ type: 'prompt', prompt: fs.readFileSync(path.join(HOOKS_DIR, 'task-completed-verify.md'), 'utf8') }]
|
|
995
1028
|
});
|
|
996
1029
|
|
|
997
1030
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
@@ -1031,11 +1064,15 @@ async function install() {
|
|
|
1031
1064
|
|
|
1032
1065
|
let apiKey = existingApiKey;
|
|
1033
1066
|
if (existingApiKey) {
|
|
1034
|
-
|
|
1067
|
+
const masked = existingApiKey.slice(0, 8) + '...' + existingApiKey.slice(-4);
|
|
1068
|
+
console.log(` Existing API key found: ${colors.cyan}${masked}${colors.reset}`);
|
|
1035
1069
|
} else {
|
|
1036
|
-
console.log(`Merlin Sights provides instant codebase context.`);
|
|
1037
|
-
console.log(`Get your API key at: ${colors.cyan}https://merlin.build${colors.reset}`);
|
|
1038
|
-
|
|
1070
|
+
console.log(` Merlin Sights provides instant codebase context.`);
|
|
1071
|
+
console.log(` Get your API key at: ${colors.cyan}https://merlin.build${colors.reset}`);
|
|
1072
|
+
}
|
|
1073
|
+
const inputKey = await promptApiKey(!!existingApiKey);
|
|
1074
|
+
if (inputKey) {
|
|
1075
|
+
apiKey = inputKey;
|
|
1039
1076
|
}
|
|
1040
1077
|
|
|
1041
1078
|
if (apiKey) {
|
package/package.json
CHANGED