pingthings 0.2.0 → 0.3.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/LICENSE +22 -3
- package/README.md +12 -2
- package/bin/pingthings.js +2 -0
- package/package.json +1 -1
- package/src/cli/init.js +4 -2
- package/src/cli/install.js +6 -2
- package/src/cli/play.js +5 -0
- package/src/cli/select.js +2 -1
- package/src/cli/test-events.js +2 -9
- package/src/cli/uninstall.js +48 -0
- package/src/packs.js +7 -2
- package/src/player.js +7 -3
package/LICENSE
CHANGED
|
@@ -6,6 +6,25 @@
|
|
|
6
6
|
Everyone is permitted to copy and distribute verbatim copies
|
|
7
7
|
of this license document, but changing it is not allowed.
|
|
8
8
|
|
|
9
|
-
Audio assets are from
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
This project is licensed under GPL v2. Audio assets are sourced from
|
|
10
|
+
multiple open source projects under compatible licenses:
|
|
11
|
+
|
|
12
|
+
7kaa-soldiers: Seven Kingdoms: Ancient Adversaries
|
|
13
|
+
Copyright 1997,1998 Enlight Software Ltd. — GPL v2
|
|
14
|
+
|
|
15
|
+
wesnoth-combat: Battle for Wesnoth
|
|
16
|
+
Copyright Wesnoth contributors — GPL v2+
|
|
17
|
+
|
|
18
|
+
openarena-announcer: OpenArena
|
|
19
|
+
Copyright OpenArena contributors — GPL v2
|
|
20
|
+
|
|
21
|
+
freedoom-arsenal: Freedoom
|
|
22
|
+
Copyright Freedoom contributors — BSD-3-Clause
|
|
23
|
+
|
|
24
|
+
warzone2100-command: Warzone 2100
|
|
25
|
+
Copyright Warzone 2100 Project — GPL v2
|
|
26
|
+
|
|
27
|
+
0ad-civilizations: 0 A.D.
|
|
28
|
+
Copyright Wildfire Games — CC-BY-SA 3.0
|
|
29
|
+
Distributed as part of this aggregate work alongside GPL v2 code.
|
|
30
|
+
See packs/0ad-civilizations/manifest.json for full attribution.
|
package/README.md
CHANGED
|
@@ -126,10 +126,16 @@ For different sounds based on what Claude is doing, set up multiple hooks:
|
|
|
126
126
|
|---------|-------------|
|
|
127
127
|
| `pingthings play [sound] [--event type]` | Play a sound (random, specific, or event-based) |
|
|
128
128
|
| `pingthings list` | Show available sound packs |
|
|
129
|
+
| `pingthings select` | Interactive pack selector |
|
|
129
130
|
| `pingthings use <pack>` | Set the active sound pack |
|
|
130
131
|
| `pingthings preview <pack>` | Preview a random sound from a pack |
|
|
132
|
+
| `pingthings test-events [pack]` | Play all event sounds to hear each one |
|
|
133
|
+
| `pingthings theme [name]` | Apply a sound theme |
|
|
131
134
|
| `pingthings config [key] [val]` | Show or update configuration |
|
|
132
|
-
| `pingthings
|
|
135
|
+
| `pingthings init` | Set up Claude Code hooks automatically |
|
|
136
|
+
| `pingthings create <dir>` | Create a pack from audio files |
|
|
137
|
+
| `pingthings install <source>` | Install a pack from GitHub or local path |
|
|
138
|
+
| `pingthings uninstall <pack>` | Remove a user-installed pack |
|
|
133
139
|
|
|
134
140
|
## Configuration
|
|
135
141
|
|
|
@@ -139,13 +145,17 @@ Config lives at `~/.config/pingthings/config.json`:
|
|
|
139
145
|
{
|
|
140
146
|
"activePack": "7kaa-soldiers",
|
|
141
147
|
"mode": "random",
|
|
142
|
-
"specificSound": null
|
|
148
|
+
"specificSound": null,
|
|
149
|
+
"volume": 100,
|
|
150
|
+
"eventPacks": {}
|
|
143
151
|
}
|
|
144
152
|
```
|
|
145
153
|
|
|
146
154
|
- **activePack** — which sound pack to use
|
|
147
155
|
- **mode** — `"random"` (default), `"specific"`, or `"informational"`
|
|
148
156
|
- **specificSound** — sound name to always play when mode is `"specific"`
|
|
157
|
+
- **volume** — playback volume, 0-100 (default: 100)
|
|
158
|
+
- **eventPacks** — per-event pack overrides (e.g. `{"error": "freedoom-arsenal"}`)
|
|
149
159
|
|
|
150
160
|
Set values via CLI:
|
|
151
161
|
|
package/bin/pingthings.js
CHANGED
|
@@ -18,6 +18,7 @@ const commands = {
|
|
|
18
18
|
preview: () => import('../src/cli/preview.js'),
|
|
19
19
|
config: () => import('../src/cli/config.js'),
|
|
20
20
|
install: () => import('../src/cli/install.js'),
|
|
21
|
+
uninstall: () => import('../src/cli/uninstall.js'),
|
|
21
22
|
init: () => import('../src/cli/init.js'),
|
|
22
23
|
create: () => import('../src/cli/create.js'),
|
|
23
24
|
theme: () => import('../src/cli/theme.js'),
|
|
@@ -42,6 +43,7 @@ Commands:
|
|
|
42
43
|
init Set up Claude Code hooks automatically
|
|
43
44
|
create <dir> Create a new pack from a folder of audio files
|
|
44
45
|
install <source> Install a pack from GitHub or URL
|
|
46
|
+
uninstall <pack> Remove a user-installed pack
|
|
45
47
|
|
|
46
48
|
Options:
|
|
47
49
|
--help, -h Show this help message
|
package/package.json
CHANGED
package/src/cli/init.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createInterface } from 'node:readline';
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, renameSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
|
|
@@ -59,7 +59,9 @@ function readSettings() {
|
|
|
59
59
|
|
|
60
60
|
function writeSettings(settings) {
|
|
61
61
|
const path = getSettingsPath();
|
|
62
|
-
|
|
62
|
+
const tmpPath = path + '.tmp';
|
|
63
|
+
writeFileSync(tmpPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
64
|
+
renameSync(tmpPath, path);
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
function applyHooks(mode) {
|
package/src/cli/install.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync,
|
|
1
|
+
import { existsSync, mkdirSync, cpSync } from 'node:fs';
|
|
2
2
|
import { join, basename } from 'node:path';
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
4
|
import { getConfigDir } from '../config.js';
|
|
@@ -75,6 +75,10 @@ function installFromLocal(source) {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
const packName = basename(source);
|
|
78
|
+
if (!packName) {
|
|
79
|
+
console.error('Could not determine pack name from path.');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
78
82
|
const packsDir = join(getConfigDir(), 'packs');
|
|
79
83
|
const destDir = join(packsDir, packName);
|
|
80
84
|
|
|
@@ -87,7 +91,7 @@ function installFromLocal(source) {
|
|
|
87
91
|
mkdirSync(packsDir, { recursive: true });
|
|
88
92
|
|
|
89
93
|
try {
|
|
90
|
-
|
|
94
|
+
cpSync(source, destDir, { recursive: true });
|
|
91
95
|
} catch {
|
|
92
96
|
console.error('Failed to copy pack.');
|
|
93
97
|
process.exit(1);
|
package/src/cli/play.js
CHANGED
package/src/cli/select.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createInterface } from 'node:readline';
|
|
|
2
2
|
import { readConfig, writeConfig } from '../config.js';
|
|
3
3
|
import { listPacks, getPackSounds } from '../packs.js';
|
|
4
4
|
import { playSound } from '../player.js';
|
|
5
|
+
import { basename } from 'node:path';
|
|
5
6
|
|
|
6
7
|
function showHelp() {
|
|
7
8
|
console.log(`
|
|
@@ -68,7 +69,7 @@ export default async function select(args) {
|
|
|
68
69
|
const sounds = getPackSounds(chosen.name);
|
|
69
70
|
if (sounds.length > 0) {
|
|
70
71
|
const sample = sounds[Math.floor(Math.random() * sounds.length)];
|
|
71
|
-
playSound(sample);
|
|
72
|
+
playSound(sample, config.volume);
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
console.log(`\nActive pack set to: ${chosen.name}`);
|
package/src/cli/test-events.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readConfig, VALID_EVENTS } from '../config.js';
|
|
2
2
|
import { getEventSounds, getPackSounds, pickRandom, resolvePack } from '../packs.js';
|
|
3
|
-
import {
|
|
3
|
+
import { playSoundSync } from '../player.js';
|
|
4
4
|
import { basename } from 'node:path';
|
|
5
5
|
|
|
6
6
|
function showHelp() {
|
|
@@ -17,10 +17,6 @@ Events played: done, permission, complete, error, blocked
|
|
|
17
17
|
`);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
function sleep(ms) {
|
|
21
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
22
|
-
}
|
|
23
|
-
|
|
24
20
|
export default async function testEvents(args) {
|
|
25
21
|
if (args.includes('--help') || args.includes('-h')) {
|
|
26
22
|
showHelp();
|
|
@@ -48,10 +44,7 @@ export default async function testEvents(args) {
|
|
|
48
44
|
|
|
49
45
|
const sound = pickRandom(sounds);
|
|
50
46
|
console.log(` ${event.padEnd(12)} ${basename(sound)}`);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// Wait between sounds so they don't overlap
|
|
54
|
-
await sleep(1500);
|
|
47
|
+
playSoundSync(sound, config.volume);
|
|
55
48
|
}
|
|
56
49
|
|
|
57
50
|
console.log('\nDone.\n');
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { existsSync, rmSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { getConfigDir, readConfig, writeConfig } from '../config.js';
|
|
4
|
+
|
|
5
|
+
function showHelp() {
|
|
6
|
+
console.log(`
|
|
7
|
+
Usage: pingthings uninstall <pack>
|
|
8
|
+
|
|
9
|
+
Remove a user-installed sound pack.
|
|
10
|
+
|
|
11
|
+
Built-in packs cannot be uninstalled. Only packs in
|
|
12
|
+
~/.config/pingthings/packs/ can be removed.
|
|
13
|
+
|
|
14
|
+
Arguments:
|
|
15
|
+
pack Name of the pack to remove
|
|
16
|
+
`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default function uninstall(args) {
|
|
20
|
+
const packName = args[0];
|
|
21
|
+
|
|
22
|
+
if (!packName || packName === '--help' || packName === '-h') {
|
|
23
|
+
showHelp();
|
|
24
|
+
if (!packName) process.exit(1);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const userPackDir = join(getConfigDir(), 'packs', packName);
|
|
29
|
+
|
|
30
|
+
if (!existsSync(userPackDir)) {
|
|
31
|
+
console.error(`Pack not found in user packs: ${packName}`);
|
|
32
|
+
console.error('Only user-installed packs can be uninstalled.');
|
|
33
|
+
console.error('Run "pingthings list" to see all packs.');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
rmSync(userPackDir, { recursive: true });
|
|
38
|
+
|
|
39
|
+
// If this was the active pack, reset to default
|
|
40
|
+
const config = readConfig();
|
|
41
|
+
if (config.activePack === packName) {
|
|
42
|
+
config.activePack = '7kaa-soldiers';
|
|
43
|
+
writeConfig(config);
|
|
44
|
+
console.log(`Active pack reset to: 7kaa-soldiers`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(`Uninstalled: ${packName}`);
|
|
48
|
+
}
|
package/src/packs.js
CHANGED
|
@@ -16,8 +16,13 @@ function readManifest(packDir) {
|
|
|
16
16
|
const manifestPath = join(packDir, 'manifest.json');
|
|
17
17
|
if (!existsSync(manifestPath)) return null;
|
|
18
18
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
20
|
+
if (!manifest.name) {
|
|
21
|
+
console.error(`Warning: manifest.json in ${packDir} is missing "name" field`);
|
|
22
|
+
}
|
|
23
|
+
return manifest;
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error(`Warning: Failed to parse ${manifestPath}: ${err.message}`);
|
|
21
26
|
return null;
|
|
22
27
|
}
|
|
23
28
|
}
|
package/src/player.js
CHANGED
|
@@ -81,8 +81,12 @@ export function playSoundSync(filePath, volume) {
|
|
|
81
81
|
const args = buildArgs(cmd, filePath, volume);
|
|
82
82
|
|
|
83
83
|
try {
|
|
84
|
-
execFileSync(cmd, args, { stdio: 'ignore', timeout:
|
|
85
|
-
} catch {
|
|
86
|
-
|
|
84
|
+
execFileSync(cmd, args, { stdio: ['ignore', 'ignore', 'pipe'], timeout: 5000 });
|
|
85
|
+
} catch (err) {
|
|
86
|
+
if (err.killed) {
|
|
87
|
+
console.error(`Warning: Sound playback timed out for ${filePath}`);
|
|
88
|
+
} else if (err.stderr?.length) {
|
|
89
|
+
console.error(`Warning: Playback error: ${err.stderr.toString().trim()}`);
|
|
90
|
+
}
|
|
87
91
|
}
|
|
88
92
|
}
|