oopsx 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -71
- package/bin/merr.js +128 -6
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
# oopsx
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Wrap any command with `oopsx`. If it fails (non-zero exit code), you hear a sound. If it passes, nothing happens. Your original output and exit code are preserved.
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
$ oopsx git puhs origin main
|
|
9
|
-
fatal: 'puhs' is not a git command.
|
|
10
|
-
🔊 *plays sound*
|
|
11
|
-
```
|
|
3
|
+
Play a meme sound when your terminal command fails. Comes with a default sound out of the box.
|
|
12
4
|
|
|
13
5
|
## Install
|
|
14
6
|
|
|
@@ -16,76 +8,18 @@ fatal: 'puhs' is not a git command.
|
|
|
16
8
|
npm install -g oopsx
|
|
17
9
|
```
|
|
18
10
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
npx oopsx ls /nonexistent/path
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Or via curl (macOS / Linux):
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
curl -fsSL https://raw.githubusercontent.com/devesh760/oopsx/main/install.sh | bash
|
|
29
|
-
```
|
|
11
|
+
Restart your terminal. Every failed command now plays a sound.
|
|
30
12
|
|
|
31
|
-
##
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
oopsx <command>
|
|
35
|
-
```
|
|
13
|
+
## Custom sound
|
|
36
14
|
|
|
37
15
|
```bash
|
|
38
|
-
oopsx
|
|
39
|
-
oopsx --sound ~/bruh.mp3 npm test # use a custom sound
|
|
40
|
-
oopsx --url https://x.com/f.mp3 make build # download + cache a sound
|
|
41
|
-
oopsx --volume 50 python script.py # set volume (0-100)
|
|
42
|
-
oopsx --sound ~/oof.mp3 --set-default echo hi # save as default
|
|
43
|
-
oopsx --once cargo build # play only once per session
|
|
16
|
+
oopsx --sound ~/bruh.mp3 --set-default echo hi
|
|
44
17
|
```
|
|
45
18
|
|
|
46
|
-
## Options
|
|
47
|
-
|
|
48
|
-
| Option | Description |
|
|
49
|
-
| ------------------- | --------------------------------------------- |
|
|
50
|
-
| `--sound <path>` | Use a local `.mp3` or `.wav` file |
|
|
51
|
-
| `--url <url>` | Download and cache a sound from a URL |
|
|
52
|
-
| `--volume <0-100>` | Playback volume (default: `80`) |
|
|
53
|
-
| `--set-default` | Save the resolved sound as the new default |
|
|
54
|
-
| `--once` | Play sound only once per shell session |
|
|
55
|
-
|
|
56
|
-
## How it works
|
|
57
|
-
|
|
58
|
-
1. Runs your command with inherited stdio — you see all output as normal.
|
|
59
|
-
2. If exit code is `0`, does nothing.
|
|
60
|
-
3. If exit code is non-zero, plays a sound.
|
|
61
|
-
4. Exits with the **same exit code** as your command.
|
|
62
|
-
|
|
63
|
-
A default sound is bundled out of the box. You can replace it with `--sound <file> --set-default`.
|
|
64
|
-
|
|
65
|
-
Sound priority: `--sound` > `--url` > `~/.merr/default.mp3`
|
|
66
|
-
|
|
67
|
-
## Config
|
|
68
|
-
|
|
69
|
-
Stored in `~/.merr/`:
|
|
70
|
-
|
|
71
|
-
```
|
|
72
|
-
~/.merr/
|
|
73
|
-
├── config.json # volume
|
|
74
|
-
├── default.mp3 # default sound
|
|
75
|
-
└── sounds/ # cached URL downloads
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## Platform support
|
|
79
|
-
|
|
80
|
-
| Platform | Backend |
|
|
81
|
-
| -------- | -------------------- |
|
|
82
|
-
| macOS | `afplay` (built-in) |
|
|
83
|
-
| Linux | `aplay` / `mpg123` |
|
|
84
|
-
| Windows | PowerShell fallback |
|
|
85
|
-
|
|
86
19
|
## Uninstall
|
|
87
20
|
|
|
88
21
|
```bash
|
|
22
|
+
oopsx remove
|
|
89
23
|
npm uninstall -g oopsx
|
|
90
24
|
rm -rf ~/.merr
|
|
91
25
|
```
|
package/bin/merr.js
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { existsSync, readFileSync, appendFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
3
6
|
import { Command } from "commander";
|
|
4
7
|
import { runCommand } from "../src/runner.js";
|
|
5
8
|
import { resolveSound, playAudio, setDefault, setVolume } from "../src/sound.js";
|
|
6
9
|
import { readConfig } from "../src/config.js";
|
|
7
10
|
|
|
11
|
+
const HOOK_MARKER = "# oopsx shell hook";
|
|
12
|
+
|
|
13
|
+
const getShellRc = () => {
|
|
14
|
+
const shell = (process.env.SHELL || "").toLowerCase();
|
|
15
|
+
if (shell.endsWith("zsh")) return { file: join(homedir(), ".zshrc"), type: "zsh" };
|
|
16
|
+
if (shell.endsWith("bash")) return { file: join(homedir(), ".bashrc"), type: "bash" };
|
|
17
|
+
return null;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const hookSnippet = (type) => {
|
|
21
|
+
if (type === "zsh") {
|
|
22
|
+
return `
|
|
23
|
+
${HOOK_MARKER}
|
|
24
|
+
eval "$(oopsx init zsh)"`;
|
|
25
|
+
}
|
|
26
|
+
return `
|
|
27
|
+
${HOOK_MARKER}
|
|
28
|
+
eval "$(oopsx init bash)"`;
|
|
29
|
+
};
|
|
30
|
+
|
|
8
31
|
const SESSION_KEY = `OOPSX_PLAYED_${process.ppid}`;
|
|
9
32
|
|
|
10
33
|
const program = new Command();
|
|
@@ -12,8 +35,111 @@ const program = new Command();
|
|
|
12
35
|
program
|
|
13
36
|
.name("oopsx")
|
|
14
37
|
.description("Play a meme sound when your terminal command fails")
|
|
15
|
-
.version("1.0.0")
|
|
16
|
-
|
|
38
|
+
.version("1.0.0");
|
|
39
|
+
|
|
40
|
+
// --- `oopsx init <shell>` — output shell hook snippet ---
|
|
41
|
+
program
|
|
42
|
+
.command("init <shell>")
|
|
43
|
+
.description("Print shell hook to auto-play sound on errors (zsh or bash)")
|
|
44
|
+
.action((shell) => {
|
|
45
|
+
const normalized = shell.toLowerCase().trim();
|
|
46
|
+
|
|
47
|
+
if (normalized === "zsh") {
|
|
48
|
+
console.log(`
|
|
49
|
+
__oopsx_precmd() {
|
|
50
|
+
local exit_code=$?
|
|
51
|
+
[[ $exit_code -ne 0 ]] && oopsx play &>/dev/null &
|
|
52
|
+
return $exit_code
|
|
53
|
+
}
|
|
54
|
+
precmd_functions+=(__oopsx_precmd)
|
|
55
|
+
`.trim());
|
|
56
|
+
} else if (normalized === "bash") {
|
|
57
|
+
console.log(`
|
|
58
|
+
__oopsx_prompt_command() {
|
|
59
|
+
local exit_code=$?
|
|
60
|
+
[[ $exit_code -ne 0 ]] && oopsx play &>/dev/null &
|
|
61
|
+
return $exit_code
|
|
62
|
+
}
|
|
63
|
+
PROMPT_COMMAND="__oopsx_prompt_command;\${PROMPT_COMMAND}"
|
|
64
|
+
`.trim());
|
|
65
|
+
} else {
|
|
66
|
+
console.error(`Unsupported shell: ${shell}. Use "zsh" or "bash".`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// --- `oopsx setup` — auto-add hook to shell rc file ---
|
|
72
|
+
program
|
|
73
|
+
.command("setup")
|
|
74
|
+
.description("Automatically add the shell hook to your .zshrc or .bashrc")
|
|
75
|
+
.action(() => {
|
|
76
|
+
const rc = getShellRc();
|
|
77
|
+
if (!rc) {
|
|
78
|
+
console.error("Could not detect your shell. Manually add: eval \"$(oopsx init zsh)\" to your rc file.");
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (existsSync(rc.file)) {
|
|
83
|
+
const contents = readFileSync(rc.file, "utf-8");
|
|
84
|
+
if (contents.includes(HOOK_MARKER)) {
|
|
85
|
+
console.log(`Already set up in ${rc.file}`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
appendFileSync(rc.file, hookSnippet(rc.type), "utf-8");
|
|
91
|
+
console.log(`Added oopsx hook to ${rc.file}`);
|
|
92
|
+
console.log("Restart your terminal or run: source " + rc.file);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// --- `oopsx remove` — remove hook from shell rc file ---
|
|
96
|
+
program
|
|
97
|
+
.command("remove")
|
|
98
|
+
.description("Remove the shell hook from your .zshrc or .bashrc")
|
|
99
|
+
.action(() => {
|
|
100
|
+
const rc = getShellRc();
|
|
101
|
+
if (!rc || !existsSync(rc.file)) {
|
|
102
|
+
console.log("Nothing to remove.");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const contents = readFileSync(rc.file, "utf-8");
|
|
107
|
+
if (!contents.includes(HOOK_MARKER)) {
|
|
108
|
+
console.log("No oopsx hook found in " + rc.file);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const cleaned = contents
|
|
113
|
+
.split("\n")
|
|
114
|
+
.filter((line) => !line.includes("oopsx") && line !== HOOK_MARKER)
|
|
115
|
+
.join("\n");
|
|
116
|
+
|
|
117
|
+
writeFileSync(rc.file, cleaned, "utf-8");
|
|
118
|
+
console.log(`Removed oopsx hook from ${rc.file}`);
|
|
119
|
+
console.log("Restart your terminal or run: source " + rc.file);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// --- `oopsx play` — just play the sound (used by shell hook) ---
|
|
123
|
+
program
|
|
124
|
+
.command("play")
|
|
125
|
+
.description("Play the error sound (used internally by the shell hook)")
|
|
126
|
+
.action(async () => {
|
|
127
|
+
const config = readConfig();
|
|
128
|
+
const soundFile = await resolveSound({});
|
|
129
|
+
|
|
130
|
+
if (!soundFile) process.exit(0);
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
await playAudio(soundFile, config.volume ?? 80);
|
|
134
|
+
} catch {
|
|
135
|
+
// silent — this runs in background, don't pollute the terminal
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// --- `oopsx run <command...>` — wrap a single command (original behavior) ---
|
|
140
|
+
program
|
|
141
|
+
.command("run <command...>", { isDefault: true })
|
|
142
|
+
.description("Execute a command and play sound on failure")
|
|
17
143
|
.option("--sound <path>", "Use a local sound file (mp3/wav)")
|
|
18
144
|
.option("--url <url>", "Download and cache a sound from a URL")
|
|
19
145
|
.option("--volume <number>", "Playback volume 0–100", parseInt)
|
|
@@ -24,7 +150,6 @@ program
|
|
|
24
150
|
const config = readConfig();
|
|
25
151
|
const volume = options.volume ?? config.volume ?? 80;
|
|
26
152
|
|
|
27
|
-
// Persist volume if explicitly provided
|
|
28
153
|
if (options.volume !== undefined) {
|
|
29
154
|
setVolume(options.volume);
|
|
30
155
|
}
|
|
@@ -35,7 +160,6 @@ program
|
|
|
35
160
|
process.exit(0);
|
|
36
161
|
}
|
|
37
162
|
|
|
38
|
-
// --once: skip if already played in this shell session
|
|
39
163
|
if (options.once && process.env[SESSION_KEY]) {
|
|
40
164
|
process.exit(exitCode);
|
|
41
165
|
}
|
|
@@ -46,7 +170,6 @@ program
|
|
|
46
170
|
process.exit(exitCode);
|
|
47
171
|
}
|
|
48
172
|
|
|
49
|
-
// --set-default: persist the resolved sound
|
|
50
173
|
if (options.setDefault) {
|
|
51
174
|
setDefault(soundFile);
|
|
52
175
|
}
|
|
@@ -57,7 +180,6 @@ program
|
|
|
57
180
|
console.error(`Sound playback failed: ${err.message}`);
|
|
58
181
|
}
|
|
59
182
|
|
|
60
|
-
// Mark as played for --once (only useful if parent reads env, but we set it anyway)
|
|
61
183
|
if (options.once) {
|
|
62
184
|
process.env[SESSION_KEY] = "1";
|
|
63
185
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oopsx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Play a meme sound when your terminal command fails",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -32,6 +32,9 @@
|
|
|
32
32
|
"bugs": {
|
|
33
33
|
"url": "https://github.com/devesh760/oopsx/issues"
|
|
34
34
|
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"postinstall": "node bin/merr.js setup"
|
|
37
|
+
},
|
|
35
38
|
"dependencies": {
|
|
36
39
|
"commander": "^12.1.0",
|
|
37
40
|
"play-sound": "^1.1.6"
|