@gonzih/cc-discord 0.2.48 → 0.2.50
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.d.ts +20 -11
- package/dist/bot.js +328 -74
- package/dist/index.d.ts +9 -5
- package/dist/index.js +17 -10
- package/dist/meta-agent-manager.d.ts +2 -2
- package/dist/meta-agent-manager.js +565 -79
- package/dist/notifier.d.ts +2 -2
- package/dist/notifier.js +87 -54
- package/dist/tokens.d.ts +1 -1
- package/dist/tokens.js +1 -1
- package/dist/voice.js +52 -5
- package/package.json +8 -5
package/dist/notifier.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* v0.2.0 channels (discord-scoped):
|
|
5
5
|
* cca:discord:notify:{ns} — job completion notifications → forward to Discord channel
|
|
6
6
|
* cca:chat:incoming:{ns} — messages from the web UI → echo to Discord + feed to meta-agent
|
|
7
|
-
* cca:discord:chat:outgoing:{ns} — meta-agent stdout lines (source=claude) → buffer+debounce → Discord
|
|
7
|
+
* cca:discord:chat:outgoing:{ns} — meta-agent stdout lines (source=claude/codex) → buffer+debounce → Discord
|
|
8
8
|
*
|
|
9
9
|
* All messages (Discord incoming, Claude responses) are also written to:
|
|
10
10
|
* cca:discord:chat:log:{ns} — LPUSH + LTRIM 0 499 (last 500 messages)
|
|
@@ -23,7 +23,7 @@ export interface EvalReport {
|
|
|
23
23
|
import type { CcDiscordBot } from "./bot.js";
|
|
24
24
|
export interface ChatMessage {
|
|
25
25
|
id: string;
|
|
26
|
-
source: "discord" | "ui" | "claude" | "cc-tg" | "cc-discord";
|
|
26
|
+
source: "discord" | "ui" | "claude" | "codex" | "cc-tg" | "cc-discord";
|
|
27
27
|
role: "user" | "assistant" | "tool";
|
|
28
28
|
content: string;
|
|
29
29
|
timestamp: string;
|
package/dist/notifier.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* v0.2.0 channels (discord-scoped):
|
|
5
5
|
* cca:discord:notify:{ns} — job completion notifications → forward to Discord channel
|
|
6
6
|
* cca:chat:incoming:{ns} — messages from the web UI → echo to Discord + feed to meta-agent
|
|
7
|
-
* cca:discord:chat:outgoing:{ns} — meta-agent stdout lines (source=claude) → buffer+debounce → Discord
|
|
7
|
+
* cca:discord:chat:outgoing:{ns} — meta-agent stdout lines (source=claude/codex) → buffer+debounce → Discord
|
|
8
8
|
*
|
|
9
9
|
* All messages (Discord incoming, Claude responses) are also written to:
|
|
10
10
|
* cca:discord:chat:log:{ns} — LPUSH + LTRIM 0 499 (last 500 messages)
|
|
@@ -270,15 +270,60 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
|
|
|
270
270
|
log("info", `psubscribed to ${outgoingPattern}`);
|
|
271
271
|
}
|
|
272
272
|
});
|
|
273
|
+
const TOOL_SLOT = "tools";
|
|
274
|
+
const RESPONSE_SLOT = "response";
|
|
275
|
+
const showToolUsage = () => /^(1|true|yes)$/i.test(process.env.CC_DISCORD_SHOW_TOOL_USAGE ?? "");
|
|
273
276
|
const liveStates = new Map();
|
|
274
277
|
function getLiveState(ns, targetChannelId) {
|
|
275
278
|
let state = liveStates.get(ns);
|
|
276
279
|
if (!state) {
|
|
277
|
-
state = {
|
|
280
|
+
state = {
|
|
281
|
+
text: "",
|
|
282
|
+
toolLog: [],
|
|
283
|
+
activeTool: "",
|
|
284
|
+
targetChannelId,
|
|
285
|
+
responseStarting: false,
|
|
286
|
+
responseStarted: false,
|
|
287
|
+
toolStarting: false,
|
|
288
|
+
toolStarted: false,
|
|
289
|
+
finalTimer: null,
|
|
290
|
+
};
|
|
278
291
|
liveStates.set(ns, state);
|
|
279
292
|
}
|
|
280
293
|
return state;
|
|
281
294
|
}
|
|
295
|
+
function toolLogText(ns, state, live) {
|
|
296
|
+
const visible = state.toolLog.slice(0, 20);
|
|
297
|
+
const body = visible.length > 0 ? visible.join("\n") : "waiting for tool activity";
|
|
298
|
+
return `← [${ns}] tools\n${body}${live ? " ▋" : ""}`;
|
|
299
|
+
}
|
|
300
|
+
function ensureToolLogMessage(ns, deliverTo, state) {
|
|
301
|
+
if (state.toolStarted) {
|
|
302
|
+
bot.updateLiveMessage(deliverTo, toolLogText(ns, state, true), TOOL_SLOT);
|
|
303
|
+
return Promise.resolve();
|
|
304
|
+
}
|
|
305
|
+
if (state.toolStarting)
|
|
306
|
+
return Promise.resolve();
|
|
307
|
+
state.toolStarting = true;
|
|
308
|
+
return bot.startOrGetLiveMessage(deliverTo, toolLogText(ns, state, true), TOOL_SLOT).then((msg) => {
|
|
309
|
+
state.toolStarting = false;
|
|
310
|
+
if (msg) {
|
|
311
|
+
state.toolStarted = true;
|
|
312
|
+
bot.updateLiveMessage(deliverTo, toolLogText(ns, state, true), TOOL_SLOT);
|
|
313
|
+
}
|
|
314
|
+
}).catch(() => {
|
|
315
|
+
state.toolStarting = false;
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
function replaceNewestActiveTool(state, replacement) {
|
|
319
|
+
const idx = state.toolLog.findIndex((line) => line.startsWith("⚙️"));
|
|
320
|
+
if (idx >= 0) {
|
|
321
|
+
state.toolLog[idx] = replacement;
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
state.toolLog.unshift(replacement);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
282
327
|
async function finalizeState(ns, state) {
|
|
283
328
|
if (state.finalTimer) {
|
|
284
329
|
clearTimeout(state.finalTimer);
|
|
@@ -288,10 +333,18 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
|
|
|
288
333
|
const deliverTo = bot.getLoopThreadId(state.targetChannelId) ?? state.targetChannelId;
|
|
289
334
|
bot.stopMetaAgentTyping(deliverTo);
|
|
290
335
|
const trimmed = state.text.trim();
|
|
291
|
-
|
|
336
|
+
const toolText = state.toolLog.length > 0 ? toolLogText(ns, state, false) : "";
|
|
337
|
+
if (showToolUsage() && (state.toolStarted || state.toolLog.length > 0)) {
|
|
338
|
+
await bot.finalizeLiveMessage(deliverTo, toolText, TOOL_SLOT);
|
|
339
|
+
}
|
|
340
|
+
if (!trimmed && !state.responseStarted)
|
|
292
341
|
return;
|
|
293
342
|
const fullText = trimmed ? `← [${ns}]\n${stripAnsi(trimmed)}` : "";
|
|
294
|
-
|
|
343
|
+
if (fullText.length > 0 && fullText.length < 30) {
|
|
344
|
+
await bot.finalizeLiveMessage(deliverTo, "", RESPONSE_SLOT);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
await bot.finalizeLiveMessage(deliverTo, fullText, RESPONSE_SLOT);
|
|
295
348
|
}
|
|
296
349
|
function scheduleFinal(ns, state) {
|
|
297
350
|
if (state.finalTimer)
|
|
@@ -312,7 +365,7 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
|
|
|
312
365
|
catch {
|
|
313
366
|
return;
|
|
314
367
|
}
|
|
315
|
-
if (parsed?.source !== "claude")
|
|
368
|
+
if (parsed?.source !== "claude" && parsed?.source !== "codex")
|
|
316
369
|
return;
|
|
317
370
|
const targetChannelId = routedChannelIds.get(ns) ??
|
|
318
371
|
(ns === namespace ? (notifyChannelId ?? getActiveChannelId?.()) : undefined);
|
|
@@ -326,52 +379,33 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
|
|
|
326
379
|
// tool_start: show activity overlay, suspend finalize timer
|
|
327
380
|
if (event === "tool_start") {
|
|
328
381
|
const toolName = parsed.content || "tool";
|
|
329
|
-
state.
|
|
382
|
+
state.activeTool = toolName;
|
|
330
383
|
if (state.finalTimer) {
|
|
331
384
|
clearTimeout(state.finalTimer);
|
|
332
385
|
state.finalTimer = null;
|
|
333
386
|
}
|
|
334
|
-
if (
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
else if (!state.starting) {
|
|
339
|
-
state.starting = true;
|
|
340
|
-
bot.startOrGetLiveMessage(deliverTo, `⚙️ \`${toolName}\`...`).then((msg) => {
|
|
341
|
-
state.starting = false;
|
|
342
|
-
if (msg)
|
|
343
|
-
state.liveStarted = true;
|
|
344
|
-
}).catch(() => { state.starting = false; });
|
|
387
|
+
if (showToolUsage()) {
|
|
388
|
+
state.toolLog.unshift(`⚙️ \`${toolName}\`...`);
|
|
389
|
+
void ensureToolLogMessage(ns, deliverTo, state);
|
|
345
390
|
}
|
|
346
391
|
return;
|
|
347
392
|
}
|
|
348
393
|
// tool_end: clear overlay, resume finalize timer if text is pending
|
|
349
394
|
if (event === "tool_end") {
|
|
350
|
-
state.
|
|
395
|
+
const toolName = state.activeTool || "tool";
|
|
396
|
+
state.activeTool = "";
|
|
397
|
+
if (showToolUsage()) {
|
|
398
|
+
replaceNewestActiveTool(state, `✓ \`${toolName}\``);
|
|
399
|
+
}
|
|
400
|
+
if (showToolUsage() && state.toolStarted) {
|
|
401
|
+
bot.updateLiveMessage(deliverTo, toolLogText(ns, state, true), TOOL_SLOT);
|
|
402
|
+
}
|
|
351
403
|
if (state.text.trim())
|
|
352
404
|
scheduleFinal(ns, state);
|
|
353
405
|
return;
|
|
354
406
|
}
|
|
355
|
-
// rate_limit: delivered after the current turn's "done" — send as a standalone message,
|
|
356
|
-
// never accumulate into live state text (which already finalized above via "done").
|
|
357
|
-
if (event === "rate_limit") {
|
|
358
|
-
const alertContent = parsed.content ?? "";
|
|
359
|
-
if (alertContent) {
|
|
360
|
-
bot.sendToChannelById(deliverTo, `← [${ns}]\n${alertContent}`).catch((err) => {
|
|
361
|
-
log("warn", `rate_limit alert send failed (ns=${ns}):`, err.message);
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
407
|
// done: immediate finalization (fired by meta-agent-manager after result event)
|
|
367
408
|
if (event === "done") {
|
|
368
|
-
if (state.starting) {
|
|
369
|
-
// startOrGetLiveMessage is still in flight — defer finalization to its .then() callback
|
|
370
|
-
// so we don't race: finalizeState runs before the Discord message exists → fallback
|
|
371
|
-
// sends a second plain message, leaving the live message orphaned with ▋.
|
|
372
|
-
state.pendingDone = true;
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
409
|
finalizeState(ns, state).catch((err) => {
|
|
376
410
|
log("warn", `meta-agent done finalize failed (ns=${ns}):`, err.message);
|
|
377
411
|
});
|
|
@@ -381,31 +415,30 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
|
|
|
381
415
|
const content = parsed.content;
|
|
382
416
|
if (!content)
|
|
383
417
|
return;
|
|
384
|
-
state.
|
|
385
|
-
|
|
418
|
+
state.text += parsed.source === "codex"
|
|
419
|
+
? content
|
|
420
|
+
: (state.text ? "\n" : "") + content;
|
|
386
421
|
const textDisplay = `← [${ns}]\n${stripAnsi(state.text)} ▋`;
|
|
387
|
-
if (!state.
|
|
388
|
-
if (!state.
|
|
389
|
-
state.
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
422
|
+
if (!state.responseStarted) {
|
|
423
|
+
if (!state.responseStarting) {
|
|
424
|
+
state.responseStarting = true;
|
|
425
|
+
const beforeResponse = showToolUsage()
|
|
426
|
+
? ensureToolLogMessage(ns, deliverTo, state)
|
|
427
|
+
: Promise.resolve();
|
|
428
|
+
beforeResponse.then(() => {
|
|
429
|
+
return bot.startOrGetLiveMessage(deliverTo, textDisplay, RESPONSE_SLOT);
|
|
430
|
+
}).then((msg) => {
|
|
431
|
+
state.responseStarting = false;
|
|
393
432
|
if (msg) {
|
|
394
|
-
state.
|
|
395
|
-
bot.updateLiveMessage(deliverTo, textDisplay);
|
|
396
|
-
}
|
|
397
|
-
// done arrived while we were creating the message — finalize now
|
|
398
|
-
if (state.pendingDone) {
|
|
399
|
-
finalizeState(ns, state).catch((err) => {
|
|
400
|
-
log("warn", `meta-agent deferred done finalize failed (ns=${ns}):`, err.message);
|
|
401
|
-
});
|
|
433
|
+
state.responseStarted = true;
|
|
434
|
+
bot.updateLiveMessage(deliverTo, textDisplay, RESPONSE_SLOT);
|
|
402
435
|
}
|
|
403
|
-
}).catch(() => { state.
|
|
436
|
+
}).catch(() => { state.responseStarting = false; });
|
|
404
437
|
}
|
|
405
438
|
// Text is buffered in state.text; applied when startOrGetLiveMessage resolves
|
|
406
439
|
}
|
|
407
440
|
else {
|
|
408
|
-
bot.updateLiveMessage(deliverTo, textDisplay);
|
|
441
|
+
bot.updateLiveMessage(deliverTo, textDisplay, RESPONSE_SLOT);
|
|
409
442
|
}
|
|
410
443
|
scheduleFinal(ns, state);
|
|
411
444
|
});
|
package/dist/tokens.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Supports CLAUDE_CODE_OAUTH_TOKENS (comma-separated list of tokens).
|
|
5
5
|
* Falls back to CLAUDE_CODE_OAUTH_TOKEN for single-token / backwards compat.
|
|
6
6
|
*
|
|
7
|
-
* cc-tg token pool rotates independently from cc-
|
|
7
|
+
* cc-tg token pool rotates independently from cc-discord's local session pool.
|
|
8
8
|
*/
|
|
9
9
|
/**
|
|
10
10
|
* Load tokens from env vars. Called on startup; also re-callable in tests.
|
package/dist/tokens.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Supports CLAUDE_CODE_OAUTH_TOKENS (comma-separated list of tokens).
|
|
5
5
|
* Falls back to CLAUDE_CODE_OAUTH_TOKEN for single-token / backwards compat.
|
|
6
6
|
*
|
|
7
|
-
* cc-tg token pool rotates independently from cc-
|
|
7
|
+
* cc-tg token pool rotates independently from cc-discord's local session pool.
|
|
8
8
|
*/
|
|
9
9
|
let tokens = [];
|
|
10
10
|
let currentIndex = 0;
|
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,11 @@ 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);
|
|
38
57
|
function findBin(candidates) {
|
|
39
58
|
for (const p of candidates) {
|
|
40
59
|
if (existsSync(p))
|
|
@@ -44,11 +63,39 @@ function findBin(candidates) {
|
|
|
44
63
|
}
|
|
45
64
|
function findModel() {
|
|
46
65
|
for (const p of WHISPER_MODELS) {
|
|
47
|
-
if (existsSync(p))
|
|
66
|
+
if (!existsSync(p))
|
|
67
|
+
continue;
|
|
68
|
+
try {
|
|
69
|
+
const entries = readdirSync(p, { withFileTypes: true });
|
|
70
|
+
const model = WHISPER_MODEL_NAMES.find((name) => entries.some((entry) => entry.isFile() && entry.name === name));
|
|
71
|
+
if (model)
|
|
72
|
+
return join(p, model);
|
|
73
|
+
const anyModel = entries.find((entry) => entry.isFile() && /^ggml.*\.bin$/i.test(entry.name));
|
|
74
|
+
if (anyModel)
|
|
75
|
+
return join(p, anyModel.name);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
48
78
|
return p;
|
|
79
|
+
}
|
|
49
80
|
}
|
|
50
81
|
return null;
|
|
51
82
|
}
|
|
83
|
+
async function ensureModel() {
|
|
84
|
+
const existing = findModel();
|
|
85
|
+
if (existing)
|
|
86
|
+
return existing;
|
|
87
|
+
const downloader = findBin(WHISPER_DOWNLOAD_CANDIDATES);
|
|
88
|
+
if (!downloader)
|
|
89
|
+
return null;
|
|
90
|
+
const modelDir = process.env.WHISPER_MODEL_DIR || `${process.env.HOME}/.local/share/whisper-cpp`;
|
|
91
|
+
await mkdir(modelDir, { recursive: true });
|
|
92
|
+
await execFileAsync(downloader, ["small.en"], {
|
|
93
|
+
cwd: modelDir,
|
|
94
|
+
timeout: 10 * 60_000,
|
|
95
|
+
maxBuffer: 1024 * 1024,
|
|
96
|
+
});
|
|
97
|
+
return findModel();
|
|
98
|
+
}
|
|
52
99
|
/**
|
|
53
100
|
* Download a file from a URL to dest.
|
|
54
101
|
* Supports file:// (copies local file), and https/http with redirect following.
|
|
@@ -95,7 +142,7 @@ export async function transcribeVoice(fileUrl) {
|
|
|
95
142
|
const ffmpegBin = findBin(FFMPEG_CANDIDATES);
|
|
96
143
|
if (!ffmpegBin)
|
|
97
144
|
throw new Error("ffmpeg not found — install with: brew install ffmpeg");
|
|
98
|
-
const model =
|
|
145
|
+
const model = await ensureModel();
|
|
99
146
|
if (!model)
|
|
100
147
|
throw new Error("No whisper model found — run: whisper-cpp-download-ggml-model small.en");
|
|
101
148
|
const tmp = join(tmpdir(), `cc-discord-voice-${Date.now()}`);
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gonzih/cc-discord",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.50",
|
|
4
|
+
"description": "Discord bot for routed Claude Code or Codex agents with ATC dispatch",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"cc-discord": "
|
|
7
|
+
"cc-discord": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "tsc && chmod +x dist/index.js",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"test": "vitest run",
|
|
14
14
|
"test:watch": "vitest",
|
|
15
15
|
"test:coverage": "vitest run --coverage",
|
|
16
|
-
"test:integration": "CLAUDE_BIN=./test/fixtures/mock-claude.js vitest run test/integration/"
|
|
16
|
+
"test:integration": "CLAUDE_BIN=./test/fixtures/mock-claude.js vitest run test/integration/",
|
|
17
|
+
"test:integration:codex": "CC_DISCORD_AGENT_DRIVER=codex CODEX_BIN=./test/fixtures/mock-codex.js vitest run test/integration/meta-agent.test.ts"
|
|
17
18
|
},
|
|
18
19
|
"files": [
|
|
19
20
|
"dist/"
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
},
|
|
34
35
|
"repository": {
|
|
35
36
|
"type": "git",
|
|
36
|
-
"url": "https://github.com/Gonzih/cc-discord.git"
|
|
37
|
+
"url": "git+https://github.com/Gonzih/cc-discord.git"
|
|
37
38
|
},
|
|
38
39
|
"homepage": "https://github.com/Gonzih/cc-discord",
|
|
39
40
|
"bugs": {
|
|
@@ -42,6 +43,8 @@
|
|
|
42
43
|
"keywords": [
|
|
43
44
|
"claude",
|
|
44
45
|
"claude-code",
|
|
46
|
+
"codex",
|
|
47
|
+
"atc",
|
|
45
48
|
"discord",
|
|
46
49
|
"bot",
|
|
47
50
|
"ai"
|