opencode-interrupt-plugin 0.4.32 → 0.4.34
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/config.d.ts +2 -0
- package/dist/config.js +20 -0
- package/dist/index.js +38 -20
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface PluginConfig {
|
|
|
10
10
|
tts?: boolean;
|
|
11
11
|
ttsVoice?: string;
|
|
12
12
|
ttsRate?: string;
|
|
13
|
+
whisperModel?: string;
|
|
13
14
|
}
|
|
14
15
|
export interface ResolvedConfig {
|
|
15
16
|
licenseKey?: string;
|
|
@@ -25,6 +26,7 @@ export interface ResolvedConfig {
|
|
|
25
26
|
tts: boolean;
|
|
26
27
|
ttsVoice: string;
|
|
27
28
|
ttsRate: string;
|
|
29
|
+
whisperModel: string;
|
|
28
30
|
}
|
|
29
31
|
export declare const FREE_DEFAULTS: ResolvedConfig;
|
|
30
32
|
export declare const SENSITIVITY_PRESETS: Record<string, Partial<ResolvedConfig>>;
|
package/dist/config.js
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
const DEFAULT_TTS_VOICE = "en-US-AvaNeural";
|
|
2
2
|
const DEFAULT_TTS_RATE = "+25%";
|
|
3
|
+
const MODEL_NAMES = {
|
|
4
|
+
base: "ggml-base.bin",
|
|
5
|
+
small: "ggml-small.bin",
|
|
6
|
+
medium: "ggml-medium.bin",
|
|
7
|
+
large: "ggml-large-v3.bin",
|
|
8
|
+
};
|
|
9
|
+
function resolveModelPath(model) {
|
|
10
|
+
const home = process.env.HOME || "/tmp";
|
|
11
|
+
const envModel = process.env.WHISPER_MODEL;
|
|
12
|
+
if (envModel)
|
|
13
|
+
return envModel;
|
|
14
|
+
const filename = MODEL_NAMES[model];
|
|
15
|
+
if (filename)
|
|
16
|
+
return `${home}/.local/bin/${filename}`;
|
|
17
|
+
return model;
|
|
18
|
+
}
|
|
3
19
|
export const FREE_DEFAULTS = {
|
|
4
20
|
isLicensed: false,
|
|
5
21
|
micThreshold: 0.008,
|
|
@@ -13,6 +29,7 @@ export const FREE_DEFAULTS = {
|
|
|
13
29
|
tts: true,
|
|
14
30
|
ttsVoice: DEFAULT_TTS_VOICE,
|
|
15
31
|
ttsRate: DEFAULT_TTS_RATE,
|
|
32
|
+
whisperModel: resolveModelPath("base"),
|
|
16
33
|
};
|
|
17
34
|
export const SENSITIVITY_PRESETS = {
|
|
18
35
|
low: {
|
|
@@ -49,6 +66,9 @@ export function resolveConfig(userConfig, isLicensed) {
|
|
|
49
66
|
tts: userConfig.tts ?? FREE_DEFAULTS.tts,
|
|
50
67
|
ttsVoice: userConfig.ttsVoice ?? FREE_DEFAULTS.ttsVoice,
|
|
51
68
|
ttsRate: userConfig.ttsRate ?? FREE_DEFAULTS.ttsRate,
|
|
69
|
+
whisperModel: userConfig.whisperModel
|
|
70
|
+
? resolveModelPath(userConfig.whisperModel)
|
|
71
|
+
: FREE_DEFAULTS.whisperModel,
|
|
52
72
|
};
|
|
53
73
|
const preset = SENSITIVITY_PRESETS[base.sensitivity];
|
|
54
74
|
if (!userConfig.timingWindowMs)
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,6 @@ import { readFileSync, existsSync, unlinkSync } from "node:fs";
|
|
|
12
12
|
let activeSessionId = null;
|
|
13
13
|
let pendingInterrupt = null;
|
|
14
14
|
const RECORDING_FILE = "/tmp/interrupt-ptt.wav";
|
|
15
|
-
const WHISPER_MODEL = process.env.WHISPER_MODEL || `${process.env.HOME}/.local/bin/ggml-base.bin`;
|
|
16
15
|
let recordingProcess = null;
|
|
17
16
|
let pttActive = false;
|
|
18
17
|
function pttStartRecording() {
|
|
@@ -33,7 +32,7 @@ function pttStopRecording() {
|
|
|
33
32
|
recordingProcess = null;
|
|
34
33
|
}
|
|
35
34
|
const TXT_FILE = "/tmp/interrupt-ptt.txt";
|
|
36
|
-
async function transcribeLocal() {
|
|
35
|
+
async function transcribeLocal(modelPath) {
|
|
37
36
|
try {
|
|
38
37
|
execSync("whisper --help", { stdio: "ignore", timeout: 3000 });
|
|
39
38
|
}
|
|
@@ -45,7 +44,7 @@ async function transcribeLocal() {
|
|
|
45
44
|
}
|
|
46
45
|
catch { /* ignore */ }
|
|
47
46
|
try {
|
|
48
|
-
execSync(`whisper -f "${RECORDING_FILE}" -m "${
|
|
47
|
+
execSync(`whisper -f "${RECORDING_FILE}" -m "${modelPath}" -otxt -of /tmp/interrupt-ptt`, { stdio: "ignore", timeout: 30000 });
|
|
49
48
|
}
|
|
50
49
|
catch {
|
|
51
50
|
return null;
|
|
@@ -83,19 +82,27 @@ async function transcribeAPI() {
|
|
|
83
82
|
return null;
|
|
84
83
|
}
|
|
85
84
|
}
|
|
86
|
-
async function transcribeAndSend(sessionID, directory, api) {
|
|
85
|
+
async function transcribeAndSend(sessionID, directory, api, modelPath) {
|
|
87
86
|
pttStopRecording();
|
|
88
87
|
if (!existsSync(RECORDING_FILE)) {
|
|
89
88
|
api.ui.toast({ variant: "warning", title: "PTT", message: "No audio captured" });
|
|
90
89
|
return;
|
|
91
90
|
}
|
|
92
|
-
|
|
91
|
+
const hasApiKey = !!process.env.OPENAI_API_KEY;
|
|
92
|
+
api.ui.toast({ variant: "info", title: "PTT", message: hasApiKey ? "⏳ Transcribing via OpenAI API..." : "⏳ Transcribing with Whisper..." });
|
|
93
93
|
let text = null;
|
|
94
|
-
|
|
95
|
-
if (!text)
|
|
94
|
+
if (hasApiKey) {
|
|
96
95
|
text = await transcribeAPI();
|
|
96
|
+
if (!text)
|
|
97
|
+
text = await transcribeLocal(modelPath);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
text = await transcribeLocal(modelPath);
|
|
101
|
+
if (!text)
|
|
102
|
+
text = await transcribeAPI();
|
|
103
|
+
}
|
|
97
104
|
if (!text) {
|
|
98
|
-
api.ui.toast({ variant: "error", title: "PTT", message: "
|
|
105
|
+
api.ui.toast({ variant: "error", title: "PTT", message: "❌ Install whisper: run scripts/install-whisper.sh, or set OPENAI_API_KEY" });
|
|
99
106
|
try {
|
|
100
107
|
unlinkSync(RECORDING_FILE);
|
|
101
108
|
}
|
|
@@ -107,25 +114,35 @@ async function transcribeAndSend(sessionID, directory, api) {
|
|
|
107
114
|
}
|
|
108
115
|
catch { /* ignore */ }
|
|
109
116
|
if (!sessionID) {
|
|
110
|
-
api.ui.toast({ variant: "warning", title: "PTT", message: "
|
|
117
|
+
api.ui.toast({ variant: "warning", title: "PTT", message: "⚠️ Open a session first, then type /ptt" });
|
|
111
118
|
return;
|
|
112
119
|
}
|
|
120
|
+
api.ui.toast({ variant: "info", title: "PTT", message: "✉️ Sending transcript..." });
|
|
113
121
|
await api.client.session.prompt({ sessionID, directory, parts: [{ type: "text", text }] });
|
|
114
|
-
|
|
122
|
+
const preview = text.length > 80 ? text.slice(0, 77) + "..." : text;
|
|
123
|
+
api.ui.toast({ variant: "success", title: "PTT", message: `✅ Sent: "${preview}"` });
|
|
115
124
|
}
|
|
116
|
-
async function transcribeAndSendV1(sessionID, client) {
|
|
125
|
+
async function transcribeAndSendV1(sessionID, client, modelPath) {
|
|
117
126
|
pttStopRecording();
|
|
118
127
|
if (!existsSync(RECORDING_FILE)) {
|
|
119
|
-
await client.tui.showToast({ body: { title: "PTT", message: "No audio captured — try again", variant: "warning" } });
|
|
128
|
+
await client.tui.showToast({ body: { title: "PTT", message: "⚠️ No audio captured — try again", variant: "warning" } });
|
|
120
129
|
return;
|
|
121
130
|
}
|
|
122
|
-
|
|
131
|
+
const hasApiKey = !!process.env.OPENAI_API_KEY;
|
|
132
|
+
await client.tui.showToast({ body: { title: "PTT", message: hasApiKey ? "⏳ Transcribing via OpenAI API..." : "⏳ Transcribing with Whisper...", variant: "info" } });
|
|
123
133
|
let text = null;
|
|
124
|
-
|
|
125
|
-
if (!text)
|
|
134
|
+
if (hasApiKey) {
|
|
126
135
|
text = await transcribeAPI();
|
|
136
|
+
if (!text)
|
|
137
|
+
text = await transcribeLocal(modelPath);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
text = await transcribeLocal(modelPath);
|
|
141
|
+
if (!text)
|
|
142
|
+
text = await transcribeAPI();
|
|
143
|
+
}
|
|
127
144
|
if (!text) {
|
|
128
|
-
await client.tui.showToast({ body: { title: "PTT", message: "Install whisper: run scripts/install-whisper.sh, or set OPENAI_API_KEY", variant: "error", duration: 8000 } });
|
|
145
|
+
await client.tui.showToast({ body: { title: "PTT", message: "❌ Install whisper: run scripts/install-whisper.sh, or set OPENAI_API_KEY", variant: "error", duration: 8000 } });
|
|
129
146
|
try {
|
|
130
147
|
unlinkSync(RECORDING_FILE);
|
|
131
148
|
}
|
|
@@ -137,12 +154,13 @@ async function transcribeAndSendV1(sessionID, client) {
|
|
|
137
154
|
}
|
|
138
155
|
catch { /* ignore */ }
|
|
139
156
|
if (!sessionID) {
|
|
140
|
-
await client.tui.showToast({ body: { title: "PTT", message: "Open a session first, then type /ptt", variant: "warning", duration: 5000 } });
|
|
157
|
+
await client.tui.showToast({ body: { title: "PTT", message: "⚠️ Open a session first, then type /ptt", variant: "warning", duration: 5000 } });
|
|
141
158
|
return;
|
|
142
159
|
}
|
|
160
|
+
await client.tui.showToast({ body: { title: "PTT", message: "✉️ Sending transcript...", variant: "info" } });
|
|
143
161
|
await client.session.prompt({ path: { id: sessionID }, body: { parts: [{ type: "text", text }] } });
|
|
144
162
|
const preview = text.length > 80 ? text.slice(0, 77) + "..." : text;
|
|
145
|
-
await client.tui.showToast({ body: { title: "PTT", message:
|
|
163
|
+
await client.tui.showToast({ body: { title: "PTT", message: `✅ Sent: "${preview}"`, variant: "success", duration: 5000 } });
|
|
146
164
|
}
|
|
147
165
|
const TTS_COMMANDS = [
|
|
148
166
|
{ name: 'tts-on', description: 'Enable streaming TTS', template: 'TTS enabled.' },
|
|
@@ -327,7 +345,7 @@ export const InterruptPlugin = (userConfig = {}) => {
|
|
|
327
345
|
const sessionID = cmdInput.sessionID;
|
|
328
346
|
if (pttActive) {
|
|
329
347
|
pttActive = false;
|
|
330
|
-
await transcribeAndSendV1(sessionID, client);
|
|
348
|
+
await transcribeAndSendV1(sessionID, client, config.whisperModel);
|
|
331
349
|
}
|
|
332
350
|
else {
|
|
333
351
|
pttActive = true;
|
|
@@ -338,7 +356,7 @@ export const InterruptPlugin = (userConfig = {}) => {
|
|
|
338
356
|
catch { /* ignore */ }
|
|
339
357
|
}
|
|
340
358
|
pttStartRecording();
|
|
341
|
-
await client.tui.showToast({ body: { title: "PTT", message: "Recording... type /ptt to stop", variant: "info" } });
|
|
359
|
+
await client.tui.showToast({ body: { title: "PTT", message: "🎤 Recording... type /ptt to stop", variant: "info" } });
|
|
342
360
|
}
|
|
343
361
|
throw new Error('Command handled by interrupt plugin');
|
|
344
362
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-interrupt-plugin",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.34",
|
|
4
4
|
"description": "Streaming TTS + voice interruption for OpenCode. Speaks responses as they arrive and detects when you talk over it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|