zelai-cli 1.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 +181 -0
- package/bin/zelai.js +20 -0
- package/connector/README.md +51 -0
- package/connector/discord.js +155 -0
- package/connector/shared.js +189 -0
- package/connector/telegram.js +176 -0
- package/package.json +76 -0
- package/src/cli.js +563 -0
- package/src/client.js +171 -0
- package/src/commands.js +501 -0
- package/src/config.js +63 -0
- package/src/index.js +58 -0
- package/src/session.js +178 -0
- package/src/ui.js +169 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zelai — Telegram connector.
|
|
3
|
+
*
|
|
4
|
+
* Pake Bot API langsung (long polling) — zero extra deps di luar axios.
|
|
5
|
+
*
|
|
6
|
+
* ENV:
|
|
7
|
+
* ZELAPI_KEY API key Zelai
|
|
8
|
+
* TELEGRAM_TOKEN Token dari @BotFather
|
|
9
|
+
* TELEGRAM_ALLOW (opsional) comma-separated chat IDs yang boleh akses
|
|
10
|
+
* ZELAI_MODEL opus|sonnet|haiku
|
|
11
|
+
* ZELAI_ENDPOINT default /ai/claila
|
|
12
|
+
*
|
|
13
|
+
* Run:
|
|
14
|
+
* node connector/telegram.js
|
|
15
|
+
*/
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const axios = require('axios');
|
|
19
|
+
const ui = require('../src/ui');
|
|
20
|
+
const { makeSdk, handleSlash, runChat, logBanner } = require('./shared');
|
|
21
|
+
|
|
22
|
+
const TG_API = 'https://api.telegram.org';
|
|
23
|
+
|
|
24
|
+
function tgUrl(token, method) {
|
|
25
|
+
return `${TG_API}/bot${token}/${method}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function channelKeyFor(msg) {
|
|
29
|
+
// groups → per-chat, DM → per-user
|
|
30
|
+
if (msg.chat && (msg.chat.type === 'group' || msg.chat.type === 'supergroup')) {
|
|
31
|
+
return `telegram:chat:${msg.chat.id}`;
|
|
32
|
+
}
|
|
33
|
+
return `telegram:user:${msg.from && msg.from.id ? msg.from.id : msg.chat.id}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function sendMessage(token, chatId, text, opts = {}) {
|
|
37
|
+
if (!text) return;
|
|
38
|
+
const MAX = 4000;
|
|
39
|
+
let body = String(text);
|
|
40
|
+
// Telegram MarkdownV2 strict — pake plain text default biar gak escape neraka.
|
|
41
|
+
while (body.length > 0) {
|
|
42
|
+
const chunk = body.length > MAX ? body.slice(0, body.lastIndexOf('\n', MAX) > MAX / 2 ? body.lastIndexOf('\n', MAX) : MAX) : body;
|
|
43
|
+
body = body.slice(chunk.length);
|
|
44
|
+
try {
|
|
45
|
+
await axios.post(tgUrl(token, 'sendMessage'), {
|
|
46
|
+
chat_id: chatId,
|
|
47
|
+
text: chunk,
|
|
48
|
+
disable_web_page_preview: true,
|
|
49
|
+
reply_to_message_id: opts.replyTo,
|
|
50
|
+
});
|
|
51
|
+
} catch (err) {
|
|
52
|
+
// ignore individual chunk error to avoid blocking next ones
|
|
53
|
+
console.log(ui.errorTag() + ' telegram sendMessage: ' + (err.message || err));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function sendChatAction(token, chatId, action) {
|
|
59
|
+
try {
|
|
60
|
+
await axios.post(tgUrl(token, 'sendChatAction'), { chat_id: chatId, action });
|
|
61
|
+
} catch {}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseAllowList(env) {
|
|
65
|
+
if (!env) return null;
|
|
66
|
+
return env.split(',').map((s) => s.trim()).filter(Boolean);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function main() {
|
|
70
|
+
const token = process.env.TELEGRAM_TOKEN;
|
|
71
|
+
if (!token) {
|
|
72
|
+
console.log(ui.errorTag() + ' TELEGRAM_TOKEN env wajib di-set.');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
const sdk = makeSdk();
|
|
76
|
+
const allowList = parseAllowList(process.env.TELEGRAM_ALLOW);
|
|
77
|
+
|
|
78
|
+
logBanner('telegram');
|
|
79
|
+
|
|
80
|
+
// Verify token + get bot info
|
|
81
|
+
let me;
|
|
82
|
+
try {
|
|
83
|
+
const r = await axios.get(tgUrl(token, 'getMe'));
|
|
84
|
+
me = r.data && r.data.result;
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.log(ui.errorTag() + ' getMe gagal: ' + (err.message || err));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
if (!me || !me.id) {
|
|
90
|
+
console.log(ui.errorTag() + ' invalid bot token.');
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
console.log(`${ui.okTag()} login as ${ui.chalk.cyan.bold('@' + me.username)} (${me.first_name})`);
|
|
94
|
+
if (allowList) {
|
|
95
|
+
console.log(`${ui.infoTag()} allow-list aktif (${allowList.length} entri).`);
|
|
96
|
+
}
|
|
97
|
+
console.log(`${ui.infoTag()} listening for updates… (long polling, Ctrl+C buat stop)`);
|
|
98
|
+
|
|
99
|
+
let offset = 0;
|
|
100
|
+
// eslint-disable-next-line no-constant-condition
|
|
101
|
+
while (true) {
|
|
102
|
+
let updates = [];
|
|
103
|
+
try {
|
|
104
|
+
const r = await axios.get(tgUrl(token, 'getUpdates'), {
|
|
105
|
+
params: { offset, timeout: 30, allowed_updates: JSON.stringify(['message']) },
|
|
106
|
+
timeout: 40000,
|
|
107
|
+
});
|
|
108
|
+
updates = (r.data && r.data.result) || [];
|
|
109
|
+
} catch (err) {
|
|
110
|
+
console.log(ui.errorTag() + ' getUpdates: ' + (err.message || err) + ' — retry…');
|
|
111
|
+
await sleep(2000);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (const upd of updates) {
|
|
116
|
+
offset = Math.max(offset, upd.update_id + 1);
|
|
117
|
+
const msg = upd.message;
|
|
118
|
+
if (!msg || !msg.text) continue;
|
|
119
|
+
|
|
120
|
+
const chatId = String(msg.chat.id);
|
|
121
|
+
const userId = msg.from ? String(msg.from.id) : '';
|
|
122
|
+
|
|
123
|
+
if (allowList && !allowList.includes(chatId) && !allowList.includes(userId)) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const channelKey = channelKeyFor(msg);
|
|
128
|
+
|
|
129
|
+
// Di group, hanya respons kalau di-mention atau di-reply, atau slash command.
|
|
130
|
+
let text = msg.text.trim();
|
|
131
|
+
if (msg.chat.type === 'group' || msg.chat.type === 'supergroup') {
|
|
132
|
+
const mention = me.username ? '@' + me.username : '';
|
|
133
|
+
const startsWithMention = mention && text.startsWith(mention);
|
|
134
|
+
const isSlash = text.startsWith('/');
|
|
135
|
+
const isReplyToBot = msg.reply_to_message && msg.reply_to_message.from && msg.reply_to_message.from.id === me.id;
|
|
136
|
+
if (!startsWithMention && !isSlash && !isReplyToBot) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (startsWithMention) text = text.slice(mention.length).trim();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!text) {
|
|
143
|
+
await sendMessage(token, chatId, 'halo! kirim pesan kamu. /help buat lihat command.', { replyTo: msg.message_id });
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// typing
|
|
148
|
+
sendChatAction(token, chatId, 'typing').catch(() => {});
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const slash = await handleSlash(sdk, channelKey, text).catch((err) => ({ handled: true, text: '❌ ' + (err.message || 'error') }));
|
|
152
|
+
if (slash.handled) {
|
|
153
|
+
await sendMessage(token, chatId, slash.text, { replyTo: msg.message_id });
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const reply = await runChat(sdk, channelKey, text);
|
|
157
|
+
await sendMessage(token, chatId, reply, { replyTo: msg.message_id });
|
|
158
|
+
} catch (err) {
|
|
159
|
+
await sendMessage(token, chatId, '❌ error: ' + (err.message || err), { replyTo: msg.message_id });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function sleep(ms) {
|
|
166
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (require.main === module) {
|
|
170
|
+
main().catch((err) => {
|
|
171
|
+
console.log(ui.errorTag() + ' ' + (err.message || err));
|
|
172
|
+
process.exit(1);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports = { main };
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zelai-cli",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Zelai — official CLI & SDK for the Zelapi Agent API. Chat with AI right from your terminal, Discord, or Telegram. Multi-turn sessions, slash commands, limit warnings, and a built-in Discord/Telegram connector.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"zelai",
|
|
7
|
+
"zelapi",
|
|
8
|
+
"zelapiofficial",
|
|
9
|
+
"ai",
|
|
10
|
+
"ai-cli",
|
|
11
|
+
"ai-sdk",
|
|
12
|
+
"agent",
|
|
13
|
+
"agent-api",
|
|
14
|
+
"chatbot",
|
|
15
|
+
"chat-cli",
|
|
16
|
+
"llm",
|
|
17
|
+
"opus",
|
|
18
|
+
"sonnet",
|
|
19
|
+
"haiku",
|
|
20
|
+
"claude",
|
|
21
|
+
"rest-api",
|
|
22
|
+
"api-client",
|
|
23
|
+
"sdk",
|
|
24
|
+
"cli",
|
|
25
|
+
"terminal",
|
|
26
|
+
"repl",
|
|
27
|
+
"discord-bot",
|
|
28
|
+
"telegram-bot",
|
|
29
|
+
"nodejs",
|
|
30
|
+
"hazel"
|
|
31
|
+
],
|
|
32
|
+
"homepage": "https://github.com/zelapi/zelai#readme",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/zelapi/zelai/issues"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/zelapi/zelai.git"
|
|
39
|
+
},
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"author": "Zelapi Team",
|
|
42
|
+
"type": "commonjs",
|
|
43
|
+
"main": "src/index.js",
|
|
44
|
+
"bin": {
|
|
45
|
+
"zelai": "bin/zelai.js"
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"bin/",
|
|
49
|
+
"src/",
|
|
50
|
+
"connector/",
|
|
51
|
+
"README.md",
|
|
52
|
+
"LICENSE"
|
|
53
|
+
],
|
|
54
|
+
"scripts": {
|
|
55
|
+
"start": "node bin/zelai.js",
|
|
56
|
+
"discord": "node connector/discord.js",
|
|
57
|
+
"telegram": "node connector/telegram.js",
|
|
58
|
+
"test": "node test/smoke.js"
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=16.0.0"
|
|
62
|
+
},
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"axios": "^1.7.0",
|
|
65
|
+
"chalk": "^4.1.2",
|
|
66
|
+
"ora": "^5.4.1",
|
|
67
|
+
"marked": "^9.1.6",
|
|
68
|
+
"marked-terminal": "^6.2.0"
|
|
69
|
+
},
|
|
70
|
+
"optionalDependencies": {
|
|
71
|
+
"discord.js": "^14.14.1"
|
|
72
|
+
},
|
|
73
|
+
"publishConfig": {
|
|
74
|
+
"access": "public"
|
|
75
|
+
}
|
|
76
|
+
}
|