camelagi 0.5.0 → 0.5.16
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 +1 -1
- package/README.md +241 -104
- package/dist/agent/agent-sdk.js +136 -65
- package/dist/agent/agent-sdk.js.map +1 -1
- package/dist/agent.js +4 -1
- package/dist/agent.js.map +1 -1
- package/dist/bootstrap.js +148 -331
- package/dist/bootstrap.js.map +1 -1
- package/dist/channels/handler.js +109 -1
- package/dist/channels/handler.js.map +1 -1
- package/dist/cli/cmd-agents.js +11 -14
- package/dist/cli/cmd-agents.js.map +1 -1
- package/dist/cli/cmd-bootstrap.js +4 -4
- package/dist/cli/cmd-bootstrap.js.map +1 -1
- package/dist/cli/cmd-install.js +80 -0
- package/dist/cli/cmd-install.js.map +1 -0
- package/dist/cli/cmd-pairing.js +14 -15
- package/dist/cli/cmd-pairing.js.map +1 -1
- package/dist/cli/cmd-reset.js +10 -12
- package/dist/cli/cmd-reset.js.map +1 -1
- package/dist/cli/cmd-sessions.js +10 -13
- package/dist/cli/cmd-sessions.js.map +1 -1
- package/dist/cli/cmd-status.js +67 -0
- package/dist/cli/cmd-status.js.map +1 -0
- package/dist/cli/cmd-uninstall.js +79 -0
- package/dist/cli/cmd-uninstall.js.map +1 -0
- package/dist/cli/cmd-update.js +47 -0
- package/dist/cli/cmd-update.js.map +1 -0
- package/dist/cli.js +44 -26
- package/dist/cli.js.map +1 -1
- package/dist/core/config.js +53 -8
- package/dist/core/config.js.map +1 -1
- package/dist/core/log.js +33 -13
- package/dist/core/log.js.map +1 -1
- package/dist/core/update-check.js +11 -13
- package/dist/core/update-check.js.map +1 -1
- package/dist/core/version.js +4 -0
- package/dist/core/version.js.map +1 -0
- package/dist/runtime/orchestrate.js +25 -15
- package/dist/runtime/orchestrate.js.map +1 -1
- package/dist/serve.js +22 -7
- package/dist/serve.js.map +1 -1
- package/dist/setup.js +259 -231
- package/dist/setup.js.map +1 -1
- package/dist/telegram/admin-bot.js +325 -1
- package/dist/telegram/admin-bot.js.map +1 -1
- package/dist/telegram/agent-bot.js +143 -2
- package/dist/telegram/agent-bot.js.map +1 -1
- package/dist/telegram/resolve.js +11 -2
- package/dist/telegram/resolve.js.map +1 -1
- package/dist/telegram/wizards.js +361 -12
- package/dist/telegram/wizards.js.map +1 -1
- package/dist/telegram.js +2 -0
- package/dist/telegram.js.map +1 -1
- package/dist/usage.js +106 -0
- package/dist/usage.js.map +1 -1
- package/dist/workspace.js +20 -0
- package/dist/workspace.js.map +1 -1
- package/package.json +7 -4
- package/dist/agent/agent-openai.js +0 -206
- package/dist/agent/agent-openai.js.map +0 -1
- package/dist/approval-forward.js +0 -42
- package/dist/approval-forward.js.map +0 -1
- package/dist/approvals.js +0 -151
- package/dist/approvals.js.map +0 -1
- package/dist/camelagi-gateway.mjs +0 -93611
- package/dist/camelagi-gateway.mjs.map +0 -7
- package/dist/compact.js +0 -92
- package/dist/compact.js.map +0 -1
- package/dist/config.js +0 -153
- package/dist/config.js.map +0 -1
- package/dist/constants.js +0 -21
- package/dist/constants.js.map +0 -1
- package/dist/cron.js +0 -81
- package/dist/cron.js.map +0 -1
- package/dist/errors.js +0 -5
- package/dist/errors.js.map +0 -1
- package/dist/hooks.js +0 -72
- package/dist/hooks.js.map +0 -1
- package/dist/lanes.js +0 -62
- package/dist/lanes.js.map +0 -1
- package/dist/policy.js +0 -22
- package/dist/policy.js.map +0 -1
- package/dist/queue.js +0 -45
- package/dist/queue.js.map +0 -1
- package/dist/retry.js +0 -96
- package/dist/retry.js.map +0 -1
- package/dist/runs.js +0 -83
- package/dist/runs.js.map +0 -1
- package/dist/skills.js +0 -89
- package/dist/skills.js.map +0 -1
- package/dist/subagent.js +0 -71
- package/dist/subagent.js.map +0 -1
- package/dist/telegram-admin.js +0 -800
- package/dist/telegram-admin.js.map +0 -1
- package/dist/tools/edit.js +0 -29
- package/dist/tools/edit.js.map +0 -1
- package/dist/tools/exec.js +0 -38
- package/dist/tools/exec.js.map +0 -1
- package/dist/tools/fetch.js +0 -28
- package/dist/tools/fetch.js.map +0 -1
- package/dist/tools/index.js +0 -16
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/read.js +0 -26
- package/dist/tools/read.js.map +0 -1
- package/dist/tools/search.js +0 -62
- package/dist/tools/search.js.map +0 -1
- package/dist/tools/subagent.js +0 -48
- package/dist/tools/subagent.js.map +0 -1
- package/dist/tools/write.js +0 -22
- package/dist/tools/write.js.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
package/dist/bootstrap.js
CHANGED
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
// Full first-time bootstrap — admin bot + pairing + optional API setup
|
|
2
2
|
// After this, everything is controlled from Telegram.
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// camelagi bootstrap (interactive)
|
|
6
|
-
// camelagi bootstrap <token> (skip bot token prompt)
|
|
7
|
-
import readline from "node:readline";
|
|
8
|
-
import ora from "ora";
|
|
9
|
-
import { saveConfig, loadConfig, ensureDirs } from "./core/config.js";
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
4
|
+
import { saveConfig, loadConfig, ensureDirs, paths } from "./core/config.js";
|
|
10
5
|
import { seedWorkspace, seedAgentWorkspace } from "./workspace.js";
|
|
11
6
|
import { PROVIDER_PRESETS, fetchOpenRouterModels } from "./core/models.js";
|
|
12
7
|
import { listPendingRequests, approveRequest } from "./telegram/pairing.js";
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
// ─── Helpers ─────────────────────────────────────────────────────────
|
|
9
|
+
function bail(msg) {
|
|
10
|
+
p.cancel(msg ?? "Setup cancelled.");
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
function check(value) {
|
|
14
|
+
if (p.isCancel(value))
|
|
15
|
+
bail();
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
async function validateBotToken(token) {
|
|
19
|
+
try {
|
|
20
|
+
const resp = await fetch(`https://api.telegram.org/bot${token}/getMe`);
|
|
21
|
+
const data = await resp.json();
|
|
22
|
+
if (data.ok && data.result)
|
|
23
|
+
return { ok: true, username: data.result.username, name: data.result.first_name };
|
|
24
|
+
return { ok: false, error: data.description ?? "Invalid token" };
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
28
|
+
}
|
|
15
29
|
}
|
|
16
30
|
// Suppress background server logs during interactive prompts
|
|
17
31
|
let _logMuted = false;
|
|
@@ -22,12 +36,7 @@ function muteLogs() {
|
|
|
22
36
|
if (_logMuted)
|
|
23
37
|
return;
|
|
24
38
|
_logMuted = true;
|
|
25
|
-
console.log = (
|
|
26
|
-
const first = typeof args[0] === "string" ? args[0] : "";
|
|
27
|
-
// Allow bootstrap's own output (indented or colored)
|
|
28
|
-
if (first.startsWith("\x1b[") || first.startsWith(" "))
|
|
29
|
-
_origLog(...args);
|
|
30
|
-
};
|
|
39
|
+
console.log = () => { };
|
|
31
40
|
console.error = () => { };
|
|
32
41
|
console.warn = () => { };
|
|
33
42
|
}
|
|
@@ -39,192 +48,12 @@ function unmuteLogs() {
|
|
|
39
48
|
console.error = _origError;
|
|
40
49
|
console.warn = _origWarn;
|
|
41
50
|
}
|
|
42
|
-
|
|
43
|
-
function showList(items, indices) {
|
|
44
|
-
for (let i = 0; i < indices.length; i++) {
|
|
45
|
-
console.log(` \x1b[33m${indices[i] + 1}\x1b[0m) ${items[i]}`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
if (!compact) {
|
|
49
|
-
// Simple numbered list for small option sets
|
|
50
|
-
return new Promise((resolve) => {
|
|
51
|
-
console.log(`\n\x1b[36m ${label}\x1b[0m`);
|
|
52
|
-
showList(options, options.map((_, i) => i));
|
|
53
|
-
rl.question(`\n Pick [1-${options.length}]: `, (answer) => {
|
|
54
|
-
const idx = parseInt(answer.trim(), 10) - 1;
|
|
55
|
-
resolve(options[idx] ?? options[0]);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
// Live-filter mode for large lists
|
|
60
|
-
return new Promise((resolve) => {
|
|
61
|
-
console.log(`\n\x1b[36m ${label}\x1b[0m`);
|
|
62
|
-
console.log(`\x1b[90m ${options.length} options — start typing to filter, arrows to navigate, enter to select\x1b[0m\n`);
|
|
63
|
-
// Pause readline so we can use raw stdin
|
|
64
|
-
rl.pause();
|
|
65
|
-
let query = "";
|
|
66
|
-
let cursor = 0;
|
|
67
|
-
let matches = options.map((o, i) => ({ option: o, index: i }));
|
|
68
|
-
const MAX_VISIBLE = 8;
|
|
69
|
-
function getVisible() {
|
|
70
|
-
if (matches.length <= MAX_VISIBLE)
|
|
71
|
-
return matches;
|
|
72
|
-
// Keep cursor in view
|
|
73
|
-
let start = Math.max(0, cursor - Math.floor(MAX_VISIBLE / 2));
|
|
74
|
-
if (start + MAX_VISIBLE > matches.length)
|
|
75
|
-
start = Math.max(0, matches.length - MAX_VISIBLE);
|
|
76
|
-
return matches.slice(start, start + MAX_VISIBLE);
|
|
77
|
-
}
|
|
78
|
-
function render() {
|
|
79
|
-
const visible = getVisible();
|
|
80
|
-
const startIdx = matches.indexOf(visible[0]);
|
|
81
|
-
// Clear: move up to header + erase everything below
|
|
82
|
-
process.stdout.write(`\x1b[2K\r`); // clear current line
|
|
83
|
-
// Build output
|
|
84
|
-
const lines = [];
|
|
85
|
-
lines.push(` \x1b[36m>\x1b[0m ${query}\x1b[90m_\x1b[0m`);
|
|
86
|
-
lines.push("");
|
|
87
|
-
if (matches.length === 0) {
|
|
88
|
-
lines.push(` \x1b[33mNo matches\x1b[0m`);
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
if (startIdx > 0)
|
|
92
|
-
lines.push(` \x1b[90m ↑ ${startIdx} more\x1b[0m`);
|
|
93
|
-
for (let i = 0; i < visible.length; i++) {
|
|
94
|
-
const m = visible[i];
|
|
95
|
-
const globalIdx = startIdx + i;
|
|
96
|
-
const selected = globalIdx === cursor;
|
|
97
|
-
if (selected) {
|
|
98
|
-
lines.push(` \x1b[36m▸ ${m.index + 1}) ${m.option}\x1b[0m`);
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
lines.push(` \x1b[33m${m.index + 1}\x1b[0m) ${m.option}`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
const remaining = matches.length - (startIdx + visible.length);
|
|
105
|
-
if (remaining > 0)
|
|
106
|
-
lines.push(` \x1b[90m ↓ ${remaining} more\x1b[0m`);
|
|
107
|
-
}
|
|
108
|
-
// Move cursor up to overwrite previous render, then write
|
|
109
|
-
if (render._prevLines) {
|
|
110
|
-
process.stdout.write(`\x1b[${render._prevLines}A`);
|
|
111
|
-
}
|
|
112
|
-
for (const line of lines) {
|
|
113
|
-
process.stdout.write(`\x1b[2K${line}\n`);
|
|
114
|
-
}
|
|
115
|
-
// Clear any leftover lines from previous longer render
|
|
116
|
-
const prevCount = render._prevLines ?? 0;
|
|
117
|
-
for (let i = lines.length; i < prevCount; i++) {
|
|
118
|
-
process.stdout.write(`\x1b[2K\n`);
|
|
119
|
-
}
|
|
120
|
-
if (prevCount > lines.length) {
|
|
121
|
-
process.stdout.write(`\x1b[${prevCount - lines.length}A`);
|
|
122
|
-
}
|
|
123
|
-
render._prevLines = lines.length;
|
|
124
|
-
}
|
|
125
|
-
function updateMatches() {
|
|
126
|
-
const q = query.toLowerCase();
|
|
127
|
-
matches = q
|
|
128
|
-
? options.map((o, i) => ({ option: o, index: i })).filter((m) => m.option.toLowerCase().includes(q))
|
|
129
|
-
: options.map((o, i) => ({ option: o, index: i }));
|
|
130
|
-
cursor = 0;
|
|
131
|
-
}
|
|
132
|
-
const stdin = process.stdin;
|
|
133
|
-
stdin.setRawMode(true);
|
|
134
|
-
stdin.resume();
|
|
135
|
-
render();
|
|
136
|
-
const onData = (buf) => {
|
|
137
|
-
const key = buf.toString();
|
|
138
|
-
if (key === "\r" || key === "\n") {
|
|
139
|
-
// Enter: select current
|
|
140
|
-
stdin.removeListener("data", onData);
|
|
141
|
-
stdin.setRawMode(false);
|
|
142
|
-
stdin.pause();
|
|
143
|
-
rl.resume();
|
|
144
|
-
// Clear the picker output
|
|
145
|
-
const prevLines = render._prevLines ?? 0;
|
|
146
|
-
process.stdout.write(`\x1b[${prevLines}A`);
|
|
147
|
-
for (let i = 0; i < prevLines; i++)
|
|
148
|
-
process.stdout.write(`\x1b[2K\n`);
|
|
149
|
-
process.stdout.write(`\x1b[${prevLines}A`);
|
|
150
|
-
if (matches.length > 0) {
|
|
151
|
-
const selected = matches[cursor];
|
|
152
|
-
console.log(` \x1b[32m→ ${selected.option}\x1b[0m\n`);
|
|
153
|
-
resolve(selected.option);
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
// No matches but query could be a custom model name
|
|
157
|
-
if (query.trim()) {
|
|
158
|
-
console.log(` \x1b[32m→ ${query.trim()}\x1b[0m\n`);
|
|
159
|
-
resolve(query.trim());
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
console.log(` \x1b[32m→ ${options[0]}\x1b[0m\n`);
|
|
163
|
-
resolve(options[0]);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
if (key === "\x03") {
|
|
169
|
-
// Ctrl+C
|
|
170
|
-
stdin.removeListener("data", onData);
|
|
171
|
-
stdin.setRawMode(false);
|
|
172
|
-
process.exit(0);
|
|
173
|
-
}
|
|
174
|
-
if (key === "\x1b[A") {
|
|
175
|
-
// Up arrow
|
|
176
|
-
if (cursor > 0)
|
|
177
|
-
cursor--;
|
|
178
|
-
render();
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
if (key === "\x1b[B") {
|
|
182
|
-
// Down arrow
|
|
183
|
-
if (cursor < matches.length - 1)
|
|
184
|
-
cursor++;
|
|
185
|
-
render();
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
if (key === "\x7f" || key === "\b") {
|
|
189
|
-
// Backspace
|
|
190
|
-
if (query.length > 0) {
|
|
191
|
-
query = query.slice(0, -1);
|
|
192
|
-
updateMatches();
|
|
193
|
-
render();
|
|
194
|
-
}
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
// Regular character
|
|
198
|
-
if (key.length === 1 && key >= " ") {
|
|
199
|
-
query += key;
|
|
200
|
-
updateMatches();
|
|
201
|
-
render();
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
stdin.on("data", onData);
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
async function validateBotToken(token) {
|
|
208
|
-
try {
|
|
209
|
-
const resp = await fetch(`https://api.telegram.org/bot${token}/getMe`);
|
|
210
|
-
const data = await resp.json();
|
|
211
|
-
if (data.ok && data.result) {
|
|
212
|
-
return { ok: true, username: data.result.username, name: data.result.first_name };
|
|
213
|
-
}
|
|
214
|
-
return { ok: false, error: data.description ?? "Invalid token" };
|
|
215
|
-
}
|
|
216
|
-
catch (err) {
|
|
217
|
-
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
218
|
-
}
|
|
219
|
-
}
|
|
51
|
+
// ─── Bootstrap ───────────────────────────────────────────────────────
|
|
220
52
|
export async function runBootstrap(tokenArg) {
|
|
221
53
|
ensureDirs();
|
|
222
54
|
seedWorkspace();
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
console.log(`\x1b[90m Sets up your admin bot, verifies your identity, then configures AI.\x1b[0m`);
|
|
226
|
-
console.log(`\x1b[90m After this, manage everything from Telegram.\x1b[0m`);
|
|
227
|
-
// ─── Resume detection ──────────────────────────────────────────────
|
|
55
|
+
p.intro("\x1b[36mCamelAGI\x1b[0m");
|
|
56
|
+
// Resume detection
|
|
228
57
|
let existingConfig;
|
|
229
58
|
try {
|
|
230
59
|
existingConfig = loadConfig();
|
|
@@ -236,204 +65,197 @@ export async function runBootstrap(tokenArg) {
|
|
|
236
65
|
const hasVerifiedUser = (existingConfig?.agents?.admin?.telegram?.allowedUsers?.length ?? 0) > 0;
|
|
237
66
|
const hasApiKey = !!existingConfig?.apiKey;
|
|
238
67
|
if (hasAdminBot && hasVerifiedUser && hasApiKey) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const resetAnswer = (await ask(rl, `\n\x1b[36m Reset and start over? (y/N):\x1b[0m `)).trim().toLowerCase();
|
|
244
|
-
if (resetAnswer !== "y") {
|
|
245
|
-
console.log(`\x1b[90m Nothing to do. Use /setup in Telegram to reconfigure.\x1b[0m\n`);
|
|
246
|
-
rl.close();
|
|
68
|
+
p.log.success(`Already configured: ${existingConfig.provider} / ${existingConfig.model}`);
|
|
69
|
+
const reset = check(await p.confirm({ message: "Reset and start over?" }));
|
|
70
|
+
if (!reset) {
|
|
71
|
+
p.outro("Use /setup in Telegram to reconfigure.");
|
|
247
72
|
return;
|
|
248
73
|
}
|
|
249
74
|
}
|
|
250
75
|
else if (hasAdminBot && hasVerifiedUser && !hasApiKey) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
76
|
+
p.log.success("Admin bot + identity configured");
|
|
77
|
+
p.log.warn("API not set \u2014 resuming from step 3");
|
|
78
|
+
const s = p.spinner();
|
|
79
|
+
s.start("Starting server...");
|
|
255
80
|
const { startServer } = await import("./serve.js");
|
|
256
81
|
startServer({ cron: true, boot: true }).catch(() => { });
|
|
257
82
|
await new Promise((r) => setTimeout(r, 2000));
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
await runApiSetup(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
console.log(`\x1b[90m Use /newagent in Telegram to create your first AI agent.\x1b[0m`);
|
|
264
|
-
console.log(`\x1b[90m Server is running. Press Ctrl+C to stop.\x1b[0m\n`);
|
|
83
|
+
s.stop("Server running");
|
|
84
|
+
muteLogs();
|
|
85
|
+
await runApiSetup();
|
|
86
|
+
unmuteLogs();
|
|
87
|
+
showDone(loadConfig());
|
|
265
88
|
await new Promise(() => { });
|
|
266
89
|
return;
|
|
267
90
|
}
|
|
268
|
-
//
|
|
91
|
+
// ── 1. Telegram Bot ────────────────────────────────────────────────
|
|
92
|
+
p.log.step("\x1b[1m1. Telegram Bot\x1b[0m");
|
|
93
|
+
p.log.info("Create a bot in @BotFather \u2192 /newbot, then paste the token.");
|
|
269
94
|
let botToken = tokenArg ?? "";
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
console.log(` \x1b[36m│\x1b[0m 1. Open Telegram → \x1b[1m@BotFather\x1b[0m → \x1b[1m/newbot\x1b[0m \x1b[36m│\x1b[0m`);
|
|
275
|
-
console.log(` \x1b[36m│\x1b[0m 2. Copy the bot token \x1b[36m│\x1b[0m`);
|
|
276
|
-
console.log(` \x1b[36m└──────────────────────────────────────────┘\x1b[0m\n`);
|
|
277
|
-
botToken = (await ask(rl, `\x1b[36m Bot token:\x1b[0m `)).trim();
|
|
278
|
-
}
|
|
279
|
-
if (!botToken) {
|
|
280
|
-
rl.close();
|
|
281
|
-
console.error("\n\x1b[31m Bot token is required.\x1b[0m\n");
|
|
282
|
-
process.exit(1);
|
|
283
|
-
}
|
|
284
|
-
// Validate the token
|
|
285
|
-
const validateSpinner = ora({ text: "Validating bot token...", indent: 2 }).start();
|
|
286
|
-
const result = await validateBotToken(botToken);
|
|
287
|
-
if (!result.ok) {
|
|
288
|
-
if (result.error?.includes("fetch failed") || result.error?.includes("ENOTFOUND")) {
|
|
289
|
-
validateSpinner.warn("Could not reach Telegram API — skipping validation");
|
|
95
|
+
let result = { ok: false };
|
|
96
|
+
while (true) {
|
|
97
|
+
if (!botToken) {
|
|
98
|
+
botToken = check(await p.password({ message: "Bot token" }));
|
|
290
99
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
100
|
+
if (!botToken.trim()) {
|
|
101
|
+
p.log.warn("Empty \u2014 try again.");
|
|
102
|
+
botToken = "";
|
|
103
|
+
continue;
|
|
295
104
|
}
|
|
105
|
+
botToken = botToken.trim();
|
|
106
|
+
const s = p.spinner();
|
|
107
|
+
s.start("Validating...");
|
|
108
|
+
result = await validateBotToken(botToken);
|
|
109
|
+
if (result.ok) {
|
|
110
|
+
s.stop(`@${result.username} (${result.name})`);
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
if (result.error?.includes("fetch failed") || result.error?.includes("ENOTFOUND")) {
|
|
114
|
+
s.stop("Could not reach Telegram \u2014 skipping validation");
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
s.stop(`Invalid: ${result.error}`);
|
|
118
|
+
p.log.warn("Try again.");
|
|
119
|
+
botToken = "";
|
|
296
120
|
}
|
|
297
|
-
else {
|
|
298
|
-
validateSpinner.succeed(`Bot valid: @${result.username} (${result.name})`);
|
|
299
|
-
}
|
|
300
|
-
// Save admin bot config (no API yet)
|
|
301
121
|
saveConfig({
|
|
302
122
|
agents: {
|
|
303
|
-
admin: {
|
|
304
|
-
name: "Admin",
|
|
305
|
-
admin: true,
|
|
306
|
-
telegram: { botToken, allowedUsers: [] },
|
|
307
|
-
},
|
|
123
|
+
admin: { name: "Admin", admin: true, telegram: { botToken, allowedUsers: [] } },
|
|
308
124
|
},
|
|
309
125
|
});
|
|
310
|
-
seedAgentWorkspace("admin", "Admin", "CamelAGI admin bot
|
|
311
|
-
|
|
312
|
-
//
|
|
313
|
-
const
|
|
126
|
+
seedAgentWorkspace("admin", "Admin", "CamelAGI admin bot");
|
|
127
|
+
p.log.success("Admin bot configured");
|
|
128
|
+
// ── 2. Identity ────────────────────────────────────────────────────
|
|
129
|
+
const s2 = p.spinner();
|
|
130
|
+
s2.start("Starting server...");
|
|
314
131
|
const { startServer } = await import("./serve.js");
|
|
315
132
|
startServer({ cron: true, boot: true }).catch(() => { });
|
|
316
133
|
await new Promise((r) => setTimeout(r, 2000));
|
|
317
|
-
|
|
134
|
+
s2.stop("Server running");
|
|
318
135
|
muteLogs();
|
|
319
136
|
const botName = result.ok ? `@${result.username}` : "your admin bot";
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
console.log(` \x1b[36m└──────────────────────────────────────────┘\x1b[0m\n`);
|
|
325
|
-
const pairingSpinner = ora({ text: "Waiting for your Telegram message...", indent: 2 }).start();
|
|
326
|
-
// Poll for pairing request
|
|
137
|
+
p.log.step("\x1b[1m2. Verify Identity\x1b[0m");
|
|
138
|
+
p.log.info(`Send any message to \x1b[36m${botName}\x1b[0m in Telegram.`);
|
|
139
|
+
const s3 = p.spinner();
|
|
140
|
+
s3.start("Waiting for message...");
|
|
327
141
|
let pairingRequest;
|
|
328
142
|
for (let i = 0; i < 120; i++) {
|
|
329
|
-
const pending = listPendingRequests().filter((
|
|
143
|
+
const pending = listPendingRequests().filter((req) => req.agentId === "admin" && req.status === "pending");
|
|
330
144
|
if (pending.length > 0) {
|
|
331
145
|
pairingRequest = pending[0];
|
|
332
146
|
break;
|
|
333
147
|
}
|
|
334
|
-
await new Promise((
|
|
148
|
+
await new Promise((res) => setTimeout(res, 1000));
|
|
335
149
|
}
|
|
336
150
|
if (!pairingRequest) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
151
|
+
s3.stop("Timeout \u2014 no message received");
|
|
152
|
+
p.log.warn("Pair later via /pairing in Telegram.");
|
|
153
|
+
p.outro("Server running. Ctrl+C to stop.");
|
|
154
|
+
unmuteLogs();
|
|
340
155
|
await new Promise(() => { });
|
|
341
156
|
return;
|
|
342
157
|
}
|
|
343
158
|
const userLabel = pairingRequest.username ? `@${pairingRequest.username}` : pairingRequest.firstName ?? String(pairingRequest.userId);
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
console.log(` \x1b[36m└──────────────────────┘\x1b[0m\n`);
|
|
350
|
-
// Ask admin to approve in CLI
|
|
351
|
-
const approveAnswer = (await ask(rl, `\x1b[36m Approve ${userLabel}? (Y/n):\x1b[0m `)).trim().toLowerCase();
|
|
352
|
-
if (approveAnswer === "n") {
|
|
353
|
-
console.log(`\x1b[90m Denied. Pair later via /pairing.\x1b[0m`);
|
|
354
|
-
rl.close();
|
|
159
|
+
s3.stop(`Request from \x1b[1m${userLabel}\x1b[0m (${pairingRequest.userId})`);
|
|
160
|
+
const approve = check(await p.confirm({ message: `Approve ${userLabel}?` }));
|
|
161
|
+
if (!approve) {
|
|
162
|
+
p.log.warn("Denied. Pair later via /pairing.");
|
|
163
|
+
unmuteLogs();
|
|
355
164
|
await new Promise(() => { });
|
|
356
165
|
return;
|
|
357
166
|
}
|
|
358
167
|
const approved = approveRequest(pairingRequest.code);
|
|
359
168
|
if (!approved) {
|
|
360
|
-
|
|
361
|
-
|
|
169
|
+
p.log.error("Approval failed.");
|
|
170
|
+
unmuteLogs();
|
|
362
171
|
await new Promise(() => { });
|
|
363
172
|
return;
|
|
364
173
|
}
|
|
365
|
-
// Notify user in Telegram
|
|
366
174
|
try {
|
|
367
175
|
await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
|
|
368
176
|
method: "POST",
|
|
369
177
|
headers: { "Content-Type": "application/json" },
|
|
370
|
-
body: JSON.stringify({
|
|
371
|
-
chat_id: pairingRequest.chatId,
|
|
372
|
-
text: "Access approved! You are now the admin.",
|
|
373
|
-
}),
|
|
178
|
+
body: JSON.stringify({ chat_id: pairingRequest.chatId, text: "Access approved! You are now the admin." }),
|
|
374
179
|
});
|
|
375
180
|
}
|
|
376
181
|
catch { /* best effort */ }
|
|
377
|
-
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
182
|
+
p.log.success(`${userLabel} approved`);
|
|
183
|
+
// ── 3. AI Provider ─────────────────────────────────────────────────
|
|
184
|
+
p.log.step("\x1b[1m3. AI Provider\x1b[0m");
|
|
185
|
+
const setupNow = check(await p.confirm({ message: "Configure now?" }));
|
|
186
|
+
if (setupNow) {
|
|
187
|
+
await runApiSetup();
|
|
382
188
|
}
|
|
383
189
|
else {
|
|
384
|
-
|
|
190
|
+
p.log.info("Skipped \u2014 use /setup in Telegram later.");
|
|
385
191
|
}
|
|
386
|
-
rl.close();
|
|
387
|
-
// ─── Done ─────────────────────────────────────────────────────────
|
|
388
192
|
unmuteLogs();
|
|
389
|
-
|
|
390
|
-
console.log(`\x1b[90m Use /newagent in Telegram to create your first AI agent.\x1b[0m`);
|
|
391
|
-
console.log(`\x1b[90m Server is running. Press Ctrl+C to stop.\x1b[0m\n`);
|
|
193
|
+
showDone(loadConfig(), botName, userLabel);
|
|
392
194
|
await new Promise(() => { });
|
|
393
195
|
}
|
|
394
|
-
// ───
|
|
395
|
-
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
"
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
196
|
+
// ─── Done ────────────────────────────────────────────────────────────
|
|
197
|
+
function showDone(config, botName, admin) {
|
|
198
|
+
const lines = [
|
|
199
|
+
`Provider ${config.provider}`,
|
|
200
|
+
`Model ${config.model}`,
|
|
201
|
+
`API Key ${config.apiKey ? "\u2022\u2022\u2022\u2022" + config.apiKey.slice(-4) : "not set"}`,
|
|
202
|
+
];
|
|
203
|
+
if (botName)
|
|
204
|
+
lines.push(`Bot ${botName}`);
|
|
205
|
+
if (admin)
|
|
206
|
+
lines.push(`Admin ${admin}`);
|
|
207
|
+
lines.push(`Config ${paths.configFile}`);
|
|
208
|
+
p.note(lines.join("\n"), "Setup complete");
|
|
209
|
+
p.log.info("Next: /newagent in Telegram to create your first agent.");
|
|
210
|
+
p.outro("Server running. Ctrl+C to stop.");
|
|
211
|
+
}
|
|
212
|
+
// ─── API setup ───────────────────────────────────────────────────────
|
|
213
|
+
async function runApiSetup() {
|
|
214
|
+
const provider = check(await p.select({
|
|
215
|
+
message: "Provider",
|
|
216
|
+
options: [
|
|
217
|
+
{ value: "anthropic", label: "Anthropic", hint: "Claude (direct)" },
|
|
218
|
+
{ value: "openai", label: "OpenAI", hint: "GPT (direct)" },
|
|
219
|
+
{ value: "openrouter", label: "OpenRouter", hint: "Any model" },
|
|
220
|
+
{ value: "ollama", label: "Ollama", hint: "Local models" },
|
|
221
|
+
{ value: "custom", label: "Custom", hint: "Custom endpoint" },
|
|
222
|
+
],
|
|
223
|
+
}));
|
|
224
|
+
const preset = PROVIDER_PRESETS[provider] ?? PROVIDER_PRESETS.custom;
|
|
405
225
|
let apiKey;
|
|
406
|
-
if (
|
|
407
|
-
const
|
|
408
|
-
apiKey = (await
|
|
226
|
+
if (provider !== "ollama") {
|
|
227
|
+
const label = provider === "anthropic" ? "Anthropic" : provider === "openai" ? "OpenAI" : provider === "openrouter" ? "OpenRouter" : "API";
|
|
228
|
+
apiKey = check(await p.password({ message: `${label} API key` })) || undefined;
|
|
409
229
|
if (!apiKey)
|
|
410
|
-
|
|
230
|
+
p.log.warn("No key \u2014 set later via /setup in Telegram.");
|
|
411
231
|
}
|
|
412
232
|
let baseUrl = preset.baseUrl;
|
|
413
|
-
if (
|
|
414
|
-
baseUrl = (await
|
|
233
|
+
if (provider === "custom") {
|
|
234
|
+
baseUrl = check(await p.text({ message: "Base URL", placeholder: "http://localhost:8080/v1" })) || undefined;
|
|
415
235
|
}
|
|
416
|
-
// Fetch live models for OpenRouter
|
|
236
|
+
// Fetch live models for OpenRouter
|
|
417
237
|
let models = [...preset.models];
|
|
418
|
-
if (
|
|
419
|
-
const
|
|
238
|
+
if (provider === "openrouter" && apiKey) {
|
|
239
|
+
const s = p.spinner();
|
|
240
|
+
s.start("Fetching models from OpenRouter...");
|
|
420
241
|
const live = await fetchOpenRouterModels(apiKey);
|
|
421
242
|
if (live.length > 0) {
|
|
422
243
|
models = live.map((m) => m.id);
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
else {
|
|
426
|
-
spinner.warn("Could not fetch live models — using defaults");
|
|
244
|
+
s.stop(`${models.length} models available`);
|
|
427
245
|
}
|
|
246
|
+
else
|
|
247
|
+
s.stop("Could not fetch \u2014 using defaults");
|
|
428
248
|
}
|
|
429
249
|
let model;
|
|
430
250
|
if (models.length > 0) {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
251
|
+
model = check(await p.autocomplete({
|
|
252
|
+
message: "Model",
|
|
253
|
+
options: models.map((m) => ({ value: m, label: m })),
|
|
254
|
+
maxItems: 8,
|
|
255
|
+
}));
|
|
434
256
|
}
|
|
435
257
|
else {
|
|
436
|
-
model = (await
|
|
258
|
+
model = check(await p.text({ message: "Model name" }));
|
|
437
259
|
}
|
|
438
260
|
const update = { provider: preset.provider, model };
|
|
439
261
|
if (apiKey)
|
|
@@ -441,11 +263,6 @@ async function runApiSetup(rl) {
|
|
|
441
263
|
if (baseUrl)
|
|
442
264
|
update.baseUrl = baseUrl;
|
|
443
265
|
saveConfig(update);
|
|
444
|
-
|
|
445
|
-
console.log(`\x1b[90m provider: ${preset.provider}\x1b[0m`);
|
|
446
|
-
console.log(`\x1b[90m model: ${model}\x1b[0m`);
|
|
447
|
-
if (baseUrl)
|
|
448
|
-
console.log(`\x1b[90m baseUrl: ${baseUrl}\x1b[0m`);
|
|
449
|
-
console.log(`\x1b[90m apiKey: ${apiKey ? "***" + apiKey.slice(-4) : "not set"}\x1b[0m`);
|
|
266
|
+
p.log.success(`${preset.provider} / ${model}`);
|
|
450
267
|
}
|
|
451
268
|
//# sourceMappingURL=bootstrap.js.map
|