claude-code-autoconfig 1.0.201 → 1.0.203
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,10 +1,10 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Turn OFF Pole Position tab status beeps
|
|
3
|
-
allowed-tools: Bash
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
Run exactly this command, then reply with a single confirmation line and nothing else:
|
|
7
|
-
|
|
8
|
-
```
|
|
9
|
-
rm -f ~/.claude/sounds/arcade-beeps.enabled && echo "arcade beeps DISABLED."
|
|
10
|
-
```
|
|
1
|
+
---
|
|
2
|
+
description: Turn OFF Pole Position tab status beeps
|
|
3
|
+
allowed-tools: Bash
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Run exactly this command, then reply with a single confirmation line and nothing else:
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
rm -f "${CLAUDE_PROJECT_DIR:-.}/.claude/sounds/arcade-beeps.enabled" ~/.claude/sounds/arcade-beeps.enabled && echo "arcade beeps DISABLED."
|
|
10
|
+
```
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Turn ON Pole Position tab status beeps (awaiting = low tone, complete = high tone)
|
|
3
|
-
allowed-tools: Bash
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
Run exactly this command, then reply with a single confirmation line and nothing else:
|
|
7
|
-
|
|
8
|
-
```
|
|
9
|
-
mkdir -p
|
|
10
|
-
```
|
|
1
|
+
---
|
|
2
|
+
description: Turn ON Pole Position tab status beeps (awaiting = low tone, complete = high tone)
|
|
3
|
+
allowed-tools: Bash
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Run exactly this command, then reply with a single confirmation line and nothing else:
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
mkdir -p "${CLAUDE_PROJECT_DIR:-.}/.claude/sounds" && touch "${CLAUDE_PROJECT_DIR:-.}/.claude/sounds/arcade-beeps.enabled" && echo "arcade beeps ENABLED for this project (every turn-end beeps: awaiting=low, complete=high). Disable with /disable-arcade-beeps."
|
|
10
|
+
```
|
|
@@ -1,136 +1,139 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* arcade-beeps — optional Pole-Position status cues for the Claude Code tab.
|
|
4
|
-
* Companion to terminal-title.js. Registered (settings.json) on Stop + Notification.
|
|
5
|
-
*
|
|
6
|
-
* OFF unless the enable flag exists:
|
|
7
|
-
* toggle via /enable-arcade-beeps /disable-arcade-beeps
|
|
8
|
-
* The flag
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* Playback
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (process.platform === '
|
|
61
|
-
return ['
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* arcade-beeps — optional Pole-Position status cues for the Claude Code tab.
|
|
4
|
+
* Companion to terminal-title.js. Registered (settings.json) on Stop + Notification.
|
|
5
|
+
*
|
|
6
|
+
* OFF unless the enable flag exists: <install>/.claude/sounds/arcade-beeps.enabled
|
|
7
|
+
* toggle via /enable-arcade-beeps /disable-arcade-beeps
|
|
8
|
+
* The flag lives beside THIS install's sounds dir, so it is PER-PROJECT: a fresh
|
|
9
|
+
* `npx claude-code-autoconfig` install never beeps until /enable-arcade-beeps is run in that
|
|
10
|
+
* project. (A copy of this hook living in ~/.claude/hooks keys off ~/.claude/sounds — a
|
|
11
|
+
* deliberate consequence: a global install gets a global toggle.)
|
|
12
|
+
*
|
|
13
|
+
* Mapping (matches the ◐/✻ tab glyph that terminal-title.js paints on the SAME event):
|
|
14
|
+
* Stop, turn ended on a question -> ◐ awaiting -> get-ready tick (pp3-getready-G4.wav, G4/384Hz)
|
|
15
|
+
* Stop, turn ended normally -> ✻ complete -> GO beep (pp3-go-F#5.wav, F#5/759Hz)
|
|
16
|
+
* Notification (permission_prompt) -> ◐ awaiting -> get-ready tick
|
|
17
|
+
*
|
|
18
|
+
* Tones are an extracted+smoothed take on the Pole Position race-start cue (arcade-ping
|
|
19
|
+
* envelope). LOWER tick = awaiting, HIGHER GO = complete.
|
|
20
|
+
*
|
|
21
|
+
* Assets resolve relative to THIS file (../sounds) so a per-project CCA install finds its own
|
|
22
|
+
* copy at <project>/.claude/sounds — no dependency on a global sounds dir.
|
|
23
|
+
*
|
|
24
|
+
* Playback is cross-platform and best-effort: PowerShell SoundPlayer on Windows, afplay on
|
|
25
|
+
* macOS, paplay||aplay on Linux. If none is present the hook simply stays silent — never errors.
|
|
26
|
+
*
|
|
27
|
+
* Playback BLOCKS (spawnSync) so the sound finishes inside the hook's lifetime — a detached
|
|
28
|
+
* fire-and-forget child can be killed by the hook runner's job/process-group cleanup before it
|
|
29
|
+
* plays. Blocking costs ~0.5s on turn-end but is reliable. Still fail-safe: wrapped so it never
|
|
30
|
+
* throws into the turn, and every path ends in exit(0).
|
|
31
|
+
*
|
|
32
|
+
* State detection reuses terminal-title.js's exported inspectLastResponse (lazy-required only
|
|
33
|
+
* when enabled) so sound and glyph agree. A tiny inline '?' check is the fallback.
|
|
34
|
+
*
|
|
35
|
+
* Diagnostic log (bounded ~64KB) at ~/.claude/hooks/.titles/arcade-beeps.log records every
|
|
36
|
+
* invocation + choice, so we can prove whether the hook fires on a real Stop.
|
|
37
|
+
*/
|
|
38
|
+
const fs = require('fs');
|
|
39
|
+
const os = require('os');
|
|
40
|
+
const path = require('path');
|
|
41
|
+
const { spawnSync } = require('child_process');
|
|
42
|
+
|
|
43
|
+
const ASSET_DIR = path.join(__dirname, '..', 'sounds'); // wavs ship beside the hook
|
|
44
|
+
const FLAG = path.join(ASSET_DIR, 'arcade-beeps.enabled'); // per-install toggle, beside the wavs
|
|
45
|
+
const LOG = path.join(os.homedir(), '.claude', 'hooks', '.titles', 'arcade-beeps.log');
|
|
46
|
+
const DEBUG = process.env.ARCADE_BEEPS_DEBUG === '1';
|
|
47
|
+
|
|
48
|
+
function logLine(msg) {
|
|
49
|
+
try {
|
|
50
|
+
try { if (fs.statSync(LOG).size > 64 * 1024) fs.renameSync(LOG, `${LOG}.1`); } catch (_) { /* none yet */ }
|
|
51
|
+
fs.appendFileSync(LOG, `${new Date().toISOString()} ${msg}\n`);
|
|
52
|
+
} catch (_) { /* logging must never throw */ }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function enabled() { try { return fs.existsSync(FLAG); } catch (_) { return false; } }
|
|
56
|
+
function delay(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
57
|
+
|
|
58
|
+
// Pick a blocking audio player for the current OS. Returns [cmd, args].
|
|
59
|
+
function playerFor(wav) {
|
|
60
|
+
if (process.platform === 'win32') {
|
|
61
|
+
return ['powershell', ['-NoProfile', '-Command', `(New-Object Media.SoundPlayer '${wav}').PlaySync()`]];
|
|
62
|
+
}
|
|
63
|
+
if (process.platform === 'darwin') {
|
|
64
|
+
return ['afplay', [wav]];
|
|
65
|
+
}
|
|
66
|
+
// linux / other: prefer paplay (PulseAudio/PipeWire), fall back to aplay (ALSA)
|
|
67
|
+
return ['sh', ['-c', `paplay "${wav}" 2>/dev/null || aplay "${wav}" 2>/dev/null`]];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function play(wavName, why) {
|
|
71
|
+
if (DEBUG) process.stderr.write(`arcade-beeps: chose ${wavName} (${why})\n`);
|
|
72
|
+
logLine(`play ${wavName} (${why})`);
|
|
73
|
+
try {
|
|
74
|
+
const wav = path.join(ASSET_DIR, wavName);
|
|
75
|
+
if (!fs.existsSync(wav)) { logLine(`MISSING ${wav}`); return; }
|
|
76
|
+
// Block until the sound finishes so it can't be reaped with the hook process.
|
|
77
|
+
const [cmd, cmdArgs] = playerFor(wav);
|
|
78
|
+
spawnSync(cmd, cmdArgs, { stdio: 'ignore', windowsHide: true });
|
|
79
|
+
} catch (e) { logLine(`play-error ${e && e.message}`); }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Fallback: does the last visible assistant text end on a question? (Mirrors terminal-title.js's regex.)
|
|
83
|
+
function endsOnQuestionInline(transcriptPath) {
|
|
84
|
+
try {
|
|
85
|
+
const lines = fs.readFileSync(transcriptPath, 'utf8').split('\n');
|
|
86
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
87
|
+
const l = lines[i].trim(); if (!l) continue;
|
|
88
|
+
let o; try { o = JSON.parse(l); } catch (_) { continue; }
|
|
89
|
+
if (!o || o.type !== 'assistant' || !o.message) continue;
|
|
90
|
+
const c = o.message.content; let t = '';
|
|
91
|
+
if (typeof c === 'string') t = c;
|
|
92
|
+
else if (Array.isArray(c)) t = c.filter(b => b && b.type === 'text' && typeof b.text === 'string').map(b => b.text).join('\n');
|
|
93
|
+
if (t.trim()) return /\?[\s)*_"]*(\([^()]*\)[\s.*_"]*)?$/.test(t);
|
|
94
|
+
}
|
|
95
|
+
} catch (_) { /* ignore */ }
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function main(input) {
|
|
100
|
+
let data = {};
|
|
101
|
+
try { data = JSON.parse(input); } catch (_) { /* ignore */ }
|
|
102
|
+
const event = data.hook_event_name || '?';
|
|
103
|
+
const en = enabled();
|
|
104
|
+
logLine(`invoked event=${event} enabled=${en ? 1 : 0}`);
|
|
105
|
+
if (!en) return; // opt-in only
|
|
106
|
+
|
|
107
|
+
if (event === 'Notification') { play('pp3-getready-G4.wav', 'notification'); return; }
|
|
108
|
+
if (event !== 'Stop') return;
|
|
109
|
+
|
|
110
|
+
// awaiting (question) vs complete — same signals terminal-title.js uses, so the tone matches the glyph.
|
|
111
|
+
const sid = data.session_id || '';
|
|
112
|
+
const askFile = path.join(os.homedir(), '.claude', 'hooks', '.titles', `${sid}.ask`);
|
|
113
|
+
let pending = false;
|
|
114
|
+
try { pending = fs.existsSync(askFile); } catch (_) { /* ignore */ }
|
|
115
|
+
|
|
116
|
+
if (!pending) {
|
|
117
|
+
let inspect = null;
|
|
118
|
+
try { ({ inspectLastResponse: inspect } = require('./terminal-title.js')); } catch (_) { /* fallback below */ }
|
|
119
|
+
if (inspect) {
|
|
120
|
+
let q = inspect(data.transcript_path);
|
|
121
|
+
let n = 0;
|
|
122
|
+
while (!q.ends && (q.suspectRace || !q.found) && n < 5) { await delay(120); q = inspect(data.transcript_path); n++; }
|
|
123
|
+
pending = q.ends;
|
|
124
|
+
} else {
|
|
125
|
+
pending = endsOnQuestionInline(data.transcript_path);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
play(pending ? 'pp3-getready-G4.wav' : 'pp3-go-F#5.wav', pending ? 'awaiting' : 'complete');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (require.main === module) {
|
|
133
|
+
let input = '';
|
|
134
|
+
process.stdin.setEncoding('utf8');
|
|
135
|
+
process.stdin.on('data', c => (input += c));
|
|
136
|
+
process.stdin.on('end', async () => { try { await main(input); } catch (_) { /* ignore */ } process.exit(0); });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = { endsOnQuestionInline };
|
|
@@ -95,6 +95,16 @@ async function handle(data) {
|
|
|
95
95
|
const file = path.join(dir, `${sid}.txt`);
|
|
96
96
|
logCtx = { event, sid, dir, note: '' };
|
|
97
97
|
|
|
98
|
+
// HARNESS-CONTRACT CANARY (debug-only surface): if Claude Code stops supplying a field the
|
|
99
|
+
// keying depends on, every fix above silently degrades to its fallback. Record the gap so the
|
|
100
|
+
// stale-glyph audit surfaces a platform change as one log line instead of the next mystery.
|
|
101
|
+
const degraded = [];
|
|
102
|
+
if (!process.env.CLAUDE_PROJECT_DIR) degraded.push('CLAUDE_PROJECT_DIR');
|
|
103
|
+
if (!data.cwd) degraded.push('cwd');
|
|
104
|
+
if (!sid) degraded.push('session_id');
|
|
105
|
+
if (event === 'Stop' && !data.transcript_path) degraded.push('transcript_path');
|
|
106
|
+
if (degraded.length) logCtx.contract = degraded.join(',');
|
|
107
|
+
|
|
98
108
|
if (event === 'UserPromptSubmit') {
|
|
99
109
|
// Ensure the state dir exists, but NOT the file — the model's Write tool refuses to overwrite a
|
|
100
110
|
// file it hasn't read, so a pre-created empty file would make its first title write fail.
|
|
@@ -224,9 +234,10 @@ function titleLog(glyph, title, ring) {
|
|
|
224
234
|
: glyph === GLYPH.awaiting ? 'awaiting'
|
|
225
235
|
: glyph === GLYPH.idle ? 'idle' : 'other';
|
|
226
236
|
const diag = logCtx.diag ? ` ${logCtx.diag}` : '';
|
|
237
|
+
const contract = logCtx.contract ? ` contract=degraded(${logCtx.contract})` : '';
|
|
227
238
|
const line = `${new Date().toISOString()} ${logCtx.event.padEnd(16)} `
|
|
228
239
|
+ `${name.padEnd(8)} ring=${ring ? 1 : 0} note=${(logCtx.note || '-').padEnd(8)} `
|
|
229
|
-
+ `sid=${logCtx.sid} | ${title}${diag}\n`;
|
|
240
|
+
+ `sid=${logCtx.sid} | ${title}${diag}${contract}\n`;
|
|
230
241
|
const f = path.join(logCtx.dir, '_debug.log');
|
|
231
242
|
try { if (fs.statSync(f).size > 512 * 1024) fs.renameSync(f, `${f}.1`); } catch (_) { /* none yet */ }
|
|
232
243
|
fs.appendFileSync(f, line);
|
|
@@ -381,6 +392,14 @@ const SOLICIT_STRONG = [
|
|
|
381
392
|
/\bok(?:ay)? to proceed\b/i,
|
|
382
393
|
/^green-?light\b/i,
|
|
383
394
|
/^confirm\b/i,
|
|
395
|
+
// Harvested 2026-07-08 from real _debug.log flag-turn tails — the historical "flag masked the
|
|
396
|
+
// prose violation" shapes. All are unambiguous waiting-on-you closers:
|
|
397
|
+
/^tell me\b/i,
|
|
398
|
+
/\byour call\b/i,
|
|
399
|
+
/\bready when you are\b/i,
|
|
400
|
+
/\bstanding by\b/i,
|
|
401
|
+
/\bon your go\b/i,
|
|
402
|
+
/\bwhenever you say\b/i,
|
|
384
403
|
];
|
|
385
404
|
function solicitsReply(text) {
|
|
386
405
|
if (!text) return false;
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.0.203
|
|
4
|
+
- fix(arcade-beeps): Arcade beeps are now off by default in every install — turn them on per project with /enable-arcade-beeps, off again with /disable-arcade-beeps.
|
|
5
|
+
|
|
6
|
+
## v1.0.202
|
|
7
|
+
- feat(terminal-title): The 'awaiting your reply' tab indicator now also catches sign-off style endings like 'Ready when you are', and quietly logs a warning if a Claude Code update changes the data the hooks rely on.
|
|
8
|
+
|
|
3
9
|
## v1.0.201
|
|
4
10
|
- fix(install): Hook commands now survive cd'ing around your project — no more 'Cannot find module' errors after changing directories, and upgrades fix existing installs automatically.
|
|
5
11
|
- fix(terminal-title): The tab title no longer freezes when a session cd's into another project, and the 'awaiting your reply' half-circle now catches replies that ask for your go-ahead without a question mark.
|
|
@@ -144,9 +150,3 @@
|
|
|
144
150
|
## v1.0.154
|
|
145
151
|
- fix: enforce ls -t over find in gls command to avoid permission prompts
|
|
146
152
|
|
|
147
|
-
## v1.0.153
|
|
148
|
-
- style: left-align all table cells in docs info cards
|
|
149
|
-
|
|
150
|
-
## v1.0.152
|
|
151
|
-
- fix: warn against ! prefix workaround in inside-Claude message
|
|
152
|
-
|
package/bin/cli.js
CHANGED
|
@@ -165,7 +165,8 @@ if (process.argv.includes('--pull-updates')) {
|
|
|
165
165
|
// 2. mergeSettingsInto dedups by EXACT command string, so without this rewrite an upgrade
|
|
166
166
|
// would ADD the anchored template entry alongside the user's old relative one -> the
|
|
167
167
|
// hook runs twice per event.
|
|
168
|
-
//
|
|
168
|
+
// Any .claude/hooks/*.js relative command is rewritten — anchored resolution is identical at
|
|
169
|
+
// the project root and cd-proof everywhere else — while commands outside .claude/hooks are
|
|
169
170
|
// never touched.
|
|
170
171
|
function migrateLegacyHookCommands(userSettings) {
|
|
171
172
|
if (!userSettings || !userSettings.hooks) return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-autoconfig",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.203",
|
|
4
4
|
"description": "Intelligent, self-configuring setup for Claude Code. One command analyzes your project, configures Claude, and shows you what it did.",
|
|
5
5
|
"author": "ADAC 1001 <info@adac1001.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"cli"
|
|
23
23
|
],
|
|
24
24
|
"scripts": {
|
|
25
|
-
"test": "node test/box-alignment.test.js && node test/cli-install.test.js && node test/update-system.test.js && node test/plugin-system.test.js && node test/terminal-title.test.js && node test/update-summary.test.js && node test/changelog-gen.test.js",
|
|
25
|
+
"test": "node test/box-alignment.test.js && node test/cli-install.test.js && node test/update-system.test.js && node test/plugin-system.test.js && node test/terminal-title.test.js && node test/golden-endings.test.js && node test/live-twin-parity.test.js && node test/update-summary.test.js && node test/changelog-gen.test.js",
|
|
26
26
|
"pub": "npm whoami && npm publish",
|
|
27
27
|
"test:box": "node test/box-alignment.test.js",
|
|
28
28
|
"test:install": "node test/cli-install.test.js",
|