opencode-tbot 0.1.7 → 0.1.9
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 +19 -1
- package/README.zh-CN.md +19 -1
- package/dist/assets/{plugin-config-BYsYAzvx.js → plugin-config-CGIe9zdA.js} +21 -2
- package/dist/assets/plugin-config-CGIe9zdA.js.map +1 -0
- package/dist/cli.js +179 -20
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/plugin.js +10 -24
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/plugin-config-BYsYAzvx.js.map +0 -1
package/README.md
CHANGED
|
@@ -22,11 +22,29 @@ 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.
|
|
29
29
|
|
|
30
|
+
Check the installed CLI version:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- --version
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Update the registered npm plugin spec in OpenCode:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- update
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Remove a legacy local npm install if OpenCode is showing the plugin as a `file:///.../node_modules/...` path:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm uninstall opencode-tbot
|
|
46
|
+
```
|
|
47
|
+
|
|
30
48
|
## Configuration
|
|
31
49
|
|
|
32
50
|
The runtime config is loaded in this order:
|
package/README.zh-CN.md
CHANGED
|
@@ -22,11 +22,29 @@
|
|
|
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
|
安装器会注册全局插件并写入默认运行时配置。
|
|
29
29
|
|
|
30
|
+
查看已安装的 CLI 版本:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- --version
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
更新 OpenCode 中注册的 npm 插件 spec:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- update
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
从当前环境卸载 npm 包:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm uninstall opencode-tbot
|
|
46
|
+
```
|
|
47
|
+
|
|
30
48
|
## 配置
|
|
31
49
|
|
|
32
50
|
运行时配置按以下优先级加载:
|
|
@@ -2,6 +2,8 @@ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
5
7
|
//#region src/app/config.ts
|
|
6
8
|
var DEFAULT_OPENROUTER_MODEL = "openai/gpt-audio-mini";
|
|
7
9
|
var DEFAULT_STATE_FILE_PATH = "./data/opencode-tbot.state.json";
|
|
@@ -64,6 +66,23 @@ function parseConfig(schema, configSource) {
|
|
|
64
66
|
throw new Error(`Invalid plugin configuration: ${JSON.stringify(parsed.error.flatten())}`);
|
|
65
67
|
}
|
|
66
68
|
//#endregion
|
|
69
|
+
//#region src/app/package-info.ts
|
|
70
|
+
var OPENCODE_TBOT_VERSION = resolvePackageVersion();
|
|
71
|
+
function resolvePackageVersion() {
|
|
72
|
+
let directory = dirname(fileURLToPath(import.meta.url));
|
|
73
|
+
while (true) {
|
|
74
|
+
const packageFilePath = join(directory, "package.json");
|
|
75
|
+
if (existsSync(packageFilePath)) try {
|
|
76
|
+
const parsed = JSON.parse(readFileSync(packageFilePath, "utf8"));
|
|
77
|
+
if (typeof parsed.version === "string" && parsed.version.trim().length > 0) return parsed.version;
|
|
78
|
+
} catch {}
|
|
79
|
+
const parentDirectory = dirname(directory);
|
|
80
|
+
if (parentDirectory === directory) break;
|
|
81
|
+
directory = parentDirectory;
|
|
82
|
+
}
|
|
83
|
+
return "unknown";
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
67
86
|
//#region src/app/plugin-config.ts
|
|
68
87
|
var PLUGIN_CONFIG_FILE_NAME = "tbot.config.json";
|
|
69
88
|
var GLOBAL_PLUGIN_DIRECTORY_NAME = "opencode-tbot";
|
|
@@ -174,6 +193,6 @@ async function pathExists(filePath) {
|
|
|
174
193
|
}
|
|
175
194
|
}
|
|
176
195
|
//#endregion
|
|
177
|
-
export { writePluginConfigFile as a, preparePluginConfiguration as i, getOpenCodeConfigFilePath as n,
|
|
196
|
+
export { writePluginConfigFile as a, loadAppConfig as c, preparePluginConfiguration as i, getOpenCodeConfigFilePath as n, OPENCODE_TBOT_VERSION as o, mergePluginConfigSources as r, DEFAULT_TELEGRAM_API_ROOT as s, getGlobalPluginConfigFilePath as t };
|
|
178
197
|
|
|
179
|
-
//# sourceMappingURL=plugin-config-
|
|
198
|
+
//# sourceMappingURL=plugin-config-CGIe9zdA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-config-CGIe9zdA.js","names":[],"sources":["../../src/app/config.ts","../../src/app/package-info.ts","../../src/app/plugin-config.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { z } from \"zod\";\n\nexport const DEFAULT_OPENROUTER_MODEL = \"openai/gpt-audio-mini\";\nexport const DEFAULT_STATE_FILE_PATH = \"./data/opencode-tbot.state.json\";\nexport const DEFAULT_TELEGRAM_API_ROOT = \"https://api.telegram.org\";\n\nconst AllowedChatIdSchema = z.union([\n z.number().int(),\n z.string().regex(/^-?\\d+$/u).transform((value) => Number(value)),\n]);\n\nconst TelegramConfigSchema = z.preprocess(\n (value) => value ?? {},\n z.object({\n botToken: z.string().trim().min(1),\n allowedChatIds: z.array(AllowedChatIdSchema).default([]),\n apiRoot: z.string().trim().url().default(DEFAULT_TELEGRAM_API_ROOT),\n }),\n);\n\nconst StateConfigSchema = z.preprocess(\n (value) => value ?? {},\n z.object({\n path: z.string().trim().min(1).default(DEFAULT_STATE_FILE_PATH),\n }),\n);\n\nconst OpenRouterConfigSchema = z.preprocess(\n (value) => value ?? {},\n z.object({\n apiKey: z.string().default(\"\"),\n model: z.string().default(DEFAULT_OPENROUTER_MODEL),\n timeoutMs: z.coerce.number().int().positive().default(30_000),\n transcriptionPrompt: z.string().default(\"\"),\n }),\n);\n\nconst AppConfigSchema = z.object({\n telegram: TelegramConfigSchema,\n state: StateConfigSchema,\n openrouter: OpenRouterConfigSchema,\n logLevel: z.string().default(\"info\"),\n});\n\nexport interface PluginConfigSource {\n telegram?: {\n botToken?: string;\n allowedChatIds?: Array<number | string>;\n apiRoot?: string;\n [key: string]: unknown;\n };\n state?: {\n path?: string;\n [key: string]: unknown;\n };\n openrouter?: {\n apiKey?: string;\n model?: string;\n timeoutMs?: number;\n transcriptionPrompt?: string;\n [key: string]: unknown;\n };\n logLevel?: string;\n [key: string]: unknown;\n}\n\nexport interface AppOpenRouterConfig {\n configured: boolean;\n apiKey: string | null;\n model: string;\n timeoutMs: number;\n transcriptionPrompt: string | null;\n}\n\nexport interface AppConfig {\n telegramBotToken: string;\n telegramAllowedChatIds: number[];\n telegramApiRoot: string;\n logLevel: string;\n stateFilePath: string;\n openrouter: AppOpenRouterConfig;\n}\n\nexport interface LoadAppConfigOptions {\n cwd?: string;\n}\n\nexport function loadAppConfig(\n configSource: PluginConfigSource | undefined = {},\n options: LoadAppConfigOptions = {},\n): AppConfig {\n const parsed = parseConfig(AppConfigSchema, configSource);\n\n return buildAppConfig(parsed, options);\n}\n\nexport const loadPluginConfig = loadAppConfig;\n\nfunction buildAppConfig(\n data: z.infer<typeof AppConfigSchema>,\n options: LoadAppConfigOptions,\n): AppConfig {\n const openRouterApiKey = normalizeOptionalString(data.openrouter.apiKey);\n const openRouterModel = normalizeOptionalString(data.openrouter.model) ?? DEFAULT_OPENROUTER_MODEL;\n const transcriptionPrompt = normalizeOptionalString(data.openrouter.transcriptionPrompt);\n\n return {\n telegramBotToken: data.telegram.botToken,\n telegramAllowedChatIds: data.telegram.allowedChatIds,\n telegramApiRoot: normalizeApiRoot(data.telegram.apiRoot),\n logLevel: data.logLevel,\n stateFilePath: resolveStatePath(data, options.cwd ?? process.cwd()),\n openrouter: {\n configured: !!openRouterApiKey,\n apiKey: openRouterApiKey,\n model: openRouterModel,\n timeoutMs: data.openrouter.timeoutMs,\n transcriptionPrompt,\n },\n };\n}\n\nfunction normalizeOptionalString(value: string): string | null {\n const normalized = value.trim();\n\n return normalized.length > 0 ? normalized : null;\n}\n\nfunction resolveStatePath(\n data: z.infer<typeof AppConfigSchema>,\n cwd: string,\n): string {\n return resolve(cwd, data.state.path || DEFAULT_STATE_FILE_PATH);\n}\n\nfunction normalizeApiRoot(value: string): string {\n const normalized = value.trim();\n\n return normalized.endsWith(\"/\")\n ? normalized.slice(0, -1)\n : normalized;\n}\n\nfunction parseConfig<TSchema extends z.ZodTypeAny>(\n schema: TSchema,\n configSource: PluginConfigSource | undefined,\n): z.infer<TSchema> {\n const parsed = schema.safeParse(configSource ?? {});\n\n if (parsed.success) {\n return parsed.data;\n }\n\n throw new Error(\n `Invalid plugin configuration: ${JSON.stringify(parsed.error.flatten())}`,\n );\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport const OPENCODE_TBOT_VERSION = resolvePackageVersion();\n\nfunction resolvePackageVersion(): string {\n let directory = dirname(fileURLToPath(import.meta.url));\n\n while (true) {\n const packageFilePath = join(directory, \"package.json\");\n\n if (existsSync(packageFilePath)) {\n try {\n const parsed = JSON.parse(readFileSync(packageFilePath, \"utf8\")) as {\n version?: unknown;\n };\n\n if (typeof parsed.version === \"string\" && parsed.version.trim().length > 0) {\n return parsed.version;\n }\n } catch {\n // Fall through and continue searching parent directories.\n }\n }\n\n const parentDirectory = dirname(directory);\n\n if (parentDirectory === directory) {\n break;\n }\n\n directory = parentDirectory;\n }\n\n return \"unknown\";\n}\n","import { access, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { PluginConfigSource } from \"./config.js\";\n\nexport const PLUGIN_CONFIG_FILE_NAME = \"tbot.config.json\";\nexport const GLOBAL_PLUGIN_DIRECTORY_NAME = \"opencode-tbot\";\nexport const GLOBAL_PLUGIN_CONFIG_FILE_NAME = \"config.json\";\nexport const OPENCODE_CONFIG_FILE_NAME = \"opencode.json\";\n\nexport interface PreparedPluginConfiguration {\n cwd: string;\n config: PluginConfigSource;\n globalConfigFilePath: string;\n projectConfigFilePath: string;\n configFilePath: string;\n}\n\nexport interface PreparePluginConfigurationOptions {\n cwd: string;\n config?: PluginConfigSource;\n homeDir?: string;\n}\n\nexport async function preparePluginConfiguration(\n options: PreparePluginConfigurationOptions,\n): Promise<PreparedPluginConfiguration> {\n const homeDir = options.homeDir ?? homedir();\n const globalConfigFilePath = getGlobalPluginConfigFilePath(homeDir);\n const projectConfigFilePath = await resolveProjectPluginConfigFilePath(options.cwd);\n const [globalConfig, projectConfig] = await Promise.all([\n loadPluginConfigFile(globalConfigFilePath),\n loadPluginConfigFile(projectConfigFilePath),\n ]);\n const config = mergePluginConfigSources(globalConfig, projectConfig, options.config);\n const configFilePath = await pathExists(projectConfigFilePath)\n ? projectConfigFilePath\n : globalConfigFilePath;\n\n return {\n cwd: options.cwd,\n config,\n globalConfigFilePath,\n projectConfigFilePath,\n configFilePath,\n };\n}\n\nexport function getOpenCodeConfigDirectory(homeDir: string = homedir()): string {\n return join(homeDir, \".config\", \"opencode\");\n}\n\nexport function getOpenCodeConfigFilePath(homeDir: string = homedir()): string {\n return join(getOpenCodeConfigDirectory(homeDir), OPENCODE_CONFIG_FILE_NAME);\n}\n\nexport function getGlobalPluginConfigFilePath(homeDir: string = homedir()): string {\n return join(\n getOpenCodeConfigDirectory(homeDir),\n GLOBAL_PLUGIN_DIRECTORY_NAME,\n GLOBAL_PLUGIN_CONFIG_FILE_NAME,\n );\n}\n\nexport async function writePluginConfigFile(\n configFilePath: string,\n config: PluginConfigSource,\n): Promise<void> {\n await mkdir(dirname(configFilePath), { recursive: true });\n await writeFile(configFilePath, serializePluginConfig(config), \"utf8\");\n}\n\nexport function mergePluginConfigSources(\n ...sources: Array<PluginConfigSource | undefined>\n): PluginConfigSource {\n const merged: PluginConfigSource = {};\n\n for (const source of sources) {\n if (!source) {\n continue;\n }\n\n const normalized = source;\n const previousTelegram = merged.telegram;\n const previousState = merged.state;\n const previousOpenRouter = merged.openrouter;\n\n Object.assign(merged, normalized);\n\n if (normalized.telegram) {\n merged.telegram = {\n ...(previousTelegram ?? {}),\n ...normalized.telegram,\n };\n }\n\n if (normalized.state) {\n merged.state = {\n ...(previousState ?? {}),\n ...normalized.state,\n };\n }\n\n if (normalized.openrouter) {\n merged.openrouter = {\n ...(previousOpenRouter ?? {}),\n ...normalized.openrouter,\n };\n }\n }\n\n return merged;\n}\n\nexport function serializePluginConfig(config: PluginConfigSource): string {\n return `${JSON.stringify(orderPluginConfig(config), null, 2)}\\n`;\n}\n\nasync function loadPluginConfigFile(configFilePath: string): Promise<PluginConfigSource> {\n try {\n const content = await readFile(configFilePath, \"utf8\");\n\n return parsePluginConfigText(content, configFilePath);\n } catch (error) {\n if (isMissingFileError(error)) {\n return {};\n }\n\n throw error;\n }\n}\n\nfunction parsePluginConfigText(\n content: string,\n configFilePath: string,\n): PluginConfigSource {\n try {\n const parsed = JSON.parse(content) as unknown;\n\n if (!isPlainObject(parsed)) {\n throw new Error(\"Config root must be a JSON object.\");\n }\n\n return parsed as PluginConfigSource;\n } catch (error) {\n throw new Error(\n [\n `Failed to parse ${configFilePath} as JSON.`,\n error instanceof Error ? error.message : String(error),\n ].join(\" \"),\n );\n }\n}\n\nfunction orderPluginConfig(config: PluginConfigSource): PluginConfigSource {\n const prioritizedKeys = new Set([\n \"telegram\",\n \"state\",\n \"openrouter\",\n \"logLevel\",\n ]);\n const ordered: PluginConfigSource = {};\n\n if (config.telegram) {\n ordered.telegram = config.telegram;\n }\n\n if (config.state) {\n ordered.state = config.state;\n }\n\n if (config.openrouter) {\n ordered.openrouter = config.openrouter;\n }\n\n if (config.logLevel !== undefined) {\n ordered.logLevel = config.logLevel;\n }\n\n for (const [key, value] of Object.entries(config)) {\n if (!prioritizedKeys.has(key)) {\n ordered[key] = value;\n }\n }\n\n return ordered;\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\nasync function resolveProjectPluginConfigFilePath(cwd: string): Promise<string> {\n const preferredPath = join(cwd, PLUGIN_CONFIG_FILE_NAME);\n\n return preferredPath;\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await access(filePath);\n return true;\n } catch (error) {\n if (isMissingFileError(error)) {\n return false;\n }\n\n throw error;\n }\n}\n"],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { a as writePluginConfigFile, n as getOpenCodeConfigFilePath, r as mergePluginConfigSources, t as getGlobalPluginConfigFilePath } from "./assets/plugin-config-
|
|
2
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
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, 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);
|
|
@@ -24,6 +25,14 @@ async function runCli(argv) {
|
|
|
24
25
|
stdout.write(`${buildHelpText()}\n`);
|
|
25
26
|
return 0;
|
|
26
27
|
}
|
|
28
|
+
if (options.command === "version") {
|
|
29
|
+
stdout.write(`${OPENCODE_TBOT_VERSION}\n`);
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
if (options.command === "update") {
|
|
33
|
+
await updatePlugin(options);
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
27
36
|
await installPlugin(options);
|
|
28
37
|
return 0;
|
|
29
38
|
}
|
|
@@ -35,8 +44,8 @@ async function installPlugin(options = {}) {
|
|
|
35
44
|
const existingPluginConfig = await readPluginConfigFile(globalPluginConfigFilePath);
|
|
36
45
|
const prompt = createPromptSession();
|
|
37
46
|
try {
|
|
38
|
-
const botToken = normalizeRequiredString(options.botToken ?? await prompt.
|
|
39
|
-
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;
|
|
40
49
|
const telegramApiRoot = normalizeOptionalString(options.telegramApiRoot) ?? "https://api.telegram.org";
|
|
41
50
|
const pluginSpec = normalizeOptionalString(options.pluginSpec) ?? DEFAULT_PLUGIN_SPEC;
|
|
42
51
|
const nextOpenCodeConfig = options.registerPlugin === false ? openCodeConfig : ensurePluginRegistered(openCodeConfig, pluginSpec);
|
|
@@ -47,16 +56,26 @@ async function installPlugin(options = {}) {
|
|
|
47
56
|
if (options.registerPlugin === false) stdout.write(`Skipped plugin registration in ${openCodeConfigFilePath}\n`);
|
|
48
57
|
else stdout.write(`Registered ${pluginSpec} in ${openCodeConfigFilePath}\n`);
|
|
49
58
|
stdout.write(`Wrote plugin defaults to ${globalPluginConfigFilePath}\n`);
|
|
59
|
+
await warnAboutLegacyLocalInstallations(homeDir);
|
|
50
60
|
} finally {
|
|
51
61
|
prompt.close();
|
|
52
62
|
}
|
|
53
63
|
}
|
|
64
|
+
async function updatePlugin(options = {}) {
|
|
65
|
+
const homeDir = options.homeDir ?? homedir();
|
|
66
|
+
const openCodeConfigFilePath = getOpenCodeConfigFilePath(homeDir);
|
|
67
|
+
const openCodeConfig = await readJsoncObject(openCodeConfigFilePath);
|
|
68
|
+
const pluginSpec = normalizeOptionalString(options.pluginSpec) ?? DEFAULT_PLUGIN_SPEC;
|
|
69
|
+
await writeJsonFile(openCodeConfigFilePath, replacePluginRegistration(openCodeConfig, pluginSpec));
|
|
70
|
+
stdout.write(`Updated plugin registration to ${pluginSpec} in ${openCodeConfigFilePath}\n`);
|
|
71
|
+
await warnAboutLegacyLocalInstallations(homeDir);
|
|
72
|
+
}
|
|
54
73
|
function parseCliOptions(argv) {
|
|
55
74
|
const args = [...argv];
|
|
56
75
|
const first = args[0];
|
|
57
76
|
const command = !first || first.startsWith("-") ? "install" : first;
|
|
58
|
-
const values = command === "install" ? args : args.slice(1);
|
|
59
|
-
const options = { command: command === "help" || command === "--help" || command === "-h" ? "help" : "install" };
|
|
77
|
+
const values = command === "install" || command === "version" ? args : args.slice(1);
|
|
78
|
+
const options = { command: command === "help" || command === "--help" || command === "-h" ? "help" : command === "version" || command === "--version" || command === "-v" ? "version" : command === "update" ? "update" : "install" };
|
|
60
79
|
for (let index = 0; index < values.length; index += 1) {
|
|
61
80
|
const value = values[index];
|
|
62
81
|
if (index === 0 && !value.startsWith("-")) continue;
|
|
@@ -89,6 +108,10 @@ function parseCliOptions(argv) {
|
|
|
89
108
|
case "-h":
|
|
90
109
|
options.command = "help";
|
|
91
110
|
break;
|
|
111
|
+
case "--version":
|
|
112
|
+
case "-v":
|
|
113
|
+
options.command = "version";
|
|
114
|
+
break;
|
|
92
115
|
default: throw new Error(`Unknown argument: ${value}`);
|
|
93
116
|
}
|
|
94
117
|
}
|
|
@@ -121,6 +144,26 @@ function ensurePluginRegistered(config, pluginSpec) {
|
|
|
121
144
|
plugin: plugins
|
|
122
145
|
};
|
|
123
146
|
}
|
|
147
|
+
function replacePluginRegistration(config, pluginSpec) {
|
|
148
|
+
const currentPlugins = Array.isArray(config.plugin) ? config.plugin.filter((item) => typeof item === "string") : [];
|
|
149
|
+
const nextPlugins = [];
|
|
150
|
+
let inserted = false;
|
|
151
|
+
for (const currentPlugin of currentPlugins) {
|
|
152
|
+
if (isOpencodeTbotPluginSpec(currentPlugin)) {
|
|
153
|
+
if (!inserted) {
|
|
154
|
+
nextPlugins.push(pluginSpec);
|
|
155
|
+
inserted = true;
|
|
156
|
+
}
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
nextPlugins.push(currentPlugin);
|
|
160
|
+
}
|
|
161
|
+
if (!inserted) nextPlugins.push(pluginSpec);
|
|
162
|
+
return {
|
|
163
|
+
...config,
|
|
164
|
+
plugin: nextPlugins
|
|
165
|
+
};
|
|
166
|
+
}
|
|
124
167
|
async function readPluginConfigFile(configFilePath) {
|
|
125
168
|
try {
|
|
126
169
|
const content = await readFile(configFilePath, "utf8");
|
|
@@ -147,34 +190,108 @@ async function writeJsonFile(filePath, value) {
|
|
|
147
190
|
async function ensureParentDirectory(filePath) {
|
|
148
191
|
await mkdir(dirname(filePath), { recursive: true });
|
|
149
192
|
}
|
|
150
|
-
function createPromptSession() {
|
|
151
|
-
|
|
193
|
+
function createPromptSession(options = {}) {
|
|
194
|
+
const input = options.input ?? stdin;
|
|
195
|
+
const output = options.output ?? stdout;
|
|
196
|
+
if (!input.isTTY || !output.isTTY) return {
|
|
152
197
|
ask: async () => "",
|
|
198
|
+
askSecret: async () => "",
|
|
153
199
|
async confirm(_question, defaultValue) {
|
|
154
200
|
return defaultValue;
|
|
155
201
|
},
|
|
156
202
|
close() {}
|
|
157
203
|
};
|
|
158
|
-
const readline = createInterface({
|
|
159
|
-
input: stdin,
|
|
160
|
-
output: stdout
|
|
161
|
-
});
|
|
162
204
|
return {
|
|
163
205
|
ask(question) {
|
|
164
|
-
return
|
|
206
|
+
return askQuestion(input, output, question);
|
|
207
|
+
},
|
|
208
|
+
askSecret(question) {
|
|
209
|
+
return askSecretQuestion(input, output, question);
|
|
165
210
|
},
|
|
166
211
|
async confirm(question, defaultValue) {
|
|
167
|
-
const answer = normalizeOptionalString(await
|
|
212
|
+
const answer = normalizeOptionalString(await askQuestion(input, output, question));
|
|
168
213
|
if (!answer) return defaultValue;
|
|
169
214
|
if (["y", "yes"].includes(answer.toLowerCase())) return true;
|
|
170
215
|
if (["n", "no"].includes(answer.toLowerCase())) return false;
|
|
171
216
|
throw new Error(`Unsupported answer: ${answer}`);
|
|
172
217
|
},
|
|
173
|
-
close() {
|
|
174
|
-
readline.close();
|
|
175
|
-
}
|
|
218
|
+
close() {}
|
|
176
219
|
};
|
|
177
220
|
}
|
|
221
|
+
async function askQuestion(input, output, question) {
|
|
222
|
+
const readline = createInterface({
|
|
223
|
+
input,
|
|
224
|
+
output
|
|
225
|
+
});
|
|
226
|
+
try {
|
|
227
|
+
return await readline.question(question);
|
|
228
|
+
} finally {
|
|
229
|
+
readline.close();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async function askSecretQuestion(input, output, question) {
|
|
233
|
+
if (typeof input.setRawMode !== "function") return askQuestion(input, output, question);
|
|
234
|
+
output.write(question);
|
|
235
|
+
try {
|
|
236
|
+
return await readMaskedInput(input, output);
|
|
237
|
+
} finally {
|
|
238
|
+
output.write("\n");
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async function readMaskedInput(input, output) {
|
|
242
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
243
|
+
const buffer = [];
|
|
244
|
+
let settled = false;
|
|
245
|
+
const cleanup = () => {
|
|
246
|
+
input.off("data", handleData);
|
|
247
|
+
input.off("error", handleError);
|
|
248
|
+
input.pause();
|
|
249
|
+
input.setRawMode?.(false);
|
|
250
|
+
};
|
|
251
|
+
const rejectWith = (error) => {
|
|
252
|
+
if (settled) return;
|
|
253
|
+
settled = true;
|
|
254
|
+
cleanup();
|
|
255
|
+
rejectPromise(error);
|
|
256
|
+
};
|
|
257
|
+
const resolveWith = (value) => {
|
|
258
|
+
if (settled) return;
|
|
259
|
+
settled = true;
|
|
260
|
+
cleanup();
|
|
261
|
+
resolvePromise(value);
|
|
262
|
+
};
|
|
263
|
+
const handleError = (error) => {
|
|
264
|
+
rejectWith(error);
|
|
265
|
+
};
|
|
266
|
+
const handleData = (chunk) => {
|
|
267
|
+
const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk;
|
|
268
|
+
for (const character of text) {
|
|
269
|
+
if (character === "\r" || character === "\n") {
|
|
270
|
+
resolveWith(buffer.join(""));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (character === "") {
|
|
274
|
+
rejectWith(new Error(PROMPT_CANCELLED_ERROR));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (character === "\b" || character === "") {
|
|
278
|
+
if (buffer.length > 0) {
|
|
279
|
+
buffer.pop();
|
|
280
|
+
output.write("\b \b");
|
|
281
|
+
}
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (character < " " || character === "") continue;
|
|
285
|
+
buffer.push(character);
|
|
286
|
+
output.write("*");
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
input.setRawMode?.(true);
|
|
290
|
+
input.resume();
|
|
291
|
+
input.on("error", handleError);
|
|
292
|
+
input.on("data", handleData);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
178
295
|
function normalizeOptionalString(value) {
|
|
179
296
|
const normalized = value?.trim();
|
|
180
297
|
return normalized ? normalized : null;
|
|
@@ -190,9 +307,50 @@ function isPlainObject(value) {
|
|
|
190
307
|
function isMissingFileError(error) {
|
|
191
308
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
192
309
|
}
|
|
310
|
+
function isOpencodeTbotPluginSpec(value) {
|
|
311
|
+
const normalized = value.trim();
|
|
312
|
+
return normalized === "opencode-tbot" || normalized.startsWith("opencode-tbot@");
|
|
313
|
+
}
|
|
314
|
+
async function warnAboutLegacyLocalInstallations(homeDir) {
|
|
315
|
+
const legacyInstallations = await findLegacyLocalInstallations(homeDir);
|
|
316
|
+
if (legacyInstallations.length === 0) return;
|
|
317
|
+
stdout.write("\nDetected local opencode-tbot npm installation(s) that can make OpenCode show the plugin as file:///.../node_modules/...\n");
|
|
318
|
+
for (const installation of legacyInstallations) {
|
|
319
|
+
stdout.write(`- ${installation.packagePath}\n`);
|
|
320
|
+
stdout.write(` cleanup: cd "${installation.rootDir}" && npm uninstall opencode-tbot\n`);
|
|
321
|
+
}
|
|
322
|
+
stdout.write("Recommended npm flow: npm exec --package opencode-tbot@latest opencode-tbot -- install\n");
|
|
323
|
+
}
|
|
324
|
+
async function findLegacyLocalInstallations(homeDir) {
|
|
325
|
+
const roots = [...new Set([resolve(process.cwd()), resolve(homeDir)])];
|
|
326
|
+
const installations = [];
|
|
327
|
+
for (const rootDir of roots) if (await pathExists(join(rootDir, "node_modules", "opencode-tbot", "package.json"))) installations.push({
|
|
328
|
+
packagePath: join(rootDir, "node_modules", "opencode-tbot"),
|
|
329
|
+
rootDir
|
|
330
|
+
});
|
|
331
|
+
return installations;
|
|
332
|
+
}
|
|
333
|
+
async function pathExists(filePath) {
|
|
334
|
+
try {
|
|
335
|
+
await stat(filePath);
|
|
336
|
+
return true;
|
|
337
|
+
} catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
193
341
|
function buildHelpText() {
|
|
194
342
|
return [
|
|
195
|
-
"Usage: opencode-tbot [install] [options]",
|
|
343
|
+
"Usage: opencode-tbot [install|update] [options]",
|
|
344
|
+
" opencode-tbot --version",
|
|
345
|
+
"",
|
|
346
|
+
"Recommended npm usage:",
|
|
347
|
+
" npm exec --package opencode-tbot@latest opencode-tbot -- install",
|
|
348
|
+
" npm exec --package opencode-tbot@latest opencode-tbot -- update",
|
|
349
|
+
" npm exec --package opencode-tbot@latest opencode-tbot -- --version",
|
|
350
|
+
"",
|
|
351
|
+
"Commands:",
|
|
352
|
+
" install",
|
|
353
|
+
" update",
|
|
196
354
|
"",
|
|
197
355
|
"Options:",
|
|
198
356
|
" --bot-token <token>",
|
|
@@ -203,6 +361,7 @@ function buildHelpText() {
|
|
|
203
361
|
" --plugin-spec <spec>",
|
|
204
362
|
" --skip-register",
|
|
205
363
|
" --home-dir <path>",
|
|
364
|
+
" --version",
|
|
206
365
|
" --help"
|
|
207
366
|
].join("\n");
|
|
208
367
|
}
|
|
@@ -210,6 +369,6 @@ function formatCliError(error) {
|
|
|
210
369
|
return error instanceof Error && error.message.trim().length > 0 ? error.message.trim() : String(error);
|
|
211
370
|
}
|
|
212
371
|
//#endregion
|
|
213
|
-
export { installPlugin, main, runCli };
|
|
372
|
+
export { createPromptSession, installPlugin, main, runCli, updatePlugin };
|
|
214
373
|
|
|
215
374
|
//# 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 {\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\";\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 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\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\"\n ? args\n : args.slice(1);\n const options: CliOptions = {\n command: command === \"help\" || command === \"--help\" || command === \"-h\"\n ? \"help\"\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 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\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 buildHelpText(): string {\n return [\n \"Usage: opencode-tbot [install] [options]\",\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 \" --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 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 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(`Updated plugin registration to ${pluginSpec} in ${openCodeConfigFilePath}\\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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"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./assets/plugin-config-
|
|
1
|
+
import "./assets/plugin-config-CGIe9zdA.js";
|
|
2
2
|
import { TelegramBotPlugin, ensureTelegramBotPluginRuntime, resetTelegramBotPluginRuntimeForTests } from "./plugin.js";
|
|
3
3
|
export { TelegramBotPlugin, TelegramBotPlugin as default, ensureTelegramBotPluginRuntime, resetTelegramBotPluginRuntimeForTests };
|
package/dist/plugin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as preparePluginConfiguration,
|
|
1
|
+
import { c as loadAppConfig, i as preparePluginConfiguration, o as OPENCODE_TBOT_VERSION } from "./assets/plugin-config-CGIe9zdA.js";
|
|
2
2
|
import { mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, extname, isAbsolute, join } from "node:path";
|
|
4
4
|
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
@@ -8,8 +8,6 @@ import { createOpencodeClient } from "@opencode-ai/sdk/v2/client";
|
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { run } from "@grammyjs/runner";
|
|
10
10
|
import { Bot, InlineKeyboard } from "grammy";
|
|
11
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
12
|
-
import { fileURLToPath } from "node:url";
|
|
13
11
|
//#region src/infra/utils/redact.ts
|
|
14
12
|
var REDACTED = "[REDACTED]";
|
|
15
13
|
var DEFAULT_PREVIEW_LENGTH = 160;
|
|
@@ -357,9 +355,11 @@ var OpenCodeClient = class {
|
|
|
357
355
|
async resolvePromptResponse(input, data, knownMessageIds) {
|
|
358
356
|
if (!shouldPollPromptMessage(data, input.structured ?? false)) return data;
|
|
359
357
|
const messageId = data.info?.id;
|
|
358
|
+
let expectedParentId = toAssistantMessage(data.info)?.parentID ?? null;
|
|
360
359
|
let bestCandidate = data;
|
|
361
360
|
if (!messageId) return await this.findLatestPromptResponse(input.sessionId, {
|
|
362
361
|
initialMessageId: null,
|
|
362
|
+
initialParentId: expectedParentId,
|
|
363
363
|
knownMessageIds,
|
|
364
364
|
structured: input.structured ?? false
|
|
365
365
|
}) ?? data;
|
|
@@ -369,6 +369,7 @@ var OpenCodeClient = class {
|
|
|
369
369
|
if (!next) {
|
|
370
370
|
const latest = await this.findLatestPromptResponse(input.sessionId, {
|
|
371
371
|
initialMessageId: messageId,
|
|
372
|
+
initialParentId: expectedParentId,
|
|
372
373
|
knownMessageIds,
|
|
373
374
|
structured: input.structured ?? false
|
|
374
375
|
});
|
|
@@ -380,9 +381,11 @@ var OpenCodeClient = class {
|
|
|
380
381
|
}
|
|
381
382
|
data = next;
|
|
382
383
|
bestCandidate = next;
|
|
384
|
+
expectedParentId = toAssistantMessage(next.info)?.parentID ?? expectedParentId;
|
|
383
385
|
if (!shouldPollPromptMessage(data, input.structured ?? false)) return data;
|
|
384
386
|
const latest = await this.findLatestPromptResponse(input.sessionId, {
|
|
385
387
|
initialMessageId: messageId,
|
|
388
|
+
initialParentId: expectedParentId,
|
|
386
389
|
knownMessageIds,
|
|
387
390
|
structured: input.structured ?? false
|
|
388
391
|
});
|
|
@@ -433,10 +436,11 @@ var OpenCodeClient = class {
|
|
|
433
436
|
isInitial: !!id && id === options.initialMessageId,
|
|
434
437
|
isNew: !!id && !options.knownMessageIds.has(id),
|
|
435
438
|
isUsable: !shouldPollPromptMessage(message, options.structured),
|
|
439
|
+
sharesParent: !!assistant?.parentID && assistant.parentID === options.initialParentId,
|
|
436
440
|
message
|
|
437
441
|
};
|
|
438
|
-
}).sort((left, right) => Number(right.isUsable) - Number(left.isUsable) || Number(right.
|
|
439
|
-
return (candidates.find((candidate) => candidate.isUsable && (candidate.isNew
|
|
442
|
+
}).sort((left, right) => Number(right.isUsable) - Number(left.isUsable) || Number(right.isInitial) - Number(left.isInitial) || Number(right.sharesParent) - Number(left.sharesParent) || Number(right.isNew) - Number(left.isNew) || right.createdAt - left.createdAt);
|
|
443
|
+
return (candidates.find((candidate) => candidate.isUsable && candidate.isInitial) ?? candidates.find((candidate) => candidate.isUsable && candidate.sharesParent && candidate.isNew) ?? candidates.find((candidate) => candidate.isUsable && candidate.isNew) ?? candidates.find((candidate) => candidate.isInitial) ?? candidates.find((candidate) => candidate.sharesParent && candidate.isNew) ?? candidates.find((candidate) => candidate.isNew) ?? null)?.message ?? null;
|
|
440
444
|
}
|
|
441
445
|
async loadModels() {
|
|
442
446
|
const [configResponse, providersResponse] = await Promise.all([this.client.config.get(void 0, SDK_OPTIONS), this.client.config.providers(void 0, SDK_OPTIONS)]);
|
|
@@ -559,8 +563,7 @@ function shouldPollPromptMessage(data, structured) {
|
|
|
559
563
|
const bodyMd = structured ? extractStructuredMarkdown(assistantInfo?.structured) : null;
|
|
560
564
|
const hasText = extractTextFromParts(Array.isArray(data.parts) ? data.parts : []).length > 0;
|
|
561
565
|
const hasAssistantError = !!assistantInfo?.error;
|
|
562
|
-
|
|
563
|
-
return !hasText && !bodyMd && !hasAssistantError && !isCompleted;
|
|
566
|
+
return !hasText && !bodyMd && !hasAssistantError;
|
|
564
567
|
}
|
|
565
568
|
function toAssistantMessage(message) {
|
|
566
569
|
if (!message || typeof message !== "object") return null;
|
|
@@ -2848,23 +2851,6 @@ function stringifyUnknown(value) {
|
|
|
2848
2851
|
}
|
|
2849
2852
|
}
|
|
2850
2853
|
//#endregion
|
|
2851
|
-
//#region src/app/package-info.ts
|
|
2852
|
-
var OPENCODE_TBOT_VERSION = resolvePackageVersion();
|
|
2853
|
-
function resolvePackageVersion() {
|
|
2854
|
-
let directory = dirname(fileURLToPath(import.meta.url));
|
|
2855
|
-
while (true) {
|
|
2856
|
-
const packageFilePath = join(directory, "package.json");
|
|
2857
|
-
if (existsSync(packageFilePath)) try {
|
|
2858
|
-
const parsed = JSON.parse(readFileSync(packageFilePath, "utf8"));
|
|
2859
|
-
if (typeof parsed.version === "string" && parsed.version.trim().length > 0) return parsed.version;
|
|
2860
|
-
} catch {}
|
|
2861
|
-
const parentDirectory = dirname(directory);
|
|
2862
|
-
if (parentDirectory === directory) break;
|
|
2863
|
-
directory = parentDirectory;
|
|
2864
|
-
}
|
|
2865
|
-
return "unknown";
|
|
2866
|
-
}
|
|
2867
|
-
//#endregion
|
|
2868
2854
|
//#region src/bot/presenters/message.presenter.ts
|
|
2869
2855
|
var VARIANT_ORDER = [
|
|
2870
2856
|
"minimal",
|