codevator 0.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/dist/bin.js +331 -0
- package/package.json +33 -0
- package/sounds/ambient.mp3 +0 -0
- package/sounds/elevator.mp3 +0 -0
- package/sounds/minimal.mp3 +0 -0
- package/sounds/retro.mp3 +0 -0
- package/sounds/typewriter.mp3 +0 -0
package/dist/bin.js
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/config.ts
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import os from "os";
|
|
7
|
+
var MODES = [
|
|
8
|
+
"elevator",
|
|
9
|
+
"typewriter",
|
|
10
|
+
"ambient",
|
|
11
|
+
"retro",
|
|
12
|
+
"minimal"
|
|
13
|
+
];
|
|
14
|
+
var DEFAULT_CONFIG = {
|
|
15
|
+
mode: "elevator",
|
|
16
|
+
volume: 70,
|
|
17
|
+
enabled: true
|
|
18
|
+
};
|
|
19
|
+
function getConfigDir() {
|
|
20
|
+
if (process.env.CODEVATOR_HOME) return process.env.CODEVATOR_HOME;
|
|
21
|
+
return path.join(os.homedir(), ".codevator");
|
|
22
|
+
}
|
|
23
|
+
function getConfigPath() {
|
|
24
|
+
return path.join(getConfigDir(), "config.json");
|
|
25
|
+
}
|
|
26
|
+
function getConfig() {
|
|
27
|
+
try {
|
|
28
|
+
const raw = fs.readFileSync(getConfigPath(), "utf-8");
|
|
29
|
+
return JSON.parse(raw);
|
|
30
|
+
} catch {
|
|
31
|
+
return { ...DEFAULT_CONFIG };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function setConfig(partial) {
|
|
35
|
+
const current = getConfig();
|
|
36
|
+
const merged = { ...current, ...partial };
|
|
37
|
+
const dir = getConfigDir();
|
|
38
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
39
|
+
fs.writeFileSync(getConfigPath(), JSON.stringify(merged, null, 2));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/player.ts
|
|
43
|
+
import { execSync, spawn } from "child_process";
|
|
44
|
+
import fs2 from "fs";
|
|
45
|
+
import path2 from "path";
|
|
46
|
+
import { fileURLToPath } from "url";
|
|
47
|
+
var __dirname = path2.dirname(fileURLToPath(import.meta.url));
|
|
48
|
+
function getPidFile() {
|
|
49
|
+
return path2.join(getConfigDir(), "player.pid");
|
|
50
|
+
}
|
|
51
|
+
function detectPlayer() {
|
|
52
|
+
const platform = process.platform;
|
|
53
|
+
if (platform === "darwin") return "afplay";
|
|
54
|
+
try {
|
|
55
|
+
execSync("which paplay", { stdio: "ignore" });
|
|
56
|
+
return "paplay";
|
|
57
|
+
} catch {
|
|
58
|
+
return "aplay";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function buildArgs(player, volume, filePath) {
|
|
62
|
+
if (player === "afplay") {
|
|
63
|
+
return ["-v", String(volume / 100), filePath];
|
|
64
|
+
}
|
|
65
|
+
return [filePath];
|
|
66
|
+
}
|
|
67
|
+
function getSoundFile(mode) {
|
|
68
|
+
const soundsDir = path2.join(__dirname, "..", "sounds");
|
|
69
|
+
return path2.join(soundsDir, `${mode}.mp3`);
|
|
70
|
+
}
|
|
71
|
+
function isPlaying() {
|
|
72
|
+
const pidFile = getPidFile();
|
|
73
|
+
try {
|
|
74
|
+
const pid = parseInt(fs2.readFileSync(pidFile, "utf-8").trim(), 10);
|
|
75
|
+
process.kill(pid, 0);
|
|
76
|
+
return true;
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function play() {
|
|
82
|
+
if (isPlaying()) return;
|
|
83
|
+
const config = getConfig();
|
|
84
|
+
if (!config.enabled) return;
|
|
85
|
+
const player = detectPlayer();
|
|
86
|
+
const soundFile = getSoundFile(config.mode);
|
|
87
|
+
if (!fs2.existsSync(soundFile)) {
|
|
88
|
+
console.error(`Sound file not found: ${soundFile}`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const args2 = buildArgs(player, config.volume, soundFile);
|
|
92
|
+
const child = spawn("sh", ["-c", `while true; do ${player} ${args2.map((a) => `"${a}"`).join(" ")}; done`], {
|
|
93
|
+
detached: true,
|
|
94
|
+
stdio: "ignore"
|
|
95
|
+
});
|
|
96
|
+
child.unref();
|
|
97
|
+
const configDir = getConfigDir();
|
|
98
|
+
fs2.mkdirSync(configDir, { recursive: true });
|
|
99
|
+
fs2.writeFileSync(getPidFile(), String(child.pid));
|
|
100
|
+
}
|
|
101
|
+
function stop() {
|
|
102
|
+
const pidFile = getPidFile();
|
|
103
|
+
try {
|
|
104
|
+
const pid = parseInt(fs2.readFileSync(pidFile, "utf-8").trim(), 10);
|
|
105
|
+
process.kill(-pid, "SIGTERM");
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
fs2.unlinkSync(pidFile);
|
|
110
|
+
} catch {
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/setup.ts
|
|
115
|
+
import fs3 from "fs";
|
|
116
|
+
import path3 from "path";
|
|
117
|
+
import os2 from "os";
|
|
118
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
119
|
+
var __dirname2 = path3.dirname(fileURLToPath2(import.meta.url));
|
|
120
|
+
function getClaudeDir() {
|
|
121
|
+
if (process.env.CODEVATOR_CLAUDE_HOME) return process.env.CODEVATOR_CLAUDE_HOME;
|
|
122
|
+
return path3.join(os2.homedir(), ".claude");
|
|
123
|
+
}
|
|
124
|
+
function getSettingsPath() {
|
|
125
|
+
return path3.join(getClaudeDir(), "settings.json");
|
|
126
|
+
}
|
|
127
|
+
function readSettings() {
|
|
128
|
+
try {
|
|
129
|
+
return JSON.parse(fs3.readFileSync(getSettingsPath(), "utf-8"));
|
|
130
|
+
} catch {
|
|
131
|
+
return {};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function writeSettings(settings) {
|
|
135
|
+
const dir = path3.dirname(getSettingsPath());
|
|
136
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
137
|
+
fs3.writeFileSync(getSettingsPath(), JSON.stringify(settings, null, 2));
|
|
138
|
+
}
|
|
139
|
+
var CODEVATOR_HOOKS = {
|
|
140
|
+
PreToolUse: {
|
|
141
|
+
matcher: "",
|
|
142
|
+
hooks: [{ type: "command", command: "codevator play", async: true }]
|
|
143
|
+
},
|
|
144
|
+
Stop: {
|
|
145
|
+
matcher: "",
|
|
146
|
+
hooks: [{ type: "command", command: "codevator stop" }]
|
|
147
|
+
},
|
|
148
|
+
Notification: {
|
|
149
|
+
matcher: "permission_prompt|idle_prompt",
|
|
150
|
+
hooks: [{ type: "command", command: "codevator stop" }]
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
function isCodevatorHook(entry) {
|
|
154
|
+
return entry?.hooks?.some(
|
|
155
|
+
(h) => typeof h.command === "string" && h.command.startsWith("codevator")
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
function setupHooks() {
|
|
159
|
+
const settings = readSettings();
|
|
160
|
+
if (!settings.hooks) settings.hooks = {};
|
|
161
|
+
for (const [event, hookEntry] of Object.entries(CODEVATOR_HOOKS)) {
|
|
162
|
+
if (!settings.hooks[event]) settings.hooks[event] = [];
|
|
163
|
+
settings.hooks[event] = settings.hooks[event].filter(
|
|
164
|
+
(e) => !isCodevatorHook(e)
|
|
165
|
+
);
|
|
166
|
+
settings.hooks[event].push(hookEntry);
|
|
167
|
+
}
|
|
168
|
+
writeSettings(settings);
|
|
169
|
+
installSkill();
|
|
170
|
+
}
|
|
171
|
+
function installSkill() {
|
|
172
|
+
const skillDir = path3.join(getClaudeDir(), "skills");
|
|
173
|
+
fs3.mkdirSync(skillDir, { recursive: true });
|
|
174
|
+
const skillSrc = path3.join(__dirname2, "..", "skill", "codevator.md");
|
|
175
|
+
const skillDest = path3.join(skillDir, "codevator.md");
|
|
176
|
+
try {
|
|
177
|
+
fs3.copyFileSync(skillSrc, skillDest);
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function removeSkill() {
|
|
182
|
+
const skillPath = path3.join(getClaudeDir(), "skills", "codevator.md");
|
|
183
|
+
try {
|
|
184
|
+
fs3.unlinkSync(skillPath);
|
|
185
|
+
} catch {
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function removeHooks() {
|
|
189
|
+
const settings = readSettings();
|
|
190
|
+
if (!settings.hooks) return;
|
|
191
|
+
for (const event of Object.keys(CODEVATOR_HOOKS)) {
|
|
192
|
+
if (!settings.hooks[event]) continue;
|
|
193
|
+
settings.hooks[event] = settings.hooks[event].filter(
|
|
194
|
+
(e) => !isCodevatorHook(e)
|
|
195
|
+
);
|
|
196
|
+
if (settings.hooks[event].length === 0) {
|
|
197
|
+
delete settings.hooks[event];
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
201
|
+
delete settings.hooks;
|
|
202
|
+
}
|
|
203
|
+
writeSettings(settings);
|
|
204
|
+
removeSkill();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/commands.ts
|
|
208
|
+
var VALID_COMMANDS = [
|
|
209
|
+
"setup",
|
|
210
|
+
"mode",
|
|
211
|
+
"on",
|
|
212
|
+
"off",
|
|
213
|
+
"volume",
|
|
214
|
+
"status",
|
|
215
|
+
"play",
|
|
216
|
+
"stop",
|
|
217
|
+
"uninstall",
|
|
218
|
+
"help"
|
|
219
|
+
];
|
|
220
|
+
function parseArgs(argv) {
|
|
221
|
+
const [cmd, ...args2] = argv;
|
|
222
|
+
if (!cmd || !VALID_COMMANDS.includes(cmd)) {
|
|
223
|
+
return { command: "help", args: [] };
|
|
224
|
+
}
|
|
225
|
+
return { command: cmd, args: args2 };
|
|
226
|
+
}
|
|
227
|
+
async function run(command2, args2) {
|
|
228
|
+
switch (command2) {
|
|
229
|
+
case "setup":
|
|
230
|
+
return runSetup();
|
|
231
|
+
case "mode":
|
|
232
|
+
return runMode(args2[0]);
|
|
233
|
+
case "on":
|
|
234
|
+
return runOn();
|
|
235
|
+
case "off":
|
|
236
|
+
return runOff();
|
|
237
|
+
case "volume":
|
|
238
|
+
return runVolume(args2[0]);
|
|
239
|
+
case "status":
|
|
240
|
+
return runStatus();
|
|
241
|
+
case "play":
|
|
242
|
+
return runPlay();
|
|
243
|
+
case "stop":
|
|
244
|
+
return runStop();
|
|
245
|
+
case "uninstall":
|
|
246
|
+
return runUninstall();
|
|
247
|
+
case "help":
|
|
248
|
+
return runHelp();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function runSetup() {
|
|
252
|
+
setupHooks();
|
|
253
|
+
console.log("\u{1F6D7} Codevator installed!");
|
|
254
|
+
console.log(" Hooks configured in ~/.claude/settings.json");
|
|
255
|
+
console.log(" Default mode: elevator");
|
|
256
|
+
console.log(" Run 'codevator mode <name>' to change sounds");
|
|
257
|
+
}
|
|
258
|
+
function runMode(mode) {
|
|
259
|
+
if (!mode || !MODES.includes(mode)) {
|
|
260
|
+
console.log(`Available modes: ${MODES.join(", ")}`);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
setConfig({ mode });
|
|
264
|
+
if (isPlaying()) {
|
|
265
|
+
stop();
|
|
266
|
+
play();
|
|
267
|
+
}
|
|
268
|
+
console.log(`\u{1F6D7} Mode set to: ${mode}`);
|
|
269
|
+
}
|
|
270
|
+
function runOn() {
|
|
271
|
+
setConfig({ enabled: true });
|
|
272
|
+
console.log("\u{1F6D7} Sounds enabled");
|
|
273
|
+
}
|
|
274
|
+
function runOff() {
|
|
275
|
+
stop();
|
|
276
|
+
setConfig({ enabled: false });
|
|
277
|
+
console.log("\u{1F6D7} Sounds disabled");
|
|
278
|
+
}
|
|
279
|
+
function runVolume(level) {
|
|
280
|
+
const vol = parseInt(level ?? "", 10);
|
|
281
|
+
if (isNaN(vol) || vol < 0 || vol > 100) {
|
|
282
|
+
console.log("Usage: codevator volume <0-100>");
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
setConfig({ volume: vol });
|
|
286
|
+
if (isPlaying()) {
|
|
287
|
+
stop();
|
|
288
|
+
play();
|
|
289
|
+
}
|
|
290
|
+
console.log(`\u{1F6D7} Volume set to: ${vol}%`);
|
|
291
|
+
}
|
|
292
|
+
function runStatus() {
|
|
293
|
+
const config = getConfig();
|
|
294
|
+
const playing = isPlaying();
|
|
295
|
+
console.log("\u{1F6D7} Codevator Status");
|
|
296
|
+
console.log(` Mode: ${config.mode}`);
|
|
297
|
+
console.log(` Volume: ${config.volume}%`);
|
|
298
|
+
console.log(` Enabled: ${config.enabled ? "yes" : "no"}`);
|
|
299
|
+
console.log(` Playing: ${playing ? "yes" : "no"}`);
|
|
300
|
+
}
|
|
301
|
+
function runPlay() {
|
|
302
|
+
play();
|
|
303
|
+
}
|
|
304
|
+
function runStop() {
|
|
305
|
+
stop();
|
|
306
|
+
}
|
|
307
|
+
function runUninstall() {
|
|
308
|
+
stop();
|
|
309
|
+
removeHooks();
|
|
310
|
+
console.log("\u{1F6D7} Codevator uninstalled");
|
|
311
|
+
console.log(" Hooks removed from ~/.claude/settings.json");
|
|
312
|
+
console.log(" Config remains at ~/.codevator/ (delete manually if desired)");
|
|
313
|
+
}
|
|
314
|
+
function runHelp() {
|
|
315
|
+
console.log(`\u{1F6D7} Codevator \u2014 Elevator music for your AI coding agent
|
|
316
|
+
|
|
317
|
+
Usage: codevator <command>
|
|
318
|
+
|
|
319
|
+
Commands:
|
|
320
|
+
setup Install hooks into Claude Code
|
|
321
|
+
mode <name> Set sound mode (elevator|typewriter|ambient|retro|minimal)
|
|
322
|
+
on Enable sounds
|
|
323
|
+
off Disable sounds
|
|
324
|
+
volume <0-100> Set volume level
|
|
325
|
+
status Show current settings
|
|
326
|
+
uninstall Remove hooks from Claude Code`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/bin.ts
|
|
330
|
+
var { command, args } = parseArgs(process.argv.slice(2));
|
|
331
|
+
run(command, args);
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "codevator",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Elevator music for your AI coding agent",
|
|
6
|
+
"bin": {
|
|
7
|
+
"codevator": "./dist/bin.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"sounds"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"dev": "tsup --watch",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"claude",
|
|
21
|
+
"ai",
|
|
22
|
+
"music",
|
|
23
|
+
"elevator",
|
|
24
|
+
"coding",
|
|
25
|
+
"cli"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"tsup": "^8.5.1",
|
|
30
|
+
"typescript": "^5.9.3",
|
|
31
|
+
"vitest": "^4.0.18"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/sounds/retro.mp3
ADDED
|
Binary file
|
|
Binary file
|