bookworm-smart-assistant/scripts/patches/patch-token-saver-6in1.js
Bookworm Admin b7a8e29d21 release: v6.7.0 - OTA E2E test release
- 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)
2026-04-27 17:59:44 +08:00

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);
}