opencode-tbot 0.1.8 → 0.1.10
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 +4 -4
- package/README.zh-CN.md +3 -3
- package/dist/cli.js +130 -24
- package/dist/cli.js.map +1 -1
- package/dist/plugin.js +253 -70
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ A Telegram plugin for driving [OpenCode](https://opencode.ai) from chat.
|
|
|
22
22
|
Run:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
npm
|
|
25
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- install
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
The installer registers the plugin globally and writes the default runtime config.
|
|
@@ -30,16 +30,16 @@ The installer registers the plugin globally and writes the default runtime confi
|
|
|
30
30
|
Check the installed CLI version:
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
npm exec opencode-tbot -- --version
|
|
33
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- --version
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
Update the registered npm plugin spec in OpenCode:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
npm exec opencode-tbot -- update
|
|
39
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- update
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Remove
|
|
42
|
+
Remove a legacy local npm install if OpenCode is showing the plugin as a `file:///.../node_modules/...` path:
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
45
|
npm uninstall opencode-tbot
|
package/README.zh-CN.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
执行:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
npm
|
|
25
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- install
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
安装器会注册全局插件并写入默认运行时配置。
|
|
@@ -30,13 +30,13 @@ npm install opencode-tbot@latest
|
|
|
30
30
|
查看已安装的 CLI 版本:
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
npm exec opencode-tbot -- --version
|
|
33
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- --version
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
更新 OpenCode 中注册的 npm 插件 spec:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
npm exec opencode-tbot -- update
|
|
39
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- update
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
从当前环境卸载 npm 包:
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { a as writePluginConfigFile, n as getOpenCodeConfigFilePath, o as OPENCODE_TBOT_VERSION, r as mergePluginConfigSources, t as getGlobalPluginConfigFilePath } from "./assets/plugin-config-CGIe9zdA.js";
|
|
2
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
-
import { dirname } from "node:path";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { stderr, stdin, stdout } from "node:process";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
7
|
import { parse } from "jsonc-parser";
|
|
8
8
|
//#region src/cli.ts
|
|
9
9
|
var DEFAULT_PLUGIN_SPEC = "opencode-tbot@latest";
|
|
10
|
+
var PROMPT_CANCELLED_ERROR = "Prompt cancelled.";
|
|
10
11
|
async function main(argv = process.argv.slice(2)) {
|
|
11
12
|
try {
|
|
12
13
|
const exitCode = await runCli(argv);
|
|
@@ -43,8 +44,8 @@ async function installPlugin(options = {}) {
|
|
|
43
44
|
const existingPluginConfig = await readPluginConfigFile(globalPluginConfigFilePath);
|
|
44
45
|
const prompt = createPromptSession();
|
|
45
46
|
try {
|
|
46
|
-
const botToken = normalizeRequiredString(options.botToken ?? await prompt.
|
|
47
|
-
const openrouterApiKey = options.enableVoice ?? await prompt.confirm("Enable voice transcription? (y/N): ", false) ? normalizeRequiredString(options.openrouterApiKey ?? await prompt.
|
|
47
|
+
const botToken = normalizeRequiredString(options.botToken ?? await prompt.askSecret("Telegram bot token: "), "Telegram bot token is required.");
|
|
48
|
+
const openrouterApiKey = options.enableVoice ?? await prompt.confirm("Enable voice transcription? (y/N): ", false) ? normalizeRequiredString(options.openrouterApiKey ?? await prompt.askSecret("OpenRouter API key: "), "OpenRouter API key is required when voice transcription is enabled.") : null;
|
|
48
49
|
const telegramApiRoot = normalizeOptionalString(options.telegramApiRoot) ?? "https://api.telegram.org";
|
|
49
50
|
const pluginSpec = normalizeOptionalString(options.pluginSpec) ?? DEFAULT_PLUGIN_SPEC;
|
|
50
51
|
const nextOpenCodeConfig = options.registerPlugin === false ? openCodeConfig : ensurePluginRegistered(openCodeConfig, pluginSpec);
|
|
@@ -52,19 +53,18 @@ async function installPlugin(options = {}) {
|
|
|
52
53
|
if (options.registerPlugin === false) await ensureParentDirectory(openCodeConfigFilePath);
|
|
53
54
|
else await writeJsonFile(openCodeConfigFilePath, nextOpenCodeConfig);
|
|
54
55
|
await writePluginConfigFile(globalPluginConfigFilePath, nextPluginConfig);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
stdout.write(`Wrote plugin defaults to ${globalPluginConfigFilePath}\n`);
|
|
56
|
+
stdout.write("Success.\n");
|
|
57
|
+
await warnAboutLegacyLocalInstallations(homeDir);
|
|
58
58
|
} finally {
|
|
59
59
|
prompt.close();
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
async function updatePlugin(options = {}) {
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
const homeDir = options.homeDir ?? homedir();
|
|
64
|
+
const openCodeConfigFilePath = getOpenCodeConfigFilePath(homeDir);
|
|
65
|
+
await writeJsonFile(openCodeConfigFilePath, replacePluginRegistration(await readJsoncObject(openCodeConfigFilePath), normalizeOptionalString(options.pluginSpec) ?? DEFAULT_PLUGIN_SPEC));
|
|
66
|
+
stdout.write("Success.\n");
|
|
67
|
+
await warnAboutLegacyLocalInstallations(homeDir);
|
|
68
68
|
}
|
|
69
69
|
function parseCliOptions(argv) {
|
|
70
70
|
const args = [...argv];
|
|
@@ -186,34 +186,108 @@ async function writeJsonFile(filePath, value) {
|
|
|
186
186
|
async function ensureParentDirectory(filePath) {
|
|
187
187
|
await mkdir(dirname(filePath), { recursive: true });
|
|
188
188
|
}
|
|
189
|
-
function createPromptSession() {
|
|
190
|
-
|
|
189
|
+
function createPromptSession(options = {}) {
|
|
190
|
+
const input = options.input ?? stdin;
|
|
191
|
+
const output = options.output ?? stdout;
|
|
192
|
+
if (!input.isTTY || !output.isTTY) return {
|
|
191
193
|
ask: async () => "",
|
|
194
|
+
askSecret: async () => "",
|
|
192
195
|
async confirm(_question, defaultValue) {
|
|
193
196
|
return defaultValue;
|
|
194
197
|
},
|
|
195
198
|
close() {}
|
|
196
199
|
};
|
|
197
|
-
const readline = createInterface({
|
|
198
|
-
input: stdin,
|
|
199
|
-
output: stdout
|
|
200
|
-
});
|
|
201
200
|
return {
|
|
202
201
|
ask(question) {
|
|
203
|
-
return
|
|
202
|
+
return askQuestion(input, output, question);
|
|
203
|
+
},
|
|
204
|
+
askSecret(question) {
|
|
205
|
+
return askSecretQuestion(input, output, question);
|
|
204
206
|
},
|
|
205
207
|
async confirm(question, defaultValue) {
|
|
206
|
-
const answer = normalizeOptionalString(await
|
|
208
|
+
const answer = normalizeOptionalString(await askQuestion(input, output, question));
|
|
207
209
|
if (!answer) return defaultValue;
|
|
208
210
|
if (["y", "yes"].includes(answer.toLowerCase())) return true;
|
|
209
211
|
if (["n", "no"].includes(answer.toLowerCase())) return false;
|
|
210
212
|
throw new Error(`Unsupported answer: ${answer}`);
|
|
211
213
|
},
|
|
212
|
-
close() {
|
|
213
|
-
readline.close();
|
|
214
|
-
}
|
|
214
|
+
close() {}
|
|
215
215
|
};
|
|
216
216
|
}
|
|
217
|
+
async function askQuestion(input, output, question) {
|
|
218
|
+
const readline = createInterface({
|
|
219
|
+
input,
|
|
220
|
+
output
|
|
221
|
+
});
|
|
222
|
+
try {
|
|
223
|
+
return await readline.question(question);
|
|
224
|
+
} finally {
|
|
225
|
+
readline.close();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async function askSecretQuestion(input, output, question) {
|
|
229
|
+
if (typeof input.setRawMode !== "function") return askQuestion(input, output, question);
|
|
230
|
+
output.write(question);
|
|
231
|
+
try {
|
|
232
|
+
return await readMaskedInput(input, output);
|
|
233
|
+
} finally {
|
|
234
|
+
output.write("\n");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async function readMaskedInput(input, output) {
|
|
238
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
239
|
+
const buffer = [];
|
|
240
|
+
let settled = false;
|
|
241
|
+
const cleanup = () => {
|
|
242
|
+
input.off("data", handleData);
|
|
243
|
+
input.off("error", handleError);
|
|
244
|
+
input.pause();
|
|
245
|
+
input.setRawMode?.(false);
|
|
246
|
+
};
|
|
247
|
+
const rejectWith = (error) => {
|
|
248
|
+
if (settled) return;
|
|
249
|
+
settled = true;
|
|
250
|
+
cleanup();
|
|
251
|
+
rejectPromise(error);
|
|
252
|
+
};
|
|
253
|
+
const resolveWith = (value) => {
|
|
254
|
+
if (settled) return;
|
|
255
|
+
settled = true;
|
|
256
|
+
cleanup();
|
|
257
|
+
resolvePromise(value);
|
|
258
|
+
};
|
|
259
|
+
const handleError = (error) => {
|
|
260
|
+
rejectWith(error);
|
|
261
|
+
};
|
|
262
|
+
const handleData = (chunk) => {
|
|
263
|
+
const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk;
|
|
264
|
+
for (const character of text) {
|
|
265
|
+
if (character === "\r" || character === "\n") {
|
|
266
|
+
resolveWith(buffer.join(""));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (character === "") {
|
|
270
|
+
rejectWith(new Error(PROMPT_CANCELLED_ERROR));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (character === "\b" || character === "") {
|
|
274
|
+
if (buffer.length > 0) {
|
|
275
|
+
buffer.pop();
|
|
276
|
+
output.write("\b \b");
|
|
277
|
+
}
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (character < " " || character === "") continue;
|
|
281
|
+
buffer.push(character);
|
|
282
|
+
output.write("*");
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
input.setRawMode?.(true);
|
|
286
|
+
input.resume();
|
|
287
|
+
input.on("error", handleError);
|
|
288
|
+
input.on("data", handleData);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
217
291
|
function normalizeOptionalString(value) {
|
|
218
292
|
const normalized = value?.trim();
|
|
219
293
|
return normalized ? normalized : null;
|
|
@@ -233,11 +307,43 @@ function isOpencodeTbotPluginSpec(value) {
|
|
|
233
307
|
const normalized = value.trim();
|
|
234
308
|
return normalized === "opencode-tbot" || normalized.startsWith("opencode-tbot@");
|
|
235
309
|
}
|
|
310
|
+
async function warnAboutLegacyLocalInstallations(homeDir) {
|
|
311
|
+
const legacyInstallations = await findLegacyLocalInstallations(homeDir);
|
|
312
|
+
if (legacyInstallations.length === 0) return;
|
|
313
|
+
stdout.write("\nDetected local opencode-tbot npm installation(s) that can make OpenCode show the plugin as file:///.../node_modules/...\n");
|
|
314
|
+
for (const installation of legacyInstallations) {
|
|
315
|
+
stdout.write(`- ${installation.packagePath}\n`);
|
|
316
|
+
stdout.write(` cleanup: cd "${installation.rootDir}" && npm uninstall opencode-tbot\n`);
|
|
317
|
+
}
|
|
318
|
+
stdout.write("Recommended npm flow: npm exec --package opencode-tbot@latest opencode-tbot -- install\n");
|
|
319
|
+
}
|
|
320
|
+
async function findLegacyLocalInstallations(homeDir) {
|
|
321
|
+
const roots = [...new Set([resolve(process.cwd()), resolve(homeDir)])];
|
|
322
|
+
const installations = [];
|
|
323
|
+
for (const rootDir of roots) if (await pathExists(join(rootDir, "node_modules", "opencode-tbot", "package.json"))) installations.push({
|
|
324
|
+
packagePath: join(rootDir, "node_modules", "opencode-tbot"),
|
|
325
|
+
rootDir
|
|
326
|
+
});
|
|
327
|
+
return installations;
|
|
328
|
+
}
|
|
329
|
+
async function pathExists(filePath) {
|
|
330
|
+
try {
|
|
331
|
+
await stat(filePath);
|
|
332
|
+
return true;
|
|
333
|
+
} catch {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
236
337
|
function buildHelpText() {
|
|
237
338
|
return [
|
|
238
339
|
"Usage: opencode-tbot [install|update] [options]",
|
|
239
340
|
" opencode-tbot --version",
|
|
240
341
|
"",
|
|
342
|
+
"Recommended npm usage:",
|
|
343
|
+
" npm exec --package opencode-tbot@latest opencode-tbot -- install",
|
|
344
|
+
" npm exec --package opencode-tbot@latest opencode-tbot -- update",
|
|
345
|
+
" npm exec --package opencode-tbot@latest opencode-tbot -- --version",
|
|
346
|
+
"",
|
|
241
347
|
"Commands:",
|
|
242
348
|
" install",
|
|
243
349
|
" update",
|
|
@@ -259,6 +365,6 @@ function formatCliError(error) {
|
|
|
259
365
|
return error instanceof Error && error.message.trim().length > 0 ? error.message.trim() : String(error);
|
|
260
366
|
}
|
|
261
367
|
//#endregion
|
|
262
|
-
export { installPlugin, main, runCli, updatePlugin };
|
|
368
|
+
export { createPromptSession, installPlugin, main, runCli, updatePlugin };
|
|
263
369
|
|
|
264
370
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname } from \"node:path\";\nimport { stderr, stdin, stdout } from \"node:process\";\nimport { createInterface } from \"node:readline/promises\";\nimport { parse } from \"jsonc-parser\";\nimport {\n DEFAULT_TELEGRAM_API_ROOT,\n type PluginConfigSource,\n} from \"./app/config.js\";\nimport { OPENCODE_TBOT_VERSION } from \"./app/package-info.js\";\nimport {\n getGlobalPluginConfigFilePath,\n getOpenCodeConfigFilePath,\n mergePluginConfigSources,\n writePluginConfigFile,\n} from \"./app/plugin-config.js\";\n\nexport interface InstallCommandOptions {\n botToken?: string;\n enableVoice?: boolean;\n homeDir?: string;\n openrouterApiKey?: string;\n pluginSpec?: string;\n registerPlugin?: boolean;\n telegramApiRoot?: string;\n}\n\ninterface CliOptions extends InstallCommandOptions {\n command: \"help\" | \"install\" | \"update\" | \"version\";\n}\n\ninterface OpenCodeGlobalConfig {\n plugin?: string[];\n [key: string]: unknown;\n}\n\nconst DEFAULT_PLUGIN_SPEC = \"opencode-tbot@latest\";\n\nexport async function main(argv: string[] = process.argv.slice(2)): Promise<number> {\n try {\n const exitCode = await runCli(argv);\n process.exitCode = exitCode;\n\n return exitCode;\n } catch (error) {\n stderr.write(`${formatCliError(error)}\\n`);\n process.exitCode = 1;\n\n return 1;\n }\n}\n\nexport async function runCli(argv: string[]): Promise<number> {\n const options = parseCliOptions(argv);\n\n if (options.command === \"help\") {\n stdout.write(`${buildHelpText()}\\n`);\n return 0;\n }\n\n if (options.command === \"version\") {\n stdout.write(`${OPENCODE_TBOT_VERSION}\\n`);\n return 0;\n }\n\n if (options.command === \"update\") {\n await updatePlugin(options);\n return 0;\n }\n\n await installPlugin(options);\n return 0;\n}\n\nexport async function installPlugin(options: InstallCommandOptions = {}): Promise<void> {\n const homeDir = options.homeDir ?? homedir();\n const openCodeConfigFilePath = getOpenCodeConfigFilePath(homeDir);\n const globalPluginConfigFilePath = getGlobalPluginConfigFilePath(homeDir);\n const openCodeConfig = await readJsoncObject<OpenCodeGlobalConfig>(openCodeConfigFilePath);\n const existingPluginConfig = await readPluginConfigFile(globalPluginConfigFilePath);\n const prompt = createPromptSession();\n\n try {\n const botToken = normalizeRequiredString(\n options.botToken ?? await prompt.ask(\"Telegram bot token: \"),\n \"Telegram bot token is required.\",\n );\n const enableVoice = options.enableVoice ?? await prompt.confirm(\n \"Enable voice transcription? (y/N): \",\n false,\n );\n const openrouterApiKey = enableVoice\n ? normalizeRequiredString(\n options.openrouterApiKey ?? await prompt.ask(\"OpenRouter API key: \"),\n \"OpenRouter API key is required when voice transcription is enabled.\",\n )\n : null;\n const telegramApiRoot = normalizeOptionalString(options.telegramApiRoot) ?? DEFAULT_TELEGRAM_API_ROOT;\n const pluginSpec = normalizeOptionalString(options.pluginSpec) ?? DEFAULT_PLUGIN_SPEC;\n const nextOpenCodeConfig = options.registerPlugin === false\n ? openCodeConfig\n : ensurePluginRegistered(openCodeConfig, pluginSpec);\n const nextPluginConfig = buildInstalledPluginConfig(\n existingPluginConfig,\n botToken,\n telegramApiRoot,\n openrouterApiKey,\n );\n\n if (options.registerPlugin === false) {\n await ensureParentDirectory(openCodeConfigFilePath);\n } else {\n await writeJsonFile(openCodeConfigFilePath, nextOpenCodeConfig);\n }\n await writePluginConfigFile(globalPluginConfigFilePath, nextPluginConfig);\n\n if (options.registerPlugin === false) {\n stdout.write(`Skipped plugin registration in ${openCodeConfigFilePath}\\n`);\n } else {\n stdout.write(`Registered ${pluginSpec} in ${openCodeConfigFilePath}\\n`);\n }\n stdout.write(`Wrote plugin defaults to ${globalPluginConfigFilePath}\\n`);\n } finally {\n prompt.close();\n }\n}\n\nexport async function updatePlugin(options: Pick<InstallCommandOptions, \"homeDir\" | \"pluginSpec\"> = {}): Promise<void> {\n const homeDir = options.homeDir ?? homedir();\n const openCodeConfigFilePath = getOpenCodeConfigFilePath(homeDir);\n const openCodeConfig = await readJsoncObject<OpenCodeGlobalConfig>(openCodeConfigFilePath);\n const pluginSpec = normalizeOptionalString(options.pluginSpec) ?? DEFAULT_PLUGIN_SPEC;\n const nextOpenCodeConfig = replacePluginRegistration(openCodeConfig, pluginSpec);\n\n await writeJsonFile(openCodeConfigFilePath, nextOpenCodeConfig);\n stdout.write(`Updated plugin registration to ${pluginSpec} in ${openCodeConfigFilePath}\\n`);\n}\n\nfunction parseCliOptions(argv: string[]): CliOptions {\n const args = [...argv];\n const first = args[0];\n const command = !first || first.startsWith(\"-\")\n ? \"install\"\n : first;\n const values = command === \"install\" || command === \"version\"\n ? args\n : args.slice(1);\n const options: CliOptions = {\n command: command === \"help\" || command === \"--help\" || command === \"-h\"\n ? \"help\"\n : command === \"version\" || command === \"--version\" || command === \"-v\"\n ? \"version\"\n : command === \"update\"\n ? \"update\"\n : \"install\",\n };\n\n for (let index = 0; index < values.length; index += 1) {\n const value = values[index];\n\n if (index === 0 && !value.startsWith(\"-\")) {\n continue;\n }\n\n switch (value) {\n case \"--bot-token\":\n options.botToken = values[++index];\n break;\n case \"--enable-voice\":\n options.enableVoice = true;\n break;\n case \"--disable-voice\":\n options.enableVoice = false;\n break;\n case \"--openrouter-api-key\":\n options.openrouterApiKey = values[++index];\n break;\n case \"--plugin-spec\":\n options.pluginSpec = values[++index];\n break;\n case \"--telegram-api-root\":\n options.telegramApiRoot = values[++index];\n break;\n case \"--skip-register\":\n options.registerPlugin = false;\n break;\n case \"--home-dir\":\n options.homeDir = values[++index];\n break;\n case \"--help\":\n case \"-h\":\n options.command = \"help\";\n break;\n case \"--version\":\n case \"-v\":\n options.command = \"version\";\n break;\n default:\n throw new Error(`Unknown argument: ${value}`);\n }\n }\n\n return options;\n}\n\nfunction buildInstalledPluginConfig(\n current: PluginConfigSource,\n botToken: string,\n telegramApiRoot: string,\n openrouterApiKey: string | null,\n): PluginConfigSource {\n const merged = mergePluginConfigSources(current, {\n telegram: {\n botToken,\n apiRoot: telegramApiRoot,\n },\n });\n const nextOpenRouter = openrouterApiKey\n ? {\n ...(merged.openrouter ?? {}),\n apiKey: openrouterApiKey,\n }\n : removeOpenRouterApiKey(merged.openrouter);\n\n return {\n ...merged,\n ...(nextOpenRouter && Object.keys(nextOpenRouter).length > 0\n ? { openrouter: nextOpenRouter }\n : {}),\n };\n}\n\nfunction removeOpenRouterApiKey(\n config: PluginConfigSource[\"openrouter\"],\n): PluginConfigSource[\"openrouter\"] | undefined {\n if (!config) {\n return undefined;\n }\n\n const { apiKey: _apiKey, ...rest } = config;\n\n return Object.keys(rest).length > 0 ? rest : undefined;\n}\n\nfunction ensurePluginRegistered(\n config: OpenCodeGlobalConfig,\n pluginSpec: string,\n): OpenCodeGlobalConfig {\n const plugins = Array.isArray(config.plugin)\n ? config.plugin.filter((item): item is string => typeof item === \"string\")\n : [];\n\n if (!plugins.includes(pluginSpec)) {\n plugins.push(pluginSpec);\n }\n\n return {\n ...config,\n plugin: plugins,\n };\n}\n\nfunction replacePluginRegistration(\n config: OpenCodeGlobalConfig,\n pluginSpec: string,\n): OpenCodeGlobalConfig {\n const currentPlugins = Array.isArray(config.plugin)\n ? config.plugin.filter((item): item is string => typeof item === \"string\")\n : [];\n const nextPlugins: string[] = [];\n let inserted = false;\n\n for (const currentPlugin of currentPlugins) {\n if (isOpencodeTbotPluginSpec(currentPlugin)) {\n if (!inserted) {\n nextPlugins.push(pluginSpec);\n inserted = true;\n }\n\n continue;\n }\n\n nextPlugins.push(currentPlugin);\n }\n\n if (!inserted) {\n nextPlugins.push(pluginSpec);\n }\n\n return {\n ...config,\n plugin: nextPlugins,\n };\n}\n\nasync function readPluginConfigFile(configFilePath: string): Promise<PluginConfigSource> {\n try {\n const content = await readFile(configFilePath, \"utf8\");\n const parsed = JSON.parse(content) as unknown;\n\n return isPlainObject(parsed)\n ? parsed as PluginConfigSource\n : {};\n } catch (error) {\n if (isMissingFileError(error)) {\n return {};\n }\n\n throw error;\n }\n}\n\nasync function readJsoncObject<TObject extends Record<string, unknown>>(\n filePath: string,\n): Promise<TObject> {\n try {\n const content = await readFile(filePath, \"utf8\");\n const parsed = parse(content) as unknown;\n\n return isPlainObject(parsed)\n ? parsed as TObject\n : {} as TObject;\n } catch (error) {\n if (isMissingFileError(error)) {\n return {} as TObject;\n }\n\n throw error;\n }\n}\n\nasync function writeJsonFile(filePath: string, value: Record<string, unknown>): Promise<void> {\n await ensureParentDirectory(filePath);\n await writeFile(filePath, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n}\n\nasync function ensureParentDirectory(filePath: string): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n}\n\nfunction createPromptSession() {\n if (!stdin.isTTY || !stdout.isTTY) {\n return {\n ask: async () => \"\",\n async confirm(_question: string, defaultValue: boolean) {\n return defaultValue;\n },\n close() { },\n };\n }\n\n const readline = createInterface({\n input: stdin,\n output: stdout,\n });\n\n return {\n ask(question: string) {\n return readline.question(question);\n },\n async confirm(question: string, defaultValue: boolean) {\n const answer = normalizeOptionalString(await readline.question(question));\n\n if (!answer) {\n return defaultValue;\n }\n\n if ([\"y\", \"yes\"].includes(answer.toLowerCase())) {\n return true;\n }\n\n if ([\"n\", \"no\"].includes(answer.toLowerCase())) {\n return false;\n }\n\n throw new Error(`Unsupported answer: ${answer}`);\n },\n close() {\n readline.close();\n },\n };\n}\n\nfunction normalizeOptionalString(value: string | undefined | null): string | null {\n const normalized = value?.trim();\n\n return normalized\n ? normalized\n : null;\n}\n\nfunction normalizeRequiredString(value: string | undefined | null, errorMessage: string): string {\n const normalized = normalizeOptionalString(value);\n\n if (!normalized) {\n throw new Error(errorMessage);\n }\n\n return normalized;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction isMissingFileError(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && \"code\" in error && error.code === \"ENOENT\";\n}\n\nfunction isOpencodeTbotPluginSpec(value: string): boolean {\n const normalized = value.trim();\n\n return normalized === \"opencode-tbot\" || normalized.startsWith(\"opencode-tbot@\");\n}\n\nfunction buildHelpText(): string {\n return [\n \"Usage: opencode-tbot [install|update] [options]\",\n \" opencode-tbot --version\",\n \"\",\n \"Commands:\",\n \" install\",\n \" update\",\n \"\",\n \"Options:\",\n \" --bot-token <token>\",\n \" --enable-voice\",\n \" --disable-voice\",\n \" --openrouter-api-key <key>\",\n \" --telegram-api-root <url>\",\n \" --plugin-spec <spec>\",\n \" --skip-register\",\n \" --home-dir <path>\",\n \" --version\",\n \" --help\",\n ].join(\"\\n\");\n}\n\nfunction formatCliError(error: unknown): string {\n return error instanceof Error && error.message.trim().length > 0\n ? error.message.trim()\n : String(error);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["import { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { stderr, stdin, stdout } from \"node:process\";\nimport { createInterface } from \"node:readline/promises\";\nimport { parse } from \"jsonc-parser\";\nimport {\n DEFAULT_TELEGRAM_API_ROOT,\n type PluginConfigSource,\n} from \"./app/config.js\";\nimport { OPENCODE_TBOT_VERSION } from \"./app/package-info.js\";\nimport {\n getGlobalPluginConfigFilePath,\n getOpenCodeConfigFilePath,\n mergePluginConfigSources,\n writePluginConfigFile,\n} from \"./app/plugin-config.js\";\n\nexport interface InstallCommandOptions {\n botToken?: string;\n enableVoice?: boolean;\n homeDir?: string;\n openrouterApiKey?: string;\n pluginSpec?: string;\n registerPlugin?: boolean;\n telegramApiRoot?: string;\n}\n\ninterface CliOptions extends InstallCommandOptions {\n command: \"help\" | \"install\" | \"update\" | \"version\";\n}\n\nexport interface PromptSessionLike {\n ask(question: string): Promise<string>;\n askSecret(question: string): Promise<string>;\n confirm(question: string, defaultValue: boolean): Promise<boolean>;\n close(): void;\n}\n\nexport interface CreatePromptSessionOptions {\n input?: NodeJS.ReadStream;\n output?: NodeJS.WriteStream;\n}\n\ninterface OpenCodeGlobalConfig {\n plugin?: string[];\n [key: string]: unknown;\n}\n\nconst DEFAULT_PLUGIN_SPEC = \"opencode-tbot@latest\";\nconst PROMPT_CANCELLED_ERROR = \"Prompt cancelled.\";\n\nexport async function main(argv: string[] = process.argv.slice(2)): Promise<number> {\n try {\n const exitCode = await runCli(argv);\n process.exitCode = exitCode;\n\n return exitCode;\n } catch (error) {\n stderr.write(`${formatCliError(error)}\\n`);\n process.exitCode = 1;\n\n return 1;\n }\n}\n\nexport async function runCli(argv: string[]): Promise<number> {\n const options = parseCliOptions(argv);\n\n if (options.command === \"help\") {\n stdout.write(`${buildHelpText()}\\n`);\n return 0;\n }\n\n if (options.command === \"version\") {\n stdout.write(`${OPENCODE_TBOT_VERSION}\\n`);\n return 0;\n }\n\n if (options.command === \"update\") {\n await updatePlugin(options);\n return 0;\n }\n\n await installPlugin(options);\n return 0;\n}\n\nexport async function installPlugin(options: InstallCommandOptions = {}): Promise<void> {\n const homeDir = options.homeDir ?? homedir();\n const openCodeConfigFilePath = getOpenCodeConfigFilePath(homeDir);\n const globalPluginConfigFilePath = getGlobalPluginConfigFilePath(homeDir);\n const openCodeConfig = await readJsoncObject<OpenCodeGlobalConfig>(openCodeConfigFilePath);\n const existingPluginConfig = await readPluginConfigFile(globalPluginConfigFilePath);\n const prompt = createPromptSession();\n\n try {\n const botToken = normalizeRequiredString(\n options.botToken ?? await prompt.askSecret(\"Telegram bot token: \"),\n \"Telegram bot token is required.\",\n );\n const enableVoice = options.enableVoice ?? await prompt.confirm(\n \"Enable voice transcription? (y/N): \",\n false,\n );\n const openrouterApiKey = enableVoice\n ? normalizeRequiredString(\n options.openrouterApiKey ?? await prompt.askSecret(\"OpenRouter API key: \"),\n \"OpenRouter API key is required when voice transcription is enabled.\",\n )\n : null;\n const telegramApiRoot = normalizeOptionalString(options.telegramApiRoot) ?? DEFAULT_TELEGRAM_API_ROOT;\n const pluginSpec = normalizeOptionalString(options.pluginSpec) ?? DEFAULT_PLUGIN_SPEC;\n const nextOpenCodeConfig = options.registerPlugin === false\n ? openCodeConfig\n : ensurePluginRegistered(openCodeConfig, pluginSpec);\n const nextPluginConfig = buildInstalledPluginConfig(\n existingPluginConfig,\n botToken,\n telegramApiRoot,\n openrouterApiKey,\n );\n\n if (options.registerPlugin === false) {\n await ensureParentDirectory(openCodeConfigFilePath);\n } else {\n await writeJsonFile(openCodeConfigFilePath, nextOpenCodeConfig);\n }\n await writePluginConfigFile(globalPluginConfigFilePath, nextPluginConfig);\n\n stdout.write(\"Success.\\n\");\n await warnAboutLegacyLocalInstallations(homeDir);\n } finally {\n prompt.close();\n }\n}\n\nexport async function updatePlugin(options: Pick<InstallCommandOptions, \"homeDir\" | \"pluginSpec\"> = {}): Promise<void> {\n const homeDir = options.homeDir ?? homedir();\n const openCodeConfigFilePath = getOpenCodeConfigFilePath(homeDir);\n const openCodeConfig = await readJsoncObject<OpenCodeGlobalConfig>(openCodeConfigFilePath);\n const pluginSpec = normalizeOptionalString(options.pluginSpec) ?? DEFAULT_PLUGIN_SPEC;\n const nextOpenCodeConfig = replacePluginRegistration(openCodeConfig, pluginSpec);\n\n await writeJsonFile(openCodeConfigFilePath, nextOpenCodeConfig);\n stdout.write(\"Success.\\n\");\n await warnAboutLegacyLocalInstallations(homeDir);\n}\n\nfunction parseCliOptions(argv: string[]): CliOptions {\n const args = [...argv];\n const first = args[0];\n const command = !first || first.startsWith(\"-\")\n ? \"install\"\n : first;\n const values = command === \"install\" || command === \"version\"\n ? args\n : args.slice(1);\n const options: CliOptions = {\n command: command === \"help\" || command === \"--help\" || command === \"-h\"\n ? \"help\"\n : command === \"version\" || command === \"--version\" || command === \"-v\"\n ? \"version\"\n : command === \"update\"\n ? \"update\"\n : \"install\",\n };\n\n for (let index = 0; index < values.length; index += 1) {\n const value = values[index];\n\n if (index === 0 && !value.startsWith(\"-\")) {\n continue;\n }\n\n switch (value) {\n case \"--bot-token\":\n options.botToken = values[++index];\n break;\n case \"--enable-voice\":\n options.enableVoice = true;\n break;\n case \"--disable-voice\":\n options.enableVoice = false;\n break;\n case \"--openrouter-api-key\":\n options.openrouterApiKey = values[++index];\n break;\n case \"--plugin-spec\":\n options.pluginSpec = values[++index];\n break;\n case \"--telegram-api-root\":\n options.telegramApiRoot = values[++index];\n break;\n case \"--skip-register\":\n options.registerPlugin = false;\n break;\n case \"--home-dir\":\n options.homeDir = values[++index];\n break;\n case \"--help\":\n case \"-h\":\n options.command = \"help\";\n break;\n case \"--version\":\n case \"-v\":\n options.command = \"version\";\n break;\n default:\n throw new Error(`Unknown argument: ${value}`);\n }\n }\n\n return options;\n}\n\nfunction buildInstalledPluginConfig(\n current: PluginConfigSource,\n botToken: string,\n telegramApiRoot: string,\n openrouterApiKey: string | null,\n): PluginConfigSource {\n const merged = mergePluginConfigSources(current, {\n telegram: {\n botToken,\n apiRoot: telegramApiRoot,\n },\n });\n const nextOpenRouter = openrouterApiKey\n ? {\n ...(merged.openrouter ?? {}),\n apiKey: openrouterApiKey,\n }\n : removeOpenRouterApiKey(merged.openrouter);\n\n return {\n ...merged,\n ...(nextOpenRouter && Object.keys(nextOpenRouter).length > 0\n ? { openrouter: nextOpenRouter }\n : {}),\n };\n}\n\nfunction removeOpenRouterApiKey(\n config: PluginConfigSource[\"openrouter\"],\n): PluginConfigSource[\"openrouter\"] | undefined {\n if (!config) {\n return undefined;\n }\n\n const { apiKey: _apiKey, ...rest } = config;\n\n return Object.keys(rest).length > 0 ? rest : undefined;\n}\n\nfunction ensurePluginRegistered(\n config: OpenCodeGlobalConfig,\n pluginSpec: string,\n): OpenCodeGlobalConfig {\n const plugins = Array.isArray(config.plugin)\n ? config.plugin.filter((item): item is string => typeof item === \"string\")\n : [];\n\n if (!plugins.includes(pluginSpec)) {\n plugins.push(pluginSpec);\n }\n\n return {\n ...config,\n plugin: plugins,\n };\n}\n\nfunction replacePluginRegistration(\n config: OpenCodeGlobalConfig,\n pluginSpec: string,\n): OpenCodeGlobalConfig {\n const currentPlugins = Array.isArray(config.plugin)\n ? config.plugin.filter((item): item is string => typeof item === \"string\")\n : [];\n const nextPlugins: string[] = [];\n let inserted = false;\n\n for (const currentPlugin of currentPlugins) {\n if (isOpencodeTbotPluginSpec(currentPlugin)) {\n if (!inserted) {\n nextPlugins.push(pluginSpec);\n inserted = true;\n }\n\n continue;\n }\n\n nextPlugins.push(currentPlugin);\n }\n\n if (!inserted) {\n nextPlugins.push(pluginSpec);\n }\n\n return {\n ...config,\n plugin: nextPlugins,\n };\n}\n\nasync function readPluginConfigFile(configFilePath: string): Promise<PluginConfigSource> {\n try {\n const content = await readFile(configFilePath, \"utf8\");\n const parsed = JSON.parse(content) as unknown;\n\n return isPlainObject(parsed)\n ? parsed as PluginConfigSource\n : {};\n } catch (error) {\n if (isMissingFileError(error)) {\n return {};\n }\n\n throw error;\n }\n}\n\nasync function readJsoncObject<TObject extends Record<string, unknown>>(\n filePath: string,\n): Promise<TObject> {\n try {\n const content = await readFile(filePath, \"utf8\");\n const parsed = parse(content) as unknown;\n\n return isPlainObject(parsed)\n ? parsed as TObject\n : {} as TObject;\n } catch (error) {\n if (isMissingFileError(error)) {\n return {} as TObject;\n }\n\n throw error;\n }\n}\n\nasync function writeJsonFile(filePath: string, value: Record<string, unknown>): Promise<void> {\n await ensureParentDirectory(filePath);\n await writeFile(filePath, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n}\n\nasync function ensureParentDirectory(filePath: string): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n}\n\nexport function createPromptSession(options: CreatePromptSessionOptions = {}): PromptSessionLike {\n const input = options.input ?? stdin;\n const output = options.output ?? stdout;\n\n if (!input.isTTY || !output.isTTY) {\n return {\n ask: async () => \"\",\n askSecret: async () => \"\",\n async confirm(_question: string, defaultValue: boolean) {\n return defaultValue;\n },\n close() { },\n };\n }\n\n return {\n ask(question: string) {\n return askQuestion(input, output, question);\n },\n askSecret(question: string) {\n return askSecretQuestion(input, output, question);\n },\n async confirm(question: string, defaultValue: boolean) {\n const answer = normalizeOptionalString(await askQuestion(input, output, question));\n\n if (!answer) {\n return defaultValue;\n }\n\n if ([\"y\", \"yes\"].includes(answer.toLowerCase())) {\n return true;\n }\n\n if ([\"n\", \"no\"].includes(answer.toLowerCase())) {\n return false;\n }\n\n throw new Error(`Unsupported answer: ${answer}`);\n },\n close() { },\n };\n}\n\nasync function askQuestion(\n input: NodeJS.ReadStream,\n output: NodeJS.WriteStream,\n question: string,\n): Promise<string> {\n const readline = createInterface({ input, output });\n\n try {\n return await readline.question(question);\n } finally {\n readline.close();\n }\n}\n\nasync function askSecretQuestion(\n input: NodeJS.ReadStream,\n output: NodeJS.WriteStream,\n question: string,\n): Promise<string> {\n if (typeof input.setRawMode !== \"function\") {\n return askQuestion(input, output, question);\n }\n\n output.write(question);\n\n try {\n return await readMaskedInput(input, output);\n } finally {\n output.write(\"\\n\");\n }\n}\n\nasync function readMaskedInput(\n input: NodeJS.ReadStream,\n output: NodeJS.WriteStream,\n): Promise<string> {\n return new Promise((resolvePromise, rejectPromise) => {\n const buffer: string[] = [];\n let settled = false;\n\n const cleanup = () => {\n input.off(\"data\", handleData);\n input.off(\"error\", handleError);\n input.pause();\n input.setRawMode?.(false);\n };\n\n const rejectWith = (error: unknown) => {\n if (settled) {\n return;\n }\n\n settled = true;\n cleanup();\n rejectPromise(error);\n };\n\n const resolveWith = (value: string) => {\n if (settled) {\n return;\n }\n\n settled = true;\n cleanup();\n resolvePromise(value);\n };\n\n const handleError = (error: unknown) => {\n rejectWith(error);\n };\n\n const handleData = (chunk: string | Buffer) => {\n const text = Buffer.isBuffer(chunk) ? chunk.toString(\"utf8\") : chunk;\n\n for (const character of text) {\n if (character === \"\\r\" || character === \"\\n\") {\n resolveWith(buffer.join(\"\"));\n return;\n }\n\n if (character === \"\\u0003\") {\n rejectWith(new Error(PROMPT_CANCELLED_ERROR));\n return;\n }\n\n if (character === \"\\u0008\" || character === \"\\u007f\") {\n if (buffer.length > 0) {\n buffer.pop();\n output.write(\"\\b \\b\");\n }\n continue;\n }\n\n if (character < \" \" || character === \"\\u007f\") {\n continue;\n }\n\n buffer.push(character);\n output.write(\"*\");\n }\n };\n\n input.setRawMode?.(true);\n input.resume();\n input.on(\"error\", handleError);\n input.on(\"data\", handleData);\n });\n}\n\nfunction normalizeOptionalString(value: string | undefined | null): string | null {\n const normalized = value?.trim();\n\n return normalized\n ? normalized\n : null;\n}\n\nfunction normalizeRequiredString(value: string | undefined | null, errorMessage: string): string {\n const normalized = normalizeOptionalString(value);\n\n if (!normalized) {\n throw new Error(errorMessage);\n }\n\n return normalized;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction isMissingFileError(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && \"code\" in error && error.code === \"ENOENT\";\n}\n\nfunction isOpencodeTbotPluginSpec(value: string): boolean {\n const normalized = value.trim();\n\n return normalized === \"opencode-tbot\" || normalized.startsWith(\"opencode-tbot@\");\n}\n\nasync function warnAboutLegacyLocalInstallations(homeDir: string): Promise<void> {\n const legacyInstallations = await findLegacyLocalInstallations(homeDir);\n\n if (legacyInstallations.length === 0) {\n return;\n }\n\n stdout.write(\"\\nDetected local opencode-tbot npm installation(s) that can make OpenCode show the plugin as file:///.../node_modules/...\\n\");\n\n for (const installation of legacyInstallations) {\n stdout.write(`- ${installation.packagePath}\\n`);\n stdout.write(` cleanup: cd \"${installation.rootDir}\" && npm uninstall opencode-tbot\\n`);\n }\n\n stdout.write(\"Recommended npm flow: npm exec --package opencode-tbot@latest opencode-tbot -- install\\n\");\n}\n\nasync function findLegacyLocalInstallations(homeDir: string) {\n const roots = [...new Set([\n resolve(process.cwd()),\n resolve(homeDir),\n ])];\n const installations: Array<{ packagePath: string; rootDir: string }> = [];\n\n for (const rootDir of roots) {\n const packagePath = join(rootDir, \"node_modules\", \"opencode-tbot\", \"package.json\");\n\n if (await pathExists(packagePath)) {\n installations.push({\n packagePath: join(rootDir, \"node_modules\", \"opencode-tbot\"),\n rootDir,\n });\n }\n }\n\n return installations;\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction buildHelpText(): string {\n return [\n \"Usage: opencode-tbot [install|update] [options]\",\n \" opencode-tbot --version\",\n \"\",\n \"Recommended npm usage:\",\n \" npm exec --package opencode-tbot@latest opencode-tbot -- install\",\n \" npm exec --package opencode-tbot@latest opencode-tbot -- update\",\n \" npm exec --package opencode-tbot@latest opencode-tbot -- --version\",\n \"\",\n \"Commands:\",\n \" install\",\n \" update\",\n \"\",\n \"Options:\",\n \" --bot-token <token>\",\n \" --enable-voice\",\n \" --disable-voice\",\n \" --openrouter-api-key <key>\",\n \" --telegram-api-root <url>\",\n \" --plugin-spec <spec>\",\n \" --skip-register\",\n \" --home-dir <path>\",\n \" --version\",\n \" --help\",\n ].join(\"\\n\");\n}\n\nfunction formatCliError(error: unknown): string {\n return error instanceof Error && error.message.trim().length > 0\n ? error.message.trim()\n : String(error);\n}\n"],"mappings":"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"}
|