@grinev/opencode-telegram-bot 0.15.0 → 0.16.1
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/.env.example +16 -0
- package/README.md +22 -1
- package/dist/app/start-bot-app.js +82 -7
- package/dist/bot/assistant-run-state.js +60 -0
- package/dist/bot/commands/abort.js +2 -0
- package/dist/bot/commands/commands.js +10 -0
- package/dist/bot/commands/definitions.js +1 -0
- package/dist/bot/commands/open.js +314 -0
- package/dist/bot/commands/projects.js +5 -38
- package/dist/bot/commands/start.js +2 -0
- package/dist/bot/handlers/inline-menu.js +9 -1
- package/dist/bot/handlers/prompt.js +11 -0
- package/dist/bot/index.js +187 -100
- package/dist/bot/streaming/response-streamer.js +26 -17
- package/dist/bot/utils/assistant-rendering.js +117 -0
- package/dist/bot/utils/assistant-run-footer.js +9 -0
- package/dist/bot/utils/browser-roots.js +140 -0
- package/dist/bot/utils/file-tree.js +92 -0
- package/dist/bot/utils/finalize-assistant-response.js +18 -24
- package/dist/bot/utils/send-with-markdown-fallback.js +3 -3
- package/dist/bot/utils/switch-project.js +48 -0
- package/dist/bot/utils/telegram-text.js +95 -1
- package/dist/cli/args.js +36 -7
- package/dist/cli.js +133 -15
- package/dist/config.js +4 -0
- package/dist/i18n/de.js +15 -10
- package/dist/i18n/en.js +15 -10
- package/dist/i18n/es.js +15 -10
- package/dist/i18n/fr.js +15 -10
- package/dist/i18n/ru.js +15 -10
- package/dist/i18n/zh.js +15 -10
- package/dist/index.js +2 -0
- package/dist/project/manager.js +2 -1
- package/dist/scheduled-task/runtime.js +8 -0
- package/dist/service/manager.js +244 -0
- package/dist/service/runtime.js +19 -0
- package/dist/service/types.js +1 -0
- package/dist/summary/aggregator.js +17 -1
- package/dist/summary/formatter.js +2 -88
- package/dist/telegram/render/block-fallback.js +28 -0
- package/dist/telegram/render/block-parser.js +295 -0
- package/dist/telegram/render/block-renderer.js +457 -0
- package/dist/telegram/render/chunker.js +281 -0
- package/dist/telegram/render/inline-renderer.js +128 -0
- package/dist/telegram/render/markdown-normalizer.js +94 -0
- package/dist/telegram/render/pipeline.js +9 -0
- package/dist/telegram/render/types.js +1 -0
- package/dist/telegram/render/validator.js +160 -0
- package/dist/utils/logger.js +200 -73
- package/package.json +6 -2
|
@@ -1,10 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { logger } from "../../utils/logger.js";
|
|
2
|
+
import { editMessageWithMarkdownFallback, isTelegramMarkdownParseError, sendMessageWithMarkdownFallback, } from "./send-with-markdown-fallback.js";
|
|
2
3
|
function resolveParseMode(format) {
|
|
3
4
|
if (format === "markdown_v2") {
|
|
4
5
|
return "MarkdownV2";
|
|
5
6
|
}
|
|
6
7
|
return undefined;
|
|
7
8
|
}
|
|
9
|
+
function stripRichFormattingOptions(options) {
|
|
10
|
+
if (!options) {
|
|
11
|
+
return options;
|
|
12
|
+
}
|
|
13
|
+
const rawOptions = {
|
|
14
|
+
...options,
|
|
15
|
+
};
|
|
16
|
+
delete rawOptions.parse_mode;
|
|
17
|
+
delete rawOptions.entities;
|
|
18
|
+
return rawOptions;
|
|
19
|
+
}
|
|
20
|
+
export function getTelegramRenderedPartSignature(part) {
|
|
21
|
+
return `${part.text}\n${JSON.stringify(part.entities ?? null)}`;
|
|
22
|
+
}
|
|
8
23
|
export async function sendBotText({ api, chatId, text, rawFallbackText, options, format = "raw", }) {
|
|
9
24
|
await sendMessageWithMarkdownFallback({
|
|
10
25
|
api,
|
|
@@ -15,6 +30,85 @@ export async function sendBotText({ api, chatId, text, rawFallbackText, options,
|
|
|
15
30
|
parseMode: resolveParseMode(format),
|
|
16
31
|
});
|
|
17
32
|
}
|
|
33
|
+
export async function sendRenderedBotPart({ api, chatId, part, options, }) {
|
|
34
|
+
const rawOptions = stripRichFormattingOptions(options);
|
|
35
|
+
logger.debug("[Bot] Sending rendered Telegram part", {
|
|
36
|
+
source: part.source,
|
|
37
|
+
textLength: part.text.length,
|
|
38
|
+
fallbackTextLength: part.fallbackText.length,
|
|
39
|
+
entityCount: part.entities?.length ?? 0,
|
|
40
|
+
});
|
|
41
|
+
if (!part.entities?.length) {
|
|
42
|
+
const sentMessage = await api.sendMessage(chatId, part.text, rawOptions);
|
|
43
|
+
return {
|
|
44
|
+
messageId: sentMessage.message_id,
|
|
45
|
+
deliveredSignature: getTelegramRenderedPartSignature({ text: part.text }),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const sentMessage = await api.sendMessage(chatId, part.text, {
|
|
50
|
+
...(rawOptions || {}),
|
|
51
|
+
entities: part.entities,
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
messageId: sentMessage.message_id,
|
|
55
|
+
deliveredSignature: getTelegramRenderedPartSignature(part),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (!isTelegramMarkdownParseError(error)) {
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
logger.warn("[Bot] Entity payload rejected, retrying assistant message part in raw mode", error);
|
|
63
|
+
const sentMessage = await api.sendMessage(chatId, part.fallbackText, rawOptions);
|
|
64
|
+
logger.debug("[Bot] Assistant message part sent in raw fallback mode", {
|
|
65
|
+
fallbackTextLength: part.fallbackText.length,
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
messageId: sentMessage.message_id,
|
|
69
|
+
deliveredSignature: getTelegramRenderedPartSignature({ text: part.fallbackText }),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export async function editRenderedBotPart({ api, chatId, messageId, part, options, }) {
|
|
74
|
+
const rawOptions = stripRichFormattingOptions(options);
|
|
75
|
+
logger.debug("[Bot] Editing rendered Telegram part", {
|
|
76
|
+
messageId,
|
|
77
|
+
source: part.source,
|
|
78
|
+
textLength: part.text.length,
|
|
79
|
+
fallbackTextLength: part.fallbackText.length,
|
|
80
|
+
entityCount: part.entities?.length ?? 0,
|
|
81
|
+
});
|
|
82
|
+
if (!part.entities?.length) {
|
|
83
|
+
await api.editMessageText(chatId, messageId, part.text, rawOptions);
|
|
84
|
+
return {
|
|
85
|
+
deliveredSignature: getTelegramRenderedPartSignature({ text: part.text }),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
await api.editMessageText(chatId, messageId, part.text, {
|
|
90
|
+
...(rawOptions || {}),
|
|
91
|
+
entities: part.entities,
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
deliveredSignature: getTelegramRenderedPartSignature(part),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (!isTelegramMarkdownParseError(error)) {
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
logger.warn("[Bot] Entity payload rejected, retrying assistant edit part in raw mode", error);
|
|
102
|
+
await api.editMessageText(chatId, messageId, part.fallbackText, rawOptions);
|
|
103
|
+
logger.debug("[Bot] Assistant edit part applied in raw fallback mode", {
|
|
104
|
+
messageId,
|
|
105
|
+
fallbackTextLength: part.fallbackText.length,
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
deliveredSignature: getTelegramRenderedPartSignature({ text: part.fallbackText }),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
18
112
|
export async function editBotText({ api, chatId, messageId, text, rawFallbackText, options, format = "raw", }) {
|
|
19
113
|
await editMessageWithMarkdownFallback({
|
|
20
114
|
api,
|
package/dist/cli/args.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
const CLI_MESSAGES = {
|
|
2
|
+
unknownCommand: (value) => `Unknown command: ${value}`,
|
|
3
|
+
modeRequiresValue: "Option --mode requires a value: sources|installed",
|
|
4
|
+
invalidMode: (value) => `Invalid mode value: ${value}. Expected sources|installed`,
|
|
5
|
+
unknownOption: (value) => `Unknown option: ${value}`,
|
|
6
|
+
modeOnlyStart: "Option --mode is supported only for the start command",
|
|
7
|
+
daemonOnlyStart: "Option --daemon is supported only for the start command",
|
|
8
|
+
};
|
|
2
9
|
const SUPPORTED_COMMANDS = ["start", "status", "stop", "config"];
|
|
3
10
|
function isCliCommand(value) {
|
|
4
11
|
return SUPPORTED_COMMANDS.includes(value);
|
|
@@ -16,6 +23,7 @@ export function parseCliArgs(argv) {
|
|
|
16
23
|
const args = [...argv];
|
|
17
24
|
let command = "start";
|
|
18
25
|
let mode;
|
|
26
|
+
let daemon = false;
|
|
19
27
|
let showHelp = false;
|
|
20
28
|
let currentIndex = 0;
|
|
21
29
|
const firstArg = args[0];
|
|
@@ -23,8 +31,9 @@ export function parseCliArgs(argv) {
|
|
|
23
31
|
if (!isCliCommand(firstArg)) {
|
|
24
32
|
return {
|
|
25
33
|
command,
|
|
34
|
+
daemon,
|
|
26
35
|
showHelp: true,
|
|
27
|
-
error:
|
|
36
|
+
error: CLI_MESSAGES.unknownCommand(firstArg),
|
|
28
37
|
};
|
|
29
38
|
}
|
|
30
39
|
command = firstArg;
|
|
@@ -37,23 +46,30 @@ export function parseCliArgs(argv) {
|
|
|
37
46
|
currentIndex += 1;
|
|
38
47
|
continue;
|
|
39
48
|
}
|
|
49
|
+
if (token === "--daemon") {
|
|
50
|
+
daemon = true;
|
|
51
|
+
currentIndex += 1;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
40
54
|
if (token === "--mode") {
|
|
41
55
|
const modeValue = args[currentIndex + 1];
|
|
42
56
|
if (!modeValue || modeValue.startsWith("-")) {
|
|
43
57
|
return {
|
|
44
58
|
command,
|
|
59
|
+
daemon,
|
|
45
60
|
mode,
|
|
46
61
|
showHelp: true,
|
|
47
|
-
error:
|
|
62
|
+
error: CLI_MESSAGES.modeRequiresValue,
|
|
48
63
|
};
|
|
49
64
|
}
|
|
50
65
|
const parsedMode = normalizeMode(modeValue);
|
|
51
66
|
if (!parsedMode) {
|
|
52
67
|
return {
|
|
53
68
|
command,
|
|
69
|
+
daemon,
|
|
54
70
|
mode,
|
|
55
71
|
showHelp: true,
|
|
56
|
-
error:
|
|
72
|
+
error: CLI_MESSAGES.invalidMode(modeValue),
|
|
57
73
|
};
|
|
58
74
|
}
|
|
59
75
|
mode = parsedMode;
|
|
@@ -66,9 +82,10 @@ export function parseCliArgs(argv) {
|
|
|
66
82
|
if (!parsedMode) {
|
|
67
83
|
return {
|
|
68
84
|
command,
|
|
85
|
+
daemon,
|
|
69
86
|
mode,
|
|
70
87
|
showHelp: true,
|
|
71
|
-
error:
|
|
88
|
+
error: CLI_MESSAGES.invalidMode(modeValue),
|
|
72
89
|
};
|
|
73
90
|
}
|
|
74
91
|
mode = parsedMode;
|
|
@@ -77,21 +94,33 @@ export function parseCliArgs(argv) {
|
|
|
77
94
|
}
|
|
78
95
|
return {
|
|
79
96
|
command,
|
|
97
|
+
daemon,
|
|
80
98
|
mode,
|
|
81
99
|
showHelp: true,
|
|
82
|
-
error:
|
|
100
|
+
error: CLI_MESSAGES.unknownOption(token),
|
|
83
101
|
};
|
|
84
102
|
}
|
|
85
103
|
if (command !== "start" && mode) {
|
|
86
104
|
return {
|
|
87
105
|
command,
|
|
106
|
+
daemon,
|
|
107
|
+
mode,
|
|
108
|
+
showHelp: true,
|
|
109
|
+
error: CLI_MESSAGES.modeOnlyStart,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (command !== "start" && daemon) {
|
|
113
|
+
return {
|
|
114
|
+
command,
|
|
115
|
+
daemon,
|
|
88
116
|
mode,
|
|
89
117
|
showHelp: true,
|
|
90
|
-
error:
|
|
118
|
+
error: CLI_MESSAGES.daemonOnlyStart,
|
|
91
119
|
};
|
|
92
120
|
}
|
|
93
121
|
return {
|
|
94
122
|
command,
|
|
123
|
+
daemon,
|
|
95
124
|
mode,
|
|
96
125
|
showHelp,
|
|
97
126
|
};
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,38 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { parseCliArgs } from "./cli/args.js";
|
|
3
3
|
import { resolveRuntimeMode, setRuntimeMode } from "./runtime/mode.js";
|
|
4
|
-
import {
|
|
4
|
+
import { getRuntimePaths } from "./runtime/paths.js";
|
|
5
5
|
const EXIT_SUCCESS = 0;
|
|
6
6
|
const EXIT_RUNTIME_ERROR = 1;
|
|
7
7
|
const EXIT_INVALID_ARGS = 2;
|
|
8
|
+
const CLI_USAGE = `Usage:
|
|
9
|
+
opencode-telegram [start] [--daemon] [--mode installed]
|
|
10
|
+
opencode-telegram status
|
|
11
|
+
opencode-telegram stop
|
|
12
|
+
opencode-telegram config
|
|
13
|
+
|
|
14
|
+
Notes:
|
|
15
|
+
- No command defaults to start
|
|
16
|
+
- start runs in foreground by default
|
|
17
|
+
- --daemon is supported for the installed runtime only`;
|
|
18
|
+
const CLI_MESSAGES = {
|
|
19
|
+
daemonRequiresInstalled: "Daemon mode is supported only for the installed runtime. Use `opencode-telegram start` for foreground source runs.",
|
|
20
|
+
unknownServiceError: "Unknown service error.",
|
|
21
|
+
cleanupStale: "Removed stale daemon state file.",
|
|
22
|
+
cleanupInvalid: "Removed invalid daemon state file.",
|
|
23
|
+
startSuccess: "OpenCode Telegram Bot daemon started.",
|
|
24
|
+
startAlreadyRunning: "OpenCode Telegram Bot daemon is already running.",
|
|
25
|
+
statusRunning: "Service status: running",
|
|
26
|
+
statusStopped: "Service status: stopped",
|
|
27
|
+
stopSuccess: "OpenCode Telegram Bot daemon stopped.",
|
|
28
|
+
stopAlreadyStopped: "OpenCode Telegram Bot daemon is not running.",
|
|
29
|
+
linePid: (pid) => `PID: ${pid}`,
|
|
30
|
+
lineStartedAt: (startedAt) => `Started at: ${startedAt}`,
|
|
31
|
+
lineUptimeSec: (seconds) => `Uptime: ${seconds} sec`,
|
|
32
|
+
lineLogFile: (filePath) => `Log file: ${filePath}`,
|
|
33
|
+
lineAppHome: (appHome) => `App home: ${appHome}`,
|
|
34
|
+
errorPrefix: (message) => `CLI error: ${message}`,
|
|
35
|
+
};
|
|
8
36
|
function writeStdout(message) {
|
|
9
37
|
process.stdout.write(`${message}\n`);
|
|
10
38
|
}
|
|
@@ -12,18 +40,33 @@ function writeStderr(message) {
|
|
|
12
40
|
process.stderr.write(`${message}\n`);
|
|
13
41
|
}
|
|
14
42
|
function printUsage() {
|
|
15
|
-
writeStdout(
|
|
43
|
+
writeStdout(CLI_USAGE);
|
|
16
44
|
}
|
|
17
|
-
function
|
|
18
|
-
if (
|
|
19
|
-
return
|
|
45
|
+
function formatServiceCleanupMessage(cleanupReason) {
|
|
46
|
+
if (cleanupReason === "stale") {
|
|
47
|
+
return CLI_MESSAGES.cleanupStale;
|
|
20
48
|
}
|
|
21
|
-
if (
|
|
22
|
-
return
|
|
49
|
+
if (cleanupReason === "invalid") {
|
|
50
|
+
return CLI_MESSAGES.cleanupInvalid;
|
|
23
51
|
}
|
|
24
|
-
return
|
|
52
|
+
return null;
|
|
25
53
|
}
|
|
26
|
-
|
|
54
|
+
function formatServiceDetails(details) {
|
|
55
|
+
const uptimeSec = Math.max(0, Math.floor((Date.now() - Date.parse(details.startedAt)) / 1000));
|
|
56
|
+
return [
|
|
57
|
+
CLI_MESSAGES.linePid(details.pid),
|
|
58
|
+
CLI_MESSAGES.lineStartedAt(details.startedAt),
|
|
59
|
+
CLI_MESSAGES.lineUptimeSec(uptimeSec),
|
|
60
|
+
CLI_MESSAGES.lineLogFile(details.logFilePath),
|
|
61
|
+
CLI_MESSAGES.lineAppHome(details.appHome),
|
|
62
|
+
].join("\n");
|
|
63
|
+
}
|
|
64
|
+
function writeOptionalLine(message) {
|
|
65
|
+
if (message) {
|
|
66
|
+
writeStdout(message);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function runStartCommand(mode, daemon) {
|
|
27
70
|
const modeResult = resolveRuntimeMode({
|
|
28
71
|
defaultMode: "installed",
|
|
29
72
|
explicitMode: mode,
|
|
@@ -32,20 +75,92 @@ async function runStartCommand(mode) {
|
|
|
32
75
|
throw new Error(modeResult.error);
|
|
33
76
|
}
|
|
34
77
|
setRuntimeMode(modeResult.mode);
|
|
78
|
+
if (daemon && modeResult.mode !== "installed") {
|
|
79
|
+
throw new Error(CLI_MESSAGES.daemonRequiresInstalled);
|
|
80
|
+
}
|
|
35
81
|
const { ensureRuntimeConfigForStart } = await import("./runtime/bootstrap.js");
|
|
36
82
|
await ensureRuntimeConfigForStart();
|
|
83
|
+
if (daemon) {
|
|
84
|
+
const { startBotDaemon } = await import("./service/manager.js");
|
|
85
|
+
const result = await startBotDaemon(modeResult.mode);
|
|
86
|
+
const cleanupMessage = formatServiceCleanupMessage(result.cleanupReason);
|
|
87
|
+
const runtimePaths = getRuntimePaths();
|
|
88
|
+
writeOptionalLine(cleanupMessage);
|
|
89
|
+
if (!result.success) {
|
|
90
|
+
if (result.alreadyRunning && result.service) {
|
|
91
|
+
writeStdout(CLI_MESSAGES.startAlreadyRunning);
|
|
92
|
+
writeStdout("");
|
|
93
|
+
writeStdout(formatServiceDetails({
|
|
94
|
+
pid: result.service.pid,
|
|
95
|
+
startedAt: result.service.startedAt,
|
|
96
|
+
logFilePath: result.service.logFilePath,
|
|
97
|
+
appHome: runtimePaths.appHome,
|
|
98
|
+
}));
|
|
99
|
+
return EXIT_SUCCESS;
|
|
100
|
+
}
|
|
101
|
+
throw new Error(result.error || CLI_MESSAGES.unknownServiceError);
|
|
102
|
+
}
|
|
103
|
+
if (!result.service) {
|
|
104
|
+
throw new Error(CLI_MESSAGES.unknownServiceError);
|
|
105
|
+
}
|
|
106
|
+
writeStdout(CLI_MESSAGES.startSuccess);
|
|
107
|
+
writeStdout("");
|
|
108
|
+
writeStdout(formatServiceDetails({
|
|
109
|
+
pid: result.service.pid,
|
|
110
|
+
startedAt: result.service.startedAt,
|
|
111
|
+
logFilePath: result.service.logFilePath,
|
|
112
|
+
appHome: runtimePaths.appHome,
|
|
113
|
+
}));
|
|
114
|
+
return EXIT_SUCCESS;
|
|
115
|
+
}
|
|
116
|
+
const { initializeLogger } = await import("./utils/logger.js");
|
|
117
|
+
await initializeLogger();
|
|
37
118
|
const { startBotApp } = await import("./app/start-bot-app.js");
|
|
38
119
|
await startBotApp();
|
|
39
120
|
return EXIT_SUCCESS;
|
|
40
121
|
}
|
|
41
122
|
async function runConfigCommand() {
|
|
42
123
|
setRuntimeMode("installed");
|
|
124
|
+
const { initializeLogger } = await import("./utils/logger.js");
|
|
125
|
+
await initializeLogger();
|
|
43
126
|
const { runConfigWizardCommand } = await import("./runtime/bootstrap.js");
|
|
44
127
|
await runConfigWizardCommand();
|
|
45
128
|
return EXIT_SUCCESS;
|
|
46
129
|
}
|
|
47
|
-
async function
|
|
48
|
-
|
|
130
|
+
async function runStatusCommand() {
|
|
131
|
+
setRuntimeMode("installed");
|
|
132
|
+
const { getBotServiceStatus } = await import("./service/manager.js");
|
|
133
|
+
const runtimePaths = getRuntimePaths();
|
|
134
|
+
const status = await getBotServiceStatus();
|
|
135
|
+
writeOptionalLine(formatServiceCleanupMessage(status.cleanupReason));
|
|
136
|
+
if (status.status !== "running" || !status.service) {
|
|
137
|
+
writeStdout(CLI_MESSAGES.statusStopped);
|
|
138
|
+
writeStdout(CLI_MESSAGES.lineAppHome(runtimePaths.appHome));
|
|
139
|
+
return EXIT_SUCCESS;
|
|
140
|
+
}
|
|
141
|
+
writeStdout(CLI_MESSAGES.statusRunning);
|
|
142
|
+
writeStdout("");
|
|
143
|
+
writeStdout(formatServiceDetails({
|
|
144
|
+
pid: status.service.pid,
|
|
145
|
+
startedAt: status.service.startedAt,
|
|
146
|
+
logFilePath: status.service.logFilePath,
|
|
147
|
+
appHome: runtimePaths.appHome,
|
|
148
|
+
}));
|
|
149
|
+
return EXIT_SUCCESS;
|
|
150
|
+
}
|
|
151
|
+
async function runStopCommand() {
|
|
152
|
+
setRuntimeMode("installed");
|
|
153
|
+
const { stopBotDaemon } = await import("./service/manager.js");
|
|
154
|
+
const result = await stopBotDaemon();
|
|
155
|
+
writeOptionalLine(formatServiceCleanupMessage(result.cleanupReason));
|
|
156
|
+
if (!result.success) {
|
|
157
|
+
throw new Error(result.error || CLI_MESSAGES.unknownServiceError);
|
|
158
|
+
}
|
|
159
|
+
if (result.alreadyStopped) {
|
|
160
|
+
writeStdout(CLI_MESSAGES.stopAlreadyStopped);
|
|
161
|
+
return EXIT_SUCCESS;
|
|
162
|
+
}
|
|
163
|
+
writeStdout(CLI_MESSAGES.stopSuccess);
|
|
49
164
|
return EXIT_SUCCESS;
|
|
50
165
|
}
|
|
51
166
|
async function runCli(argv) {
|
|
@@ -58,12 +173,15 @@ async function runCli(argv) {
|
|
|
58
173
|
return parsedArgs.error ? EXIT_INVALID_ARGS : EXIT_SUCCESS;
|
|
59
174
|
}
|
|
60
175
|
if (parsedArgs.command === "start") {
|
|
61
|
-
return runStartCommand(parsedArgs.mode);
|
|
176
|
+
return runStartCommand(parsedArgs.mode, parsedArgs.daemon);
|
|
62
177
|
}
|
|
63
178
|
if (parsedArgs.command === "config") {
|
|
64
179
|
return runConfigCommand();
|
|
65
180
|
}
|
|
66
|
-
|
|
181
|
+
if (parsedArgs.command === "status") {
|
|
182
|
+
return runStatusCommand();
|
|
183
|
+
}
|
|
184
|
+
return runStopCommand();
|
|
67
185
|
}
|
|
68
186
|
void runCli(process.argv.slice(2))
|
|
69
187
|
.then((exitCode) => {
|
|
@@ -71,10 +189,10 @@ void runCli(process.argv.slice(2))
|
|
|
71
189
|
})
|
|
72
190
|
.catch((error) => {
|
|
73
191
|
if (error instanceof Error) {
|
|
74
|
-
writeStderr(
|
|
192
|
+
writeStderr(CLI_MESSAGES.errorPrefix(error.message));
|
|
75
193
|
}
|
|
76
194
|
else {
|
|
77
|
-
writeStderr(
|
|
195
|
+
writeStderr(CLI_MESSAGES.errorPrefix(String(error)));
|
|
78
196
|
}
|
|
79
197
|
process.exitCode = EXIT_RUNTIME_ERROR;
|
|
80
198
|
});
|
package/dist/config.js
CHANGED
|
@@ -78,11 +78,15 @@ export const config = {
|
|
|
78
78
|
locale: getOptionalLocaleEnvVar("BOT_LOCALE", "en"),
|
|
79
79
|
hideThinkingMessages: getOptionalBooleanEnvVar("HIDE_THINKING_MESSAGES", false),
|
|
80
80
|
hideToolCallMessages: getOptionalBooleanEnvVar("HIDE_TOOL_CALL_MESSAGES", false),
|
|
81
|
+
hideToolFileMessages: getOptionalBooleanEnvVar("HIDE_TOOL_FILE_MESSAGES", false),
|
|
81
82
|
messageFormatMode: getOptionalMessageFormatModeEnvVar("MESSAGE_FORMAT_MODE", "markdown"),
|
|
82
83
|
},
|
|
83
84
|
files: {
|
|
84
85
|
maxFileSizeKb: parseInt(getEnvVar("CODE_FILE_MAX_SIZE_KB", false) || "100", 10),
|
|
85
86
|
},
|
|
87
|
+
open: {
|
|
88
|
+
browserRoots: getEnvVar("OPEN_BROWSER_ROOTS", false),
|
|
89
|
+
},
|
|
86
90
|
stt: {
|
|
87
91
|
apiUrl: getEnvVar("STT_API_URL", false),
|
|
88
92
|
apiKey: getEnvVar("STT_API_KEY", false),
|
package/dist/i18n/de.js
CHANGED
|
@@ -324,16 +324,6 @@ export const de = {
|
|
|
324
324
|
"commands.page_empty_callback": "Keine Befehle auf dieser Seite",
|
|
325
325
|
"commands.page_load_error_callback": "Diese Seite konnte nicht geladen werden. Bitte versuche es erneut.",
|
|
326
326
|
"cmd.description.rename": "Aktuelle Sitzung umbenennen",
|
|
327
|
-
"cli.usage": "Verwendung:\n opencode-telegram [start] [--mode sources|installed]\n opencode-telegram status\n opencode-telegram stop\n opencode-telegram config\n\nHinweise:\n - Ohne Befehl wird standardmäßig `start` verwendet\n - `--mode` wird derzeit nur für `start` unterstützt",
|
|
328
|
-
"cli.placeholder.status": "Befehl `status` ist derzeit ein Platzhalter. Echte Statusprüfungen werden in der Service-Schicht hinzugefügt (Phase 5).",
|
|
329
|
-
"cli.placeholder.stop": "Befehl `stop` ist derzeit ein Platzhalter. Ein echter Stop des Hintergrundprozesses wird in der Service-Schicht hinzugefügt (Phase 5).",
|
|
330
|
-
"cli.placeholder.unavailable": "Befehl ist nicht verfügbar.",
|
|
331
|
-
"cli.error.prefix": "CLI-Fehler: {message}",
|
|
332
|
-
"cli.args.unknown_command": "Unbekannter Befehl: {value}",
|
|
333
|
-
"cli.args.mode_requires_value": "Option --mode erfordert einen Wert: sources|installed",
|
|
334
|
-
"cli.args.invalid_mode": "Ungültiger Wert für --mode: {value}. Erwartet sources|installed",
|
|
335
|
-
"cli.args.unknown_option": "Unbekannte Option: {value}",
|
|
336
|
-
"cli.args.mode_only_start": "Option --mode wird nur für den start-Befehl unterstützt",
|
|
337
327
|
"legacy.models.fetch_error": "🔴 Modellliste konnte nicht geladen werden. Prüfe den Serverstatus mit /status.",
|
|
338
328
|
"legacy.models.empty": "📋 Keine verfügbaren Modelle. Konfiguriere Provider in OpenCode.",
|
|
339
329
|
"legacy.models.header": "📋 Verfügbare Modelle:\n\n",
|
|
@@ -345,4 +335,19 @@ export const de = {
|
|
|
345
335
|
"stt.not_configured": "🎤 Spracherkennung ist nicht konfiguriert.\n\nSetze STT_API_URL und STT_API_KEY in .env, um sie zu aktivieren.",
|
|
346
336
|
"stt.error": "🔴 Audio konnte nicht erkannt werden: {error}",
|
|
347
337
|
"stt.empty_result": "🎤 Keine Sprache in der Audionachricht erkannt.",
|
|
338
|
+
"cmd.description.open": "Projekt durch Ordner-Auswahl hinzufügen",
|
|
339
|
+
"open.back": "⬆️ Hoch",
|
|
340
|
+
"open.roots": "📋 Zurück zur Auswahl",
|
|
341
|
+
"open.prev_page": "⬅️ Zurück",
|
|
342
|
+
"open.next_page": "Weiter ➡️",
|
|
343
|
+
"open.select_current": "✅ Diesen Ordner wählen",
|
|
344
|
+
"open.select_root": "📂 Stammverzeichnis zum Durchsuchen wählen:",
|
|
345
|
+
"open.access_denied": "⛔ Zugriff verweigert: Pfad liegt außerhalb erlaubter Verzeichnisse",
|
|
346
|
+
"open.scan_error": "🔴 Verzeichnis kann nicht durchsucht werden: {error}",
|
|
347
|
+
"open.open_error": "🔴 Verzeichnisbrowser konnte nicht geöffnet werden.",
|
|
348
|
+
"open.selected": "✅ Projekt hinzugefügt: {project}\n\n📋 Verwende /sessions oder /new zum Arbeiten.",
|
|
349
|
+
"open.select_error": "🔴 Projekt konnte nicht hinzugefügt werden.",
|
|
350
|
+
"open.no_subfolders": "📭 Keine Unterordner",
|
|
351
|
+
"open.subfolder_count": "{count} Unterordner",
|
|
352
|
+
"open.subfolders_count": "{count} Unterordner",
|
|
348
353
|
};
|
package/dist/i18n/en.js
CHANGED
|
@@ -324,16 +324,6 @@ export const en = {
|
|
|
324
324
|
"commands.page_empty_callback": "No commands on this page",
|
|
325
325
|
"commands.page_load_error_callback": "Cannot load this page. Please try again.",
|
|
326
326
|
"cmd.description.rename": "Rename current session",
|
|
327
|
-
"cli.usage": "Usage:\n opencode-telegram [start] [--mode sources|installed]\n opencode-telegram status\n opencode-telegram stop\n opencode-telegram config\n\nNotes:\n - No command defaults to `start`\n - `--mode` is currently supported for `start` only",
|
|
328
|
-
"cli.placeholder.status": "Command `status` is currently a placeholder. Real status checks will be added in service layer (Phase 5).",
|
|
329
|
-
"cli.placeholder.stop": "Command `stop` is currently a placeholder. Real background process stop will be added in service layer (Phase 5).",
|
|
330
|
-
"cli.placeholder.unavailable": "Command is unavailable.",
|
|
331
|
-
"cli.error.prefix": "CLI error: {message}",
|
|
332
|
-
"cli.args.unknown_command": "Unknown command: {value}",
|
|
333
|
-
"cli.args.mode_requires_value": "Option --mode requires a value: sources|installed",
|
|
334
|
-
"cli.args.invalid_mode": "Invalid mode value: {value}. Expected sources|installed",
|
|
335
|
-
"cli.args.unknown_option": "Unknown option: {value}",
|
|
336
|
-
"cli.args.mode_only_start": "Option --mode is supported only for the start command",
|
|
337
327
|
"legacy.models.fetch_error": "🔴 Failed to get models list. Check server status with /status.",
|
|
338
328
|
"legacy.models.empty": "📋 No available models. Configure providers in OpenCode.",
|
|
339
329
|
"legacy.models.header": "📋 Available models:\n\n",
|
|
@@ -345,4 +335,19 @@ export const en = {
|
|
|
345
335
|
"stt.not_configured": "🎤 Voice recognition is not configured.\n\nSet STT_API_URL and STT_API_KEY in .env to enable it.",
|
|
346
336
|
"stt.error": "🔴 Failed to recognize audio: {error}",
|
|
347
337
|
"stt.empty_result": "🎤 No speech detected in the audio message.",
|
|
338
|
+
"cmd.description.open": "Add a project by browsing directories",
|
|
339
|
+
"open.back": "⬆️ Up",
|
|
340
|
+
"open.roots": "📋 Back to roots",
|
|
341
|
+
"open.prev_page": "⬅️ Previous",
|
|
342
|
+
"open.next_page": "Next ➡️",
|
|
343
|
+
"open.select_current": "✅ Select this folder",
|
|
344
|
+
"open.select_root": "📂 Select a root directory to browse:",
|
|
345
|
+
"open.access_denied": "⛔ Access denied: path is outside allowed roots",
|
|
346
|
+
"open.scan_error": "🔴 Cannot browse directory: {error}",
|
|
347
|
+
"open.open_error": "🔴 Failed to open directory browser.",
|
|
348
|
+
"open.selected": "✅ Project added: {project}\n\n📋 Use /sessions or /new to start working.",
|
|
349
|
+
"open.select_error": "🔴 Failed to add project.",
|
|
350
|
+
"open.no_subfolders": "📭 No subfolders",
|
|
351
|
+
"open.subfolder_count": "{count} subfolder",
|
|
352
|
+
"open.subfolders_count": "{count} subfolders",
|
|
348
353
|
};
|
package/dist/i18n/es.js
CHANGED
|
@@ -324,16 +324,6 @@ export const es = {
|
|
|
324
324
|
"commands.page_empty_callback": "No hay comandos en esta página",
|
|
325
325
|
"commands.page_load_error_callback": "No se pudo cargar esta página. Por favor, inténtalo de nuevo.",
|
|
326
326
|
"cmd.description.rename": "Renombrar la sesión actual",
|
|
327
|
-
"cli.usage": "Uso:\n opencode-telegram [start] [--mode sources|installed]\n opencode-telegram status\n opencode-telegram stop\n opencode-telegram config\n\nNotas:\n - Sin comando, el valor por defecto es `start`\n - `--mode` actualmente solo se admite para `start`",
|
|
328
|
-
"cli.placeholder.status": "El comando `status` es actualmente un marcador de posición. Las comprobaciones reales de estado se agregarán en la capa de servicio (Fase 5).",
|
|
329
|
-
"cli.placeholder.stop": "El comando `stop` es actualmente un marcador de posición. La detención real del proceso en segundo plano se agregará en la capa de servicio (Fase 5).",
|
|
330
|
-
"cli.placeholder.unavailable": "El comando no esta disponible.",
|
|
331
|
-
"cli.error.prefix": "Error de CLI: {message}",
|
|
332
|
-
"cli.args.unknown_command": "Comando desconocido: {value}",
|
|
333
|
-
"cli.args.mode_requires_value": "La opción --mode requiere un valor: sources|installed",
|
|
334
|
-
"cli.args.invalid_mode": "Valor de --mode inválido: {value}. Se espera sources|installed",
|
|
335
|
-
"cli.args.unknown_option": "Opción desconocida: {value}",
|
|
336
|
-
"cli.args.mode_only_start": "La opción --mode solo se admite para el comando start",
|
|
337
327
|
"legacy.models.fetch_error": "🔴 No se pudo obtener la lista de modelos. Revisa el estado del servidor con /status.",
|
|
338
328
|
"legacy.models.empty": "📋 No hay modelos disponibles. Configura los proveedores en OpenCode.",
|
|
339
329
|
"legacy.models.header": "📋 Modelos disponibles:\n\n",
|
|
@@ -345,4 +335,19 @@ export const es = {
|
|
|
345
335
|
"stt.not_configured": "🎤 El reconocimiento de voz no está configurado.\n\nConfigura STT_API_URL y STT_API_KEY en .env para habilitarlo.",
|
|
346
336
|
"stt.error": "🔴 No se pudo reconocer el audio: {error}",
|
|
347
337
|
"stt.empty_result": "🎤 No se detectó voz en el mensaje de audio.",
|
|
338
|
+
"cmd.description.open": "Añadir proyecto navegando directorios",
|
|
339
|
+
"open.back": "⬆️ Subir",
|
|
340
|
+
"open.roots": "📋 Volver a raíces",
|
|
341
|
+
"open.prev_page": "⬅️ Anterior",
|
|
342
|
+
"open.next_page": "Siguiente ➡️",
|
|
343
|
+
"open.select_current": "✅ Seleccionar esta carpeta",
|
|
344
|
+
"open.select_root": "📂 Selecciona un directorio raíz para explorar:",
|
|
345
|
+
"open.access_denied": "⛔ Acceso denegado: la ruta está fuera de los directorios permitidos",
|
|
346
|
+
"open.scan_error": "🔴 No se puede explorar el directorio: {error}",
|
|
347
|
+
"open.open_error": "🔴 No se pudo abrir el explorador de directorios.",
|
|
348
|
+
"open.selected": "✅ Proyecto añadido: {project}\n\n📋 Usa /sessions o /new para empezar a trabajar.",
|
|
349
|
+
"open.select_error": "🔴 No se pudo añadir el proyecto.",
|
|
350
|
+
"open.no_subfolders": "📭 Sin subcarpetas",
|
|
351
|
+
"open.subfolder_count": "{count} subcarpeta",
|
|
352
|
+
"open.subfolders_count": "{count} subcarpetas",
|
|
348
353
|
};
|
package/dist/i18n/fr.js
CHANGED
|
@@ -324,16 +324,6 @@ export const fr = {
|
|
|
324
324
|
"commands.page_empty_callback": "Aucune commande sur cette page",
|
|
325
325
|
"commands.page_load_error_callback": "Impossible de charger cette page. Veuillez réessayer.",
|
|
326
326
|
"cmd.description.rename": "Renommer la session actuelle",
|
|
327
|
-
"cli.usage": "Utilisation :\n opencode-telegram [start] [--mode sources|installed]\n opencode-telegram status\n opencode-telegram stop\n opencode-telegram config\n\nNotes :\n - Sans commande, `start` est utilisé par défaut\n - `--mode` n'est actuellement pris en charge que pour `start`",
|
|
328
|
-
"cli.placeholder.status": "La commande `status` est actuellement un placeholder. Les vraies vérifications d'état seront ajoutées dans la couche service (Phase 5).",
|
|
329
|
-
"cli.placeholder.stop": "La commande `stop` est actuellement un placeholder. Le véritable arrêt du processus en arrière-plan sera ajouté dans la couche service (Phase 5).",
|
|
330
|
-
"cli.placeholder.unavailable": "Commande indisponible.",
|
|
331
|
-
"cli.error.prefix": "Erreur CLI : {message}",
|
|
332
|
-
"cli.args.unknown_command": "Commande inconnue : {value}",
|
|
333
|
-
"cli.args.mode_requires_value": "L'option --mode nécessite une valeur : sources|installed",
|
|
334
|
-
"cli.args.invalid_mode": "Valeur de mode invalide : {value}. Attendu : sources|installed",
|
|
335
|
-
"cli.args.unknown_option": "Option inconnue : {value}",
|
|
336
|
-
"cli.args.mode_only_start": "L'option --mode est prise en charge uniquement pour la commande start",
|
|
337
327
|
"legacy.models.fetch_error": "🔴 Impossible de récupérer la liste des modèles. Vérifiez l'état du serveur avec /status.",
|
|
338
328
|
"legacy.models.empty": "📋 Aucun modèle disponible. Configurez les fournisseurs dans OpenCode.",
|
|
339
329
|
"legacy.models.header": "📋 Modèles disponibles :\n\n",
|
|
@@ -345,4 +335,19 @@ export const fr = {
|
|
|
345
335
|
"stt.not_configured": "🎤 La reconnaissance vocale n'est pas configurée.\n\nDéfinissez STT_API_URL et STT_API_KEY dans .env pour l'activer.",
|
|
346
336
|
"stt.error": "🔴 Impossible de reconnaître l'audio : {error}",
|
|
347
337
|
"stt.empty_result": "🎤 Aucune parole détectée dans le message audio.",
|
|
338
|
+
"cmd.description.open": "Ajouter un projet en parcourant les dossiers",
|
|
339
|
+
"open.back": "⬆️ Remonter",
|
|
340
|
+
"open.roots": "📋 Retour aux racines",
|
|
341
|
+
"open.prev_page": "⬅️ Précédent",
|
|
342
|
+
"open.next_page": "Suivant ➡️",
|
|
343
|
+
"open.select_current": "✅ Sélectionner ce dossier",
|
|
344
|
+
"open.select_root": "📂 Sélectionnez un répertoire racine à parcourir :",
|
|
345
|
+
"open.access_denied": "⛔ Accès refusé : le chemin est en dehors des répertoires autorisés",
|
|
346
|
+
"open.scan_error": "🔴 Impossible de parcourir le répertoire : {error}",
|
|
347
|
+
"open.open_error": "🔴 Impossible d'ouvrir l'explorateur de répertoires.",
|
|
348
|
+
"open.selected": "✅ Projet ajouté : {project}\n\n📋 Utilisez /sessions ou /new pour commencer.",
|
|
349
|
+
"open.select_error": "🔴 Impossible d'ajouter le projet.",
|
|
350
|
+
"open.no_subfolders": "📭 Aucun sous-dossier",
|
|
351
|
+
"open.subfolder_count": "{count} sous-dossier",
|
|
352
|
+
"open.subfolders_count": "{count} sous-dossiers",
|
|
348
353
|
};
|
package/dist/i18n/ru.js
CHANGED
|
@@ -324,16 +324,6 @@ export const ru = {
|
|
|
324
324
|
"commands.page_empty_callback": "На этой странице нет команд",
|
|
325
325
|
"commands.page_load_error_callback": "Не удалось загрузить эту страницу. Пожалуйста, попробуйте снова.",
|
|
326
326
|
"cmd.description.rename": "Переименовать текущую сессию",
|
|
327
|
-
"cli.usage": "Использование:\n opencode-telegram [start] [--mode sources|installed]\n opencode-telegram status\n opencode-telegram stop\n opencode-telegram config\n\nЗаметки:\n - Без команды по умолчанию используется `start`\n - `--mode` сейчас поддерживается только для `start`",
|
|
328
|
-
"cli.placeholder.status": "Команда `status` пока работает как заглушка. Реальная проверка статуса появится на этапе service-слоя (Этап 5).",
|
|
329
|
-
"cli.placeholder.stop": "Команда `stop` пока работает как заглушка. Реальная остановка фонового процесса появится на этапе service-слоя (Этап 5).",
|
|
330
|
-
"cli.placeholder.unavailable": "Команда недоступна.",
|
|
331
|
-
"cli.error.prefix": "CLI error: {message}",
|
|
332
|
-
"cli.args.unknown_command": "Неизвестная команда: {value}",
|
|
333
|
-
"cli.args.mode_requires_value": "Опция --mode требует значение: sources|installed",
|
|
334
|
-
"cli.args.invalid_mode": "Некорректное значение --mode: {value}. Ожидается sources|installed",
|
|
335
|
-
"cli.args.unknown_option": "Неизвестная опция: {value}",
|
|
336
|
-
"cli.args.mode_only_start": "Опция --mode поддерживается только для команды start",
|
|
337
327
|
"legacy.models.fetch_error": "🔴 Не удалось получить список моделей. Проверьте статус сервера /status.",
|
|
338
328
|
"legacy.models.empty": "📋 Нет доступных моделей. Настройте провайдеры через OpenCode.",
|
|
339
329
|
"legacy.models.header": "📋 Доступные модели:\n\n",
|
|
@@ -345,4 +335,19 @@ export const ru = {
|
|
|
345
335
|
"stt.not_configured": "🎤 Распознавание голоса не настроено.\n\nУстановите STT_API_URL и STT_API_KEY в .env для включения.",
|
|
346
336
|
"stt.error": "🔴 Не удалось распознать аудио: {error}",
|
|
347
337
|
"stt.empty_result": "🎤 В аудиосообщении не обнаружена речь.",
|
|
338
|
+
"cmd.description.open": "Добавить проект через обзор папок",
|
|
339
|
+
"open.back": "⬆️ Наверх",
|
|
340
|
+
"open.roots": "📋 К списку корней",
|
|
341
|
+
"open.prev_page": "⬅️ Назад",
|
|
342
|
+
"open.next_page": "Далее ➡️",
|
|
343
|
+
"open.select_current": "✅ Выбрать эту папку",
|
|
344
|
+
"open.select_root": "📂 Выберите корневой каталог для просмотра:",
|
|
345
|
+
"open.access_denied": "⛔ Доступ запрещён: путь за пределами разрешённых каталогов",
|
|
346
|
+
"open.scan_error": "🔴 Не удалось открыть каталог: {error}",
|
|
347
|
+
"open.open_error": "🔴 Не удалось открыть обозреватель каталогов.",
|
|
348
|
+
"open.selected": "✅ Проект добавлен: {project}\n\n📋 Используйте /sessions или /new для начала работы.",
|
|
349
|
+
"open.select_error": "🔴 Не удалось добавить проект.",
|
|
350
|
+
"open.no_subfolders": "📭 Нет подпапок",
|
|
351
|
+
"open.subfolder_count": "{count} подпапка",
|
|
352
|
+
"open.subfolders_count": "{count} подпапок",
|
|
348
353
|
};
|