discord-agent-bridge 0.1.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 +21 -0
- package/README.md +195 -0
- package/dist/app.js +210 -0
- package/dist/app.js.map +1 -0
- package/dist/app.test.js +117 -0
- package/dist/app.test.js.map +1 -0
- package/dist/cli.js +63 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.test.js +91 -0
- package/dist/cli.test.js.map +1 -0
- package/dist/core/auditLog.js +65 -0
- package/dist/core/auditLog.js.map +1 -0
- package/dist/core/auditLog.test.js +124 -0
- package/dist/core/auditLog.test.js.map +1 -0
- package/dist/core/auth.js +106 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/auth.test.js +216 -0
- package/dist/core/auth.test.js.map +1 -0
- package/dist/core/channelRegistry.js +119 -0
- package/dist/core/channelRegistry.js.map +1 -0
- package/dist/core/channelRegistry.test.js +114 -0
- package/dist/core/channelRegistry.test.js.map +1 -0
- package/dist/core/commandPolicy.js +6 -0
- package/dist/core/commandPolicy.js.map +1 -0
- package/dist/core/commandRouter.js +9 -0
- package/dist/core/commandRouter.js.map +1 -0
- package/dist/core/config.js +141 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/config.test.js +149 -0
- package/dist/core/config.test.js.map +1 -0
- package/dist/core/configResolver.js +90 -0
- package/dist/core/configResolver.js.map +1 -0
- package/dist/core/configResolver.test.js +138 -0
- package/dist/core/configResolver.test.js.map +1 -0
- package/dist/core/configSchema.js +164 -0
- package/dist/core/configSchema.js.map +1 -0
- package/dist/core/contracts.js +5 -0
- package/dist/core/contracts.js.map +1 -0
- package/dist/core/eventBus.js +42 -0
- package/dist/core/eventBus.js.map +1 -0
- package/dist/core/eventBus.test.js +72 -0
- package/dist/core/eventBus.test.js.map +1 -0
- package/dist/core/hookBridge.js +11 -0
- package/dist/core/hookBridge.js.map +1 -0
- package/dist/core/logger.js +116 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/logger.test.js +88 -0
- package/dist/core/logger.test.js.map +1 -0
- package/dist/core/modeRegistry.js +33 -0
- package/dist/core/modeRegistry.js.map +1 -0
- package/dist/core/permissionResolver.js +43 -0
- package/dist/core/permissionResolver.js.map +1 -0
- package/dist/core/permissionResolver.test.js +136 -0
- package/dist/core/permissionResolver.test.js.map +1 -0
- package/dist/core/providerCatalog.js +240 -0
- package/dist/core/providerCatalog.js.map +1 -0
- package/dist/core/providerCatalog.test.js +230 -0
- package/dist/core/providerCatalog.test.js.map +1 -0
- package/dist/core/sessionOrchestrator.js +463 -0
- package/dist/core/sessionOrchestrator.js.map +1 -0
- package/dist/core/sessionOrchestrator.test.js +344 -0
- package/dist/core/sessionOrchestrator.test.js.map +1 -0
- package/dist/core/state/schema.js +49 -0
- package/dist/core/state/schema.js.map +1 -0
- package/dist/core/state/store.js +86 -0
- package/dist/core/state/store.js.map +1 -0
- package/dist/core/state/store.test.js +99 -0
- package/dist/core/state/store.test.js.map +1 -0
- package/dist/core/usageService.js +310 -0
- package/dist/core/usageService.js.map +1 -0
- package/dist/core/usageService.test.js +234 -0
- package/dist/core/usageService.test.js.map +1 -0
- package/dist/discord/alwaysAllow.test.js +181 -0
- package/dist/discord/alwaysAllow.test.js.map +1 -0
- package/dist/discord/client.js +666 -0
- package/dist/discord/client.js.map +1 -0
- package/dist/discord/client.test.js +256 -0
- package/dist/discord/client.test.js.map +1 -0
- package/dist/discord/configPanel.js +270 -0
- package/dist/discord/configPanel.js.map +1 -0
- package/dist/discord/configPanel.test.js +332 -0
- package/dist/discord/configPanel.test.js.map +1 -0
- package/dist/discord/directoryBrowser.js +151 -0
- package/dist/discord/directoryBrowser.js.map +1 -0
- package/dist/discord/directoryBrowser.test.js +163 -0
- package/dist/discord/directoryBrowser.test.js.map +1 -0
- package/dist/discord/favorites.js +10 -0
- package/dist/discord/favorites.js.map +1 -0
- package/dist/discord/fileDownload.js +94 -0
- package/dist/discord/fileDownload.js.map +1 -0
- package/dist/discord/fileDownload.test.js +59 -0
- package/dist/discord/fileDownload.test.js.map +1 -0
- package/dist/discord/format.js +104 -0
- package/dist/discord/format.js.map +1 -0
- package/dist/discord/format.test.js +40 -0
- package/dist/discord/format.test.js.map +1 -0
- package/dist/discord/guildChannels.js +97 -0
- package/dist/discord/guildChannels.js.map +1 -0
- package/dist/discord/guildChannels.test.js +231 -0
- package/dist/discord/guildChannels.test.js.map +1 -0
- package/dist/discord/i18n.js +207 -0
- package/dist/discord/i18n.js.map +1 -0
- package/dist/discord/i18n.test.js +31 -0
- package/dist/discord/i18n.test.js.map +1 -0
- package/dist/discord/interactionRouter.js +954 -0
- package/dist/discord/interactionRouter.js.map +1 -0
- package/dist/discord/interactionRouter.test.js +1245 -0
- package/dist/discord/interactionRouter.test.js.map +1 -0
- package/dist/discord/messageRouter.js +221 -0
- package/dist/discord/messageRouter.js.map +1 -0
- package/dist/discord/messageRouter.test.js +360 -0
- package/dist/discord/messageRouter.test.js.map +1 -0
- package/dist/discord/ports.js +12 -0
- package/dist/discord/ports.js.map +1 -0
- package/dist/discord/renderers/defaultSet.test.js +97 -0
- package/dist/discord/renderers/defaultSet.test.js.map +1 -0
- package/dist/discord/renderers/diffView.js +73 -0
- package/dist/discord/renderers/diffView.js.map +1 -0
- package/dist/discord/renderers/diffView.test.js +54 -0
- package/dist/discord/renderers/diffView.test.js.map +1 -0
- package/dist/discord/renderers/index.js +143 -0
- package/dist/discord/renderers/index.js.map +1 -0
- package/dist/discord/renderers/index.test.js +124 -0
- package/dist/discord/renderers/index.test.js.map +1 -0
- package/dist/discord/renderers/mentionOnComplete.js +19 -0
- package/dist/discord/renderers/mentionOnComplete.js.map +1 -0
- package/dist/discord/renderers/mentionOnComplete.test.js +35 -0
- package/dist/discord/renderers/mentionOnComplete.test.js.map +1 -0
- package/dist/discord/renderers/permissionButtons.js +150 -0
- package/dist/discord/renderers/permissionButtons.js.map +1 -0
- package/dist/discord/renderers/permissionButtons.test.js +108 -0
- package/dist/discord/renderers/permissionButtons.test.js.map +1 -0
- package/dist/discord/renderers/resultLine.js +25 -0
- package/dist/discord/renderers/resultLine.js.map +1 -0
- package/dist/discord/renderers/statusEmbed.js +20 -0
- package/dist/discord/renderers/statusEmbed.js.map +1 -0
- package/dist/discord/renderers/statusEmbed.test.js +32 -0
- package/dist/discord/renderers/statusEmbed.test.js.map +1 -0
- package/dist/discord/renderers/streamEmbed.js +134 -0
- package/dist/discord/renderers/streamEmbed.js.map +1 -0
- package/dist/discord/renderers/streamEmbed.test.js +97 -0
- package/dist/discord/renderers/streamEmbed.test.js.map +1 -0
- package/dist/discord/renderers/toolThread.js +65 -0
- package/dist/discord/renderers/toolThread.js.map +1 -0
- package/dist/discord/renderers/toolThread.test.js +54 -0
- package/dist/discord/renderers/toolThread.test.js.map +1 -0
- package/dist/discord/renderers/transcriptFeed.js +33 -0
- package/dist/discord/renderers/transcriptFeed.js.map +1 -0
- package/dist/discord/renderers/usageEmbed.js +78 -0
- package/dist/discord/renderers/usageEmbed.js.map +1 -0
- package/dist/discord/renderers/usageEmbed.test.js +42 -0
- package/dist/discord/renderers/usageEmbed.test.js.map +1 -0
- package/dist/discord/wiring.js +214 -0
- package/dist/discord/wiring.js.map +1 -0
- package/dist/discord/wiring.test.js +252 -0
- package/dist/discord/wiring.test.js.map +1 -0
- package/dist/discord/wizard/channelWizard.js +305 -0
- package/dist/discord/wizard/channelWizard.js.map +1 -0
- package/dist/discord/wizard/channelWizard.test.js +298 -0
- package/dist/discord/wizard/channelWizard.test.js.map +1 -0
- package/dist/discord/wizard/resumeWizard.js +123 -0
- package/dist/discord/wizard/resumeWizard.js.map +1 -0
- package/dist/modes/claude/index.js +80 -0
- package/dist/modes/claude/index.js.map +1 -0
- package/dist/modes/claude/listResumable.test.js +72 -0
- package/dist/modes/claude/listResumable.test.js.map +1 -0
- package/dist/modes/claude/mcpFileTool.js +82 -0
- package/dist/modes/claude/mcpFileTool.js.map +1 -0
- package/dist/modes/claude/permissions.js +35 -0
- package/dist/modes/claude/permissions.js.map +1 -0
- package/dist/modes/claude/plugins.js +56 -0
- package/dist/modes/claude/plugins.js.map +1 -0
- package/dist/modes/claude/session.js +324 -0
- package/dist/modes/claude/session.js.map +1 -0
- package/dist/modes/claude/session.test.js +415 -0
- package/dist/modes/claude/session.test.js.map +1 -0
- package/dist/modes/codex/discovery.js +221 -0
- package/dist/modes/codex/discovery.js.map +1 -0
- package/dist/modes/codex/discovery.test.js +213 -0
- package/dist/modes/codex/discovery.test.js.map +1 -0
- package/dist/modes/codex/eventMapper.js +262 -0
- package/dist/modes/codex/eventMapper.js.map +1 -0
- package/dist/modes/codex/index.js +132 -0
- package/dist/modes/codex/index.js.map +1 -0
- package/dist/modes/codex/index.test.js +170 -0
- package/dist/modes/codex/index.test.js.map +1 -0
- package/dist/modes/codex/runner.js +207 -0
- package/dist/modes/codex/runner.js.map +1 -0
- package/dist/modes/codex/runner.test.js +587 -0
- package/dist/modes/codex/runner.test.js.map +1 -0
- package/dist/modes/codex/sqliteReader.js +171 -0
- package/dist/modes/codex/sqliteReader.js.map +1 -0
- package/dist/modes/codex/sqliteReader.test.js +137 -0
- package/dist/modes/codex/sqliteReader.test.js.map +1 -0
- package/dist/setup/wizard.js +115 -0
- package/dist/setup/wizard.js.map +1 -0
- package/dist/setup/wizard.test.js +216 -0
- package/dist/setup/wizard.test.js.map +1 -0
- package/dist/startBot.test.js +78 -0
- package/dist/startBot.test.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { readFile as realReadFile, readdir as realReaddir } from 'node:fs/promises';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
// Typed failure so discovery can distinguish "reader failed → fall back to index"
|
|
5
|
+
// from a programming error. Carries the underlying cause for logging.
|
|
6
|
+
export class CodexSqliteError extends Error {
|
|
7
|
+
cause;
|
|
8
|
+
constructor(message, cause) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.cause = cause;
|
|
11
|
+
this.name = 'CodexSqliteError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
// Lazily initialize sql.js once and reuse the engine across reads. locateFile
|
|
15
|
+
// resolves sql-wasm.wasm from the installed sql.js package's dist directory via
|
|
16
|
+
// require.resolve so it works regardless of cwd (dev, test, packaged dist).
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
function defaultSqlLoader() {
|
|
19
|
+
let cached;
|
|
20
|
+
return () => {
|
|
21
|
+
if (!cached) {
|
|
22
|
+
cached = (async () => {
|
|
23
|
+
const initSqlJs = (await import('sql.js')).default;
|
|
24
|
+
const wasmPath = require.resolve('sql.js/dist/sql-wasm.wasm');
|
|
25
|
+
return initSqlJs({ locateFile: () => wasmPath });
|
|
26
|
+
})();
|
|
27
|
+
}
|
|
28
|
+
return cached;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export class CodexSqliteReader {
|
|
32
|
+
loadSql;
|
|
33
|
+
readFile;
|
|
34
|
+
readdir;
|
|
35
|
+
constructor(options = {}) {
|
|
36
|
+
this.loadSql = options.loadSql ?? defaultSqlLoader();
|
|
37
|
+
this.readFile = options.readFile ?? ((p) => realReadFile(p));
|
|
38
|
+
this.readdir = options.readdir ?? ((dir) => realReaddir(dir));
|
|
39
|
+
}
|
|
40
|
+
// Read thread states from a specific state_*.sqlite file.
|
|
41
|
+
async readThreadStatesFromFile(dbPath) {
|
|
42
|
+
let bytes;
|
|
43
|
+
try {
|
|
44
|
+
bytes = await this.readFile(dbPath);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw new CodexSqliteError(`Failed to read Codex state db: ${dbPath}`, error);
|
|
48
|
+
}
|
|
49
|
+
let SQL;
|
|
50
|
+
try {
|
|
51
|
+
SQL = await this.loadSql();
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw new CodexSqliteError('Failed to initialize sql.js engine', error);
|
|
55
|
+
}
|
|
56
|
+
let db;
|
|
57
|
+
try {
|
|
58
|
+
db = new SQL.Database(bytes);
|
|
59
|
+
const rows = queryThreadRows(db);
|
|
60
|
+
const subAgentChildIds = querySubAgentChildIds(db);
|
|
61
|
+
return { rows, subAgentChildIds };
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
throw new CodexSqliteError(`Failed to query Codex state db: ${dbPath}`, error);
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
db?.close();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Resolve the highest state_<N>.sqlite in codexHome, then read it. Throws
|
|
71
|
+
// CodexSqliteError when no state db exists so discovery falls back to the index.
|
|
72
|
+
async readThreadStates(codexHome) {
|
|
73
|
+
const dbPath = await this.findStateDatabase(codexHome);
|
|
74
|
+
if (!dbPath) {
|
|
75
|
+
throw new CodexSqliteError(`No state_<N>.sqlite found in ${codexHome}`);
|
|
76
|
+
}
|
|
77
|
+
return this.readThreadStatesFromFile(dbPath);
|
|
78
|
+
}
|
|
79
|
+
// Pick the highest N among state_<N>.sqlite; ignores logs_*/goals_*/memories_*.
|
|
80
|
+
async findStateDatabase(codexHome) {
|
|
81
|
+
let names;
|
|
82
|
+
try {
|
|
83
|
+
names = await this.readdir(codexHome);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (isEnoent(error))
|
|
87
|
+
return null;
|
|
88
|
+
throw new CodexSqliteError(`Failed to list ${codexHome}`, error);
|
|
89
|
+
}
|
|
90
|
+
let best = null;
|
|
91
|
+
for (const name of names) {
|
|
92
|
+
const match = /^state_(\d+)\.sqlite$/.exec(name);
|
|
93
|
+
if (!match)
|
|
94
|
+
continue;
|
|
95
|
+
const version = Number.parseInt(match[1], 10);
|
|
96
|
+
if (!best || version > best.version)
|
|
97
|
+
best = { version, name };
|
|
98
|
+
}
|
|
99
|
+
return best ? path.join(codexHome, best.name) : null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// ---- SQL helpers ------------------------------------------------------------
|
|
103
|
+
// Read the `threads` rows we care about. Selects a superset of columns and reads
|
|
104
|
+
// each by name from the result so a schema that lacks an optional column (older
|
|
105
|
+
// codex) still yields the columns it does have rather than throwing.
|
|
106
|
+
function queryThreadRows(db) {
|
|
107
|
+
const result = db.exec('SELECT id, cwd, title, preview, updated_at_ms, archived, source FROM threads');
|
|
108
|
+
if (result.length === 0)
|
|
109
|
+
return [];
|
|
110
|
+
const { columns, values } = result[0];
|
|
111
|
+
const idx = (name) => columns.indexOf(name);
|
|
112
|
+
const iId = idx('id');
|
|
113
|
+
const iCwd = idx('cwd');
|
|
114
|
+
const iTitle = idx('title');
|
|
115
|
+
const iPreview = idx('preview');
|
|
116
|
+
const iUpdated = idx('updated_at_ms');
|
|
117
|
+
const iArchived = idx('archived');
|
|
118
|
+
const iSource = idx('source');
|
|
119
|
+
const rows = [];
|
|
120
|
+
for (const value of values) {
|
|
121
|
+
const id = asString(value[iId]);
|
|
122
|
+
if (!id)
|
|
123
|
+
continue;
|
|
124
|
+
const row = {
|
|
125
|
+
id,
|
|
126
|
+
archived: asNumber(value[iArchived]) === 1,
|
|
127
|
+
};
|
|
128
|
+
const cwd = asString(value[iCwd]);
|
|
129
|
+
if (cwd !== undefined)
|
|
130
|
+
row.cwd = cwd;
|
|
131
|
+
const title = asString(value[iTitle]);
|
|
132
|
+
if (title !== undefined)
|
|
133
|
+
row.title = title;
|
|
134
|
+
const preview = asString(value[iPreview]);
|
|
135
|
+
if (preview !== undefined)
|
|
136
|
+
row.preview = preview;
|
|
137
|
+
const updatedAtMs = asNumber(value[iUpdated]);
|
|
138
|
+
if (updatedAtMs !== undefined)
|
|
139
|
+
row.updatedAtMs = updatedAtMs;
|
|
140
|
+
const source = asString(value[iSource]);
|
|
141
|
+
if (source !== undefined)
|
|
142
|
+
row.source = source;
|
|
143
|
+
rows.push(row);
|
|
144
|
+
}
|
|
145
|
+
return rows;
|
|
146
|
+
}
|
|
147
|
+
function querySubAgentChildIds(db) {
|
|
148
|
+
const ids = new Set();
|
|
149
|
+
const result = db.exec('SELECT child_thread_id FROM thread_spawn_edges');
|
|
150
|
+
if (result.length === 0)
|
|
151
|
+
return ids;
|
|
152
|
+
for (const value of result[0].values) {
|
|
153
|
+
const childId = asString(value[0]);
|
|
154
|
+
if (childId)
|
|
155
|
+
ids.add(childId);
|
|
156
|
+
}
|
|
157
|
+
return ids;
|
|
158
|
+
}
|
|
159
|
+
function asString(value) {
|
|
160
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
161
|
+
}
|
|
162
|
+
function asNumber(value) {
|
|
163
|
+
return typeof value === 'number' ? value : undefined;
|
|
164
|
+
}
|
|
165
|
+
function isEnoent(error) {
|
|
166
|
+
return (typeof error === 'object' &&
|
|
167
|
+
error !== null &&
|
|
168
|
+
'code' in error &&
|
|
169
|
+
error.code === 'ENOENT');
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=sqliteReader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqliteReader.js","sourceRoot":"","sources":["../../../src/modes/codex/sqliteReader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,YAAY,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AA+BlC,kFAAkF;AAClF,sEAAsE;AACtE,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAG9B;IAFX,YACE,OAAe,EACN,KAAe;QAExB,KAAK,CAAC,OAAO,CAAC,CAAC;QAFN,UAAK,GAAL,KAAK,CAAU;QAGxB,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAeD,8EAA8E;AAC9E,gFAAgF;AAChF,4EAA4E;AAC5E,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,SAAS,gBAAgB;IACvB,IAAI,MAAwC,CAAC;IAC7C,OAAO,GAAG,EAAE;QACV,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE;gBACnB,MAAM,SAAS,GAAG,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;gBACnD,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;gBAC9D,OAAO,SAAS,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC;YACnD,CAAC,CAAC,EAAE,CAAC;QACP,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,iBAAiB;IACX,OAAO,CAAY;IACnB,QAAQ,CAAa;IACrB,OAAO,CAAY;IAEpC,YAAY,UAAoC,EAAE;QAChD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,gBAAgB,EAAE,CAAC;QACrD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7D,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,0DAA0D;IAC1D,KAAK,CAAC,wBAAwB,CAAC,MAAc;QAC3C,IAAI,KAAa,CAAC;QAClB,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACtC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,gBAAgB,CAAC,kCAAkC,MAAM,EAAE,EAAE,KAAK,CAAC,CAAC;QAChF,CAAC;QAED,IAAI,GAAgB,CAAC;QACrB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAC7B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,gBAAgB,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAC;QAC1E,CAAC;QAED,IAAI,EAAwB,CAAC;QAC7B,IAAI,CAAC;YACH,EAAE,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAC7B,MAAM,IAAI,GAAG,eAAe,CAAC,EAAE,CAAC,CAAC;YACjC,MAAM,gBAAgB,GAAG,qBAAqB,CAAC,EAAE,CAAC,CAAC;YACnD,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC;QACpC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,gBAAgB,CAAC,mCAAmC,MAAM,EAAE,EAAE,KAAK,CAAC,CAAC;QACjF,CAAC;gBAAS,CAAC;YACT,EAAE,EAAE,KAAK,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IAED,0EAA0E;IAC1E,iFAAiF;IACjF,KAAK,CAAC,gBAAgB,CAAC,SAAiB;QACtC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;QACvD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,gBAAgB,CAAC,gCAAgC,SAAS,EAAE,CAAC,CAAC;QAC1E,CAAC;QACD,OAAO,IAAI,CAAC,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAC/C,CAAC;IAED,gFAAgF;IAChF,KAAK,CAAC,iBAAiB,CAAC,SAAiB;QACvC,IAAI,KAAe,CAAC;QACpB,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACxC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,QAAQ,CAAC,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YACjC,MAAM,IAAI,gBAAgB,CAAC,kBAAkB,SAAS,EAAE,EAAE,KAAK,CAAC,CAAC;QACnE,CAAC;QAED,IAAI,IAAI,GAA6C,IAAI,CAAC;QAC1D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjD,IAAI,CAAC,KAAK;gBAAE,SAAS;YACrB,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC9C,IAAI,CAAC,IAAI,IAAI,OAAO,GAAG,IAAI,CAAC,OAAO;gBAAE,IAAI,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAChE,CAAC;QAED,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACvD,CAAC;CACF;AAED,gFAAgF;AAChF,iFAAiF;AACjF,gFAAgF;AAChF,qEAAqE;AACrE,SAAS,eAAe,CAAC,EAAY;IACnC,MAAM,MAAM,GAAG,EAAE,CAAC,IAAI,CACpB,8EAA8E,CAC/E,CAAC;IACF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEnC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;IACtB,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;IACxB,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;IAC5B,MAAM,QAAQ,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC;IAChC,MAAM,QAAQ,GAAG,GAAG,CAAC,eAAe,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;IAClC,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;IAE9B,MAAM,IAAI,GAA0B,EAAE,CAAC;IACvC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAChC,IAAI,CAAC,EAAE;YAAE,SAAS;QAClB,MAAM,GAAG,GAAwB;YAC/B,EAAE;YACF,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC;SAC3C,CAAC;QACF,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAClC,IAAI,GAAG,KAAK,SAAS;YAAE,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC;QACrC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;QACtC,IAAI,KAAK,KAAK,SAAS;YAAE,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC;QAC3C,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC1C,IAAI,OAAO,KAAK,SAAS;YAAE,GAAG,CAAC,OAAO,GAAG,OAAO,CAAC;QACjD,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC9C,IAAI,WAAW,KAAK,SAAS;YAAE,GAAG,CAAC,WAAW,GAAG,WAAW,CAAC;QAC7D,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QACxC,IAAI,MAAM,KAAK,SAAS;YAAE,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC;QAC9C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,qBAAqB,CAAC,EAAY;IACzC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,MAAM,MAAM,GAAG,EAAE,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;IACzE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC;IACpC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QACrC,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACnC,IAAI,OAAO;YAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3E,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACvD,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,MAAM,IAAI,KAAK;QACd,KAA2B,CAAC,IAAI,KAAK,QAAQ,CAC/C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
2
|
+
import initSqlJs from 'sql.js';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
|
|
5
|
+
import * as os from 'node:os';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import { CodexSqliteReader, CodexSqliteError } from './sqliteReader.js';
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
// Shared engine for both building fixtures and (injected) reading them back, so
|
|
10
|
+
// the suite never touches the real ~/.codex and never spawns a subprocess.
|
|
11
|
+
let SQL;
|
|
12
|
+
beforeAll(async () => {
|
|
13
|
+
const wasmPath = require.resolve('sql.js/dist/sql-wasm.wasm');
|
|
14
|
+
SQL = await initSqlJs({ locateFile: () => wasmPath });
|
|
15
|
+
});
|
|
16
|
+
// Build a state_*.sqlite fixture with the columns discovery keys on, plus a
|
|
17
|
+
// spawn edge for the sub-agent thread. Returns the raw db bytes.
|
|
18
|
+
function buildFixtureBytes() {
|
|
19
|
+
const db = new SQL.Database();
|
|
20
|
+
db.run(`CREATE TABLE threads (
|
|
21
|
+
id TEXT, cwd TEXT, title TEXT, preview TEXT,
|
|
22
|
+
updated_at_ms INTEGER, updated_at TEXT,
|
|
23
|
+
archived INTEGER, sandbox_policy TEXT, approval_mode TEXT,
|
|
24
|
+
source TEXT, thread_source TEXT
|
|
25
|
+
);
|
|
26
|
+
CREATE TABLE thread_spawn_edges (child_thread_id TEXT);`);
|
|
27
|
+
// A normal, resumable user thread.
|
|
28
|
+
db.run(`INSERT INTO threads (id, cwd, title, preview, updated_at_ms, archived, source, thread_source)
|
|
29
|
+
VALUES ('user-1', '/work/user', 'User Title', 'preview text', 2000, 0, 'cli', 'user');`);
|
|
30
|
+
// An archived thread.
|
|
31
|
+
db.run(`INSERT INTO threads (id, cwd, title, updated_at_ms, archived, source)
|
|
32
|
+
VALUES ('archived-1', '/work/archived', 'Archived', 1000, 1, 'cli');`);
|
|
33
|
+
// A sub-agent thread (source is a {"subagent":…} blob AND it is a spawn child).
|
|
34
|
+
db.run(`INSERT INTO threads (id, cwd, updated_at_ms, archived, source)
|
|
35
|
+
VALUES ('sub-1', '/work/sub', 1500, 0, '{"subagent":{"parent":"user-1"}}');`);
|
|
36
|
+
db.run(`INSERT INTO thread_spawn_edges (child_thread_id) VALUES ('sub-1');`);
|
|
37
|
+
// A non-interactive exec-source thread.
|
|
38
|
+
db.run(`INSERT INTO threads (id, cwd, updated_at_ms, archived, source)
|
|
39
|
+
VALUES ('exec-1', '/work/exec', 1800, 0, 'exec');`);
|
|
40
|
+
const bytes = db.export();
|
|
41
|
+
db.close();
|
|
42
|
+
return bytes;
|
|
43
|
+
}
|
|
44
|
+
async function withTempDir(fn) {
|
|
45
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), 'dab-sqlite-'));
|
|
46
|
+
try {
|
|
47
|
+
return await fn(dir);
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
await rm(dir, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// A reader wired to the shared, already-initialized engine (no WASM reload).
|
|
54
|
+
function makeReader() {
|
|
55
|
+
return new CodexSqliteReader({ loadSql: async () => SQL });
|
|
56
|
+
}
|
|
57
|
+
describe('CodexSqliteReader.readThreadStatesFromFile', () => {
|
|
58
|
+
it('returns the thread rows and the sub-agent child id set', async () => {
|
|
59
|
+
await withTempDir(async (dir) => {
|
|
60
|
+
const dbPath = path.join(dir, 'state_1.sqlite');
|
|
61
|
+
await writeFile(dbPath, Buffer.from(buildFixtureBytes()));
|
|
62
|
+
const { rows, subAgentChildIds } = await makeReader().readThreadStatesFromFile(dbPath);
|
|
63
|
+
const byId = new Map(rows.map((r) => [r.id, r]));
|
|
64
|
+
expect([...byId.keys()].sort()).toEqual(['archived-1', 'exec-1', 'sub-1', 'user-1']);
|
|
65
|
+
const user = byId.get('user-1');
|
|
66
|
+
expect(user).toMatchObject({
|
|
67
|
+
id: 'user-1',
|
|
68
|
+
cwd: '/work/user',
|
|
69
|
+
title: 'User Title',
|
|
70
|
+
preview: 'preview text',
|
|
71
|
+
updatedAtMs: 2000,
|
|
72
|
+
archived: false,
|
|
73
|
+
source: 'cli',
|
|
74
|
+
});
|
|
75
|
+
expect(byId.get('archived-1').archived).toBe(true);
|
|
76
|
+
expect(byId.get('sub-1').source).toBe('{"subagent":{"parent":"user-1"}}');
|
|
77
|
+
expect(byId.get('exec-1').source).toBe('exec');
|
|
78
|
+
expect([...subAgentChildIds]).toEqual(['sub-1']);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
it('throws CodexSqliteError on a missing file', async () => {
|
|
82
|
+
await withTempDir(async (dir) => {
|
|
83
|
+
const missing = path.join(dir, 'state_9.sqlite');
|
|
84
|
+
await expect(makeReader().readThreadStatesFromFile(missing)).rejects.toBeInstanceOf(CodexSqliteError);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
it('throws CodexSqliteError on a corrupt db', async () => {
|
|
88
|
+
await withTempDir(async (dir) => {
|
|
89
|
+
const bad = path.join(dir, 'state_1.sqlite');
|
|
90
|
+
await writeFile(bad, Buffer.from('not a sqlite database at all', 'utf8'));
|
|
91
|
+
await expect(makeReader().readThreadStatesFromFile(bad)).rejects.toBeInstanceOf(CodexSqliteError);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
describe('CodexSqliteReader.findStateDatabase', () => {
|
|
96
|
+
it('picks the highest state_<N>.sqlite and ignores other db families', async () => {
|
|
97
|
+
await withTempDir(async (dir) => {
|
|
98
|
+
const empty = Buffer.from(buildFixtureBytes());
|
|
99
|
+
await Promise.all([
|
|
100
|
+
writeFile(path.join(dir, 'state_1.sqlite'), empty),
|
|
101
|
+
writeFile(path.join(dir, 'state_2.sqlite'), empty),
|
|
102
|
+
writeFile(path.join(dir, 'state_10.sqlite'), empty),
|
|
103
|
+
writeFile(path.join(dir, 'logs_99.sqlite'), empty),
|
|
104
|
+
writeFile(path.join(dir, 'goals_5.sqlite'), empty),
|
|
105
|
+
writeFile(path.join(dir, 'memories_7.sqlite'), empty),
|
|
106
|
+
]);
|
|
107
|
+
const picked = await makeReader().findStateDatabase(dir);
|
|
108
|
+
expect(picked).toBe(path.join(dir, 'state_10.sqlite'));
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
it('returns null when no state db exists', async () => {
|
|
112
|
+
await withTempDir(async (dir) => {
|
|
113
|
+
expect(await makeReader().findStateDatabase(dir)).toBeNull();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
it('returns null when codexHome is absent (ENOENT)', async () => {
|
|
117
|
+
const picked = await makeReader().findStateDatabase(path.join(os.tmpdir(), 'dab-does-not-exist-xyz'));
|
|
118
|
+
expect(picked).toBeNull();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe('CodexSqliteReader.readThreadStates', () => {
|
|
122
|
+
it('resolves the highest state db under codexHome and reads it', async () => {
|
|
123
|
+
await withTempDir(async (dir) => {
|
|
124
|
+
const bytes = Buffer.from(buildFixtureBytes());
|
|
125
|
+
await writeFile(path.join(dir, 'state_1.sqlite'), bytes);
|
|
126
|
+
await writeFile(path.join(dir, 'state_3.sqlite'), bytes);
|
|
127
|
+
const { rows } = await makeReader().readThreadStates(dir);
|
|
128
|
+
expect(rows.map((r) => r.id).sort()).toEqual(['archived-1', 'exec-1', 'sub-1', 'user-1']);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
it('throws CodexSqliteError when no state db is present', async () => {
|
|
132
|
+
await withTempDir(async (dir) => {
|
|
133
|
+
await expect(makeReader().readThreadStates(dir)).rejects.toBeInstanceOf(CodexSqliteError);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
//# sourceMappingURL=sqliteReader.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqliteReader.test.js","sourceRoot":"","sources":["../../../src/modes/codex/sqliteReader.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Interactive first-run config (token + Client ID only) via @inquirer/prompts
|
|
2
|
+
// (§4, §8). Writes config.json through ConfigStore (0600).
|
|
3
|
+
//
|
|
4
|
+
// Neither role tiers NOR defaults are set here: the terminal step needs only the
|
|
5
|
+
// secret (the bot token, which must be pasted in the terminal, never through
|
|
6
|
+
// Discord) plus the Client ID and the Message Content Intent confirmation. Role
|
|
7
|
+
// allowlists are left empty (deny-by-default) and every default (Claude model,
|
|
8
|
+
// codexHome, locale, permission mode) comes from CONFIG_DEFAULTS. The operator
|
|
9
|
+
// adjusts roles AND defaults AFTER inviting the bot, in Discord via the `/config`
|
|
10
|
+
// command (clicking role names / picking from menus — no IDs, no retyping). §7.1.
|
|
11
|
+
//
|
|
12
|
+
// Everything the wizard touches — the prompt functions, the browser opener, the
|
|
13
|
+
// output sink, the ConfigStore, and the logger — is injectable through `deps` so
|
|
14
|
+
// tests never block on real stdin or open a real browser (they feed scripted
|
|
15
|
+
// answers and assert against a temp-dir ConfigStore). The defaults below wire the
|
|
16
|
+
// real @inquirer/prompts + open + ConfigStore for the actual CLI path.
|
|
17
|
+
import { input, password, confirm } from '@inquirer/prompts';
|
|
18
|
+
import open from 'open';
|
|
19
|
+
import { PermissionFlagsBits } from 'discord.js';
|
|
20
|
+
import { ConfigStore } from '../core/config.js';
|
|
21
|
+
import { CONFIG_DEFAULTS, CONFIG_VERSION } from '../core/configSchema.js';
|
|
22
|
+
import { createLogger } from '../core/logger.js';
|
|
23
|
+
import { setLocale, t } from '../discord/i18n.js';
|
|
24
|
+
// The bot's required permission bitfield, computed from the exact permission set
|
|
25
|
+
// documented in README ("초대 링크 만들기" → Bot Permissions). Kept as the discord.js
|
|
26
|
+
// PermissionFlagsBits so the bits stay authoritative if Discord renumbers them.
|
|
27
|
+
const INVITE_PERMISSION_BITS = PermissionFlagsBits.ManageChannels |
|
|
28
|
+
PermissionFlagsBits.SendMessages |
|
|
29
|
+
PermissionFlagsBits.EmbedLinks |
|
|
30
|
+
PermissionFlagsBits.AttachFiles |
|
|
31
|
+
PermissionFlagsBits.ReadMessageHistory |
|
|
32
|
+
PermissionFlagsBits.CreatePublicThreads |
|
|
33
|
+
PermissionFlagsBits.SendMessagesInThreads |
|
|
34
|
+
PermissionFlagsBits.ManageThreads |
|
|
35
|
+
PermissionFlagsBits.AddReactions;
|
|
36
|
+
const DEVELOPER_PORTAL_URL = 'https://discord.com/developers/applications';
|
|
37
|
+
// Build the OAuth2 bot-invite URL for the given client id with the bot +
|
|
38
|
+
// applications.commands scopes and the required permission bitfield (§ README).
|
|
39
|
+
export function buildInviteUrl(clientId) {
|
|
40
|
+
const permissions = INVITE_PERMISSION_BITS.toString();
|
|
41
|
+
return ('https://discord.com/api/oauth2/authorize' +
|
|
42
|
+
`?client_id=${encodeURIComponent(clientId)}` +
|
|
43
|
+
'&scope=bot%20applications.commands' +
|
|
44
|
+
`&permissions=${permissions}`);
|
|
45
|
+
}
|
|
46
|
+
const defaultPrompts = { input, password, confirm };
|
|
47
|
+
export async function runSetup(deps = {}) {
|
|
48
|
+
const prompts = deps.prompts ?? defaultPrompts;
|
|
49
|
+
const openUrl = deps.open ?? open;
|
|
50
|
+
const store = deps.store ?? new ConfigStore();
|
|
51
|
+
const logger = deps.logger ?? createLogger('setup');
|
|
52
|
+
const log = deps.log ?? ((m) => console.log(m));
|
|
53
|
+
log('=== discord-agent-bridge 셋업 ===\n');
|
|
54
|
+
// Step 1 — bot token (masked; never echoed back in plaintext).
|
|
55
|
+
log('1단계 — Discord 봇 토큰');
|
|
56
|
+
log(` ${DEVELOPER_PORTAL_URL} 의 애플리케이션 → Bot 탭에서 토큰을 복사하세요.`);
|
|
57
|
+
log(' ⚠️ 토큰은 비밀번호입니다. 노출되면 즉시 Reset Token 으로 재발급하세요.');
|
|
58
|
+
const token = await prompts.password({
|
|
59
|
+
message: '봇 토큰을 입력하세요:',
|
|
60
|
+
mask: '*',
|
|
61
|
+
validate: (v) => (v.trim().length > 0 ? true : '토큰은 필수입니다.'),
|
|
62
|
+
});
|
|
63
|
+
// Step 2 — Client ID (Application ID).
|
|
64
|
+
log('\n2단계 — Client ID (Application ID)');
|
|
65
|
+
log(' OAuth2 탭(또는 General Information)에서 Client ID 를 복사하세요.');
|
|
66
|
+
const clientId = await prompts.input({
|
|
67
|
+
message: 'Client ID 를 입력하세요:',
|
|
68
|
+
validate: (v) => (v.trim().length > 0 ? true : 'Client ID 는 필수입니다.'),
|
|
69
|
+
});
|
|
70
|
+
// Step 3 — Message Content Intent reminder.
|
|
71
|
+
log('\n3단계 — Message Content Intent');
|
|
72
|
+
log(' Bot 탭 → Privileged Gateway Intents 에서 MESSAGE CONTENT INTENT 를 켜고 저장하세요.');
|
|
73
|
+
log(' (켜져 있지 않으면 봇이 메시지 내용을 읽지 못합니다.)');
|
|
74
|
+
await prompts.confirm({
|
|
75
|
+
message: 'Message Content Intent 를 켰나요?',
|
|
76
|
+
default: true,
|
|
77
|
+
});
|
|
78
|
+
// Role tiers are NOT prompted here. They are left empty (deny-by-default) and
|
|
79
|
+
// configured in Discord via `/config` after the bot is invited (§7.1). Defaults
|
|
80
|
+
// (model, codexHome, locale, permission mode) are NOT prompted either — they all
|
|
81
|
+
// come from CONFIG_DEFAULTS and are set in Discord `/config`. The terminal step
|
|
82
|
+
// needs only the secret (the token) plus the Client ID + intent confirmation, so
|
|
83
|
+
// nothing sensitive or fiddly is typed twice. Guidance is printed after the write.
|
|
84
|
+
// Step 4 — invite URL: print, then open in the browser (skipped/mocked in tests).
|
|
85
|
+
const inviteUrl = buildInviteUrl(clientId.trim());
|
|
86
|
+
log('\n4단계 — 봇 초대 링크');
|
|
87
|
+
log(` ${inviteUrl}`);
|
|
88
|
+
log(' 브라우저에서 이 링크를 열어 내 서버에 봇을 초대하세요.');
|
|
89
|
+
try {
|
|
90
|
+
await openUrl(inviteUrl);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
log(' 브라우저를 열지 못했습니다. 위 링크를 직접 방문하세요.');
|
|
94
|
+
}
|
|
95
|
+
// Step 5 — write config.json (0600) straight from CONFIG_DEFAULTS + the secret.
|
|
96
|
+
// Role allowlists are left EMPTY (from CONFIG_DEFAULTS.auth): deny-by-default until
|
|
97
|
+
// an admin sets them in Discord via `/config` (roles are per-server, §7.1). Every
|
|
98
|
+
// default (model, codexHome, locale, permission mode) is CONFIG_DEFAULTS and is
|
|
99
|
+
// adjusted later in Discord `/config`, not here.
|
|
100
|
+
const config = {
|
|
101
|
+
...CONFIG_DEFAULTS,
|
|
102
|
+
version: CONFIG_VERSION,
|
|
103
|
+
discord: { token: token.trim(), clientId: clientId.trim() },
|
|
104
|
+
};
|
|
105
|
+
store.save(config);
|
|
106
|
+
logger.info('setup wrote config', { path: store.configPath, clientId: config.discord.clientId });
|
|
107
|
+
log(`\n설정을 저장했어요: ${store.configPath} (권한 600)`);
|
|
108
|
+
// Guidance: roles AND defaults move from the terminal to Discord's `/config`
|
|
109
|
+
// (§7.1). Render in the config's (default) locale so the message language matches.
|
|
110
|
+
setLocale(CONFIG_DEFAULTS.locale);
|
|
111
|
+
log(`\n${t('setup.rolesInDiscord')}`);
|
|
112
|
+
log(t('setup.defaultsInDiscord'));
|
|
113
|
+
log('이제 `node dist/cli.js` 로 봇을 실행하세요.');
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=wizard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wizard.js","sourceRoot":"","sources":["../../src/setup/wizard.ts"],"names":[],"mappings":"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"}
|