opencode-tbot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,218 @@
1
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { z } from "zod";
5
+ //#region src/app/config.ts
6
+ var DEFAULT_OPENROUTER_MODEL = "openai/gpt-audio-mini";
7
+ var DEFAULT_STATE_FILE_PATH = "./data/opencode-tbot.state.json";
8
+ var DEFAULT_TELEGRAM_API_ROOT = "https://api.telegram.org";
9
+ var AllowedChatIdSchema = z.union([z.number().int(), z.string().regex(/^-?\d+$/u).transform((value) => Number(value))]);
10
+ var TelegramConfigSchema = z.preprocess((value) => value ?? {}, z.object({
11
+ botToken: z.string().trim().min(1),
12
+ allowedChatIds: z.array(AllowedChatIdSchema).default([]),
13
+ apiRoot: z.string().trim().url().default(DEFAULT_TELEGRAM_API_ROOT)
14
+ }));
15
+ var StateConfigSchema = z.preprocess((value) => value ?? {}, z.object({ path: z.string().trim().min(1).default(DEFAULT_STATE_FILE_PATH) }));
16
+ var LegacyDatabaseConfigSchema = z.preprocess((value) => value ?? {}, z.object({ path: z.string().trim().min(1).optional() }));
17
+ var OpenRouterConfigSchema = z.preprocess((value) => value ?? {}, z.object({
18
+ apiKey: z.string().default(""),
19
+ model: z.string().default(DEFAULT_OPENROUTER_MODEL),
20
+ timeoutMs: z.coerce.number().int().positive().default(3e4),
21
+ transcriptionPrompt: z.string().default("")
22
+ }));
23
+ var AppConfigSchema = z.preprocess(normalizeLegacyConfigSource, z.object({
24
+ telegram: TelegramConfigSchema,
25
+ state: StateConfigSchema,
26
+ database: LegacyDatabaseConfigSchema.optional(),
27
+ openrouter: OpenRouterConfigSchema,
28
+ logLevel: z.string().default("info")
29
+ }));
30
+ function loadAppConfig(configSource = {}, options = {}) {
31
+ return buildAppConfig(parseConfig(AppConfigSchema, configSource), options);
32
+ }
33
+ function buildAppConfig(data, options) {
34
+ const openRouterApiKey = normalizeOptionalString(data.openrouter.apiKey);
35
+ const openRouterModel = normalizeOptionalString(data.openrouter.model) ?? "openai/gpt-audio-mini";
36
+ const transcriptionPrompt = normalizeOptionalString(data.openrouter.transcriptionPrompt);
37
+ return {
38
+ telegramBotToken: data.telegram.botToken,
39
+ telegramAllowedChatIds: data.telegram.allowedChatIds,
40
+ telegramApiRoot: normalizeApiRoot(data.telegram.apiRoot),
41
+ logLevel: data.logLevel,
42
+ stateFilePath: resolveStatePath(data, options.cwd ?? process.cwd()),
43
+ openrouter: {
44
+ configured: !!openRouterApiKey,
45
+ apiKey: openRouterApiKey,
46
+ model: openRouterModel,
47
+ timeoutMs: data.openrouter.timeoutMs,
48
+ transcriptionPrompt
49
+ }
50
+ };
51
+ }
52
+ function normalizeOptionalString(value) {
53
+ const normalized = value.trim();
54
+ return normalized.length > 0 ? normalized : null;
55
+ }
56
+ function resolveStatePath(data, cwd) {
57
+ return resolve(cwd, data.state.path || data.database?.path || "./data/opencode-tbot.state.json");
58
+ }
59
+ function normalizeApiRoot(value) {
60
+ const normalized = value.trim();
61
+ return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
62
+ }
63
+ function normalizeLegacyConfigSource(value) {
64
+ if (!isPlainObject$1(value)) return value ?? {};
65
+ const source = value;
66
+ if (source.state?.path || !source.database?.path) return source;
67
+ return {
68
+ ...source,
69
+ state: {
70
+ ...source.state ?? {},
71
+ path: source.database.path
72
+ }
73
+ };
74
+ }
75
+ function parseConfig(schema, configSource) {
76
+ const parsed = schema.safeParse(configSource ?? {});
77
+ if (parsed.success) return parsed.data;
78
+ throw new Error(`Invalid plugin configuration: ${JSON.stringify(parsed.error.flatten())}`);
79
+ }
80
+ function isPlainObject$1(value) {
81
+ return value !== null && typeof value === "object" && !Array.isArray(value);
82
+ }
83
+ //#endregion
84
+ //#region src/app/plugin-config.ts
85
+ var PLUGIN_CONFIG_FILE_NAME = "tbot.config.json";
86
+ var LEGACY_PLUGIN_CONFIG_FILE_NAME = "opencode-tbot.config.json";
87
+ var GLOBAL_PLUGIN_DIRECTORY_NAME = "opencode-tbot";
88
+ var GLOBAL_PLUGIN_CONFIG_FILE_NAME = "config.json";
89
+ var OPENCODE_CONFIG_FILE_NAME = "opencode.json";
90
+ async function preparePluginConfiguration(options) {
91
+ const globalConfigFilePath = getGlobalPluginConfigFilePath(options.homeDir ?? homedir());
92
+ const projectConfigFilePath = await resolveProjectPluginConfigFilePath(options.cwd);
93
+ const [globalConfig, projectConfig] = await Promise.all([loadPluginConfigFile(globalConfigFilePath), loadPluginConfigFile(projectConfigFilePath)]);
94
+ const config = mergePluginConfigSources(globalConfig, projectConfig, options.config);
95
+ const configFilePath = await pathExists(projectConfigFilePath) ? projectConfigFilePath : globalConfigFilePath;
96
+ return {
97
+ cwd: options.cwd,
98
+ config,
99
+ globalConfigFilePath,
100
+ projectConfigFilePath,
101
+ configFilePath
102
+ };
103
+ }
104
+ function getOpenCodeConfigDirectory(homeDir = homedir()) {
105
+ return join(homeDir, ".config", "opencode");
106
+ }
107
+ function getOpenCodeConfigFilePath(homeDir = homedir()) {
108
+ return join(getOpenCodeConfigDirectory(homeDir), OPENCODE_CONFIG_FILE_NAME);
109
+ }
110
+ function getGlobalPluginConfigFilePath(homeDir = homedir()) {
111
+ return join(getOpenCodeConfigDirectory(homeDir), GLOBAL_PLUGIN_DIRECTORY_NAME, GLOBAL_PLUGIN_CONFIG_FILE_NAME);
112
+ }
113
+ async function writePluginConfigFile(configFilePath, config) {
114
+ await mkdir(dirname(configFilePath), { recursive: true });
115
+ await writeFile(configFilePath, serializePluginConfig(config), "utf8");
116
+ }
117
+ function mergePluginConfigSources(...sources) {
118
+ const merged = {};
119
+ for (const source of sources) {
120
+ if (!source) continue;
121
+ const normalized = normalizePluginConfigSource(source);
122
+ const previousTelegram = merged.telegram;
123
+ const previousState = merged.state;
124
+ const previousDatabase = merged.database;
125
+ const previousOpenRouter = merged.openrouter;
126
+ Object.assign(merged, normalized);
127
+ if (normalized.telegram) merged.telegram = {
128
+ ...previousTelegram ?? {},
129
+ ...normalized.telegram
130
+ };
131
+ if (normalized.state) merged.state = {
132
+ ...previousState ?? {},
133
+ ...normalized.state
134
+ };
135
+ if (normalized.database) merged.database = {
136
+ ...previousDatabase ?? {},
137
+ ...normalized.database
138
+ };
139
+ if (normalized.openrouter) merged.openrouter = {
140
+ ...previousOpenRouter ?? {},
141
+ ...normalized.openrouter
142
+ };
143
+ }
144
+ return merged;
145
+ }
146
+ function serializePluginConfig(config) {
147
+ return `${JSON.stringify(orderPluginConfig(config), null, 2)}\n`;
148
+ }
149
+ async function loadPluginConfigFile(configFilePath) {
150
+ try {
151
+ return parsePluginConfigText(await readFile(configFilePath, "utf8"), configFilePath);
152
+ } catch (error) {
153
+ if (isMissingFileError(error)) return {};
154
+ throw error;
155
+ }
156
+ }
157
+ function parsePluginConfigText(content, configFilePath) {
158
+ try {
159
+ const parsed = JSON.parse(content);
160
+ if (!isPlainObject(parsed)) throw new Error("Config root must be a JSON object.");
161
+ return parsed;
162
+ } catch (error) {
163
+ throw new Error([`Failed to parse ${configFilePath} as JSON.`, error instanceof Error ? error.message : String(error)].join(" "));
164
+ }
165
+ }
166
+ function orderPluginConfig(config) {
167
+ const prioritizedKeys = new Set([
168
+ "telegram",
169
+ "state",
170
+ "database",
171
+ "openrouter",
172
+ "logLevel"
173
+ ]);
174
+ const ordered = {};
175
+ if (config.telegram) ordered.telegram = config.telegram;
176
+ if (config.state) ordered.state = config.state;
177
+ if (config.database) ordered.database = config.database;
178
+ if (config.openrouter) ordered.openrouter = config.openrouter;
179
+ if (config.logLevel !== void 0) ordered.logLevel = config.logLevel;
180
+ for (const [key, value] of Object.entries(config)) if (!prioritizedKeys.has(key)) ordered[key] = value;
181
+ return ordered;
182
+ }
183
+ function isPlainObject(value) {
184
+ return value !== null && typeof value === "object" && !Array.isArray(value);
185
+ }
186
+ function isMissingFileError(error) {
187
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
188
+ }
189
+ async function resolveProjectPluginConfigFilePath(cwd) {
190
+ const preferredPath = join(cwd, PLUGIN_CONFIG_FILE_NAME);
191
+ if (await pathExists(preferredPath)) return preferredPath;
192
+ const legacyPath = join(cwd, LEGACY_PLUGIN_CONFIG_FILE_NAME);
193
+ if (await pathExists(legacyPath)) return legacyPath;
194
+ return preferredPath;
195
+ }
196
+ async function pathExists(filePath) {
197
+ try {
198
+ await access(filePath);
199
+ return true;
200
+ } catch (error) {
201
+ if (isMissingFileError(error)) return false;
202
+ throw error;
203
+ }
204
+ }
205
+ function normalizePluginConfigSource(source) {
206
+ if (source.state?.path || !source.database?.path) return source;
207
+ return {
208
+ ...source,
209
+ state: {
210
+ ...source.state ?? {},
211
+ path: source.database.path
212
+ }
213
+ };
214
+ }
215
+ //#endregion
216
+ export { writePluginConfigFile as a, preparePluginConfiguration as i, getOpenCodeConfigFilePath as n, DEFAULT_TELEGRAM_API_ROOT as o, mergePluginConfigSources as r, loadAppConfig as s, getGlobalPluginConfigFilePath as t };
217
+
218
+ //# sourceMappingURL=plugin-config-Crgl_PZz.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-config-Crgl_PZz.js","names":[],"sources":["../../src/app/config.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 LegacyDatabaseConfigSchema = z.preprocess(\n (value) => value ?? {},\n z.object({\n path: z.string().trim().min(1).optional(),\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.preprocess(\n normalizeLegacyConfigSource,\n z.object({\n telegram: TelegramConfigSchema,\n state: StateConfigSchema,\n database: LegacyDatabaseConfigSchema.optional(),\n openrouter: OpenRouterConfigSchema,\n logLevel: z.string().default(\"info\"),\n }),\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 database?: {\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 || data.database?.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 normalizeLegacyConfigSource(value: unknown): unknown {\n if (!isPlainObject(value)) {\n return value ?? {};\n }\n\n const source = value as PluginConfigSource;\n\n if (source.state?.path || !source.database?.path) {\n return source;\n }\n\n return {\n ...source,\n state: {\n ...(source.state ?? {}),\n path: source.database.path,\n },\n };\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\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\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 LEGACY_PLUGIN_CONFIG_FILE_NAME = \"opencode-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 = normalizePluginConfigSource(source);\n const previousTelegram = merged.telegram;\n const previousState = merged.state;\n const previousDatabase = merged.database;\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.database) {\n merged.database = {\n ...(previousDatabase ?? {}),\n ...normalized.database,\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 \"database\",\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.database) {\n ordered.database = config.database;\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 if (await pathExists(preferredPath)) {\n return preferredPath;\n }\n\n const legacyPath = join(cwd, LEGACY_PLUGIN_CONFIG_FILE_NAME);\n\n if (await pathExists(legacyPath)) {\n return legacyPath;\n }\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\nfunction normalizePluginConfigSource(source: PluginConfigSource): PluginConfigSource {\n if (source.state?.path || !source.database?.path) {\n return source;\n }\n\n return {\n ...source,\n state: {\n ...(source.state ?? {}),\n path: source.database.path,\n },\n };\n}\n"],"mappings":"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"}
package/dist/cli.js ADDED
@@ -0,0 +1,215 @@
1
+ import { a as writePluginConfigFile, n as getOpenCodeConfigFilePath, r as mergePluginConfigSources, t as getGlobalPluginConfigFilePath } from "./assets/plugin-config-Crgl_PZz.js";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { dirname } from "node:path";
5
+ import { stderr, stdin, stdout } from "node:process";
6
+ import { createInterface } from "node:readline/promises";
7
+ import { parse } from "jsonc-parser";
8
+ //#region src/cli.ts
9
+ var DEFAULT_PLUGIN_SPEC = "opencode-tbot@latest";
10
+ async function main(argv = process.argv.slice(2)) {
11
+ try {
12
+ const exitCode = await runCli(argv);
13
+ process.exitCode = exitCode;
14
+ return exitCode;
15
+ } catch (error) {
16
+ stderr.write(`${formatCliError(error)}\n`);
17
+ process.exitCode = 1;
18
+ return 1;
19
+ }
20
+ }
21
+ async function runCli(argv) {
22
+ const options = parseCliOptions(argv);
23
+ if (options.command === "help") {
24
+ stdout.write(`${buildHelpText()}\n`);
25
+ return 0;
26
+ }
27
+ await installPlugin(options);
28
+ return 0;
29
+ }
30
+ async function installPlugin(options = {}) {
31
+ const homeDir = options.homeDir ?? homedir();
32
+ const openCodeConfigFilePath = getOpenCodeConfigFilePath(homeDir);
33
+ const globalPluginConfigFilePath = getGlobalPluginConfigFilePath(homeDir);
34
+ const openCodeConfig = await readJsoncObject(openCodeConfigFilePath);
35
+ const existingPluginConfig = await readPluginConfigFile(globalPluginConfigFilePath);
36
+ const prompt = createPromptSession();
37
+ try {
38
+ const botToken = normalizeRequiredString(options.botToken ?? await prompt.ask("Telegram bot token: "), "Telegram bot token is required.");
39
+ const openrouterApiKey = options.enableVoice ?? await prompt.confirm("Enable voice transcription? (y/N): ", false) ? normalizeRequiredString(options.openrouterApiKey ?? await prompt.ask("OpenRouter API key: "), "OpenRouter API key is required when voice transcription is enabled.") : null;
40
+ const telegramApiRoot = normalizeOptionalString(options.telegramApiRoot) ?? "https://api.telegram.org";
41
+ const pluginSpec = normalizeOptionalString(options.pluginSpec) ?? DEFAULT_PLUGIN_SPEC;
42
+ const nextOpenCodeConfig = options.registerPlugin === false ? openCodeConfig : ensurePluginRegistered(openCodeConfig, pluginSpec);
43
+ const nextPluginConfig = buildInstalledPluginConfig(existingPluginConfig, botToken, telegramApiRoot, openrouterApiKey);
44
+ if (options.registerPlugin === false) await ensureParentDirectory(openCodeConfigFilePath);
45
+ else await writeJsonFile(openCodeConfigFilePath, nextOpenCodeConfig);
46
+ await writePluginConfigFile(globalPluginConfigFilePath, nextPluginConfig);
47
+ if (options.registerPlugin === false) stdout.write(`Skipped plugin registration in ${openCodeConfigFilePath}\n`);
48
+ else stdout.write(`Registered ${pluginSpec} in ${openCodeConfigFilePath}\n`);
49
+ stdout.write(`Wrote plugin defaults to ${globalPluginConfigFilePath}\n`);
50
+ } finally {
51
+ prompt.close();
52
+ }
53
+ }
54
+ function parseCliOptions(argv) {
55
+ const args = [...argv];
56
+ const first = args[0];
57
+ 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" };
60
+ for (let index = 0; index < values.length; index += 1) {
61
+ const value = values[index];
62
+ if (index === 0 && !value.startsWith("-")) continue;
63
+ switch (value) {
64
+ case "--bot-token":
65
+ options.botToken = values[++index];
66
+ break;
67
+ case "--enable-voice":
68
+ options.enableVoice = true;
69
+ break;
70
+ case "--disable-voice":
71
+ options.enableVoice = false;
72
+ break;
73
+ case "--openrouter-api-key":
74
+ options.openrouterApiKey = values[++index];
75
+ break;
76
+ case "--plugin-spec":
77
+ options.pluginSpec = values[++index];
78
+ break;
79
+ case "--telegram-api-root":
80
+ options.telegramApiRoot = values[++index];
81
+ break;
82
+ case "--skip-register":
83
+ options.registerPlugin = false;
84
+ break;
85
+ case "--home-dir":
86
+ options.homeDir = values[++index];
87
+ break;
88
+ case "--help":
89
+ case "-h":
90
+ options.command = "help";
91
+ break;
92
+ default: throw new Error(`Unknown argument: ${value}`);
93
+ }
94
+ }
95
+ return options;
96
+ }
97
+ function buildInstalledPluginConfig(current, botToken, telegramApiRoot, openrouterApiKey) {
98
+ const merged = mergePluginConfigSources(current, { telegram: {
99
+ botToken,
100
+ apiRoot: telegramApiRoot
101
+ } });
102
+ const nextOpenRouter = openrouterApiKey ? {
103
+ ...merged.openrouter ?? {},
104
+ apiKey: openrouterApiKey
105
+ } : removeOpenRouterApiKey(merged.openrouter);
106
+ return {
107
+ ...merged,
108
+ ...nextOpenRouter && Object.keys(nextOpenRouter).length > 0 ? { openrouter: nextOpenRouter } : {}
109
+ };
110
+ }
111
+ function removeOpenRouterApiKey(config) {
112
+ if (!config) return;
113
+ const { apiKey: _apiKey, ...rest } = config;
114
+ return Object.keys(rest).length > 0 ? rest : void 0;
115
+ }
116
+ function ensurePluginRegistered(config, pluginSpec) {
117
+ const plugins = Array.isArray(config.plugin) ? config.plugin.filter((item) => typeof item === "string") : [];
118
+ if (!plugins.includes(pluginSpec)) plugins.push(pluginSpec);
119
+ return {
120
+ ...config,
121
+ plugin: plugins
122
+ };
123
+ }
124
+ async function readPluginConfigFile(configFilePath) {
125
+ try {
126
+ const content = await readFile(configFilePath, "utf8");
127
+ const parsed = JSON.parse(content);
128
+ return isPlainObject(parsed) ? parsed : {};
129
+ } catch (error) {
130
+ if (isMissingFileError(error)) return {};
131
+ throw error;
132
+ }
133
+ }
134
+ async function readJsoncObject(filePath) {
135
+ try {
136
+ const parsed = parse(await readFile(filePath, "utf8"));
137
+ return isPlainObject(parsed) ? parsed : {};
138
+ } catch (error) {
139
+ if (isMissingFileError(error)) return {};
140
+ throw error;
141
+ }
142
+ }
143
+ async function writeJsonFile(filePath, value) {
144
+ await ensureParentDirectory(filePath);
145
+ await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
146
+ }
147
+ async function ensureParentDirectory(filePath) {
148
+ await mkdir(dirname(filePath), { recursive: true });
149
+ }
150
+ function createPromptSession() {
151
+ if (!stdin.isTTY || !stdout.isTTY) return {
152
+ ask: async () => "",
153
+ async confirm(_question, defaultValue) {
154
+ return defaultValue;
155
+ },
156
+ close() {}
157
+ };
158
+ const readline = createInterface({
159
+ input: stdin,
160
+ output: stdout
161
+ });
162
+ return {
163
+ ask(question) {
164
+ return readline.question(question);
165
+ },
166
+ async confirm(question, defaultValue) {
167
+ const answer = normalizeOptionalString(await readline.question(question));
168
+ if (!answer) return defaultValue;
169
+ if (["y", "yes"].includes(answer.toLowerCase())) return true;
170
+ if (["n", "no"].includes(answer.toLowerCase())) return false;
171
+ throw new Error(`Unsupported answer: ${answer}`);
172
+ },
173
+ close() {
174
+ readline.close();
175
+ }
176
+ };
177
+ }
178
+ function normalizeOptionalString(value) {
179
+ const normalized = value?.trim();
180
+ return normalized ? normalized : null;
181
+ }
182
+ function normalizeRequiredString(value, errorMessage) {
183
+ const normalized = normalizeOptionalString(value);
184
+ if (!normalized) throw new Error(errorMessage);
185
+ return normalized;
186
+ }
187
+ function isPlainObject(value) {
188
+ return value !== null && typeof value === "object" && !Array.isArray(value);
189
+ }
190
+ function isMissingFileError(error) {
191
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
192
+ }
193
+ function buildHelpText() {
194
+ return [
195
+ "Usage: opencode-tbot [install] [options]",
196
+ "",
197
+ "Options:",
198
+ " --bot-token <token>",
199
+ " --enable-voice",
200
+ " --disable-voice",
201
+ " --openrouter-api-key <key>",
202
+ " --telegram-api-root <url>",
203
+ " --plugin-spec <spec>",
204
+ " --skip-register",
205
+ " --home-dir <path>",
206
+ " --help"
207
+ ].join("\n");
208
+ }
209
+ function formatCliError(error) {
210
+ return error instanceof Error && error.message.trim().length > 0 ? error.message.trim() : String(error);
211
+ }
212
+ //#endregion
213
+ export { installPlugin, main, runCli };
214
+
215
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +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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"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import "./assets/plugin-config-Crgl_PZz.js";
2
+ import { TelegramBotPlugin, ensureTelegramBotPluginRuntime, resetTelegramBotPluginRuntimeForTests } from "./plugin.js";
3
+ export { TelegramBotPlugin, TelegramBotPlugin as default, ensureTelegramBotPluginRuntime, resetTelegramBotPluginRuntimeForTests };