telecodex 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.
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/dist/bot/auth.js +64 -0
- package/dist/bot/commandSupport.js +239 -0
- package/dist/bot/createBot.js +51 -0
- package/dist/bot/handlerDeps.js +1 -0
- package/dist/bot/handlers/messageHandlers.js +71 -0
- package/dist/bot/handlers/operationalHandlers.js +131 -0
- package/dist/bot/handlers/projectHandlers.js +192 -0
- package/dist/bot/handlers/sessionConfigHandlers.js +319 -0
- package/dist/bot/inputService.js +372 -0
- package/dist/bot/registerHandlers.js +10 -0
- package/dist/bot/session.js +22 -0
- package/dist/bot/sessionFlow.js +51 -0
- package/dist/cli.js +14 -0
- package/dist/codex/sdkRuntime.js +165 -0
- package/dist/config.js +69 -0
- package/dist/runtime/appPaths.js +14 -0
- package/dist/runtime/bootstrap.js +213 -0
- package/dist/runtime/instanceLock.js +89 -0
- package/dist/runtime/logger.js +75 -0
- package/dist/runtime/secrets.js +45 -0
- package/dist/runtime/sessionRuntime.js +53 -0
- package/dist/runtime/startTelecodex.js +118 -0
- package/dist/store/db.js +267 -0
- package/dist/store/projects.js +47 -0
- package/dist/store/sessions.js +328 -0
- package/dist/telegram/attachments.js +67 -0
- package/dist/telegram/delivery.js +140 -0
- package/dist/telegram/messageBuffer.js +272 -0
- package/dist/telegram/renderer.js +146 -0
- package/dist/telegram/splitMessage.js +141 -0
- package/package.json +66 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const TELEGRAM_SAFE_TEXT_LIMIT = 3900;
|
|
2
|
+
export function splitTelegramText(text, limit = TELEGRAM_SAFE_TEXT_LIMIT) {
|
|
3
|
+
if (text.length <= limit)
|
|
4
|
+
return [text];
|
|
5
|
+
const chunks = [];
|
|
6
|
+
let remaining = text;
|
|
7
|
+
while (remaining.length > limit) {
|
|
8
|
+
const slice = remaining.slice(0, limit);
|
|
9
|
+
const splitAt = Math.max(slice.lastIndexOf("\n\n"), slice.lastIndexOf("\n"), slice.lastIndexOf(" "));
|
|
10
|
+
const cut = splitAt > limit * 0.55 ? splitAt : limit;
|
|
11
|
+
chunks.push(remaining.slice(0, cut).trimEnd());
|
|
12
|
+
remaining = remaining.slice(cut).trimStart();
|
|
13
|
+
}
|
|
14
|
+
if (remaining)
|
|
15
|
+
chunks.push(remaining);
|
|
16
|
+
return chunks;
|
|
17
|
+
}
|
|
18
|
+
export function splitTelegramHtml(html, limit = TELEGRAM_SAFE_TEXT_LIMIT) {
|
|
19
|
+
if (html.length <= limit)
|
|
20
|
+
return [html];
|
|
21
|
+
const tokens = html.match(/<[^>]+>|[^<]+/g) ?? [];
|
|
22
|
+
const chunks = [];
|
|
23
|
+
const stack = [];
|
|
24
|
+
let body = "";
|
|
25
|
+
let chunkHasContent = false;
|
|
26
|
+
for (const token of tokens) {
|
|
27
|
+
if (token.startsWith("<")) {
|
|
28
|
+
const parsed = parseHtmlTag(token);
|
|
29
|
+
if (!parsed) {
|
|
30
|
+
appendTextToken(token);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (parsed.kind === "close") {
|
|
34
|
+
const matchingIndex = findMatchingStackIndex(stack, parsed.name);
|
|
35
|
+
const nextStack = matchingIndex >= 0 ? stack.slice(0, matchingIndex) : stack;
|
|
36
|
+
if (!canFit(token.length, closingSuffixLength(nextStack), body.length, limit) && chunkHasContent) {
|
|
37
|
+
pushChunk();
|
|
38
|
+
}
|
|
39
|
+
body += token;
|
|
40
|
+
if (matchingIndex >= 0) {
|
|
41
|
+
stack.splice(matchingIndex, 1);
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const nextStack = [...stack, parsed];
|
|
46
|
+
if (!canFit(token.length, closingSuffixLength(nextStack), body.length, limit) && chunkHasContent) {
|
|
47
|
+
pushChunk();
|
|
48
|
+
}
|
|
49
|
+
body += token;
|
|
50
|
+
stack.push(parsed);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
appendTextToken(token);
|
|
54
|
+
}
|
|
55
|
+
if (chunkHasContent) {
|
|
56
|
+
chunks.push(body + closingSuffix(stack));
|
|
57
|
+
}
|
|
58
|
+
else if (!chunks.length && body) {
|
|
59
|
+
chunks.push(body);
|
|
60
|
+
}
|
|
61
|
+
return chunks.filter(Boolean);
|
|
62
|
+
function appendTextToken(token) {
|
|
63
|
+
let remaining = token;
|
|
64
|
+
while (remaining) {
|
|
65
|
+
const available = limit - body.length - closingSuffixLength(stack);
|
|
66
|
+
if (available <= 0 && chunkHasContent) {
|
|
67
|
+
pushChunk();
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (available <= 0) {
|
|
71
|
+
body += remaining;
|
|
72
|
+
chunkHasContent = true;
|
|
73
|
+
remaining = "";
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (remaining.length <= available) {
|
|
77
|
+
body += remaining;
|
|
78
|
+
chunkHasContent = true;
|
|
79
|
+
remaining = "";
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const cut = chooseSplitPoint(remaining, available);
|
|
83
|
+
body += remaining.slice(0, cut);
|
|
84
|
+
chunkHasContent = true;
|
|
85
|
+
remaining = remaining.slice(cut);
|
|
86
|
+
pushChunk();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function pushChunk() {
|
|
90
|
+
if (!chunkHasContent)
|
|
91
|
+
return;
|
|
92
|
+
chunks.push(body + closingSuffix(stack));
|
|
93
|
+
body = openingPrefix(stack);
|
|
94
|
+
chunkHasContent = false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function chooseSplitPoint(text, limit) {
|
|
98
|
+
if (text.length <= limit)
|
|
99
|
+
return text.length;
|
|
100
|
+
const slice = text.slice(0, limit);
|
|
101
|
+
const splitAt = Math.max(slice.lastIndexOf("\n\n"), slice.lastIndexOf("\n"), slice.lastIndexOf(" "));
|
|
102
|
+
return splitAt > limit * 0.55 ? splitAt : limit;
|
|
103
|
+
}
|
|
104
|
+
function parseHtmlTag(token) {
|
|
105
|
+
const closeMatch = token.match(/^<\/([a-z]+)>$/i);
|
|
106
|
+
if (closeMatch?.[1]) {
|
|
107
|
+
return {
|
|
108
|
+
kind: "close",
|
|
109
|
+
name: closeMatch[1].toLowerCase(),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const openMatch = token.match(/^<([a-z]+)(?:\s[^>]*)?>$/i);
|
|
113
|
+
if (!openMatch?.[1])
|
|
114
|
+
return null;
|
|
115
|
+
const name = openMatch[1].toLowerCase();
|
|
116
|
+
return {
|
|
117
|
+
kind: "open",
|
|
118
|
+
name,
|
|
119
|
+
open: token,
|
|
120
|
+
close: `</${name}>`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function openingPrefix(stack) {
|
|
124
|
+
return stack.map((frame) => frame.open).join("");
|
|
125
|
+
}
|
|
126
|
+
function closingSuffix(stack) {
|
|
127
|
+
return [...stack].reverse().map((frame) => frame.close).join("");
|
|
128
|
+
}
|
|
129
|
+
function closingSuffixLength(stack) {
|
|
130
|
+
return stack.reduce((total, frame) => total + frame.close.length, 0);
|
|
131
|
+
}
|
|
132
|
+
function canFit(tokenLength, suffixLength, currentLength, limit) {
|
|
133
|
+
return currentLength + tokenLength + suffixLength <= limit;
|
|
134
|
+
}
|
|
135
|
+
function findMatchingStackIndex(stack, name) {
|
|
136
|
+
for (let index = stack.length - 1; index >= 0; index -= 1) {
|
|
137
|
+
if (stack[index]?.name === name)
|
|
138
|
+
return index;
|
|
139
|
+
}
|
|
140
|
+
return -1;
|
|
141
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "telecodex",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Telegram bridge for local Codex.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"telecodex": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/bot/**",
|
|
12
|
+
"dist/codex/**",
|
|
13
|
+
"dist/runtime/**",
|
|
14
|
+
"dist/store/**",
|
|
15
|
+
"dist/telegram/**",
|
|
16
|
+
"dist/config.js",
|
|
17
|
+
"dist/cli.js",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"codex",
|
|
23
|
+
"telegram",
|
|
24
|
+
"bot",
|
|
25
|
+
"cli",
|
|
26
|
+
"automation"
|
|
27
|
+
],
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/jiangege/telecodex.git"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/jiangege/telecodex#readme",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/jiangege/telecodex/issues"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=24.0.0"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"dev": "NODE_OPTIONS=--disable-warning=ExperimentalWarning tsx src/cli.ts",
|
|
44
|
+
"build": "tsc -p tsconfig.json",
|
|
45
|
+
"prepack": "npm run build",
|
|
46
|
+
"start": "node --disable-warning=ExperimentalWarning dist/cli.js",
|
|
47
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
48
|
+
"test": "node --import tsx --test src/tests/**/*.test.ts"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@clack/prompts": "^0.11.0",
|
|
52
|
+
"@grammyjs/runner": "^2.0.3",
|
|
53
|
+
"@napi-rs/keyring": "^1.2.0",
|
|
54
|
+
"@openai/codex-sdk": "^0.120.0",
|
|
55
|
+
"clipboardy": "^4.0.0",
|
|
56
|
+
"grammy": "^1.36.3",
|
|
57
|
+
"markdown-it": "^14.1.0",
|
|
58
|
+
"pino": "^10.3.1"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/markdown-it": "^14.1.2",
|
|
62
|
+
"@types/node": "^24.0.0",
|
|
63
|
+
"tsx": "^4.20.5",
|
|
64
|
+
"typescript": "^5.9.3"
|
|
65
|
+
}
|
|
66
|
+
}
|