maxsimcli 5.0.3 → 5.0.4

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.
@@ -1,3 +1,10 @@
1
+ ## [5.0.3](https://github.com/maystudios/maxsimcli/compare/v5.0.2...v5.0.3) (2026-03-12)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **templates:** rewrite [@path](https://github.com/path) references to resolve correctly after install ([11aeffa](https://github.com/maystudios/maxsimcli/commit/11aeffad7b355ccbbd3c1c9cab58419113360eb3))
7
+
1
8
  ## [5.0.2](https://github.com/maystudios/maxsimcli/compare/v5.0.1...v5.0.2) (2026-03-12)
2
9
 
3
10
 
@@ -52,6 +52,7 @@ function checkForUpdate(options) {
52
52
  const projectVersionFile = node_path.join(cwd, CLAUDE_DIR, "maxsim", "VERSION");
53
53
  const globalVersionFile = node_path.join(homeDir, CLAUDE_DIR, "maxsim", "VERSION");
54
54
  if (!node_fs.existsSync(cacheDir)) node_fs.mkdirSync(cacheDir, { recursive: true });
55
+ const isWindows = process.platform === "win32";
55
56
  (0, node_child_process.spawn)(process.execPath, ["-e", `
56
57
  const fs = require('fs');
57
58
  const { execSync } = require('child_process');
@@ -86,7 +87,7 @@ function checkForUpdate(options) {
86
87
  `], {
87
88
  stdio: "ignore",
88
89
  windowsHide: true,
89
- detached: true
90
+ detached: !isWindows
90
91
  }).unref();
91
92
  }
92
93
  /**
@@ -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', '-Command', `(New-Object Media.SoundPlayer '${file}').PlaySync()`],\n { stdio: 'ignore', windowsHide: true, detached: 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 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: true,\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;AAyC7C,+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;EACX,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","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"}
@@ -34,12 +34,13 @@ function playSound(type) {
34
34
  const { spawn } = require("node:child_process");
35
35
  spawn("powershell", [
36
36
  "-NoProfile",
37
+ "-WindowStyle",
38
+ "Hidden",
37
39
  "-Command",
38
40
  `(New-Object Media.SoundPlayer '${file}').PlaySync()`
39
41
  ], {
40
42
  stdio: "ignore",
41
- windowsHide: true,
42
- detached: true
43
+ windowsHide: true
43
44
  }).unref();
44
45
  } else if (platform === "darwin") {
45
46
  const file = type === "question" ? "/System/Library/Sounds/Ping.aiff" : "/System/Library/Sounds/Glass.aiff";
@@ -1 +1 @@
1
- {"version":3,"file":"maxsim-notification-sound.cjs","names":[],"sources":["../../../src/hooks/shared.ts","../../../src/hooks/maxsim-notification-sound.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', '-Command', `(New-Object Media.SoundPlayer '${file}').PlaySync()`],\n { stdio: 'ignore', windowsHide: true, detached: 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 * Notification Sound Hook — PostToolUse hook that plays a sound\n * when Claude asks the user a question (AskUserQuestion tool).\n */\n\nimport { readStdinJson, playSound } from './shared';\n\ninterface NotificationSoundInput {\n tool_name?: string;\n}\n\nexport function processNotificationSound(data: NotificationSoundInput): void {\n playSound('question');\n}\n\n// Standalone entry\nif (require.main === module) {\n readStdinJson<NotificationSoundInput>((data) => {\n processNotificationSound(data);\n });\n}\n"],"mappings":";;;;;;;;;;;AAQA,SAAgB,cAAiB,UAAmC;CAClE,IAAI,QAAQ;AACZ,SAAQ,MAAM,YAAY,OAAO;AACjC,SAAQ,MAAM,GAAG,SAAS,UAAmB,SAAS,MAAO;AAC7D,SAAQ,MAAM,GAAG,aAAa;AAC5B,MAAI;AAEF,YADa,KAAK,MAAM,MAAM,CAChB;UACR;AAEN,WAAQ,KAAK,EAAE;;GAEjB;;;;;;AAUJ,SAAgB,UAAU,MAAiC;AACzD,KAAI;AACF,MACE,QAAQ,IAAI,iBAAiB,OAC7B,QAAQ,IAAI,OAAO,UACnB,QAAQ,IAAI,eAEZ;EAGF,MAAM,WAAW,QAAQ;AAEzB,MAAI,aAAa,SAAS;GACxB,MAAM,OACJ,SAAS,aACL,mCACA;GACN,MAAM,EAAE,UAAU,QAAQ,qBAAqB;AAM/C,GALc,MACZ,cACA;IAAC;IAAc;IAAY,kCAAkC,KAAK;IAAe,EACjF;IAAE,OAAO;IAAU,aAAa;IAAM,UAAU;IAAM,CACvD,CACK,OAAO;aACJ,aAAa,UAAU;GAChC,MAAM,OACJ,SAAS,aACL,qCACA;GACN,MAAM,EAAE,UAAU,QAAQ,qBAAqB;AAK/C,GAJc,MAAM,UAAU,CAAC,KAAK,EAAE;IACpC,OAAO;IACP,UAAU;IACX,CAAC,CACI,OAAO;QAGb,SAAQ,OAAO,MAAM,OAAO;SAExB;;;;;;;;;ACzDV,SAAgB,yBAAyB,MAAoC;AAC3E,WAAU,WAAW;;AAIvB,IAAI,QAAQ,SAAS,OACnB,gBAAuC,SAAS;AAC9C,0BAAyB,KAAK;EAC9B"}
1
+ {"version":3,"file":"maxsim-notification-sound.cjs","names":[],"sources":["../../../src/hooks/shared.ts","../../../src/hooks/maxsim-notification-sound.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 * Notification Sound Hook — PostToolUse hook that plays a sound\n * when Claude asks the user a question (AskUserQuestion tool).\n */\n\nimport { readStdinJson, playSound } from './shared';\n\ninterface NotificationSoundInput {\n tool_name?: string;\n}\n\nexport function processNotificationSound(data: NotificationSoundInput): void {\n playSound('question');\n}\n\n// Standalone entry\nif (require.main === module) {\n readStdinJson<NotificationSoundInput>((data) => {\n processNotificationSound(data);\n });\n}\n"],"mappings":";;;;;;;;;;;AAQA,SAAgB,cAAiB,UAAmC;CAClE,IAAI,QAAQ;AACZ,SAAQ,MAAM,YAAY,OAAO;AACjC,SAAQ,MAAM,GAAG,SAAS,UAAmB,SAAS,MAAO;AAC7D,SAAQ,MAAM,GAAG,aAAa;AAC5B,MAAI;AAEF,YADa,KAAK,MAAM,MAAM,CAChB;UACR;AAEN,WAAQ,KAAK,EAAE;;GAEjB;;;;;;AAUJ,SAAgB,UAAU,MAAiC;AACzD,KAAI;AACF,MACE,QAAQ,IAAI,iBAAiB,OAC7B,QAAQ,IAAI,OAAO,UACnB,QAAQ,IAAI,eAEZ;EAGF,MAAM,WAAW,QAAQ;AAEzB,MAAI,aAAa,SAAS;GACxB,MAAM,OACJ,SAAS,aACL,mCACA;GACN,MAAM,EAAE,UAAU,QAAQ,qBAAqB;AAM/C,GALc,MACZ,cACA;IAAC;IAAc;IAAgB;IAAU;IAAY,kCAAkC,KAAK;IAAe,EAC3G;IAAE,OAAO;IAAU,aAAa;IAAM,CACvC,CACK,OAAO;aACJ,aAAa,UAAU;GAChC,MAAM,OACJ,SAAS,aACL,qCACA;GACN,MAAM,EAAE,UAAU,QAAQ,qBAAqB;AAK/C,GAJc,MAAM,UAAU,CAAC,KAAK,EAAE;IACpC,OAAO;IACP,UAAU;IACX,CAAC,CACI,OAAO;QAGb,SAAQ,OAAO,MAAM,OAAO;SAExB;;;;;;;;;ACzDV,SAAgB,yBAAyB,MAAoC;AAC3E,WAAU,WAAW;;AAIvB,IAAI,QAAQ,SAAS,OACnB,gBAAuC,SAAS;AAC9C,0BAAyB,KAAK;EAC9B"}
@@ -81,6 +81,7 @@ try {
81
81
  encoding: 'utf8',
82
82
  timeout: 10000,
83
83
  stdio: ['pipe', 'pipe', 'pipe'],
84
+ windowsHide: true,
84
85
  }).trim();
85
86
 
86
87
  if (!nameWithOwner || !nameWithOwner.includes('/')) {
@@ -95,7 +96,7 @@ try {
95
96
  try {
96
97
  const milestonesRaw = execSync(
97
98
  'gh api repos/' + owner + '/' + repo + '/milestones --jq "."',
98
- { encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] }
99
+ { encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true }
99
100
  ).trim();
100
101
  if (milestonesRaw) {
101
102
  const milestones = JSON.parse(milestonesRaw);
@@ -118,7 +119,7 @@ try {
118
119
  try {
119
120
  const phaseRaw = execSync(
120
121
  'gh api "repos/' + owner + '/' + repo + '/issues?state=open&labels=phase&per_page=1&sort=updated&direction=desc" --jq ".[0] | {number: .number, title: .title}"',
121
- { encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] }
122
+ { encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true }
122
123
  ).trim();
123
124
  const phaseData = JSON.parse(phaseRaw || '{}');
124
125
  const titleMatch = (phaseData.title || '').match(/^\\[Phase\\s+(\\S+)\\]/);
@@ -137,7 +138,7 @@ try {
137
138
  const gqlQuery = '{ repository(owner: "' + owner + '", name: "' + repo + '") { issue(number: ' + issueNumber + ') { projectItems(first: 5, includeArchived: false) { nodes { fieldValueByName(name: "Status") { ... on ProjectV2ItemFieldSingleSelectValue { name } } } } } } }';
138
139
  const boardRaw = execSync(
139
140
  'gh api graphql -f query=@-',
140
- { input: gqlQuery, encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] }
141
+ { input: gqlQuery, encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true }
141
142
  ).trim();
142
143
  const boardData = JSON.parse(boardRaw);
143
144
  const nodes = boardData?.data?.repository?.issue?.projectItems?.nodes || [];
@@ -177,10 +178,11 @@ try {
177
178
  process.exit(0);
178
179
  }
179
180
  `;
181
+ const isWindows = process.platform === "win32";
180
182
  (0, node_child_process.spawn)(process.execPath, ["-e", script], {
181
183
  stdio: "ignore",
182
184
  windowsHide: true,
183
- detached: true
185
+ detached: !isWindows
184
186
  }).unref();
185
187
  } catch {}
186
188
  }
@@ -1 +1 @@
1
- {"version":3,"file":"maxsim-statusline.cjs","names":["path","fs"],"sources":["../../../src/hooks/shared.ts","../../../src/hooks/maxsim-statusline.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', '-Command', `(New-Object Media.SoundPlayer '${file}').PlaySync()`],\n { stdio: 'ignore', windowsHide: true, detached: 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 * Claude Code Statusline - MAXSIM Edition\n * Shows: [update] model | P{N} {BoardColumn} | {milestone}: {pct}% | dirname\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { spawn } from 'node:child_process';\nimport { readStdinJson, CLAUDE_DIR } from './shared';\n\nexport interface StatuslineInput {\n model?: { display_name?: string };\n workspace?: { current_dir?: string; project_dir?: string };\n session_id?: string;\n}\n\nexport interface ProgressCache {\n phase_number: string | null;\n milestone_title: string | null;\n milestone_pct: number;\n board_column: string | null;\n offline?: boolean;\n updated: number;\n}\n\nconst CACHE_TTL_SECONDS = 60;\n\n/**\n * Spawn a detached Node child process to refresh the progress cache in the background.\n * The child runs gh CLI commands to detect owner/repo, find the first open milestone,\n * compute progress, and find the current phase label.\n */\nfunction spawnBackgroundRefresh(cacheDir: string, cacheFile: string): void {\n try {\n const script = `\nconst { execSync } = require('child_process');\nconst fs = require('fs');\nconst path = require('path');\n\ntry {\n // Detect owner/repo\n const nameWithOwner = execSync('gh repo view --json nameWithOwner -q .nameWithOwner', {\n encoding: 'utf8',\n timeout: 10000,\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n if (!nameWithOwner || !nameWithOwner.includes('/')) {\n process.exit(0);\n }\n\n const [owner, repo] = nameWithOwner.split('/');\n\n // Get milestones\n let milestoneTitle = null;\n let milestonePct = 0;\n try {\n const milestonesRaw = execSync(\n 'gh api repos/' + owner + '/' + repo + '/milestones --jq \".\"',\n { encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] }\n ).trim();\n if (milestonesRaw) {\n const milestones = JSON.parse(milestonesRaw);\n const openMilestone = milestones.find(function(m) { return m.state === 'open'; });\n if (openMilestone) {\n milestoneTitle = openMilestone.title || null;\n const total = (openMilestone.open_issues || 0) + (openMilestone.closed_issues || 0);\n if (total > 0) {\n milestonePct = Math.round(((openMilestone.closed_issues || 0) / total) * 100);\n }\n }\n }\n } catch (e) {\n // gh api failed for milestones, continue with defaults\n }\n\n // Get current phase from open issues with 'phase' label, parse number from title\n let phaseNumber = null;\n let issueNumber = null;\n try {\n const phaseRaw = execSync(\n 'gh api \"repos/' + owner + '/' + repo + '/issues?state=open&labels=phase&per_page=1&sort=updated&direction=desc\" --jq \".[0] | {number: .number, title: .title}\"',\n { encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] }\n ).trim();\n const phaseData = JSON.parse(phaseRaw || '{}');\n const titleMatch = (phaseData.title || '').match(/^\\\\[Phase\\\\s+(\\\\S+)\\\\]/);\n if (titleMatch) {\n phaseNumber = titleMatch[1];\n }\n issueNumber = phaseData.number || null;\n } catch (e) {\n // gh api failed for phase, continue with null\n }\n\n // Get board column via GraphQL\n let boardColumn = null;\n if (issueNumber) {\n try {\n const gqlQuery = '{ repository(owner: \"' + owner + '\", name: \"' + repo + '\") { issue(number: ' + issueNumber + ') { projectItems(first: 5, includeArchived: false) { nodes { fieldValueByName(name: \"Status\") { ... on ProjectV2ItemFieldSingleSelectValue { name } } } } } } }';\n const boardRaw = execSync(\n 'gh api graphql -f query=@-',\n { input: gqlQuery, encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] }\n ).trim();\n const boardData = JSON.parse(boardRaw);\n const nodes = boardData?.data?.repository?.issue?.projectItems?.nodes || [];\n if (nodes.length > 0 && nodes[0]?.fieldValueByName?.name) {\n boardColumn = nodes[0].fieldValueByName.name;\n }\n } catch (e) {\n boardColumn = null;\n }\n }\n\n // Write cache\n const cacheData = JSON.stringify({\n phase_number: phaseNumber,\n milestone_title: milestoneTitle,\n milestone_pct: milestonePct,\n board_column: boardColumn,\n updated: Math.floor(Date.now() / 1000),\n });\n\n const dir = ${JSON.stringify(cacheDir)};\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(${JSON.stringify(cacheFile)}, cacheData);\n} catch (e) {\n try {\n const dir = ${JSON.stringify(cacheDir)};\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(${JSON.stringify(cacheFile)}, JSON.stringify({\n phase_number: null,\n milestone_title: null,\n milestone_pct: 0,\n board_column: null,\n offline: true,\n updated: Math.floor(Date.now() / 1000),\n }));\n } catch (_) {}\n process.exit(0);\n}\n`;\n\n const child = spawn(process.execPath, ['-e', script], {\n stdio: 'ignore',\n windowsHide: true,\n detached: true,\n });\n child.unref();\n } catch {\n // Silent fail -- never break statusline\n }\n}\n\nexport function formatStatusline(data: StatuslineInput): string {\n const model = data.model?.display_name || 'Claude';\n const dir = data.workspace?.project_dir || data.workspace?.current_dir || process.cwd();\n const dirname = path.basename(dir);\n\n const SEP = ' \\u2502 ';\n const DIM = '\\x1b[2m';\n const RESET = '\\x1b[0m';\n\n // MAXSIM update available?\n let updateIndicator = '';\n const updateCacheFile = path.join(dir, CLAUDE_DIR, 'cache', 'maxsim-update-check.json');\n if (fs.existsSync(updateCacheFile)) {\n try {\n const cache = JSON.parse(fs.readFileSync(updateCacheFile, 'utf8'));\n if (cache.update_available) {\n updateIndicator = '\\x1b[33m\\u2B06\\x1b[0m ';\n }\n } catch {\n // ignore\n }\n }\n\n // Check if this is a MAXSIM project\n const planningDir = path.join(dir, '.planning');\n const isMaxsimProject = fs.existsSync(planningDir);\n\n if (!isMaxsimProject) {\n return `${updateIndicator}${DIM}${model}${RESET}${SEP}${DIM}${dirname}${RESET}`;\n }\n\n // Read progress cache\n const cacheDir = path.join(dir, CLAUDE_DIR, 'cache');\n const cacheFile = path.join(cacheDir, 'maxsim-progress.json');\n let cache: ProgressCache | null = null;\n let cacheAge = Infinity;\n\n if (fs.existsSync(cacheFile)) {\n try {\n cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8')) as ProgressCache;\n cacheAge = Math.floor(Date.now() / 1000) - (cache.updated || 0);\n } catch {\n cache = null;\n }\n }\n\n // Spawn background refresh if cache is stale or missing\n if (cacheAge > CACHE_TTL_SECONDS) {\n spawnBackgroundRefresh(cacheDir, cacheFile);\n }\n\n // Offline fallback\n if (cache?.offline) {\n return `${updateIndicator}${DIM}${model}${RESET}${SEP}${DIM}P? offline${RESET}${SEP}${DIM}${dirname}${RESET}`;\n }\n\n // Build phase segment: P{N} {BoardColumn}\n let phaseSegment = '';\n if (cache?.phase_number) {\n const column = cache.board_column ? ` ${cache.board_column}` : '';\n phaseSegment = `${SEP}${DIM}P${cache.phase_number}${column}${RESET}`;\n }\n\n // Build milestone segment\n let milestoneSegment = '';\n if (cache?.milestone_title) {\n milestoneSegment = `${SEP}${DIM}${cache.milestone_title}: ${cache.milestone_pct}%${RESET}`;\n }\n\n return `${updateIndicator}${DIM}${model}${RESET}${phaseSegment}${milestoneSegment}${SEP}${DIM}${dirname}${RESET}`;\n}\n\n// Standalone entry\nif (require.main === module) {\n readStdinJson<StatuslineInput>((data) => {\n process.stdout.write(formatStatusline(data));\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAQA,SAAgB,cAAiB,UAAmC;CAClE,IAAI,QAAQ;AACZ,SAAQ,MAAM,YAAY,OAAO;AACjC,SAAQ,MAAM,GAAG,SAAS,UAAmB,SAAS,MAAO;AAC7D,SAAQ,MAAM,GAAG,aAAa;AAC5B,MAAI;AAEF,YADa,KAAK,MAAM,MAAM,CAChB;UACR;AAEN,WAAQ,KAAK,EAAE;;GAEjB;;;AAIJ,MAAa,aAAa;;;;;;;;ACE1B,MAAM,oBAAoB;;;;;;AAO1B,SAAS,uBAAuB,UAAkB,WAAyB;AACzE,KAAI;EACF,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAwFH,KAAK,UAAU,SAAS,CAAC;;qBAEpB,KAAK,UAAU,UAAU,CAAC;;;kBAG7B,KAAK,UAAU,SAAS,CAAC;;uBAEpB,KAAK,UAAU,UAAU,CAAC;;;;;;;;;;;;AAkB7C,gCALoB,QAAQ,UAAU,CAAC,MAAM,OAAO,EAAE;GACpD,OAAO;GACP,aAAa;GACb,UAAU;GACX,CAAC,CACI,OAAO;SACP;;AAKV,SAAgB,iBAAiB,MAA+B;CAC9D,MAAM,QAAQ,KAAK,OAAO,gBAAgB;CAC1C,MAAM,MAAM,KAAK,WAAW,eAAe,KAAK,WAAW,eAAe,QAAQ,KAAK;CACvF,MAAM,UAAUA,UAAK,SAAS,IAAI;CAElC,MAAM,MAAM;CACZ,MAAM,MAAM;CACZ,MAAM,QAAQ;CAGd,IAAI,kBAAkB;CACtB,MAAM,kBAAkBA,UAAK,KAAK,KAAK,YAAY,SAAS,2BAA2B;AACvF,KAAIC,QAAG,WAAW,gBAAgB,CAChC,KAAI;AAEF,MADc,KAAK,MAAMA,QAAG,aAAa,iBAAiB,OAAO,CAAC,CACxD,iBACR,mBAAkB;SAEd;CAMV,MAAM,cAAcD,UAAK,KAAK,KAAK,YAAY;AAG/C,KAAI,CAFoBC,QAAG,WAAW,YAAY,CAGhD,QAAO,GAAG,kBAAkB,MAAM,QAAQ,QAAQ,MAAM,MAAM,UAAU;CAI1E,MAAM,WAAWD,UAAK,KAAK,KAAK,YAAY,QAAQ;CACpD,MAAM,YAAYA,UAAK,KAAK,UAAU,uBAAuB;CAC7D,IAAI,QAA8B;CAClC,IAAI,WAAW;AAEf,KAAIC,QAAG,WAAW,UAAU,CAC1B,KAAI;AACF,UAAQ,KAAK,MAAMA,QAAG,aAAa,WAAW,OAAO,CAAC;AACtD,aAAW,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK,IAAI,MAAM,WAAW;SACvD;AACN,UAAQ;;AAKZ,KAAI,WAAW,kBACb,wBAAuB,UAAU,UAAU;AAI7C,KAAI,OAAO,QACT,QAAO,GAAG,kBAAkB,MAAM,QAAQ,QAAQ,MAAM,IAAI,YAAY,QAAQ,MAAM,MAAM,UAAU;CAIxG,IAAI,eAAe;AACnB,KAAI,OAAO,cAAc;EACvB,MAAM,SAAS,MAAM,eAAe,IAAI,MAAM,iBAAiB;AAC/D,iBAAe,GAAG,MAAM,IAAI,GAAG,MAAM,eAAe,SAAS;;CAI/D,IAAI,mBAAmB;AACvB,KAAI,OAAO,gBACT,oBAAmB,GAAG,MAAM,MAAM,MAAM,gBAAgB,IAAI,MAAM,cAAc,GAAG;AAGrF,QAAO,GAAG,kBAAkB,MAAM,QAAQ,QAAQ,eAAe,mBAAmB,MAAM,MAAM,UAAU;;AAI5G,IAAI,QAAQ,SAAS,OACnB,gBAAgC,SAAS;AACvC,SAAQ,OAAO,MAAM,iBAAiB,KAAK,CAAC;EAC5C"}
1
+ {"version":3,"file":"maxsim-statusline.cjs","names":["path","fs"],"sources":["../../../src/hooks/shared.ts","../../../src/hooks/maxsim-statusline.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 * Claude Code Statusline - MAXSIM Edition\n * Shows: [update] model | P{N} {BoardColumn} | {milestone}: {pct}% | dirname\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { spawn } from 'node:child_process';\nimport { readStdinJson, CLAUDE_DIR } from './shared';\n\nexport interface StatuslineInput {\n model?: { display_name?: string };\n workspace?: { current_dir?: string; project_dir?: string };\n session_id?: string;\n}\n\nexport interface ProgressCache {\n phase_number: string | null;\n milestone_title: string | null;\n milestone_pct: number;\n board_column: string | null;\n offline?: boolean;\n updated: number;\n}\n\nconst CACHE_TTL_SECONDS = 60;\n\n/**\n * Spawn a detached Node child process to refresh the progress cache in the background.\n * The child runs gh CLI commands to detect owner/repo, find the first open milestone,\n * compute progress, and find the current phase label.\n */\nfunction spawnBackgroundRefresh(cacheDir: string, cacheFile: string): void {\n try {\n const script = `\nconst { execSync } = require('child_process');\nconst fs = require('fs');\nconst path = require('path');\n\ntry {\n // Detect owner/repo\n const nameWithOwner = execSync('gh repo view --json nameWithOwner -q .nameWithOwner', {\n encoding: 'utf8',\n timeout: 10000,\n stdio: ['pipe', 'pipe', 'pipe'],\n windowsHide: true,\n }).trim();\n\n if (!nameWithOwner || !nameWithOwner.includes('/')) {\n process.exit(0);\n }\n\n const [owner, repo] = nameWithOwner.split('/');\n\n // Get milestones\n let milestoneTitle = null;\n let milestonePct = 0;\n try {\n const milestonesRaw = execSync(\n 'gh api repos/' + owner + '/' + repo + '/milestones --jq \".\"',\n { encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true }\n ).trim();\n if (milestonesRaw) {\n const milestones = JSON.parse(milestonesRaw);\n const openMilestone = milestones.find(function(m) { return m.state === 'open'; });\n if (openMilestone) {\n milestoneTitle = openMilestone.title || null;\n const total = (openMilestone.open_issues || 0) + (openMilestone.closed_issues || 0);\n if (total > 0) {\n milestonePct = Math.round(((openMilestone.closed_issues || 0) / total) * 100);\n }\n }\n }\n } catch (e) {\n // gh api failed for milestones, continue with defaults\n }\n\n // Get current phase from open issues with 'phase' label, parse number from title\n let phaseNumber = null;\n let issueNumber = null;\n try {\n const phaseRaw = execSync(\n 'gh api \"repos/' + owner + '/' + repo + '/issues?state=open&labels=phase&per_page=1&sort=updated&direction=desc\" --jq \".[0] | {number: .number, title: .title}\"',\n { encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true }\n ).trim();\n const phaseData = JSON.parse(phaseRaw || '{}');\n const titleMatch = (phaseData.title || '').match(/^\\\\[Phase\\\\s+(\\\\S+)\\\\]/);\n if (titleMatch) {\n phaseNumber = titleMatch[1];\n }\n issueNumber = phaseData.number || null;\n } catch (e) {\n // gh api failed for phase, continue with null\n }\n\n // Get board column via GraphQL\n let boardColumn = null;\n if (issueNumber) {\n try {\n const gqlQuery = '{ repository(owner: \"' + owner + '\", name: \"' + repo + '\") { issue(number: ' + issueNumber + ') { projectItems(first: 5, includeArchived: false) { nodes { fieldValueByName(name: \"Status\") { ... on ProjectV2ItemFieldSingleSelectValue { name } } } } } } }';\n const boardRaw = execSync(\n 'gh api graphql -f query=@-',\n { input: gqlQuery, encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true }\n ).trim();\n const boardData = JSON.parse(boardRaw);\n const nodes = boardData?.data?.repository?.issue?.projectItems?.nodes || [];\n if (nodes.length > 0 && nodes[0]?.fieldValueByName?.name) {\n boardColumn = nodes[0].fieldValueByName.name;\n }\n } catch (e) {\n boardColumn = null;\n }\n }\n\n // Write cache\n const cacheData = JSON.stringify({\n phase_number: phaseNumber,\n milestone_title: milestoneTitle,\n milestone_pct: milestonePct,\n board_column: boardColumn,\n updated: Math.floor(Date.now() / 1000),\n });\n\n const dir = ${JSON.stringify(cacheDir)};\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(${JSON.stringify(cacheFile)}, cacheData);\n} catch (e) {\n try {\n const dir = ${JSON.stringify(cacheDir)};\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(${JSON.stringify(cacheFile)}, JSON.stringify({\n phase_number: null,\n milestone_title: null,\n milestone_pct: 0,\n board_column: null,\n offline: true,\n updated: Math.floor(Date.now() / 1000),\n }));\n } catch (_) {}\n process.exit(0);\n}\n`;\n\n const isWindows = process.platform === 'win32';\n const child = spawn(process.execPath, ['-e', script], {\n stdio: 'ignore',\n windowsHide: true,\n detached: !isWindows,\n });\n child.unref();\n } catch {\n // Silent fail -- never break statusline\n }\n}\n\nexport function formatStatusline(data: StatuslineInput): string {\n const model = data.model?.display_name || 'Claude';\n const dir = data.workspace?.project_dir || data.workspace?.current_dir || process.cwd();\n const dirname = path.basename(dir);\n\n const SEP = ' \\u2502 ';\n const DIM = '\\x1b[2m';\n const RESET = '\\x1b[0m';\n\n // MAXSIM update available?\n let updateIndicator = '';\n const updateCacheFile = path.join(dir, CLAUDE_DIR, 'cache', 'maxsim-update-check.json');\n if (fs.existsSync(updateCacheFile)) {\n try {\n const cache = JSON.parse(fs.readFileSync(updateCacheFile, 'utf8'));\n if (cache.update_available) {\n updateIndicator = '\\x1b[33m\\u2B06\\x1b[0m ';\n }\n } catch {\n // ignore\n }\n }\n\n // Check if this is a MAXSIM project\n const planningDir = path.join(dir, '.planning');\n const isMaxsimProject = fs.existsSync(planningDir);\n\n if (!isMaxsimProject) {\n return `${updateIndicator}${DIM}${model}${RESET}${SEP}${DIM}${dirname}${RESET}`;\n }\n\n // Read progress cache\n const cacheDir = path.join(dir, CLAUDE_DIR, 'cache');\n const cacheFile = path.join(cacheDir, 'maxsim-progress.json');\n let cache: ProgressCache | null = null;\n let cacheAge = Infinity;\n\n if (fs.existsSync(cacheFile)) {\n try {\n cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8')) as ProgressCache;\n cacheAge = Math.floor(Date.now() / 1000) - (cache.updated || 0);\n } catch {\n cache = null;\n }\n }\n\n // Spawn background refresh if cache is stale or missing\n if (cacheAge > CACHE_TTL_SECONDS) {\n spawnBackgroundRefresh(cacheDir, cacheFile);\n }\n\n // Offline fallback\n if (cache?.offline) {\n return `${updateIndicator}${DIM}${model}${RESET}${SEP}${DIM}P? offline${RESET}${SEP}${DIM}${dirname}${RESET}`;\n }\n\n // Build phase segment: P{N} {BoardColumn}\n let phaseSegment = '';\n if (cache?.phase_number) {\n const column = cache.board_column ? ` ${cache.board_column}` : '';\n phaseSegment = `${SEP}${DIM}P${cache.phase_number}${column}${RESET}`;\n }\n\n // Build milestone segment\n let milestoneSegment = '';\n if (cache?.milestone_title) {\n milestoneSegment = `${SEP}${DIM}${cache.milestone_title}: ${cache.milestone_pct}%${RESET}`;\n }\n\n return `${updateIndicator}${DIM}${model}${RESET}${phaseSegment}${milestoneSegment}${SEP}${DIM}${dirname}${RESET}`;\n}\n\n// Standalone entry\nif (require.main === module) {\n readStdinJson<StatuslineInput>((data) => {\n process.stdout.write(formatStatusline(data));\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAQA,SAAgB,cAAiB,UAAmC;CAClE,IAAI,QAAQ;AACZ,SAAQ,MAAM,YAAY,OAAO;AACjC,SAAQ,MAAM,GAAG,SAAS,UAAmB,SAAS,MAAO;AAC7D,SAAQ,MAAM,GAAG,aAAa;AAC5B,MAAI;AAEF,YADa,KAAK,MAAM,MAAM,CAChB;UACR;AAEN,WAAQ,KAAK,EAAE;;GAEjB;;;AAIJ,MAAa,aAAa;;;;;;;;ACE1B,MAAM,oBAAoB;;;;;;AAO1B,SAAS,uBAAuB,UAAkB,WAAyB;AACzE,KAAI;EACF,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAyFH,KAAK,UAAU,SAAS,CAAC;;qBAEpB,KAAK,UAAU,UAAU,CAAC;;;kBAG7B,KAAK,UAAU,SAAS,CAAC;;uBAEpB,KAAK,UAAU,UAAU,CAAC;;;;;;;;;;;;EAa7C,MAAM,YAAY,QAAQ,aAAa;AAMvC,gCALoB,QAAQ,UAAU,CAAC,MAAM,OAAO,EAAE;GACpD,OAAO;GACP,aAAa;GACb,UAAU,CAAC;GACZ,CAAC,CACI,OAAO;SACP;;AAKV,SAAgB,iBAAiB,MAA+B;CAC9D,MAAM,QAAQ,KAAK,OAAO,gBAAgB;CAC1C,MAAM,MAAM,KAAK,WAAW,eAAe,KAAK,WAAW,eAAe,QAAQ,KAAK;CACvF,MAAM,UAAUA,UAAK,SAAS,IAAI;CAElC,MAAM,MAAM;CACZ,MAAM,MAAM;CACZ,MAAM,QAAQ;CAGd,IAAI,kBAAkB;CACtB,MAAM,kBAAkBA,UAAK,KAAK,KAAK,YAAY,SAAS,2BAA2B;AACvF,KAAIC,QAAG,WAAW,gBAAgB,CAChC,KAAI;AAEF,MADc,KAAK,MAAMA,QAAG,aAAa,iBAAiB,OAAO,CAAC,CACxD,iBACR,mBAAkB;SAEd;CAMV,MAAM,cAAcD,UAAK,KAAK,KAAK,YAAY;AAG/C,KAAI,CAFoBC,QAAG,WAAW,YAAY,CAGhD,QAAO,GAAG,kBAAkB,MAAM,QAAQ,QAAQ,MAAM,MAAM,UAAU;CAI1E,MAAM,WAAWD,UAAK,KAAK,KAAK,YAAY,QAAQ;CACpD,MAAM,YAAYA,UAAK,KAAK,UAAU,uBAAuB;CAC7D,IAAI,QAA8B;CAClC,IAAI,WAAW;AAEf,KAAIC,QAAG,WAAW,UAAU,CAC1B,KAAI;AACF,UAAQ,KAAK,MAAMA,QAAG,aAAa,WAAW,OAAO,CAAC;AACtD,aAAW,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK,IAAI,MAAM,WAAW;SACvD;AACN,UAAQ;;AAKZ,KAAI,WAAW,kBACb,wBAAuB,UAAU,UAAU;AAI7C,KAAI,OAAO,QACT,QAAO,GAAG,kBAAkB,MAAM,QAAQ,QAAQ,MAAM,IAAI,YAAY,QAAQ,MAAM,MAAM,UAAU;CAIxG,IAAI,eAAe;AACnB,KAAI,OAAO,cAAc;EACvB,MAAM,SAAS,MAAM,eAAe,IAAI,MAAM,iBAAiB;AAC/D,iBAAe,GAAG,MAAM,IAAI,GAAG,MAAM,eAAe,SAAS;;CAI/D,IAAI,mBAAmB;AACvB,KAAI,OAAO,gBACT,oBAAmB,GAAG,MAAM,MAAM,MAAM,gBAAgB,IAAI,MAAM,cAAc,GAAG;AAGrF,QAAO,GAAG,kBAAkB,MAAM,QAAQ,QAAQ,eAAe,mBAAmB,MAAM,MAAM,UAAU;;AAI5G,IAAI,QAAQ,SAAS,OACnB,gBAAgC,SAAS;AACvC,SAAQ,OAAO,MAAM,iBAAiB,KAAK,CAAC;EAC5C"}
@@ -34,12 +34,13 @@ function playSound(type) {
34
34
  const { spawn } = require("node:child_process");
35
35
  spawn("powershell", [
36
36
  "-NoProfile",
37
+ "-WindowStyle",
38
+ "Hidden",
37
39
  "-Command",
38
40
  `(New-Object Media.SoundPlayer '${file}').PlaySync()`
39
41
  ], {
40
42
  stdio: "ignore",
41
- windowsHide: true,
42
- detached: true
43
+ windowsHide: true
43
44
  }).unref();
44
45
  } else if (platform === "darwin") {
45
46
  const file = type === "question" ? "/System/Library/Sounds/Ping.aiff" : "/System/Library/Sounds/Glass.aiff";
@@ -1 +1 @@
1
- {"version":3,"file":"maxsim-stop-sound.cjs","names":[],"sources":["../../../src/hooks/shared.ts","../../../src/hooks/maxsim-stop-sound.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', '-Command', `(New-Object Media.SoundPlayer '${file}').PlaySync()`],\n { stdio: 'ignore', windowsHide: true, detached: 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 * Stop Sound Hook — Stop event hook that plays a sound\n * when Claude finishes working.\n */\n\nimport { readStdinJson, playSound } from './shared';\n\ninterface StopSoundInput {\n stop_hook_active?: boolean;\n}\n\nexport function processStopSound(data: StopSoundInput): void {\n playSound('stop');\n}\n\n// Standalone entry\nif (require.main === module) {\n readStdinJson<StopSoundInput>((data) => {\n processStopSound(data);\n });\n}\n"],"mappings":";;;;;;;;;;;AAQA,SAAgB,cAAiB,UAAmC;CAClE,IAAI,QAAQ;AACZ,SAAQ,MAAM,YAAY,OAAO;AACjC,SAAQ,MAAM,GAAG,SAAS,UAAmB,SAAS,MAAO;AAC7D,SAAQ,MAAM,GAAG,aAAa;AAC5B,MAAI;AAEF,YADa,KAAK,MAAM,MAAM,CAChB;UACR;AAEN,WAAQ,KAAK,EAAE;;GAEjB;;;;;;AAUJ,SAAgB,UAAU,MAAiC;AACzD,KAAI;AACF,MACE,QAAQ,IAAI,iBAAiB,OAC7B,QAAQ,IAAI,OAAO,UACnB,QAAQ,IAAI,eAEZ;EAGF,MAAM,WAAW,QAAQ;AAEzB,MAAI,aAAa,SAAS;GACxB,MAAM,OACJ,SAAS,aACL,mCACA;GACN,MAAM,EAAE,UAAU,QAAQ,qBAAqB;AAM/C,GALc,MACZ,cACA;IAAC;IAAc;IAAY,kCAAkC,KAAK;IAAe,EACjF;IAAE,OAAO;IAAU,aAAa;IAAM,UAAU;IAAM,CACvD,CACK,OAAO;aACJ,aAAa,UAAU;GAChC,MAAM,OACJ,SAAS,aACL,qCACA;GACN,MAAM,EAAE,UAAU,QAAQ,qBAAqB;AAK/C,GAJc,MAAM,UAAU,CAAC,KAAK,EAAE;IACpC,OAAO;IACP,UAAU;IACX,CAAC,CACI,OAAO;QAGb,SAAQ,OAAO,MAAM,OAAO;SAExB;;;;;;;;;ACzDV,SAAgB,iBAAiB,MAA4B;AAC3D,WAAU,OAAO;;AAInB,IAAI,QAAQ,SAAS,OACnB,gBAA+B,SAAS;AACtC,kBAAiB,KAAK;EACtB"}
1
+ {"version":3,"file":"maxsim-stop-sound.cjs","names":[],"sources":["../../../src/hooks/shared.ts","../../../src/hooks/maxsim-stop-sound.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 * Stop Sound Hook — Stop event hook that plays a sound\n * when Claude finishes working.\n */\n\nimport { readStdinJson, playSound } from './shared';\n\ninterface StopSoundInput {\n stop_hook_active?: boolean;\n}\n\nexport function processStopSound(data: StopSoundInput): void {\n playSound('stop');\n}\n\n// Standalone entry\nif (require.main === module) {\n readStdinJson<StopSoundInput>((data) => {\n processStopSound(data);\n });\n}\n"],"mappings":";;;;;;;;;;;AAQA,SAAgB,cAAiB,UAAmC;CAClE,IAAI,QAAQ;AACZ,SAAQ,MAAM,YAAY,OAAO;AACjC,SAAQ,MAAM,GAAG,SAAS,UAAmB,SAAS,MAAO;AAC7D,SAAQ,MAAM,GAAG,aAAa;AAC5B,MAAI;AAEF,YADa,KAAK,MAAM,MAAM,CAChB;UACR;AAEN,WAAQ,KAAK,EAAE;;GAEjB;;;;;;AAUJ,SAAgB,UAAU,MAAiC;AACzD,KAAI;AACF,MACE,QAAQ,IAAI,iBAAiB,OAC7B,QAAQ,IAAI,OAAO,UACnB,QAAQ,IAAI,eAEZ;EAGF,MAAM,WAAW,QAAQ;AAEzB,MAAI,aAAa,SAAS;GACxB,MAAM,OACJ,SAAS,aACL,mCACA;GACN,MAAM,EAAE,UAAU,QAAQ,qBAAqB;AAM/C,GALc,MACZ,cACA;IAAC;IAAc;IAAgB;IAAU;IAAY,kCAAkC,KAAK;IAAe,EAC3G;IAAE,OAAO;IAAU,aAAa;IAAM,CACvC,CACK,OAAO;aACJ,aAAa,UAAU;GAChC,MAAM,OACJ,SAAS,aACL,qCACA;GACN,MAAM,EAAE,UAAU,QAAQ,qBAAqB;AAK/C,GAJc,MAAM,UAAU,CAAC,KAAK,EAAE;IACpC,OAAO;IACP,UAAU;IACX,CAAC,CACI,OAAO;QAGb,SAAQ,OAAO,MAAM,OAAO;SAExB;;;;;;;;;ACzDV,SAAgB,iBAAiB,MAA4B;AAC3D,WAAU,OAAO;;AAInB,IAAI,QAAQ,SAAS,OACnB,gBAA+B,SAAS;AACtC,kBAAiB,KAAK;EACtB"}
@@ -1 +1 @@
1
- {"version":3,"file":"maxsim-sync-reminder.cjs","names":[],"sources":["../../../src/hooks/shared.ts","../../../src/hooks/maxsim-sync-reminder.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', '-Command', `(New-Object Media.SoundPlayer '${file}').PlaySync()`],\n { stdio: 'ignore', windowsHide: true, detached: 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 * Sync Reminder Hook — No longer needed.\n * GitHub Issues is the sole source of truth for phase artifacts and todos.\n * Local .planning/ writes no longer need sync reminders.\n */\n\nimport { readStdinJson } from './shared';\n\nexport interface SyncReminderInput {\n session_id?: string;\n cwd?: string;\n tool_input?: { file_path?: string };\n}\n\nexport interface SyncReminderOutput {\n hookSpecificOutput: {\n hookEventName: string;\n additionalContext: string;\n };\n}\n\nexport const DEBOUNCE_CALLS = 10;\n\nexport function processSyncReminder(\n _data: SyncReminderInput,\n): SyncReminderOutput | null {\n // No-op: GitHub Issues is SSOT for phase artifacts and todos.\n return null;\n}\n\n// Standalone entry\nif (require.main === module) {\n readStdinJson<SyncReminderInput>((data) => {\n const result = processSyncReminder(data);\n if (result) {\n process.stdout.write(JSON.stringify(result));\n }\n });\n}\n"],"mappings":";;;;;;;;;;;AAQA,SAAgB,cAAiB,UAAmC;CAClE,IAAI,QAAQ;AACZ,SAAQ,MAAM,YAAY,OAAO;AACjC,SAAQ,MAAM,GAAG,SAAS,UAAmB,SAAS,MAAO;AAC7D,SAAQ,MAAM,GAAG,aAAa;AAC5B,MAAI;AAEF,YADa,KAAK,MAAM,MAAM,CAChB;UACR;AAEN,WAAQ,KAAK,EAAE;;GAEjB;;;;;;;;;;ACEJ,MAAa,iBAAiB;AAE9B,SAAgB,oBACd,OAC2B;AAE3B,QAAO;;AAIT,IAAI,QAAQ,SAAS,OACnB,gBAAkC,SAAS;CACzC,MAAM,SAAS,oBAAoB,KAAK;AACxC,KAAI,OACF,SAAQ,OAAO,MAAM,KAAK,UAAU,OAAO,CAAC;EAE9C"}
1
+ {"version":3,"file":"maxsim-sync-reminder.cjs","names":[],"sources":["../../../src/hooks/shared.ts","../../../src/hooks/maxsim-sync-reminder.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 * Sync Reminder Hook — No longer needed.\n * GitHub Issues is the sole source of truth for phase artifacts and todos.\n * Local .planning/ writes no longer need sync reminders.\n */\n\nimport { readStdinJson } from './shared';\n\nexport interface SyncReminderInput {\n session_id?: string;\n cwd?: string;\n tool_input?: { file_path?: string };\n}\n\nexport interface SyncReminderOutput {\n hookSpecificOutput: {\n hookEventName: string;\n additionalContext: string;\n };\n}\n\nexport const DEBOUNCE_CALLS = 10;\n\nexport function processSyncReminder(\n _data: SyncReminderInput,\n): SyncReminderOutput | null {\n // No-op: GitHub Issues is SSOT for phase artifacts and todos.\n return null;\n}\n\n// Standalone entry\nif (require.main === module) {\n readStdinJson<SyncReminderInput>((data) => {\n const result = processSyncReminder(data);\n if (result) {\n process.stdout.write(JSON.stringify(result));\n }\n });\n}\n"],"mappings":";;;;;;;;;;;AAQA,SAAgB,cAAiB,UAAmC;CAClE,IAAI,QAAQ;AACZ,SAAQ,MAAM,YAAY,OAAO;AACjC,SAAQ,MAAM,GAAG,SAAS,UAAmB,SAAS,MAAO;AAC7D,SAAQ,MAAM,GAAG,aAAa;AAC5B,MAAI;AAEF,YADa,KAAK,MAAM,MAAM,CAChB;UACR;AAEN,WAAQ,KAAK,EAAE;;GAEjB;;;;;;;;;;ACEJ,MAAa,iBAAiB;AAE9B,SAAgB,oBACd,OAC2B;AAE3B,QAAO;;AAIT,IAAI,QAAQ,SAAS,OACnB,gBAAkC,SAAS;CACzC,MAAM,SAAS,oBAAoB,KAAK;AACxC,KAAI,OACF,SAAQ,OAAO,MAAM,KAAK,UAAU,OAAO,CAAC;EAE9C"}
package/dist/install.cjs CHANGED
@@ -7699,6 +7699,7 @@ function checkForUpdate(options) {
7699
7699
  const projectVersionFile = node_path.join(cwd, CLAUDE_DIR, "maxsim", "VERSION");
7700
7700
  const globalVersionFile = node_path.join(homeDir, CLAUDE_DIR, "maxsim", "VERSION");
7701
7701
  if (!node_fs.existsSync(cacheDir)) node_fs.mkdirSync(cacheDir, { recursive: true });
7702
+ const isWindows = process.platform === "win32";
7702
7703
  (0, node_child_process.spawn)(process.execPath, ["-e", `
7703
7704
  const fs = require('fs');
7704
7705
  const { execSync } = require('child_process');
@@ -7733,7 +7734,7 @@ function checkForUpdate(options) {
7733
7734
  `], {
7734
7735
  stdio: "ignore",
7735
7736
  windowsHide: true,
7736
- detached: true
7737
+ detached: !isWindows
7737
7738
  }).unref();
7738
7739
  }
7739
7740
  /**