@gonzih/cc-discord 0.2.49 → 0.2.51
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/bot.js +1 -1
- package/dist/claude.d.ts +1 -1
- package/dist/notifier.js +14 -6
- package/dist/voice.js +61 -5
- package/package.json +1 -1
package/dist/bot.js
CHANGED
|
@@ -413,7 +413,7 @@ export class CcDiscordBot {
|
|
|
413
413
|
await this.editLiveMessageWithPacing(key, entry.msg, entry.text, 1);
|
|
414
414
|
}
|
|
415
415
|
catch (err) {
|
|
416
|
-
if (this.liveMessages.get(key)?.msg.id === entry.msg.id) {
|
|
416
|
+
if (!this.liveEditQueue.has(key) && this.liveMessages.get(key)?.msg.id === entry.msg.id) {
|
|
417
417
|
this.liveEditQueue.set(key, entry);
|
|
418
418
|
}
|
|
419
419
|
}
|
package/dist/claude.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface ClaudeMessage {
|
|
|
15
15
|
export interface ClaudeOptions {
|
|
16
16
|
cwd?: string;
|
|
17
17
|
systemPrompt?: string;
|
|
18
|
-
/** OAuth token (
|
|
18
|
+
/** OAuth token (FILL_IN_TUESDAY) or API key (FILL_IN_TUESDAY) */
|
|
19
19
|
token?: string;
|
|
20
20
|
}
|
|
21
21
|
export interface UsageEvent {
|
package/dist/notifier.js
CHANGED
|
@@ -272,6 +272,7 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
|
|
|
272
272
|
});
|
|
273
273
|
const TOOL_SLOT = "tools";
|
|
274
274
|
const RESPONSE_SLOT = "response";
|
|
275
|
+
const showToolUsage = () => /^(1|true|yes)$/i.test(process.env.CC_DISCORD_SHOW_TOOL_USAGE ?? "");
|
|
275
276
|
const liveStates = new Map();
|
|
276
277
|
function getLiveState(ns, targetChannelId) {
|
|
277
278
|
let state = liveStates.get(ns);
|
|
@@ -333,7 +334,7 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
|
|
|
333
334
|
bot.stopMetaAgentTyping(deliverTo);
|
|
334
335
|
const trimmed = state.text.trim();
|
|
335
336
|
const toolText = state.toolLog.length > 0 ? toolLogText(ns, state, false) : "";
|
|
336
|
-
if (state.toolStarted || state.toolLog.length > 0) {
|
|
337
|
+
if (showToolUsage() && (state.toolStarted || state.toolLog.length > 0)) {
|
|
337
338
|
await bot.finalizeLiveMessage(deliverTo, toolText, TOOL_SLOT);
|
|
338
339
|
}
|
|
339
340
|
if (!trimmed && !state.responseStarted)
|
|
@@ -379,20 +380,24 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
|
|
|
379
380
|
if (event === "tool_start") {
|
|
380
381
|
const toolName = parsed.content || "tool";
|
|
381
382
|
state.activeTool = toolName;
|
|
382
|
-
state.toolLog.unshift(`⚙️ \`${toolName}\`...`);
|
|
383
383
|
if (state.finalTimer) {
|
|
384
384
|
clearTimeout(state.finalTimer);
|
|
385
385
|
state.finalTimer = null;
|
|
386
386
|
}
|
|
387
|
-
|
|
387
|
+
if (showToolUsage()) {
|
|
388
|
+
state.toolLog.unshift(`⚙️ \`${toolName}\`...`);
|
|
389
|
+
void ensureToolLogMessage(ns, deliverTo, state);
|
|
390
|
+
}
|
|
388
391
|
return;
|
|
389
392
|
}
|
|
390
393
|
// tool_end: clear overlay, resume finalize timer if text is pending
|
|
391
394
|
if (event === "tool_end") {
|
|
392
395
|
const toolName = state.activeTool || "tool";
|
|
393
|
-
replaceNewestActiveTool(state, `✓ \`${toolName}\``);
|
|
394
396
|
state.activeTool = "";
|
|
395
|
-
if (
|
|
397
|
+
if (showToolUsage()) {
|
|
398
|
+
replaceNewestActiveTool(state, `✓ \`${toolName}\``);
|
|
399
|
+
}
|
|
400
|
+
if (showToolUsage() && state.toolStarted) {
|
|
396
401
|
bot.updateLiveMessage(deliverTo, toolLogText(ns, state, true), TOOL_SLOT);
|
|
397
402
|
}
|
|
398
403
|
if (state.text.trim())
|
|
@@ -417,7 +422,10 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
|
|
|
417
422
|
if (!state.responseStarted) {
|
|
418
423
|
if (!state.responseStarting) {
|
|
419
424
|
state.responseStarting = true;
|
|
420
|
-
|
|
425
|
+
const beforeResponse = showToolUsage()
|
|
426
|
+
? ensureToolLogMessage(ns, deliverTo, state)
|
|
427
|
+
: Promise.resolve();
|
|
428
|
+
beforeResponse.then(() => {
|
|
421
429
|
return bot.startOrGetLiveMessage(deliverTo, textDisplay, RESPONSE_SLOT);
|
|
422
430
|
}).then((msg) => {
|
|
423
431
|
state.responseStarting = false;
|
package/dist/voice.js
CHANGED
|
@@ -4,22 +4,36 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { execFile } from "child_process";
|
|
6
6
|
import { promisify } from "util";
|
|
7
|
-
import { existsSync, createWriteStream } from "fs";
|
|
8
|
-
import { unlink, readFile, access } from "fs/promises";
|
|
7
|
+
import { existsSync, createWriteStream, readdirSync } from "fs";
|
|
8
|
+
import { unlink, readFile, access, mkdir } from "fs/promises";
|
|
9
9
|
import { tmpdir } from "os";
|
|
10
10
|
import { join } from "path";
|
|
11
11
|
import https from "https";
|
|
12
12
|
import http from "http";
|
|
13
13
|
const execFileAsync = promisify(execFile);
|
|
14
|
-
// Env var overrides allow test injection: WHISPER_BIN, FFMPEG_BIN, WHISPER_MODEL
|
|
14
|
+
// Env var overrides allow test injection: WHISPER_BIN, FFMPEG_BIN, WHISPER_MODEL.
|
|
15
|
+
// WHISPER_MODEL may point to either a model file or a directory containing ggml*.bin.
|
|
16
|
+
const WHISPER_MODEL_NAMES = [
|
|
17
|
+
"ggml-small.en.bin",
|
|
18
|
+
"ggml-small.bin",
|
|
19
|
+
"ggml-base.en.bin",
|
|
20
|
+
"ggml-base.bin",
|
|
21
|
+
"ggml-tiny.en.bin",
|
|
22
|
+
"ggml-tiny.bin",
|
|
23
|
+
];
|
|
15
24
|
const WHISPER_MODELS = [
|
|
16
25
|
process.env.WHISPER_MODEL,
|
|
26
|
+
process.env.WHISPER_MODEL_DIR,
|
|
17
27
|
"/opt/homebrew/share/whisper-cpp/ggml-small.en.bin",
|
|
18
28
|
"/opt/homebrew/share/whisper-cpp/ggml-small.bin",
|
|
19
29
|
"/opt/homebrew/share/whisper-cpp/ggml-base.en.bin",
|
|
20
30
|
"/opt/homebrew/share/whisper-cpp/ggml-base.bin",
|
|
31
|
+
"/opt/homebrew/share/whisper-cpp",
|
|
32
|
+
"/usr/local/share/whisper-cpp",
|
|
21
33
|
`${process.env.HOME}/.local/share/whisper-cpp/ggml-small.en.bin`,
|
|
22
34
|
`${process.env.HOME}/.local/share/whisper-cpp/ggml-base.en.bin`,
|
|
35
|
+
`${process.env.HOME}/.local/share/whisper-cpp`,
|
|
36
|
+
`${process.env.HOME}/Library/Application Support/whisper-cpp`,
|
|
23
37
|
].filter(Boolean);
|
|
24
38
|
const WHISPER_BIN_CANDIDATES = [
|
|
25
39
|
process.env.WHISPER_BIN,
|
|
@@ -35,6 +49,13 @@ const FFMPEG_CANDIDATES = [
|
|
|
35
49
|
"/usr/local/bin/ffmpeg",
|
|
36
50
|
"/usr/bin/ffmpeg",
|
|
37
51
|
].filter(Boolean);
|
|
52
|
+
const WHISPER_DOWNLOAD_CANDIDATES = [
|
|
53
|
+
process.env.WHISPER_DOWNLOAD_BIN,
|
|
54
|
+
"/opt/homebrew/bin/whisper-cpp-download-ggml-model",
|
|
55
|
+
"/usr/local/bin/whisper-cpp-download-ggml-model",
|
|
56
|
+
].filter(Boolean);
|
|
57
|
+
const DEFAULT_WHISPER_MODEL_URL = process.env.WHISPER_MODEL_URL ||
|
|
58
|
+
"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.en.bin";
|
|
38
59
|
function findBin(candidates) {
|
|
39
60
|
for (const p of candidates) {
|
|
40
61
|
if (existsSync(p))
|
|
@@ -44,11 +65,46 @@ function findBin(candidates) {
|
|
|
44
65
|
}
|
|
45
66
|
function findModel() {
|
|
46
67
|
for (const p of WHISPER_MODELS) {
|
|
47
|
-
if (existsSync(p))
|
|
68
|
+
if (!existsSync(p))
|
|
69
|
+
continue;
|
|
70
|
+
try {
|
|
71
|
+
const entries = readdirSync(p, { withFileTypes: true });
|
|
72
|
+
const model = WHISPER_MODEL_NAMES.find((name) => entries.some((entry) => entry.isFile() && entry.name === name));
|
|
73
|
+
if (model)
|
|
74
|
+
return join(p, model);
|
|
75
|
+
const anyModel = entries.find((entry) => entry.isFile() &&
|
|
76
|
+
/ggml.*\.bin$/i.test(entry.name) &&
|
|
77
|
+
!entry.name.startsWith("for-tests-"));
|
|
78
|
+
if (anyModel)
|
|
79
|
+
return join(p, anyModel.name);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
48
82
|
return p;
|
|
83
|
+
}
|
|
49
84
|
}
|
|
50
85
|
return null;
|
|
51
86
|
}
|
|
87
|
+
async function ensureModel() {
|
|
88
|
+
const existing = findModel();
|
|
89
|
+
if (existing)
|
|
90
|
+
return existing;
|
|
91
|
+
if (process.env.NODE_ENV === "test" && process.env.WHISPER_ALLOW_DOWNLOAD !== "1")
|
|
92
|
+
return null;
|
|
93
|
+
const downloader = findBin(WHISPER_DOWNLOAD_CANDIDATES);
|
|
94
|
+
const modelDir = process.env.WHISPER_MODEL_DIR || `${process.env.HOME}/.local/share/whisper-cpp`;
|
|
95
|
+
await mkdir(modelDir, { recursive: true });
|
|
96
|
+
if (downloader) {
|
|
97
|
+
await execFileAsync(downloader, ["small.en"], {
|
|
98
|
+
cwd: modelDir,
|
|
99
|
+
timeout: 10 * 60_000,
|
|
100
|
+
maxBuffer: 1024 * 1024,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
await downloadFile(DEFAULT_WHISPER_MODEL_URL, join(modelDir, "ggml-small.en.bin"));
|
|
105
|
+
}
|
|
106
|
+
return findModel();
|
|
107
|
+
}
|
|
52
108
|
/**
|
|
53
109
|
* Download a file from a URL to dest.
|
|
54
110
|
* Supports file:// (copies local file), and https/http with redirect following.
|
|
@@ -95,7 +151,7 @@ export async function transcribeVoice(fileUrl) {
|
|
|
95
151
|
const ffmpegBin = findBin(FFMPEG_CANDIDATES);
|
|
96
152
|
if (!ffmpegBin)
|
|
97
153
|
throw new Error("ffmpeg not found — install with: brew install ffmpeg");
|
|
98
|
-
const model =
|
|
154
|
+
const model = await ensureModel();
|
|
99
155
|
if (!model)
|
|
100
156
|
throw new Error("No whisper model found — run: whisper-cpp-download-ggml-model small.en");
|
|
101
157
|
const tmp = join(tmpdir(), `cc-discord-voice-${Date.now()}`);
|