maxsimcli 5.0.7 → 5.1.0
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/README.md +101 -99
- package/dist/assets/CHANGELOG.md +7 -0
- package/dist/assets/hooks/maxsim-capture-learnings.cjs +128 -0
- package/dist/assets/hooks/maxsim-capture-learnings.cjs.map +1 -0
- package/dist/assets/hooks/maxsim-check-update.cjs +126 -88
- package/dist/assets/hooks/maxsim-check-update.cjs.map +1 -1
- package/dist/assets/hooks/maxsim-notification-sound.cjs +87 -43
- package/dist/assets/hooks/maxsim-notification-sound.cjs.map +1 -1
- package/dist/assets/hooks/maxsim-statusline.cjs +45 -171
- package/dist/assets/hooks/maxsim-statusline.cjs.map +1 -1
- package/dist/assets/hooks/maxsim-stop-sound.cjs +86 -43
- package/dist/assets/hooks/maxsim-stop-sound.cjs.map +1 -1
- package/dist/assets/hooks/maxsim-sync-reminder.cjs +72 -21
- package/dist/assets/hooks/maxsim-sync-reminder.cjs.map +1 -1
- package/dist/assets/templates/agents/AGENTS.md +62 -51
- package/dist/assets/templates/agents/executor.md +44 -59
- package/dist/assets/templates/agents/planner.md +36 -31
- package/dist/assets/templates/agents/researcher.md +35 -43
- package/dist/assets/templates/agents/verifier.md +29 -31
- package/dist/assets/templates/commands/maxsim/debug.md +20 -154
- package/dist/assets/templates/commands/maxsim/execute.md +19 -33
- package/dist/assets/templates/commands/maxsim/go.md +21 -20
- package/dist/assets/templates/commands/maxsim/help.md +5 -14
- package/dist/assets/templates/commands/maxsim/init.md +18 -40
- package/dist/assets/templates/commands/maxsim/plan.md +22 -37
- package/dist/assets/templates/commands/maxsim/progress.md +15 -16
- package/dist/assets/templates/commands/maxsim/quick.md +18 -29
- package/dist/assets/templates/commands/maxsim/settings.md +18 -26
- package/dist/assets/templates/references/continuation-format.md +2 -4
- package/dist/assets/templates/references/model-profiles.md +2 -2
- package/dist/assets/templates/references/planning-config.md +10 -11
- package/dist/assets/templates/references/self-improvement.md +120 -0
- package/dist/assets/templates/rules/conventions.md +1 -1
- package/dist/assets/templates/rules/verification-protocol.md +1 -1
- package/dist/assets/templates/skills/brainstorming/SKILL.md +35 -26
- package/dist/assets/templates/skills/code-review/SKILL.md +78 -55
- package/dist/assets/templates/skills/commit-conventions/SKILL.md +70 -36
- package/dist/assets/templates/skills/github-operations/SKILL.md +142 -0
- package/dist/assets/templates/skills/handoff-contract/SKILL.md +62 -28
- package/dist/assets/templates/skills/maxsim-batch/SKILL.md +68 -42
- package/dist/assets/templates/skills/maxsim-simplify/SKILL.md +65 -40
- package/dist/assets/templates/skills/project-memory/SKILL.md +121 -0
- package/dist/assets/templates/skills/research/SKILL.md +126 -0
- package/dist/assets/templates/skills/roadmap-writing/SKILL.md +71 -68
- package/dist/assets/templates/skills/systematic-debugging/SKILL.md +37 -25
- package/dist/assets/templates/skills/tdd/SKILL.md +36 -39
- package/dist/assets/templates/skills/using-maxsim/SKILL.md +69 -55
- package/dist/assets/templates/skills/verification/SKILL.md +167 -0
- package/dist/assets/templates/workflows/batch.md +249 -268
- package/dist/assets/templates/workflows/diagnose-issues.md +225 -151
- package/dist/assets/templates/workflows/execute-plan.md +191 -981
- package/dist/assets/templates/workflows/execute.md +350 -309
- package/dist/assets/templates/workflows/go.md +119 -138
- package/dist/assets/templates/workflows/health.md +71 -114
- package/dist/assets/templates/workflows/help.md +85 -147
- package/dist/assets/templates/workflows/init-existing.md +180 -1373
- package/dist/assets/templates/workflows/init.md +53 -165
- package/dist/assets/templates/workflows/new-milestone.md +91 -334
- package/dist/assets/templates/workflows/new-project.md +165 -1384
- package/dist/assets/templates/workflows/plan-create.md +182 -73
- package/dist/assets/templates/workflows/plan-discuss.md +89 -82
- package/dist/assets/templates/workflows/plan-research.md +191 -85
- package/dist/assets/templates/workflows/plan.md +122 -58
- package/dist/assets/templates/workflows/progress.md +76 -310
- package/dist/assets/templates/workflows/quick.md +70 -495
- package/dist/assets/templates/workflows/sdd.md +231 -221
- package/dist/assets/templates/workflows/settings.md +90 -120
- package/dist/assets/templates/workflows/verify-phase.md +296 -258
- package/dist/cli.cjs +17 -23465
- package/dist/cli.cjs.map +1 -1
- package/dist/install.cjs +356 -8358
- package/dist/install.cjs.map +1 -1
- package/package.json +16 -22
- package/dist/assets/templates/skills/agent-system-map/SKILL.md +0 -92
- package/dist/assets/templates/skills/evidence-collection/SKILL.md +0 -87
- package/dist/assets/templates/skills/github-artifact-protocol/SKILL.md +0 -67
- package/dist/assets/templates/skills/github-tools-guide/SKILL.md +0 -89
- package/dist/assets/templates/skills/input-validation/SKILL.md +0 -51
- package/dist/assets/templates/skills/memory-management/SKILL.md +0 -75
- package/dist/assets/templates/skills/research-methodology/SKILL.md +0 -137
- package/dist/assets/templates/skills/sdd/SKILL.md +0 -91
- package/dist/assets/templates/skills/tool-priority-guide/SKILL.md +0 -80
- package/dist/assets/templates/skills/verification-before-completion/SKILL.md +0 -71
- package/dist/assets/templates/skills/verification-gates/SKILL.md +0 -169
- package/dist/assets/templates/workflows/discuss-phase.md +0 -683
- package/dist/assets/templates/workflows/research-phase.md +0 -73
- package/dist/assets/templates/workflows/verify-work.md +0 -572
- package/dist/core-D5zUr9cb.cjs +0 -4305
- package/dist/core-D5zUr9cb.cjs.map +0 -1
- package/dist/skills-CjFWZIGM.cjs +0 -6824
- package/dist/skills-CjFWZIGM.cjs.map +0 -1
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
3
1
|
//#region \0rolldown/runtime.js
|
|
4
2
|
var __create = Object.create;
|
|
5
3
|
var __defProp = Object.defineProperty;
|
|
@@ -29,110 +27,150 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
29
27
|
//#endregion
|
|
30
28
|
let node_fs = require("node:fs");
|
|
31
29
|
node_fs = __toESM(node_fs);
|
|
32
|
-
let node_path = require("node:path");
|
|
33
|
-
node_path = __toESM(node_path);
|
|
34
30
|
let node_os = require("node:os");
|
|
35
31
|
node_os = __toESM(node_os);
|
|
32
|
+
let node_path = require("node:path");
|
|
33
|
+
node_path = __toESM(node_path);
|
|
36
34
|
let node_child_process = require("node:child_process");
|
|
37
35
|
|
|
38
36
|
//#region src/hooks/shared.ts
|
|
39
|
-
/**
|
|
40
|
-
|
|
37
|
+
/** Shared utilities for MAXSIM hooks. */
|
|
38
|
+
function readStdinJson(callback) {
|
|
39
|
+
let input = "";
|
|
40
|
+
process.stdin.setEncoding("utf8");
|
|
41
|
+
process.stdin.on("data", (chunk) => {
|
|
42
|
+
input += chunk;
|
|
43
|
+
});
|
|
44
|
+
process.stdin.on("end", () => {
|
|
45
|
+
try {
|
|
46
|
+
callback(JSON.parse(input));
|
|
47
|
+
} catch {
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
41
52
|
|
|
42
53
|
//#endregion
|
|
43
54
|
//#region src/hooks/maxsim-check-update.ts
|
|
44
55
|
/**
|
|
45
|
-
*
|
|
46
|
-
* Called by SessionStart hook - runs once per session.
|
|
47
|
-
*/
|
|
48
|
-
function checkForUpdate(options) {
|
|
49
|
-
const { homeDir, cwd } = options;
|
|
50
|
-
const cacheDir = node_path.join(homeDir, CLAUDE_DIR, "cache");
|
|
51
|
-
const cacheFile = node_path.join(cacheDir, "maxsim-update-check.json");
|
|
52
|
-
const projectVersionFile = node_path.join(cwd, CLAUDE_DIR, "maxsim", "VERSION");
|
|
53
|
-
const globalVersionFile = node_path.join(homeDir, CLAUDE_DIR, "maxsim", "VERSION");
|
|
54
|
-
if (!node_fs.existsSync(cacheDir)) node_fs.mkdirSync(cacheDir, { recursive: true });
|
|
55
|
-
const isWindows = process.platform === "win32";
|
|
56
|
-
(0, node_child_process.spawn)(process.execPath, ["-e", `
|
|
57
|
-
const fs = require('fs');
|
|
58
|
-
const { execSync } = require('child_process');
|
|
59
|
-
|
|
60
|
-
const cacheFile = ${JSON.stringify(cacheFile)};
|
|
61
|
-
const projectVersionFile = ${JSON.stringify(projectVersionFile)};
|
|
62
|
-
const globalVersionFile = ${JSON.stringify(globalVersionFile)};
|
|
63
|
-
|
|
64
|
-
// Check project directory first (local install), then global
|
|
65
|
-
let installed = '0.0.0';
|
|
66
|
-
try {
|
|
67
|
-
if (fs.existsSync(projectVersionFile)) {
|
|
68
|
-
installed = fs.readFileSync(projectVersionFile, 'utf8').trim();
|
|
69
|
-
} else if (fs.existsSync(globalVersionFile)) {
|
|
70
|
-
installed = fs.readFileSync(globalVersionFile, 'utf8').trim();
|
|
71
|
-
}
|
|
72
|
-
} catch (e) {}
|
|
73
|
-
|
|
74
|
-
let latest = null;
|
|
75
|
-
try {
|
|
76
|
-
latest = execSync('npm view maxsimcli version', { encoding: 'utf8', timeout: 10000, windowsHide: true }).trim();
|
|
77
|
-
} catch (e) {}
|
|
78
|
-
|
|
79
|
-
const result = {
|
|
80
|
-
update_available: latest && installed !== latest,
|
|
81
|
-
installed,
|
|
82
|
-
latest: latest || 'unknown',
|
|
83
|
-
checked: Math.floor(Date.now() / 1000)
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
fs.writeFileSync(cacheFile, JSON.stringify(result));
|
|
87
|
-
`], {
|
|
88
|
-
stdio: "ignore",
|
|
89
|
-
windowsHide: true,
|
|
90
|
-
detached: !isWindows
|
|
91
|
-
}).unref();
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Create a backup of the current MAXSIM installation before an update.
|
|
95
|
-
* Called by the installer (not by the SessionStart hook).
|
|
56
|
+
* SessionStart hook — check if a newer maxsimcli version is available on npm.
|
|
96
57
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
58
|
+
* Behaviour:
|
|
59
|
+
* - Reads session info from stdin (ignored; hook fires on every session start).
|
|
60
|
+
* - Uses a one-hour cache file so the npm registry is queried at most once/hour.
|
|
61
|
+
* - Spawns a detached background process when the cache is stale so the check
|
|
62
|
+
* never blocks the session from starting.
|
|
63
|
+
* - If a newer version is found (from the cache), emits an additionalContext
|
|
64
|
+
* JSON message to stdout.
|
|
65
|
+
* - Always exits 0 — never blocks the user's session.
|
|
99
66
|
*/
|
|
100
|
-
|
|
67
|
+
const PACKAGE_NAME = "maxsimcli";
|
|
68
|
+
const CACHE_TTL_MS = 3600 * 1e3;
|
|
69
|
+
const CACHE_FILE = node_path.join(node_os.tmpdir(), ".maxsimcli-update-cache.json");
|
|
70
|
+
/** Read the currently installed version from package.json next to this script. */
|
|
71
|
+
function getInstalledVersion() {
|
|
101
72
|
try {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
"skills"
|
|
111
|
-
]) {
|
|
112
|
-
const src = node_path.join(sourceDir, relDir);
|
|
113
|
-
if (!node_fs.existsSync(src)) continue;
|
|
114
|
-
const dest = node_path.join(backupDir, relDir);
|
|
115
|
-
node_fs.mkdirSync(node_path.dirname(dest), { recursive: true });
|
|
116
|
-
node_fs.cpSync(src, dest, { recursive: true });
|
|
73
|
+
let dir = node_path.dirname(process.argv[1] ?? __filename);
|
|
74
|
+
for (let i = 0; i < 6; i++) {
|
|
75
|
+
const pkgPath = node_path.join(dir, "package.json");
|
|
76
|
+
if (node_fs.existsSync(pkgPath)) {
|
|
77
|
+
const pkg = JSON.parse(node_fs.readFileSync(pkgPath, "utf8"));
|
|
78
|
+
if (pkg.name === PACKAGE_NAME && pkg.version) return pkg.version;
|
|
79
|
+
}
|
|
80
|
+
dir = node_path.dirname(dir);
|
|
117
81
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
82
|
+
} catch {}
|
|
83
|
+
return "0.0.0";
|
|
84
|
+
}
|
|
85
|
+
/** Read the update cache from disk. Returns null if absent or expired. */
|
|
86
|
+
function readCache() {
|
|
87
|
+
try {
|
|
88
|
+
if (!node_fs.existsSync(CACHE_FILE)) return null;
|
|
89
|
+
const entry = JSON.parse(node_fs.readFileSync(CACHE_FILE, "utf8"));
|
|
90
|
+
if (Date.now() - entry.checkedAt > CACHE_TTL_MS) return null;
|
|
91
|
+
return entry;
|
|
126
92
|
} catch {
|
|
127
93
|
return null;
|
|
128
94
|
}
|
|
129
95
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
96
|
+
/** Write a cache entry to disk (best-effort). */
|
|
97
|
+
function writeCache(latestVersion) {
|
|
98
|
+
try {
|
|
99
|
+
const entry = {
|
|
100
|
+
checkedAt: Date.now(),
|
|
101
|
+
latestVersion
|
|
102
|
+
};
|
|
103
|
+
node_fs.writeFileSync(CACHE_FILE, JSON.stringify(entry), "utf8");
|
|
104
|
+
} catch {}
|
|
105
|
+
}
|
|
106
|
+
/** Compare semver strings. Returns true if b > a. */
|
|
107
|
+
function isNewer(installed, latest) {
|
|
108
|
+
const parse = (v) => v.replace(/^[^0-9]*/, "").split(".").map(Number);
|
|
109
|
+
const [aMaj = 0, aMin = 0, aPat = 0] = parse(installed);
|
|
110
|
+
const [bMaj = 0, bMin = 0, bPat = 0] = parse(latest);
|
|
111
|
+
if (bMaj !== aMaj) return bMaj > aMaj;
|
|
112
|
+
if (bMin !== aMin) return bMin > aMin;
|
|
113
|
+
return bPat > aPat;
|
|
114
|
+
}
|
|
115
|
+
/** Spawn a background npm view query and write its result to the cache file. */
|
|
116
|
+
function spawnBackgroundCheck() {
|
|
117
|
+
try {
|
|
118
|
+
const child = (0, node_child_process.spawn)("npm", [
|
|
119
|
+
"view",
|
|
120
|
+
PACKAGE_NAME,
|
|
121
|
+
"version",
|
|
122
|
+
"--json"
|
|
123
|
+
], {
|
|
124
|
+
detached: true,
|
|
125
|
+
stdio: [
|
|
126
|
+
"ignore",
|
|
127
|
+
"pipe",
|
|
128
|
+
"ignore"
|
|
129
|
+
],
|
|
130
|
+
windowsHide: true
|
|
131
|
+
});
|
|
132
|
+
let stdout = "";
|
|
133
|
+
child.stdout?.on("data", (d) => {
|
|
134
|
+
stdout += d.toString();
|
|
135
|
+
});
|
|
136
|
+
child.on("close", (code) => {
|
|
137
|
+
if (code === 0) try {
|
|
138
|
+
writeCache(JSON.parse(stdout.trim()));
|
|
139
|
+
} catch {}
|
|
140
|
+
});
|
|
141
|
+
child.unref();
|
|
142
|
+
} catch {}
|
|
143
|
+
}
|
|
144
|
+
readStdinJson(() => {
|
|
145
|
+
const installed = getInstalledVersion();
|
|
146
|
+
const cache = readCache();
|
|
147
|
+
if (cache === null) try {
|
|
148
|
+
const result = (0, node_child_process.spawnSync)("npm", [
|
|
149
|
+
"view",
|
|
150
|
+
PACKAGE_NAME,
|
|
151
|
+
"version",
|
|
152
|
+
"--json"
|
|
153
|
+
], {
|
|
154
|
+
encoding: "utf8",
|
|
155
|
+
timeout: 4e3,
|
|
156
|
+
windowsHide: true,
|
|
157
|
+
stdio: [
|
|
158
|
+
"ignore",
|
|
159
|
+
"pipe",
|
|
160
|
+
"ignore"
|
|
161
|
+
]
|
|
162
|
+
});
|
|
163
|
+
if (result.status === 0 && result.stdout) {
|
|
164
|
+
const latest = JSON.parse(result.stdout.trim());
|
|
165
|
+
writeCache(latest);
|
|
166
|
+
if (isNewer(installed, latest)) process.stdout.write(JSON.stringify({ additionalContext: `MaxsimCLI update available: v${installed} → v${latest}. Run: npm i -g maxsimcli` }) + "\n");
|
|
167
|
+
} else spawnBackgroundCheck();
|
|
168
|
+
} catch {
|
|
169
|
+
spawnBackgroundCheck();
|
|
170
|
+
}
|
|
171
|
+
else if (isNewer(installed, cache.latestVersion)) process.stdout.write(JSON.stringify({ additionalContext: `MaxsimCLI update available: v${installed} → v${cache.latestVersion}. Run: npm i -g maxsimcli` }) + "\n");
|
|
172
|
+
process.exit(0);
|
|
133
173
|
});
|
|
134
174
|
|
|
135
175
|
//#endregion
|
|
136
|
-
exports.checkForUpdate = checkForUpdate;
|
|
137
|
-
exports.createBackupBeforeUpdate = createBackupBeforeUpdate;
|
|
138
176
|
//# sourceMappingURL=maxsim-check-update.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"maxsim-check-update.cjs","names":["path","fs","os"],"sources":["../../../src/hooks/shared.ts","../../../src/hooks/maxsim-check-update.ts"],"sourcesContent":["/**\n * Shared utilities for MAXSIM hooks.\n */\n\n/**\n * Read all stdin as a string, then invoke callback with parsed JSON.\n * Used by statusline and sync-reminder hooks.\n */\nexport function readStdinJson<T>(callback: (data: T) => void): void {\n let input = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk: string) => (input += chunk));\n process.stdin.on('end', () => {\n try {\n const data = JSON.parse(input) as T;\n callback(data);\n } catch {\n // Silent fail -- never block hook execution\n process.exit(0);\n }\n });\n}\n\n/** The '.claude' path segment -- template marker replaced during install. */\nexport const CLAUDE_DIR = '.claude';\n\n/**\n * Play a system sound for notifications. Fire-and-forget, never blocks.\n * Suppressed when MAXSIM_SOUND=0, CI=true, or SSH_CONNECTION is set.\n */\nexport function playSound(type: 'question' | 'stop'): void {\n try {\n if (\n process.env.MAXSIM_SOUND === '0' ||\n process.env.CI === 'true' ||\n process.env.SSH_CONNECTION\n ) {\n return;\n }\n\n const platform = process.platform;\n\n if (platform === 'win32') {\n const file =\n type === 'question'\n ? 'C:\\\\Windows\\\\Media\\\\notify.wav'\n : 'C:\\\\Windows\\\\Media\\\\chimes.wav';\n const { spawn } = require('node:child_process') as typeof import('node:child_process');\n const child = spawn(\n 'powershell',\n ['-NoProfile', '-WindowStyle', 'Hidden', '-Command', `(New-Object Media.SoundPlayer '${file}').PlaySync()`],\n { stdio: 'ignore', windowsHide: true },\n );\n child.unref();\n } else if (platform === 'darwin') {\n const file =\n type === 'question'\n ? '/System/Library/Sounds/Ping.aiff'\n : '/System/Library/Sounds/Glass.aiff';\n const { spawn } = require('node:child_process') as typeof import('node:child_process');\n const child = spawn('afplay', [file], {\n stdio: 'ignore',\n detached: true,\n });\n child.unref();\n } else {\n // Linux / unknown — terminal bell fallback\n process.stderr.write('\\x07');\n }\n } catch {\n // Silent fail — never block hook execution\n }\n}\n","#!/usr/bin/env node\n/**\n * Check for MAXSIM updates in background, write result to cache.\n * Called by SessionStart hook - runs once per session.\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as os from 'node:os';\nimport { spawn } from 'node:child_process';\nimport { CLAUDE_DIR } from './shared';\n\nexport interface UpdateCheckResult {\n update_available: boolean;\n installed: string;\n latest: string;\n checked: number;\n}\n\nexport interface CheckForUpdateOptions {\n homeDir: string;\n cwd: string;\n}\n\nexport function checkForUpdate(options: CheckForUpdateOptions): void {\n const { homeDir, cwd } = options;\n const cacheDir = path.join(homeDir, CLAUDE_DIR, 'cache');\n const cacheFile = path.join(cacheDir, 'maxsim-update-check.json');\n\n // VERSION file locations (check project first, then global)\n const projectVersionFile = path.join(cwd, CLAUDE_DIR, 'maxsim', 'VERSION');\n const globalVersionFile = path.join(homeDir, CLAUDE_DIR, 'maxsim', 'VERSION');\n\n // Ensure cache directory exists\n if (!fs.existsSync(cacheDir)) {\n fs.mkdirSync(cacheDir, { recursive: true });\n }\n\n // Run check in background (spawn background process, windowsHide prevents console flash)\n const isWindows = process.platform === 'win32';\n const child = spawn(process.execPath, ['-e', `\n const fs = require('fs');\n const { execSync } = require('child_process');\n\n const cacheFile = ${JSON.stringify(cacheFile)};\n const projectVersionFile = ${JSON.stringify(projectVersionFile)};\n const globalVersionFile = ${JSON.stringify(globalVersionFile)};\n\n // Check project directory first (local install), then global\n let installed = '0.0.0';\n try {\n if (fs.existsSync(projectVersionFile)) {\n installed = fs.readFileSync(projectVersionFile, 'utf8').trim();\n } else if (fs.existsSync(globalVersionFile)) {\n installed = fs.readFileSync(globalVersionFile, 'utf8').trim();\n }\n } catch (e) {}\n\n let latest = null;\n try {\n latest = execSync('npm view maxsimcli version', { encoding: 'utf8', timeout: 10000, windowsHide: true }).trim();\n } catch (e) {}\n\n const result = {\n update_available: latest && installed !== latest,\n installed,\n latest: latest || 'unknown',\n checked: Math.floor(Date.now() / 1000)\n };\n\n fs.writeFileSync(cacheFile, JSON.stringify(result));\n`], {\n stdio: 'ignore',\n windowsHide: true,\n detached: !isWindows,\n });\n\n child.unref();\n}\n\n/**\n * Create a backup of the current MAXSIM installation before an update.\n * Called by the installer (not by the SessionStart hook).\n *\n * @param cwd - The project working directory containing .claude/\n * @returns The backup directory path on success, null on failure.\n */\nexport function createBackupBeforeUpdate(cwd: string): string | null {\n try {\n const sourceDir = path.join(cwd, CLAUDE_DIR);\n const backupDir = path.join(sourceDir, 'maxsim-backup');\n\n fs.mkdirSync(backupDir, { recursive: true });\n\n // Key directories to back up\n const dirsToBackup = [\n 'commands/maxsim',\n 'maxsim',\n 'hooks',\n 'agents',\n 'skills',\n ];\n\n for (const relDir of dirsToBackup) {\n const src = path.join(sourceDir, relDir);\n if (!fs.existsSync(src)) continue;\n\n const dest = path.join(backupDir, relDir);\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.cpSync(src, dest, { recursive: true });\n }\n\n // Write backup metadata\n let version = 'unknown';\n const versionFile = path.join(sourceDir, 'maxsim', 'VERSION');\n if (fs.existsSync(versionFile)) {\n version = fs.readFileSync(versionFile, 'utf8').trim();\n }\n\n fs.writeFileSync(\n path.join(backupDir, 'backup-meta.json'),\n JSON.stringify(\n { created: new Date().toISOString(), version },\n null,\n 2,\n ),\n );\n\n return backupDir;\n } catch {\n // Backup failure should not block the update\n return null;\n }\n}\n\n// Standalone entry\nif (require.main === module) {\n checkForUpdate({ homeDir: os.homedir(), cwd: process.cwd() });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,aAAa;;;;;;;;ACA1B,SAAgB,eAAe,SAAsC;CACnE,MAAM,EAAE,SAAS,QAAQ;CACzB,MAAM,WAAWA,UAAK,KAAK,SAAS,YAAY,QAAQ;CACxD,MAAM,YAAYA,UAAK,KAAK,UAAU,2BAA2B;CAGjE,MAAM,qBAAqBA,UAAK,KAAK,KAAK,YAAY,UAAU,UAAU;CAC1E,MAAM,oBAAoBA,UAAK,KAAK,SAAS,YAAY,UAAU,UAAU;AAG7E,KAAI,CAACC,QAAG,WAAW,SAAS,CAC1B,SAAG,UAAU,UAAU,EAAE,WAAW,MAAM,CAAC;CAI7C,MAAM,YAAY,QAAQ,aAAa;AAsCvC,+BArCoB,QAAQ,UAAU,CAAC,MAAM;;;;sBAIzB,KAAK,UAAU,UAAU,CAAC;+BACjB,KAAK,UAAU,mBAAmB,CAAC;8BACpC,KAAK,UAAU,kBAAkB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;EAyB9D,EAAE;EACA,OAAO;EACP,aAAa;EACb,UAAU,CAAC;EACZ,CAAC,CAEI,OAAO;;;;;;;;;AAUf,SAAgB,yBAAyB,KAA4B;AACnE,KAAI;EACF,MAAM,YAAYD,UAAK,KAAK,KAAK,WAAW;EAC5C,MAAM,YAAYA,UAAK,KAAK,WAAW,gBAAgB;AAEvD,UAAG,UAAU,WAAW,EAAE,WAAW,MAAM,CAAC;AAW5C,OAAK,MAAM,UARU;GACnB;GACA;GACA;GACA;GACA;GACD,EAEkC;GACjC,MAAM,MAAMA,UAAK,KAAK,WAAW,OAAO;AACxC,OAAI,CAACC,QAAG,WAAW,IAAI,CAAE;GAEzB,MAAM,OAAOD,UAAK,KAAK,WAAW,OAAO;AACzC,WAAG,UAAUA,UAAK,QAAQ,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;AACrD,WAAG,OAAO,KAAK,MAAM,EAAE,WAAW,MAAM,CAAC;;EAI3C,IAAI,UAAU;EACd,MAAM,cAAcA,UAAK,KAAK,WAAW,UAAU,UAAU;AAC7D,MAAIC,QAAG,WAAW,YAAY,CAC5B,WAAUA,QAAG,aAAa,aAAa,OAAO,CAAC,MAAM;AAGvD,UAAG,cACDD,UAAK,KAAK,WAAW,mBAAmB,EACxC,KAAK,UACH;GAAE,0BAAS,IAAI,MAAM,EAAC,aAAa;GAAE;GAAS,EAC9C,MACA,EACD,CACF;AAED,SAAO;SACD;AAEN,SAAO;;;AAKX,IAAI,QAAQ,SAAS,OACnB,gBAAe;CAAE,SAASE,QAAG,SAAS;CAAE,KAAK,QAAQ,KAAK;CAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"maxsim-check-update.cjs","names":["path","os","fs"],"sources":["../../../src/hooks/shared.ts","../../../src/hooks/maxsim-check-update.ts"],"sourcesContent":["/** Shared utilities for MAXSIM hooks. */\n\nimport { spawnSync } from 'node:child_process';\nimport * as os from 'node:os';\n\nexport function readStdinJson<T>(callback: (data: T) => void): void {\n let input = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk: string) => {\n input += chunk;\n });\n process.stdin.on('end', () => {\n try {\n const data = JSON.parse(input) as T;\n callback(data);\n } catch {\n process.exit(0);\n }\n });\n}\n\nexport const CLAUDE_DIR = '.claude';\n\n/** Returns true when running on Windows. */\nexport function isWindows(): boolean {\n return os.platform() === 'win32';\n}\n\n/** Returns true when running on macOS. */\nexport function isMac(): boolean {\n return os.platform() === 'darwin';\n}\n\n/**\n * Play a system sound file cross-platform.\n * Never throws — sound failure is always silently swallowed.\n *\n * @param soundFile Absolute path to a WAV/MP3/etc. file, or a named system\n * sound token recognised by the platform helper (e.g. the\n * Windows-only SystemAsterisk token).\n */\nexport function playSound(soundFile: string): void {\n try {\n if (isWindows()) {\n // PowerShell's SoundPlayer works with WAV files synchronously.\n // For named system sounds (no extension) fall back to rundll32.\n const isWav = soundFile.toLowerCase().endsWith('.wav');\n if (isWav) {\n spawnSync(\n 'powershell',\n [\n '-NoProfile',\n '-NonInteractive',\n '-Command',\n `(New-Object System.Media.SoundPlayer '${soundFile.replace(/'/g, \"''\")}').PlaySync()`,\n ],\n { stdio: 'ignore' },\n );\n } else {\n // Named system sound token (e.g. \"SystemAsterisk\") or unsupported format —\n // use the rundll32 winsound bridge.\n spawnSync(\n 'rundll32',\n ['user32.dll,MessageBeep'],\n { stdio: 'ignore' },\n );\n }\n } else if (isMac()) {\n spawnSync('afplay', [soundFile], { stdio: 'ignore' });\n } else {\n // Linux: try paplay (PulseAudio) then aplay (ALSA)\n const paplay = spawnSync('paplay', [soundFile], { stdio: 'ignore' });\n if (paplay.status !== 0) {\n spawnSync('aplay', [soundFile], { stdio: 'ignore' });\n }\n }\n } catch {\n // Never crash on sound failure\n }\n}\n","/**\n * SessionStart hook — check if a newer maxsimcli version is available on npm.\n *\n * Behaviour:\n * - Reads session info from stdin (ignored; hook fires on every session start).\n * - Uses a one-hour cache file so the npm registry is queried at most once/hour.\n * - Spawns a detached background process when the cache is stale so the check\n * never blocks the session from starting.\n * - If a newer version is found (from the cache), emits an additionalContext\n * JSON message to stdout.\n * - Always exits 0 — never blocks the user's session.\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { spawnSync, spawn } from 'node:child_process';\nimport { readStdinJson } from './shared.js';\n\nconst PACKAGE_NAME = 'maxsimcli';\nconst CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour\nconst CACHE_FILE = path.join(os.tmpdir(), '.maxsimcli-update-cache.json');\n\ninterface CacheEntry {\n checkedAt: number;\n latestVersion: string;\n}\n\ninterface SessionStartInput {\n session_id?: string;\n [key: string]: unknown;\n}\n\n/** Read the currently installed version from package.json next to this script. */\nfunction getInstalledVersion(): string {\n try {\n // Walk up from this file's location to find the package root\n let dir = path.dirname(process.argv[1] ?? __filename);\n for (let i = 0; i < 6; i++) {\n const pkgPath = path.join(dir, 'package.json');\n if (fs.existsSync(pkgPath)) {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) as { name?: string; version?: string };\n if (pkg.name === PACKAGE_NAME && pkg.version) {\n return pkg.version;\n }\n }\n dir = path.dirname(dir);\n }\n } catch {\n // Ignore\n }\n return '0.0.0';\n}\n\n/** Read the update cache from disk. Returns null if absent or expired. */\nfunction readCache(): CacheEntry | null {\n try {\n if (!fs.existsSync(CACHE_FILE)) return null;\n const entry = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8')) as CacheEntry;\n if (Date.now() - entry.checkedAt > CACHE_TTL_MS) return null;\n return entry;\n } catch {\n return null;\n }\n}\n\n/** Write a cache entry to disk (best-effort). */\nfunction writeCache(latestVersion: string): void {\n try {\n const entry: CacheEntry = { checkedAt: Date.now(), latestVersion };\n fs.writeFileSync(CACHE_FILE, JSON.stringify(entry), 'utf8');\n } catch {\n // Ignore write failures\n }\n}\n\n/** Compare semver strings. Returns true if b > a. */\nfunction isNewer(installed: string, latest: string): boolean {\n const parse = (v: string) => v.replace(/^[^0-9]*/, '').split('.').map(Number);\n const [aMaj = 0, aMin = 0, aPat = 0] = parse(installed);\n const [bMaj = 0, bMin = 0, bPat = 0] = parse(latest);\n if (bMaj !== aMaj) return bMaj > aMaj;\n if (bMin !== aMin) return bMin > aMin;\n return bPat > aPat;\n}\n\n/** Spawn a background npm view query and write its result to the cache file. */\nfunction spawnBackgroundCheck(): void {\n try {\n const child = spawn(\n 'npm',\n ['view', PACKAGE_NAME, 'version', '--json'],\n {\n detached: true,\n stdio: ['ignore', 'pipe', 'ignore'],\n windowsHide: true,\n },\n );\n\n let stdout = '';\n child.stdout?.on('data', (d: Buffer) => { stdout += d.toString(); });\n child.on('close', (code: number) => {\n if (code === 0) {\n try {\n const version = JSON.parse(stdout.trim()) as string;\n writeCache(version);\n } catch {\n // Malformed output — ignore\n }\n }\n });\n\n child.unref();\n } catch {\n // Ignore spawn failures (npm not available, etc.)\n }\n}\n\nreadStdinJson<SessionStartInput>(() => {\n const installed = getInstalledVersion();\n const cache = readCache();\n\n if (cache === null) {\n // Cache is stale or absent — kick off a background refresh.\n // We do a quick synchronous check first so we don't miss the very first run.\n try {\n const result = spawnSync('npm', ['view', PACKAGE_NAME, 'version', '--json'], {\n encoding: 'utf8',\n timeout: 4000,\n windowsHide: true,\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n if (result.status === 0 && result.stdout) {\n const latest = JSON.parse(result.stdout.trim()) as string;\n writeCache(latest);\n if (isNewer(installed, latest)) {\n process.stdout.write(\n JSON.stringify({\n additionalContext: `MaxsimCLI update available: v${installed} → v${latest}. Run: npm i -g maxsimcli`,\n }) + '\\n',\n );\n }\n } else {\n // npm timed out or failed — schedule a detached background check for next time\n spawnBackgroundCheck();\n }\n } catch {\n spawnBackgroundCheck();\n }\n } else if (isNewer(installed, cache.latestVersion)) {\n process.stdout.write(\n JSON.stringify({\n additionalContext: `MaxsimCLI update available: v${installed} → v${cache.latestVersion}. Run: npm i -g maxsimcli`,\n }) + '\\n',\n );\n }\n\n process.exit(0);\n});\n"],"mappings":"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"}
|
|
@@ -1,18 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
let node_path = require("node:path");
|
|
29
|
+
node_path = __toESM(node_path);
|
|
30
|
+
let node_child_process = require("node:child_process");
|
|
31
|
+
let node_os = require("node:os");
|
|
32
|
+
node_os = __toESM(node_os);
|
|
3
33
|
|
|
4
34
|
//#region src/hooks/shared.ts
|
|
5
|
-
/**
|
|
6
|
-
* Shared utilities for MAXSIM hooks.
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* Read all stdin as a string, then invoke callback with parsed JSON.
|
|
10
|
-
* Used by statusline and sync-reminder hooks.
|
|
11
|
-
*/
|
|
35
|
+
/** Shared utilities for MAXSIM hooks. */
|
|
12
36
|
function readStdinJson(callback) {
|
|
13
37
|
let input = "";
|
|
14
38
|
process.stdin.setEncoding("utf8");
|
|
15
|
-
process.stdin.on("data", (chunk) =>
|
|
39
|
+
process.stdin.on("data", (chunk) => {
|
|
40
|
+
input += chunk;
|
|
41
|
+
});
|
|
16
42
|
process.stdin.on("end", () => {
|
|
17
43
|
try {
|
|
18
44
|
callback(JSON.parse(input));
|
|
@@ -21,51 +47,69 @@ function readStdinJson(callback) {
|
|
|
21
47
|
}
|
|
22
48
|
});
|
|
23
49
|
}
|
|
50
|
+
/** Returns true when running on Windows. */
|
|
51
|
+
function isWindows() {
|
|
52
|
+
return node_os.platform() === "win32";
|
|
53
|
+
}
|
|
54
|
+
/** Returns true when running on macOS. */
|
|
55
|
+
function isMac() {
|
|
56
|
+
return node_os.platform() === "darwin";
|
|
57
|
+
}
|
|
24
58
|
/**
|
|
25
|
-
* Play a system sound
|
|
26
|
-
*
|
|
59
|
+
* Play a system sound file cross-platform.
|
|
60
|
+
* Never throws — sound failure is always silently swallowed.
|
|
61
|
+
*
|
|
62
|
+
* @param soundFile Absolute path to a WAV/MP3/etc. file, or a named system
|
|
63
|
+
* sound token recognised by the platform helper (e.g. the
|
|
64
|
+
* Windows-only SystemAsterisk token).
|
|
27
65
|
*/
|
|
28
|
-
function playSound(
|
|
66
|
+
function playSound(soundFile) {
|
|
29
67
|
try {
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"-Command",
|
|
40
|
-
`(New-Object Media.SoundPlayer '${file}').PlaySync()`
|
|
41
|
-
], {
|
|
42
|
-
stdio: "ignore",
|
|
43
|
-
windowsHide: true
|
|
44
|
-
}).unref();
|
|
45
|
-
} else if (platform === "darwin") {
|
|
46
|
-
const file = type === "question" ? "/System/Library/Sounds/Ping.aiff" : "/System/Library/Sounds/Glass.aiff";
|
|
47
|
-
const { spawn } = require("node:child_process");
|
|
48
|
-
spawn("afplay", [file], {
|
|
49
|
-
stdio: "ignore",
|
|
50
|
-
detached: true
|
|
51
|
-
}).unref();
|
|
52
|
-
} else process.stderr.write("\x07");
|
|
68
|
+
if (isWindows()) if (soundFile.toLowerCase().endsWith(".wav")) (0, node_child_process.spawnSync)("powershell", [
|
|
69
|
+
"-NoProfile",
|
|
70
|
+
"-NonInteractive",
|
|
71
|
+
"-Command",
|
|
72
|
+
`(New-Object System.Media.SoundPlayer '${soundFile.replace(/'/g, "''")}').PlaySync()`
|
|
73
|
+
], { stdio: "ignore" });
|
|
74
|
+
else (0, node_child_process.spawnSync)("rundll32", ["user32.dll,MessageBeep"], { stdio: "ignore" });
|
|
75
|
+
else if (isMac()) (0, node_child_process.spawnSync)("afplay", [soundFile], { stdio: "ignore" });
|
|
76
|
+
else if ((0, node_child_process.spawnSync)("paplay", [soundFile], { stdio: "ignore" }).status !== 0) (0, node_child_process.spawnSync)("aplay", [soundFile], { stdio: "ignore" });
|
|
53
77
|
} catch {}
|
|
54
78
|
}
|
|
55
79
|
|
|
56
80
|
//#endregion
|
|
57
81
|
//#region src/hooks/maxsim-notification-sound.ts
|
|
58
82
|
/**
|
|
59
|
-
* Notification
|
|
60
|
-
*
|
|
83
|
+
* Notification hook — play a short, non-intrusive sound when Claude asks a
|
|
84
|
+
* question (i.e. the Notification event fires).
|
|
85
|
+
*
|
|
86
|
+
* Uses the platform's built-in system sounds so no external audio files are
|
|
87
|
+
* required. Falls through silently if playback fails for any reason.
|
|
61
88
|
*/
|
|
62
|
-
|
|
63
|
-
|
|
89
|
+
/** Resolve a bundled WAV asset relative to this script, or return null. */
|
|
90
|
+
function bundledSound(name) {
|
|
91
|
+
const candidates = [node_path.join(node_path.dirname(process.argv[1] ?? __filename), "sounds", name), node_path.join(__dirname, "sounds", name)];
|
|
92
|
+
for (const p of candidates) try {
|
|
93
|
+
require("node:fs").existsSync(p);
|
|
94
|
+
if (require("node:fs").existsSync(p)) return p;
|
|
95
|
+
} catch {}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
/** Play the best available notification sound for the current platform. */
|
|
99
|
+
function playNotification() {
|
|
100
|
+
const wav = bundledSound("notification.wav");
|
|
101
|
+
if (wav) {
|
|
102
|
+
playSound(wav);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (isWindows()) playSound("SystemAsterisk");
|
|
106
|
+
else if (isMac()) playSound("/System/Library/Sounds/Funk.aiff");
|
|
107
|
+
else playSound("/usr/share/sounds/freedesktop/stereo/message-new-instant.oga");
|
|
64
108
|
}
|
|
65
|
-
|
|
66
|
-
|
|
109
|
+
readStdinJson((_input) => {
|
|
110
|
+
playNotification();
|
|
111
|
+
process.exit(0);
|
|
67
112
|
});
|
|
68
113
|
|
|
69
114
|
//#endregion
|
|
70
|
-
exports.processNotificationSound = processNotificationSound;
|
|
71
115
|
//# sourceMappingURL=maxsim-notification-sound.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"maxsim-notification-sound.cjs","names":[],"sources":["../../../src/hooks/shared.ts","../../../src/hooks/maxsim-notification-sound.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"maxsim-notification-sound.cjs","names":["os","path"],"sources":["../../../src/hooks/shared.ts","../../../src/hooks/maxsim-notification-sound.ts"],"sourcesContent":["/** Shared utilities for MAXSIM hooks. */\n\nimport { spawnSync } from 'node:child_process';\nimport * as os from 'node:os';\n\nexport function readStdinJson<T>(callback: (data: T) => void): void {\n let input = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk: string) => {\n input += chunk;\n });\n process.stdin.on('end', () => {\n try {\n const data = JSON.parse(input) as T;\n callback(data);\n } catch {\n process.exit(0);\n }\n });\n}\n\nexport const CLAUDE_DIR = '.claude';\n\n/** Returns true when running on Windows. */\nexport function isWindows(): boolean {\n return os.platform() === 'win32';\n}\n\n/** Returns true when running on macOS. */\nexport function isMac(): boolean {\n return os.platform() === 'darwin';\n}\n\n/**\n * Play a system sound file cross-platform.\n * Never throws — sound failure is always silently swallowed.\n *\n * @param soundFile Absolute path to a WAV/MP3/etc. file, or a named system\n * sound token recognised by the platform helper (e.g. the\n * Windows-only SystemAsterisk token).\n */\nexport function playSound(soundFile: string): void {\n try {\n if (isWindows()) {\n // PowerShell's SoundPlayer works with WAV files synchronously.\n // For named system sounds (no extension) fall back to rundll32.\n const isWav = soundFile.toLowerCase().endsWith('.wav');\n if (isWav) {\n spawnSync(\n 'powershell',\n [\n '-NoProfile',\n '-NonInteractive',\n '-Command',\n `(New-Object System.Media.SoundPlayer '${soundFile.replace(/'/g, \"''\")}').PlaySync()`,\n ],\n { stdio: 'ignore' },\n );\n } else {\n // Named system sound token (e.g. \"SystemAsterisk\") or unsupported format —\n // use the rundll32 winsound bridge.\n spawnSync(\n 'rundll32',\n ['user32.dll,MessageBeep'],\n { stdio: 'ignore' },\n );\n }\n } else if (isMac()) {\n spawnSync('afplay', [soundFile], { stdio: 'ignore' });\n } else {\n // Linux: try paplay (PulseAudio) then aplay (ALSA)\n const paplay = spawnSync('paplay', [soundFile], { stdio: 'ignore' });\n if (paplay.status !== 0) {\n spawnSync('aplay', [soundFile], { stdio: 'ignore' });\n }\n }\n } catch {\n // Never crash on sound failure\n }\n}\n","/**\n * Notification hook — play a short, non-intrusive sound when Claude asks a\n * question (i.e. the Notification event fires).\n *\n * Uses the platform's built-in system sounds so no external audio files are\n * required. Falls through silently if playback fails for any reason.\n */\n\nimport * as path from 'node:path';\nimport { readStdinJson, playSound, isWindows, isMac } from './shared.js';\n\ninterface NotificationInput {\n session_id?: string;\n message?: string;\n [key: string]: unknown;\n}\n\n/** Resolve a bundled WAV asset relative to this script, or return null. */\nfunction bundledSound(name: string): string | null {\n // When compiled to a CJS bundle the file sits next to the .cjs file in\n // dist/assets/hooks/. We look for a sibling sounds/ directory.\n const candidates = [\n path.join(path.dirname(process.argv[1] ?? __filename), 'sounds', name),\n path.join(__dirname, 'sounds', name),\n ];\n for (const p of candidates) {\n try {\n // Use dynamic require check — fs.existsSync would also work\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n require('node:fs').existsSync(p);\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n if (require('node:fs').existsSync(p)) return p;\n } catch {\n // ignore\n }\n }\n return null;\n}\n\n/** Play the best available notification sound for the current platform. */\nfunction playNotification(): void {\n // 1. Prefer a bundled WAV if present (future-proof for custom sounds)\n const wav = bundledSound('notification.wav');\n if (wav) {\n playSound(wav);\n return;\n }\n\n // 2. Fall back to a built-in system sound\n if (isWindows()) {\n // SystemAsterisk maps to the Windows \"Asterisk\" event sound\n playSound('SystemAsterisk');\n } else if (isMac()) {\n // /System/Library/Sounds/Funk.aiff — short, non-intrusive\n playSound('/System/Library/Sounds/Funk.aiff');\n } else {\n // Linux: /usr/share/sounds/freedesktop/stereo/message-new-instant.oga\n playSound('/usr/share/sounds/freedesktop/stereo/message-new-instant.oga');\n }\n}\n\nreadStdinJson<NotificationInput>((_input) => {\n playNotification();\n process.exit(0);\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKA,SAAgB,cAAiB,UAAmC;CAClE,IAAI,QAAQ;AACZ,SAAQ,MAAM,YAAY,OAAO;AACjC,SAAQ,MAAM,GAAG,SAAS,UAAkB;AAC1C,WAAS;GACT;AACF,SAAQ,MAAM,GAAG,aAAa;AAC5B,MAAI;AAEF,YADa,KAAK,MAAM,MAAM,CAChB;UACR;AACN,WAAQ,KAAK,EAAE;;GAEjB;;;AAMJ,SAAgB,YAAqB;AACnC,QAAOA,QAAG,UAAU,KAAK;;;AAI3B,SAAgB,QAAiB;AAC/B,QAAOA,QAAG,UAAU,KAAK;;;;;;;;;;AAW3B,SAAgB,UAAU,WAAyB;AACjD,KAAI;AACF,MAAI,WAAW,CAIb,KADc,UAAU,aAAa,CAAC,SAAS,OAAO,CAEpD,mCACE,cACA;GACE;GACA;GACA;GACA,yCAAyC,UAAU,QAAQ,MAAM,KAAK,CAAC;GACxE,EACD,EAAE,OAAO,UAAU,CACpB;MAID,mCACE,YACA,CAAC,yBAAyB,EAC1B,EAAE,OAAO,UAAU,CACpB;WAEM,OAAO,CAChB,mCAAU,UAAU,CAAC,UAAU,EAAE,EAAE,OAAO,UAAU,CAAC;6CAG5B,UAAU,CAAC,UAAU,EAAE,EAAE,OAAO,UAAU,CAAC,CACzD,WAAW,EACpB,mCAAU,SAAS,CAAC,UAAU,EAAE,EAAE,OAAO,UAAU,CAAC;SAGlD;;;;;;;;;;;;;AC1DV,SAAS,aAAa,MAA6B;CAGjD,MAAM,aAAa,CACjBC,UAAK,KAAKA,UAAK,QAAQ,QAAQ,KAAK,MAAM,WAAW,EAAE,UAAU,KAAK,EACtEA,UAAK,KAAK,WAAW,UAAU,KAAK,CACrC;AACD,MAAK,MAAM,KAAK,WACd,KAAI;AAGF,UAAQ,UAAU,CAAC,WAAW,EAAE;AAEhC,MAAI,QAAQ,UAAU,CAAC,WAAW,EAAE,CAAE,QAAO;SACvC;AAIV,QAAO;;;AAIT,SAAS,mBAAyB;CAEhC,MAAM,MAAM,aAAa,mBAAmB;AAC5C,KAAI,KAAK;AACP,YAAU,IAAI;AACd;;AAIF,KAAI,WAAW,CAEb,WAAU,iBAAiB;UAClB,OAAO,CAEhB,WAAU,mCAAmC;KAG7C,WAAU,+DAA+D;;AAI7E,eAAkC,WAAW;AAC3C,mBAAkB;AAClB,SAAQ,KAAK,EAAE;EACf"}
|