- VERSION file as authoritative version source - export.mjs reads VERSION with package.json fallback - bw-ota.ps1 DryRun mode for safe testing - auto-setup.ps1 bumped to v3.2.0 (Phase 8 OTA)
97 lines
3.5 KiB
JavaScript
97 lines
3.5 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* patch-token-saver-6in1.js
|
|
* Consolidate 6 token-saver hooks into a single dispatcher
|
|
* Idempotent: sentinel file prevents re-execution
|
|
*/
|
|
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const CLAUDE_ROOT = path.join(process.env.USERPROFILE || process.env.HOME, '.claude');
|
|
const HOOKS_DIR = path.join(CLAUDE_ROOT, 'hooks');
|
|
const SETTINGS_PATH = path.join(CLAUDE_ROOT, 'settings.json');
|
|
const STATE_DIR = path.join(CLAUDE_ROOT, 'session-state');
|
|
const SENTINEL = path.join(STATE_DIR, 'patch-token-saver-6in1.done');
|
|
const SOURCE = path.join(__dirname, 'token-saver-dispatcher-source.js');
|
|
const TARGET = path.join(HOOKS_DIR, 'token-saver-dispatcher.js');
|
|
const D = 'node C:/Users/leesu/.claude/hooks/token-saver-dispatcher.js';
|
|
|
|
if (fs.existsSync(SENTINEL)) {
|
|
console.log('[SKIP] patch-token-saver-6in1 already applied');
|
|
process.exit(0);
|
|
}
|
|
|
|
const OLD_HOOKS = [
|
|
'token-saver-model-advisor.js',
|
|
'token-saver-read-guard.js',
|
|
'token-saver-bash-limiter.js',
|
|
'token-saver-post-output-guard.js',
|
|
'token-saver-mcp-tracker.js',
|
|
'token-saver-session-report.js',
|
|
];
|
|
|
|
const REPLACEMENTS = [
|
|
['node C:/Users/leesu/.claude/hooks/token-saver-model-advisor.js', D + ' --mode=model-advisor'],
|
|
['node C:/Users/leesu/.claude/hooks/token-saver-read-guard.js', D + ' --mode=read-guard'],
|
|
['node C:/Users/leesu/.claude/hooks/token-saver-bash-limiter.js', D + ' --mode=bash-limiter'],
|
|
['node C:/Users/leesu/.claude/hooks/token-saver-post-output-guard.js', D + ' --mode=post-output-guard'],
|
|
['node C:/Users/leesu/.claude/hooks/token-saver-mcp-tracker.js', D + ' --mode=mcp-tracker'],
|
|
['node C:/Users/leesu/.claude/hooks/token-saver-session-report.js', D + ' --mode=session-report'],
|
|
];
|
|
|
|
try {
|
|
// Step 1: Copy dispatcher source to hooks/
|
|
if (!fs.existsSync(SOURCE)) { console.error('[FAIL] Source not found: ' + SOURCE); process.exit(1); }
|
|
fs.copyFileSync(SOURCE, TARGET);
|
|
console.log('[OK] Dispatcher copied to ' + TARGET);
|
|
|
|
// Step 2: Backup and update settings.json
|
|
const settingsBak = SETTINGS_PATH + '.bak-tse6in1-' + Date.now();
|
|
fs.copyFileSync(SETTINGS_PATH, settingsBak);
|
|
console.log('[OK] Settings backup: ' + path.basename(settingsBak));
|
|
|
|
let settings = fs.readFileSync(SETTINGS_PATH, 'utf8');
|
|
let replaced = 0;
|
|
for (const [oldCmd, newCmd] of REPLACEMENTS) {
|
|
if (settings.includes(oldCmd)) {
|
|
settings = settings.replace(oldCmd, newCmd);
|
|
replaced++;
|
|
}
|
|
}
|
|
if (replaced === 0) {
|
|
console.log('[WARN] No replacements made in settings.json - may already be updated');
|
|
} else {
|
|
const tmp = SETTINGS_PATH + '.tmp.' + process.pid;
|
|
fs.writeFileSync(tmp, settings, 'utf8');
|
|
fs.renameSync(tmp, SETTINGS_PATH);
|
|
console.log('[OK] settings.json updated: ' + replaced + '/6 commands replaced');
|
|
}
|
|
|
|
// Step 3: Rename old hooks to .bak
|
|
let backed = 0;
|
|
for (const name of OLD_HOOKS) {
|
|
const p = path.join(HOOKS_DIR, name);
|
|
if (fs.existsSync(p)) {
|
|
fs.renameSync(p, p + '.bak-6in1');
|
|
backed++;
|
|
}
|
|
}
|
|
console.log('[OK] Old hooks renamed: ' + backed + '/6 → .bak-6in1');
|
|
|
|
// Step 4: Write sentinel
|
|
if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
fs.writeFileSync(SENTINEL, JSON.stringify({
|
|
applied: new Date().toISOString(),
|
|
replaced: replaced,
|
|
backed: backed,
|
|
settingsBackup: path.basename(settingsBak)
|
|
}, null, 2), 'utf8');
|
|
console.log('[DONE] patch-token-saver-6in1 applied successfully');
|
|
|
|
} catch (err) {
|
|
console.error('[FAIL] ' + err.message);
|
|
process.exit(1);
|
|
}
|