@nodetool-ai/telegram 0.8.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/README.md +49 -0
- package/dist/.nodetool-build-stamp +1 -0
- package/dist/bot-api.d.ts +115 -0
- package/dist/bot-api.d.ts.map +1 -0
- package/dist/bot-api.js +145 -0
- package/dist/bot-api.js.map +1 -0
- package/dist/chunk.d.ts +36 -0
- package/dist/chunk.d.ts.map +1 -0
- package/dist/chunk.js +280 -0
- package/dist/chunk.js.map +1 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +62 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands.d.ts +83 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +191 -0
- package/dist/commands.js.map +1 -0
- package/dist/config.d.ts +65 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +139 -0
- package/dist/config.js.map +1 -0
- package/dist/frame-renderer.d.ts +135 -0
- package/dist/frame-renderer.d.ts.map +1 -0
- package/dist/frame-renderer.js +407 -0
- package/dist/frame-renderer.js.map +1 -0
- package/dist/identity-client.d.ts +132 -0
- package/dist/identity-client.d.ts.map +1 -0
- package/dist/identity-client.js +204 -0
- package/dist/identity-client.js.map +1 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +90 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown-html.d.ts +32 -0
- package/dist/markdown-html.d.ts.map +1 -0
- package/dist/markdown-html.js +251 -0
- package/dist/markdown-html.js.map +1 -0
- package/dist/nodetool-client.d.ts +32 -0
- package/dist/nodetool-client.d.ts.map +1 -0
- package/dist/nodetool-client.js +127 -0
- package/dist/nodetool-client.js.map +1 -0
- package/dist/register-commands.d.ts +24 -0
- package/dist/register-commands.d.ts.map +1 -0
- package/dist/register-commands.js +29 -0
- package/dist/register-commands.js.map +1 -0
- package/dist/strings.d.ts +7 -0
- package/dist/strings.d.ts.map +1 -0
- package/dist/strings.js +13 -0
- package/dist/strings.js.map +1 -0
- package/dist/telegram-adapter.d.ts +100 -0
- package/dist/telegram-adapter.d.ts.map +1 -0
- package/dist/telegram-adapter.js +366 -0
- package/dist/telegram-adapter.js.map +1 -0
- package/dist/turn-router.d.ts +167 -0
- package/dist/turn-router.d.ts.map +1 -0
- package/dist/turn-router.js +360 -0
- package/dist/turn-router.js.map +1 -0
- package/package.json +68 -0
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `npx @nodetool-ai/telegram` — run the bridge standalone next to any server.
|
|
4
|
+
*
|
|
5
|
+
* Config comes from the environment plus an optional `telegram-bot.json`
|
|
6
|
+
* (design §11). A config mistake is the most likely failure here, so it prints
|
|
7
|
+
* the offending fields and exits — never a stack trace.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;GAMG"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `npx @nodetool-ai/telegram` — run the bridge standalone next to any server.
|
|
4
|
+
*
|
|
5
|
+
* Config comes from the environment plus an optional `telegram-bot.json`
|
|
6
|
+
* (design §11). A config mistake is the most likely failure here, so it prints
|
|
7
|
+
* the offending fields and exits — never a stack trace.
|
|
8
|
+
*/
|
|
9
|
+
import { TelegramConfigError, loadConfig } from "./config.js";
|
|
10
|
+
import { registerCommands } from "./register-commands.js";
|
|
11
|
+
import { startTelegramBot } from "./index.js";
|
|
12
|
+
const USAGE = `nodetool-telegram — NodeTool's Telegram bridge
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
nodetool-telegram [serve] Long-poll for updates and run turns
|
|
16
|
+
nodetool-telegram register Publish the bot's command list (setMyCommands)
|
|
17
|
+
|
|
18
|
+
Environment:
|
|
19
|
+
TELEGRAM_BOT_TOKEN required (from @BotFather)
|
|
20
|
+
NODETOOL_API_URL default http://127.0.0.1:7777
|
|
21
|
+
NODETOOL_INTEGRATION_TOKEN required (the bot's service token)
|
|
22
|
+
`;
|
|
23
|
+
async function main(argv) {
|
|
24
|
+
const command = argv[0] ?? "serve";
|
|
25
|
+
if (command === "--help" || command === "-h" || command === "help") {
|
|
26
|
+
process.stdout.write(USAGE);
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
if (command !== "serve" && command !== "register" && command !== "register-commands") {
|
|
30
|
+
process.stderr.write(`Unknown command: ${command}\n\n${USAGE}`);
|
|
31
|
+
return 2;
|
|
32
|
+
}
|
|
33
|
+
const config = loadConfig();
|
|
34
|
+
if (command !== "serve") {
|
|
35
|
+
const commands = await registerCommands(config.botToken);
|
|
36
|
+
process.stdout.write(`Registered ${commands.length} bot commands.\n`);
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
const handle = startTelegramBot(config);
|
|
40
|
+
const shutdown = () => {
|
|
41
|
+
void handle.stop();
|
|
42
|
+
};
|
|
43
|
+
process.on("SIGINT", shutdown);
|
|
44
|
+
process.on("SIGTERM", shutdown);
|
|
45
|
+
process.stdout.write(`Telegram bridge polling against ${config.apiUrl}\n`);
|
|
46
|
+
const reason = await handle.finished;
|
|
47
|
+
return reason === "stopped" ? 0 : 1;
|
|
48
|
+
}
|
|
49
|
+
main(process.argv.slice(2))
|
|
50
|
+
.then((code) => {
|
|
51
|
+
process.exitCode = code;
|
|
52
|
+
})
|
|
53
|
+
.catch((err) => {
|
|
54
|
+
if (err instanceof TelegramConfigError) {
|
|
55
|
+
process.stderr.write(`${err.message}\n`);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
59
|
+
}
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
});
|
|
62
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;GAMG;AAEH,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,KAAK,GAAG;;;;;;;;;;CAUb,CAAC;AAEF,KAAK,UAAU,IAAI,CAAC,IAAuB;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC;IACnC,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;QACnE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5B,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO,KAAK,UAAU,IAAI,OAAO,KAAK,mBAAmB,EAAE,CAAC;QACrF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC;QAChE,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAE5B,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACxB,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACzD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,QAAQ,CAAC,MAAM,kBAAkB,CAAC,CAAC;QACtE,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,GAAS,EAAE;QAC1B,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC;IACrB,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAEhC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mCAAmC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;IAC3E,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC;IACrC,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACtC,CAAC;AAED,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;KACxB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;IACb,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;AAC1B,CAAC,CAAC;KACD,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IACtB,IAAI,GAAG,YAAY,mBAAmB,EAAE,CAAC;QACvC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;IAC3C,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bot commands (design §7). They never reach the LLM: each one is answered
|
|
3
|
+
* here, from the identity layer and the router's own state.
|
|
4
|
+
*
|
|
5
|
+
* The handlers are I/O-free apart from the collaborators handed to them, so
|
|
6
|
+
* every command is a unit test rather than a live Telegram session.
|
|
7
|
+
*/
|
|
8
|
+
import type { DeepLinkResult, IdentityResolution, LinkStart } from "./identity-client.js";
|
|
9
|
+
import type { BotCommandSpec } from "./bot-api.js";
|
|
10
|
+
/** The identity calls the commands make. A subset of `IdentityClient`. */
|
|
11
|
+
export interface CommandIdentity {
|
|
12
|
+
resolve(externalId: string): Promise<IdentityResolution>;
|
|
13
|
+
linkStart(externalId: string): Promise<LinkStart>;
|
|
14
|
+
completeDeepLink(externalId: string, code: string): Promise<DeepLinkResult>;
|
|
15
|
+
unlink(externalId: string): Promise<boolean>;
|
|
16
|
+
}
|
|
17
|
+
/** The router state the commands read. A subset of `TurnRouter`. */
|
|
18
|
+
export interface CommandRouter {
|
|
19
|
+
stop(chatId: string): boolean;
|
|
20
|
+
newThread(chatId: string): string | null;
|
|
21
|
+
currentThreadId(chatId: string): string | null;
|
|
22
|
+
queueDepth(chatId: string): number;
|
|
23
|
+
isRunning(chatId: string): boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface CommandDeps {
|
|
26
|
+
readonly identity: CommandIdentity;
|
|
27
|
+
readonly router: CommandRouter;
|
|
28
|
+
/** NodeTool server base URL, for the `/status` health probe. */
|
|
29
|
+
readonly apiUrl: string;
|
|
30
|
+
readonly fetch: typeof fetch;
|
|
31
|
+
/** Telegram ids allowed to link. Empty = anyone. */
|
|
32
|
+
readonly allowUsers: readonly string[];
|
|
33
|
+
}
|
|
34
|
+
export interface CommandInput {
|
|
35
|
+
/** Command name without the leading slash, lowercased, `@bot` stripped. */
|
|
36
|
+
readonly command: string;
|
|
37
|
+
/** Everything after the command word, trimmed. */
|
|
38
|
+
readonly args: string;
|
|
39
|
+
readonly chatId: string;
|
|
40
|
+
readonly telegramUserId: string;
|
|
41
|
+
}
|
|
42
|
+
export interface CommandOutcome {
|
|
43
|
+
/** Lines to send back, in order. Empty means the command answered silently. */
|
|
44
|
+
readonly replies: readonly string[];
|
|
45
|
+
}
|
|
46
|
+
/** Commands anyone may run; the rest need a linked account (design §4). */
|
|
47
|
+
export declare const PUBLIC_COMMANDS: ReadonlySet<string>;
|
|
48
|
+
/** What `setMyCommands` registers, so the commands autocomplete in Telegram. */
|
|
49
|
+
export declare const BOT_COMMANDS: readonly BotCommandSpec[];
|
|
50
|
+
export declare const MESSAGES: {
|
|
51
|
+
readonly welcome: string;
|
|
52
|
+
readonly linkPrompt: string;
|
|
53
|
+
readonly notAllowed: string;
|
|
54
|
+
readonly groupDecline: "I work in private chat for now.";
|
|
55
|
+
readonly busy: "Still working on your previous message — send it again when I am done.";
|
|
56
|
+
readonly unknownCommand: "I do not know that command. Try /status.";
|
|
57
|
+
readonly needsLink: "That command needs a linked NodeTool account. Send /link first.";
|
|
58
|
+
readonly nothingRunning: "Nothing is running in this chat.";
|
|
59
|
+
readonly stopping: "⏹ stopping";
|
|
60
|
+
readonly unsupportedMedia: string;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Whether this Telegram user is allowed to use the bot at all. An empty
|
|
64
|
+
* allowlist means anyone may link (design §4).
|
|
65
|
+
*/
|
|
66
|
+
export declare function isAllowedUser(allowUsers: readonly string[], telegramUserId: string): boolean;
|
|
67
|
+
/** Parse a `/command@bot args` message into its parts, or null when it is not one. */
|
|
68
|
+
export declare function parseCommand(text: string, entities?: readonly {
|
|
69
|
+
type: string;
|
|
70
|
+
offset: number;
|
|
71
|
+
length: number;
|
|
72
|
+
}[]): {
|
|
73
|
+
command: string;
|
|
74
|
+
args: string;
|
|
75
|
+
} | null;
|
|
76
|
+
/**
|
|
77
|
+
* Run one command.
|
|
78
|
+
*
|
|
79
|
+
* @returns the replies to send. Commands that need an account are refused
|
|
80
|
+
* before any work when the account is not linked.
|
|
81
|
+
*/
|
|
82
|
+
export declare function handleCommand(deps: CommandDeps, input: CommandInput): Promise<CommandOutcome>;
|
|
83
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bot commands (design §7). They never reach the LLM: each one is answered
|
|
3
|
+
* here, from the identity layer and the router's own state.
|
|
4
|
+
*
|
|
5
|
+
* The handlers are I/O-free apart from the collaborators handed to them, so
|
|
6
|
+
* every command is a unit test rather than a live Telegram session.
|
|
7
|
+
*/
|
|
8
|
+
/** Commands anyone may run; the rest need a linked account (design §4). */
|
|
9
|
+
export const PUBLIC_COMMANDS = new Set(["start", "link", "status"]);
|
|
10
|
+
/** What `setMyCommands` registers, so the commands autocomplete in Telegram. */
|
|
11
|
+
export const BOT_COMMANDS = [
|
|
12
|
+
{ command: "start", description: "Welcome and link state" },
|
|
13
|
+
{ command: "link", description: "Link this Telegram account to NodeTool" },
|
|
14
|
+
{ command: "unlink", description: "Unlink this Telegram account" },
|
|
15
|
+
{ command: "new", description: "Start a fresh conversation thread" },
|
|
16
|
+
{ command: "stop", description: "Cancel the turn that is running" },
|
|
17
|
+
{ command: "status", description: "Server connectivity, link state, queue depth" }
|
|
18
|
+
];
|
|
19
|
+
export const MESSAGES = {
|
|
20
|
+
welcome: "I am NodeTool. Message me and I run the turn on your own NodeTool " +
|
|
21
|
+
"account — your tools, your assets, your budget.",
|
|
22
|
+
linkPrompt: "This Telegram account is not linked to a NodeTool user yet. Send /link " +
|
|
23
|
+
"and open the URL I answer with.",
|
|
24
|
+
notAllowed: "This bot is restricted to a configured list of Telegram accounts, and " +
|
|
25
|
+
"yours is not on it.",
|
|
26
|
+
groupDecline: "I work in private chat for now.",
|
|
27
|
+
busy: "Still working on your previous message — send it again when I am done.",
|
|
28
|
+
unknownCommand: "I do not know that command. Try /status.",
|
|
29
|
+
needsLink: "That command needs a linked NodeTool account. Send /link first.",
|
|
30
|
+
nothingRunning: "Nothing is running in this chat.",
|
|
31
|
+
stopping: "⏹ stopping",
|
|
32
|
+
unsupportedMedia: "I only handle text for now — photos, documents and voice notes arrive in " +
|
|
33
|
+
"the next milestone."
|
|
34
|
+
};
|
|
35
|
+
function linkInstructions(start) {
|
|
36
|
+
const expiry = start.expiresAt === null ? "" : `\nThe link expires at ${start.expiresAt}.`;
|
|
37
|
+
return `Open this link while signed in to NodeTool to finish linking:\n${start.url}${expiry}`;
|
|
38
|
+
}
|
|
39
|
+
function describeIdentity(resolution) {
|
|
40
|
+
if (!resolution.unlinked) {
|
|
41
|
+
return `Linked to NodeTool user ${resolution.userId}.`;
|
|
42
|
+
}
|
|
43
|
+
return resolution.reason === "local-mode"
|
|
44
|
+
? `This server is single-user; you are already in. (${resolution.message})`
|
|
45
|
+
: "Not linked.";
|
|
46
|
+
}
|
|
47
|
+
async function probeHealth(deps) {
|
|
48
|
+
try {
|
|
49
|
+
const response = await deps.fetch(`${deps.apiUrl}/health`, { method: "GET" });
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
return `unreachable (HTTP ${response.status})`;
|
|
52
|
+
}
|
|
53
|
+
const body = await response.json().catch(() => null);
|
|
54
|
+
const status = typeof body === "object" && body !== null && "status" in body
|
|
55
|
+
? String(body.status)
|
|
56
|
+
: "ok";
|
|
57
|
+
return `${status} at ${deps.apiUrl}`;
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
return `unreachable (${err.message})`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Whether this Telegram user is allowed to use the bot at all. An empty
|
|
65
|
+
* allowlist means anyone may link (design §4).
|
|
66
|
+
*/
|
|
67
|
+
export function isAllowedUser(allowUsers, telegramUserId) {
|
|
68
|
+
return allowUsers.length === 0 || allowUsers.includes(telegramUserId);
|
|
69
|
+
}
|
|
70
|
+
/** Parse a `/command@bot args` message into its parts, or null when it is not one. */
|
|
71
|
+
export function parseCommand(text, entities) {
|
|
72
|
+
const isCommand = entities?.some((entity) => entity.type === "bot_command" && entity.offset === 0) === true ||
|
|
73
|
+
text.startsWith("/");
|
|
74
|
+
if (!isCommand) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const match = /^\/([A-Za-z0-9_]+)(?:@[A-Za-z0-9_]+)?\s*([\s\S]*)$/.exec(text.trim());
|
|
78
|
+
if (!match) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return { command: match[1].toLowerCase(), args: match[2].trim() };
|
|
82
|
+
}
|
|
83
|
+
async function handleStart(deps, input) {
|
|
84
|
+
if (input.args.length === 0) {
|
|
85
|
+
const resolution = await deps.identity.resolve(input.telegramUserId);
|
|
86
|
+
return { replies: [`${MESSAGES.welcome}\n\n${describeIdentity(resolution)}`] };
|
|
87
|
+
}
|
|
88
|
+
const result = await deps.identity.completeDeepLink(input.telegramUserId, input.args);
|
|
89
|
+
if (result.ok) {
|
|
90
|
+
return { replies: ["Linked. Send me a message and I will run it on your account."] };
|
|
91
|
+
}
|
|
92
|
+
if (result.reason === "expired") {
|
|
93
|
+
return { replies: [`${result.message}\nSend /link to start again.`] };
|
|
94
|
+
}
|
|
95
|
+
if (result.reason === "mismatch") {
|
|
96
|
+
return { replies: [result.message] };
|
|
97
|
+
}
|
|
98
|
+
// The deep-link direction the server cannot express yet: fall back to the
|
|
99
|
+
// bot-initiated flow rather than leaving the user with a dead code.
|
|
100
|
+
const start = await deps.identity.linkStart(input.telegramUserId);
|
|
101
|
+
return {
|
|
102
|
+
replies: [
|
|
103
|
+
`${MESSAGES.welcome}\n\nThat start link could not be completed from here.\n` +
|
|
104
|
+
linkInstructions(start)
|
|
105
|
+
]
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async function handleLink(deps, input) {
|
|
109
|
+
const resolution = await deps.identity.resolve(input.telegramUserId);
|
|
110
|
+
if (!resolution.unlinked) {
|
|
111
|
+
return { replies: [`Already linked to NodeTool user ${resolution.userId}.`] };
|
|
112
|
+
}
|
|
113
|
+
if (resolution.reason === "local-mode") {
|
|
114
|
+
return { replies: [describeIdentity(resolution)] };
|
|
115
|
+
}
|
|
116
|
+
const start = await deps.identity.linkStart(input.telegramUserId);
|
|
117
|
+
return { replies: [linkInstructions(start)] };
|
|
118
|
+
}
|
|
119
|
+
async function handleUnlink(deps, input) {
|
|
120
|
+
const unlinked = await deps.identity.unlink(input.telegramUserId);
|
|
121
|
+
return {
|
|
122
|
+
replies: [unlinked ? "Unlinked. Send /link to connect again." : "This account was not linked."]
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async function handleStatus(deps, input) {
|
|
126
|
+
const health = await probeHealth(deps);
|
|
127
|
+
let link;
|
|
128
|
+
try {
|
|
129
|
+
link = describeIdentity(await deps.identity.resolve(input.telegramUserId));
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
link = `unknown (${err.message})`;
|
|
133
|
+
}
|
|
134
|
+
const thread = deps.router.currentThreadId(input.chatId) ?? "none yet";
|
|
135
|
+
const lines = [
|
|
136
|
+
`Server: ${health}`,
|
|
137
|
+
`Account: ${link}`,
|
|
138
|
+
`Thread: ${thread}`,
|
|
139
|
+
`Turn: ${deps.router.isRunning(input.chatId) ? "running" : "idle"}`,
|
|
140
|
+
`Queued: ${deps.router.queueDepth(input.chatId)}`
|
|
141
|
+
];
|
|
142
|
+
return { replies: [lines.join("\n")] };
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Run one command.
|
|
146
|
+
*
|
|
147
|
+
* @returns the replies to send. Commands that need an account are refused
|
|
148
|
+
* before any work when the account is not linked.
|
|
149
|
+
*/
|
|
150
|
+
export async function handleCommand(deps, input) {
|
|
151
|
+
if (!isAllowedUser(deps.allowUsers, input.telegramUserId)) {
|
|
152
|
+
return { replies: [MESSAGES.notAllowed] };
|
|
153
|
+
}
|
|
154
|
+
switch (input.command) {
|
|
155
|
+
case "start":
|
|
156
|
+
return handleStart(deps, input);
|
|
157
|
+
case "link":
|
|
158
|
+
return handleLink(deps, input);
|
|
159
|
+
case "status":
|
|
160
|
+
return handleStatus(deps, input);
|
|
161
|
+
case "unlink":
|
|
162
|
+
case "new":
|
|
163
|
+
case "stop":
|
|
164
|
+
break;
|
|
165
|
+
default:
|
|
166
|
+
return { replies: [MESSAGES.unknownCommand] };
|
|
167
|
+
}
|
|
168
|
+
const resolution = await deps.identity.resolve(input.telegramUserId);
|
|
169
|
+
if (resolution.unlinked && resolution.reason === "not-linked") {
|
|
170
|
+
return { replies: [MESSAGES.needsLink] };
|
|
171
|
+
}
|
|
172
|
+
switch (input.command) {
|
|
173
|
+
case "unlink":
|
|
174
|
+
return handleUnlink(deps, input);
|
|
175
|
+
case "new": {
|
|
176
|
+
const threadId = deps.router.newThread(input.chatId);
|
|
177
|
+
return {
|
|
178
|
+
replies: [
|
|
179
|
+
threadId === null
|
|
180
|
+
? "No conversation here yet — your next message starts a fresh thread."
|
|
181
|
+
: `New thread: ${threadId}`
|
|
182
|
+
]
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
default:
|
|
186
|
+
return {
|
|
187
|
+
replies: [deps.router.stop(input.chatId) ? MESSAGES.stopping : MESSAGES.nothingRunning]
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge configuration: environment variables plus an optional
|
|
3
|
+
* `telegram-bot.json`, validated at the boundary (design §11).
|
|
4
|
+
*
|
|
5
|
+
* Everything the bridge needs to reach Telegram and the NodeTool server is an
|
|
6
|
+
* env var; the JSON file carries only the tuning knobs an operator changes
|
|
7
|
+
* without redeploying. Both are parsed with zod so a bad value fails at
|
|
8
|
+
* startup naming the field, not on the first message.
|
|
9
|
+
*/
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
/** Fully resolved bridge configuration. */
|
|
12
|
+
export interface TelegramBotConfig {
|
|
13
|
+
/** Bot token from @BotFather. */
|
|
14
|
+
readonly botToken: string;
|
|
15
|
+
/** Base URL of the NodeTool server, no trailing slash. */
|
|
16
|
+
readonly apiUrl: string;
|
|
17
|
+
/** Service token the server recognizes as the messaging integration. */
|
|
18
|
+
readonly integrationToken: string;
|
|
19
|
+
/** Set = webhook mode; unset = long polling. */
|
|
20
|
+
readonly webhookUrl: string | null;
|
|
21
|
+
/** Verified against `X-Telegram-Bot-Api-Secret-Token`; set iff webhook mode. */
|
|
22
|
+
readonly webhookSecret: string | null;
|
|
23
|
+
/** Telegram user ids allowed to link. Empty = anyone. */
|
|
24
|
+
readonly allowUsers: readonly string[];
|
|
25
|
+
/** Minimum gap between `editMessageText` calls on one conversation. */
|
|
26
|
+
readonly editThrottleMs: number;
|
|
27
|
+
/** Messages queued behind an in-flight turn before the bot answers busy. */
|
|
28
|
+
readonly maxQueuedTurns: number;
|
|
29
|
+
}
|
|
30
|
+
/** Thrown when env or file config is missing or malformed. */
|
|
31
|
+
export declare class TelegramConfigError extends Error {
|
|
32
|
+
/** Field names that failed, in the order reported. */
|
|
33
|
+
readonly fields: readonly string[];
|
|
34
|
+
constructor(message: string, fields: readonly string[]);
|
|
35
|
+
}
|
|
36
|
+
export declare const DEFAULT_API_URL = "http://127.0.0.1:7777";
|
|
37
|
+
export declare const DEFAULT_EDIT_THROTTLE_MS = 1500;
|
|
38
|
+
export declare const DEFAULT_MAX_QUEUED_TURNS = 3;
|
|
39
|
+
export declare const DEFAULT_CONFIG_FILE = "telegram-bot.json";
|
|
40
|
+
declare const fileSchema: z.ZodObject<{
|
|
41
|
+
allowUsers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
42
|
+
editThrottleMs: z.ZodDefault<z.ZodNumber>;
|
|
43
|
+
maxQueuedTurns: z.ZodDefault<z.ZodNumber>;
|
|
44
|
+
}, z.core.$strip>;
|
|
45
|
+
/** The `telegram-bot.json` shape, all keys optional. */
|
|
46
|
+
export type TelegramBotFileConfig = z.infer<typeof fileSchema>;
|
|
47
|
+
export interface LoadConfigOptions {
|
|
48
|
+
/** Environment to read. Defaults to `process.env`. */
|
|
49
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
50
|
+
/**
|
|
51
|
+
* Path to `telegram-bot.json`. A missing file at the default path is not an
|
|
52
|
+
* error; a missing file at an explicitly requested path is.
|
|
53
|
+
*/
|
|
54
|
+
readonly configPath?: string;
|
|
55
|
+
/** Already-parsed file config, for callers that hold it in memory. */
|
|
56
|
+
readonly fileConfig?: unknown;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Read and validate the bridge configuration.
|
|
60
|
+
*
|
|
61
|
+
* @throws {TelegramConfigError} naming every offending field.
|
|
62
|
+
*/
|
|
63
|
+
export declare function loadConfig(options?: LoadConfigOptions): TelegramBotConfig;
|
|
64
|
+
export {};
|
|
65
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,2CAA2C;AAC3C,MAAM,WAAW,iBAAiB;IAChC,iCAAiC;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,gDAAgD;IAChD,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,gFAAgF;IAChF,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,yDAAyD;IACzD,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,uEAAuE;IACvE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,4EAA4E;IAC5E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,8DAA8D;AAC9D,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,sDAAsD;IACtD,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAEnC,YAAY,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,EAIrD;CACF;AAED,eAAO,MAAM,eAAe,0BAA0B,CAAC;AACvD,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAC1C,eAAO,MAAM,mBAAmB,sBAAsB,CAAC;AAkDvD,QAAA,MAAM,UAAU;;;;iBAId,CAAC;AAEH,wDAAwD;AACxD,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAE/D,MAAM,WAAW,iBAAiB;IAChC,sDAAsD;IACtD,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B;AAuCD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,iBAAiB,CA4B7E"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge configuration: environment variables plus an optional
|
|
3
|
+
* `telegram-bot.json`, validated at the boundary (design §11).
|
|
4
|
+
*
|
|
5
|
+
* Everything the bridge needs to reach Telegram and the NodeTool server is an
|
|
6
|
+
* env var; the JSON file carries only the tuning knobs an operator changes
|
|
7
|
+
* without redeploying. Both are parsed with zod so a bad value fails at
|
|
8
|
+
* startup naming the field, not on the first message.
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { trimTrailingSlashes } from "./strings.js";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
/** Thrown when env or file config is missing or malformed. */
|
|
14
|
+
export class TelegramConfigError extends Error {
|
|
15
|
+
/** Field names that failed, in the order reported. */
|
|
16
|
+
fields;
|
|
17
|
+
constructor(message, fields) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "TelegramConfigError";
|
|
20
|
+
this.fields = fields;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export const DEFAULT_API_URL = "http://127.0.0.1:7777";
|
|
24
|
+
export const DEFAULT_EDIT_THROTTLE_MS = 1500;
|
|
25
|
+
export const DEFAULT_MAX_QUEUED_TURNS = 3;
|
|
26
|
+
export const DEFAULT_CONFIG_FILE = "telegram-bot.json";
|
|
27
|
+
function isHttpUrl(value) {
|
|
28
|
+
try {
|
|
29
|
+
const url = new URL(value);
|
|
30
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const required = (field) => z
|
|
37
|
+
.string({ error: `${field} is required` })
|
|
38
|
+
.trim()
|
|
39
|
+
.min(1, { error: `${field} is required` });
|
|
40
|
+
const optional = z
|
|
41
|
+
.string()
|
|
42
|
+
.trim()
|
|
43
|
+
.min(1)
|
|
44
|
+
.nullish()
|
|
45
|
+
.transform((v) => v ?? null);
|
|
46
|
+
const envSchema = z
|
|
47
|
+
.object({
|
|
48
|
+
TELEGRAM_BOT_TOKEN: required("TELEGRAM_BOT_TOKEN"),
|
|
49
|
+
NODETOOL_API_URL: z
|
|
50
|
+
.string()
|
|
51
|
+
.trim()
|
|
52
|
+
.min(1)
|
|
53
|
+
.nullish()
|
|
54
|
+
.transform((v) => (v == null || v === "" ? DEFAULT_API_URL : v))
|
|
55
|
+
.refine(isHttpUrl, { error: "must be an absolute http(s) URL" }),
|
|
56
|
+
NODETOOL_INTEGRATION_TOKEN: required("NODETOOL_INTEGRATION_TOKEN"),
|
|
57
|
+
TELEGRAM_WEBHOOK_URL: optional.refine((v) => v === null || isHttpUrl(v), {
|
|
58
|
+
error: "must be an absolute http(s) URL"
|
|
59
|
+
}),
|
|
60
|
+
TELEGRAM_WEBHOOK_SECRET: optional
|
|
61
|
+
})
|
|
62
|
+
.superRefine((value, ctx) => {
|
|
63
|
+
if (value.TELEGRAM_WEBHOOK_URL !== null && value.TELEGRAM_WEBHOOK_SECRET === null) {
|
|
64
|
+
ctx.addIssue({
|
|
65
|
+
code: "custom",
|
|
66
|
+
path: ["TELEGRAM_WEBHOOK_SECRET"],
|
|
67
|
+
message: "is required when TELEGRAM_WEBHOOK_URL is set (webhook mode)"
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
const fileSchema = z.object({
|
|
72
|
+
allowUsers: z.array(z.string().trim().min(1)).default([]),
|
|
73
|
+
editThrottleMs: z.number().int().positive().default(DEFAULT_EDIT_THROTTLE_MS),
|
|
74
|
+
maxQueuedTurns: z.number().int().positive().default(DEFAULT_MAX_QUEUED_TURNS)
|
|
75
|
+
});
|
|
76
|
+
function fail(prefix, error) {
|
|
77
|
+
const issues = error.issues.map((issue) => {
|
|
78
|
+
const field = issue.path.join(".");
|
|
79
|
+
return field.length > 0 ? `${field}: ${issue.message}` : issue.message;
|
|
80
|
+
});
|
|
81
|
+
const fields = error.issues.map((issue) => issue.path.join("."));
|
|
82
|
+
throw new TelegramConfigError(`${prefix}\n ${issues.join("\n ")}`, fields);
|
|
83
|
+
}
|
|
84
|
+
function readFileConfig(options) {
|
|
85
|
+
if (options.fileConfig !== undefined) {
|
|
86
|
+
return options.fileConfig;
|
|
87
|
+
}
|
|
88
|
+
const path = options.configPath ?? DEFAULT_CONFIG_FILE;
|
|
89
|
+
const explicit = options.configPath !== undefined;
|
|
90
|
+
let raw;
|
|
91
|
+
try {
|
|
92
|
+
raw = readFileSync(path, "utf8");
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
if (!explicit && err.code === "ENOENT") {
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
throw new TelegramConfigError(`Cannot read ${path}: ${err.message}`, [path]);
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(raw);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
throw new TelegramConfigError(`${path} is not valid JSON: ${err.message}`, [path]);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Read and validate the bridge configuration.
|
|
109
|
+
*
|
|
110
|
+
* @throws {TelegramConfigError} naming every offending field.
|
|
111
|
+
*/
|
|
112
|
+
export function loadConfig(options = {}) {
|
|
113
|
+
const env = options.env ?? process.env;
|
|
114
|
+
const fileParsed = fileSchema.safeParse(readFileConfig(options));
|
|
115
|
+
if (!fileParsed.success) {
|
|
116
|
+
fail("Invalid telegram-bot.json:", fileParsed.error);
|
|
117
|
+
}
|
|
118
|
+
const envParsed = envSchema.safeParse({
|
|
119
|
+
TELEGRAM_BOT_TOKEN: env.TELEGRAM_BOT_TOKEN,
|
|
120
|
+
NODETOOL_API_URL: env.NODETOOL_API_URL,
|
|
121
|
+
NODETOOL_INTEGRATION_TOKEN: env.NODETOOL_INTEGRATION_TOKEN,
|
|
122
|
+
TELEGRAM_WEBHOOK_URL: env.TELEGRAM_WEBHOOK_URL,
|
|
123
|
+
TELEGRAM_WEBHOOK_SECRET: env.TELEGRAM_WEBHOOK_SECRET
|
|
124
|
+
});
|
|
125
|
+
if (!envParsed.success) {
|
|
126
|
+
fail("Invalid Telegram bridge environment:", envParsed.error);
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
botToken: envParsed.data.TELEGRAM_BOT_TOKEN,
|
|
130
|
+
apiUrl: trimTrailingSlashes(envParsed.data.NODETOOL_API_URL),
|
|
131
|
+
integrationToken: envParsed.data.NODETOOL_INTEGRATION_TOKEN,
|
|
132
|
+
webhookUrl: envParsed.data.TELEGRAM_WEBHOOK_URL,
|
|
133
|
+
webhookSecret: envParsed.data.TELEGRAM_WEBHOOK_SECRET,
|
|
134
|
+
allowUsers: fileParsed.data.allowUsers,
|
|
135
|
+
editThrottleMs: fileParsed.data.editThrottleMs,
|
|
136
|
+
maxQueuedTurns: fileParsed.data.maxQueuedTurns
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|