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.
Files changed (113) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +241 -104
  3. package/dist/agent/agent-sdk.js +136 -65
  4. package/dist/agent/agent-sdk.js.map +1 -1
  5. package/dist/agent.js +4 -1
  6. package/dist/agent.js.map +1 -1
  7. package/dist/bootstrap.js +148 -331
  8. package/dist/bootstrap.js.map +1 -1
  9. package/dist/channels/handler.js +109 -1
  10. package/dist/channels/handler.js.map +1 -1
  11. package/dist/cli/cmd-agents.js +11 -14
  12. package/dist/cli/cmd-agents.js.map +1 -1
  13. package/dist/cli/cmd-bootstrap.js +4 -4
  14. package/dist/cli/cmd-bootstrap.js.map +1 -1
  15. package/dist/cli/cmd-install.js +80 -0
  16. package/dist/cli/cmd-install.js.map +1 -0
  17. package/dist/cli/cmd-pairing.js +14 -15
  18. package/dist/cli/cmd-pairing.js.map +1 -1
  19. package/dist/cli/cmd-reset.js +10 -12
  20. package/dist/cli/cmd-reset.js.map +1 -1
  21. package/dist/cli/cmd-sessions.js +10 -13
  22. package/dist/cli/cmd-sessions.js.map +1 -1
  23. package/dist/cli/cmd-status.js +67 -0
  24. package/dist/cli/cmd-status.js.map +1 -0
  25. package/dist/cli/cmd-uninstall.js +79 -0
  26. package/dist/cli/cmd-uninstall.js.map +1 -0
  27. package/dist/cli/cmd-update.js +47 -0
  28. package/dist/cli/cmd-update.js.map +1 -0
  29. package/dist/cli.js +44 -26
  30. package/dist/cli.js.map +1 -1
  31. package/dist/core/config.js +53 -8
  32. package/dist/core/config.js.map +1 -1
  33. package/dist/core/log.js +33 -13
  34. package/dist/core/log.js.map +1 -1
  35. package/dist/core/update-check.js +11 -13
  36. package/dist/core/update-check.js.map +1 -1
  37. package/dist/core/version.js +4 -0
  38. package/dist/core/version.js.map +1 -0
  39. package/dist/runtime/orchestrate.js +25 -15
  40. package/dist/runtime/orchestrate.js.map +1 -1
  41. package/dist/serve.js +22 -7
  42. package/dist/serve.js.map +1 -1
  43. package/dist/setup.js +259 -231
  44. package/dist/setup.js.map +1 -1
  45. package/dist/telegram/admin-bot.js +325 -1
  46. package/dist/telegram/admin-bot.js.map +1 -1
  47. package/dist/telegram/agent-bot.js +143 -2
  48. package/dist/telegram/agent-bot.js.map +1 -1
  49. package/dist/telegram/resolve.js +11 -2
  50. package/dist/telegram/resolve.js.map +1 -1
  51. package/dist/telegram/wizards.js +361 -12
  52. package/dist/telegram/wizards.js.map +1 -1
  53. package/dist/telegram.js +2 -0
  54. package/dist/telegram.js.map +1 -1
  55. package/dist/usage.js +106 -0
  56. package/dist/usage.js.map +1 -1
  57. package/dist/workspace.js +20 -0
  58. package/dist/workspace.js.map +1 -1
  59. package/package.json +7 -4
  60. package/dist/agent/agent-openai.js +0 -206
  61. package/dist/agent/agent-openai.js.map +0 -1
  62. package/dist/approval-forward.js +0 -42
  63. package/dist/approval-forward.js.map +0 -1
  64. package/dist/approvals.js +0 -151
  65. package/dist/approvals.js.map +0 -1
  66. package/dist/camelagi-gateway.mjs +0 -93611
  67. package/dist/camelagi-gateway.mjs.map +0 -7
  68. package/dist/compact.js +0 -92
  69. package/dist/compact.js.map +0 -1
  70. package/dist/config.js +0 -153
  71. package/dist/config.js.map +0 -1
  72. package/dist/constants.js +0 -21
  73. package/dist/constants.js.map +0 -1
  74. package/dist/cron.js +0 -81
  75. package/dist/cron.js.map +0 -1
  76. package/dist/errors.js +0 -5
  77. package/dist/errors.js.map +0 -1
  78. package/dist/hooks.js +0 -72
  79. package/dist/hooks.js.map +0 -1
  80. package/dist/lanes.js +0 -62
  81. package/dist/lanes.js.map +0 -1
  82. package/dist/policy.js +0 -22
  83. package/dist/policy.js.map +0 -1
  84. package/dist/queue.js +0 -45
  85. package/dist/queue.js.map +0 -1
  86. package/dist/retry.js +0 -96
  87. package/dist/retry.js.map +0 -1
  88. package/dist/runs.js +0 -83
  89. package/dist/runs.js.map +0 -1
  90. package/dist/skills.js +0 -89
  91. package/dist/skills.js.map +0 -1
  92. package/dist/subagent.js +0 -71
  93. package/dist/subagent.js.map +0 -1
  94. package/dist/telegram-admin.js +0 -800
  95. package/dist/telegram-admin.js.map +0 -1
  96. package/dist/tools/edit.js +0 -29
  97. package/dist/tools/edit.js.map +0 -1
  98. package/dist/tools/exec.js +0 -38
  99. package/dist/tools/exec.js.map +0 -1
  100. package/dist/tools/fetch.js +0 -28
  101. package/dist/tools/fetch.js.map +0 -1
  102. package/dist/tools/index.js +0 -16
  103. package/dist/tools/index.js.map +0 -1
  104. package/dist/tools/read.js +0 -26
  105. package/dist/tools/read.js.map +0 -1
  106. package/dist/tools/search.js +0 -62
  107. package/dist/tools/search.js.map +0 -1
  108. package/dist/tools/subagent.js +0 -48
  109. package/dist/tools/subagent.js.map +0 -1
  110. package/dist/tools/write.js +0 -22
  111. package/dist/tools/write.js.map +0 -1
  112. package/dist/types.js +0 -3
  113. 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
- // Usage:
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
- function ask(rl, question) {
14
- return new Promise((resolve) => rl.question(question, resolve));
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 = (...args) => {
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
- function pick(rl, label, options, compact = false) {
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
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
224
- console.log(`\n\x1b[36m CamelAGI Bootstrap\x1b[0m`);
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
- console.log(`\n\x1b[32m ✔ Already fully configured.\x1b[0m`);
240
- console.log(`\x1b[90m Admin bot: configured\x1b[0m`);
241
- console.log(`\x1b[90m Identity: verified\x1b[0m`);
242
- console.log(`\x1b[90m API: ${existingConfig.provider} / ${existingConfig.model}\x1b[0m`);
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
- console.log(`\n\x1b[32m ✔ Admin bot configured, identity verified.\x1b[0m`);
252
- console.log(`\x1b[33m ⚠ API not configured resuming from Step 3.\x1b[0m`);
253
- // Start server for background operation
254
- const serverSpinner = ora({ text: "Starting server...", indent: 2 }).start();
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
- serverSpinner.succeed("Server running");
259
- // Jump straight to step 3
260
- await runApiSetup(rl);
261
- rl.close();
262
- console.log(`\n\x1b[36m ✅ Bootstrap complete!\x1b[0m`);
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
- // ─── Step 1: Telegram Admin Bot ───────────────────────────────────
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
- if (!botToken) {
271
- console.log(`\n\x1b[36m Step 1: Telegram Admin Bot\x1b[0m`);
272
- console.log(`\x1b[90m This bot lets you manage CamelAGI from Telegram.\x1b[0m\n`);
273
- console.log(` \x1b[36m┌──────────────────────────────────────────┐\x1b[0m`);
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
- else {
292
- validateSpinner.fail(`Invalid token: ${result.error}`);
293
- rl.close();
294
- process.exit(1);
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 — manages your AI agents from Telegram");
311
- ora({ indent: 2 }).succeed("Admin bot configured");
312
- // ─── Step 2: Start server + pairing ───────────────────────────────
313
- const serverSpinner = ora({ text: "Starting server...", indent: 2 }).start();
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
- serverSpinner.succeed("Server running");
134
+ s2.stop("Server running");
318
135
  muteLogs();
319
136
  const botName = result.ok ? `@${result.username}` : "your admin bot";
320
- console.log(`\n\x1b[36m Step 2: Verify Your Identity\x1b[0m\n`);
321
- console.log(` \x1b[36m┌──────────────────────────────────────────┐\x1b[0m`);
322
- console.log(` \x1b[36m│\x1b[0m Open Telegram and send any message to \x1b[36m│\x1b[0m`);
323
- console.log(` \x1b[36m│\x1b[0m \x1b[1m\x1b[36m${botName.padEnd(38)}\x1b[0m\x1b[36m│\x1b[0m`);
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((r) => r.agentId === "admin" && r.status === "pending");
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((r) => setTimeout(r, 1000));
148
+ await new Promise((res) => setTimeout(res, 1000));
335
149
  }
336
150
  if (!pairingRequest) {
337
- pairingSpinner.fail("Timeout no message received. Pair later via /pairing.");
338
- rl.close();
339
- console.log(`\x1b[90m Server is running. Press Ctrl+C to stop.\x1b[0m\n`);
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
- pairingSpinner.succeed(`Pairing request from ${userLabel}`);
345
- console.log(`\n \x1b[36m┌──────────────────────┐\x1b[0m`);
346
- console.log(` \x1b[36m│\x1b[0m Code: \x1b[1m\x1b[33m${pairingRequest.code}\x1b[0m \x1b[36m│\x1b[0m`);
347
- console.log(` \x1b[36m│\x1b[0m User: \x1b[1m${userLabel.padEnd(14)}\x1b[0m\x1b[36m│\x1b[0m`);
348
- console.log(` \x1b[36m│\x1b[0m ID: ${String(pairingRequest.userId).padEnd(14)}\x1b[36m│\x1b[0m`);
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
- ora({ indent: 2 }).fail("Failed to approve. Pair manually via /pairing.");
361
- rl.close();
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
- ora({ indent: 2 }).succeed(`${userLabel} approved! You are now the admin.`);
378
- // ─── Step 3: API Setup (optional) ─────────────────────────────────
379
- const setupNow = (await ask(rl, `\n\x1b[36m Step 3: Configure AI provider now? (Y/n):\x1b[0m `)).trim().toLowerCase();
380
- if (setupNow !== "n") {
381
- await runApiSetup(rl);
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
- console.log(`\x1b[90m Skipped configure later via /setup in Telegram.\x1b[0m`);
190
+ p.log.info("Skipped \u2014 use /setup in Telegram later.");
385
191
  }
386
- rl.close();
387
- // ─── Done ─────────────────────────────────────────────────────────
388
192
  unmuteLogs();
389
- console.log(`\n\x1b[36m ✅ Bootstrap complete!\x1b[0m`);
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
- // ─── Extracted API setup ──────────────────────────────────────────────
395
- async function runApiSetup(rl) {
396
- const service = await pick(rl, "Which provider?", [
397
- "anthropic — Claude (direct)",
398
- "openai — GPT (direct)",
399
- "openrouter Any model via OpenRouter",
400
- "ollama — Local models",
401
- "custom — Custom OpenAI-compatible endpoint",
402
- ]);
403
- const serviceKey = service.split(/\s/)[0];
404
- const preset = PROVIDER_PRESETS[serviceKey] ?? PROVIDER_PRESETS.custom;
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 (serviceKey !== "ollama") {
407
- const keyLabel = serviceKey === "anthropic" ? "Anthropic" : serviceKey === "openai" ? "OpenAI" : serviceKey === "openrouter" ? "OpenRouter" : "API";
408
- apiKey = (await ask(rl, `\n\x1b[36m ${keyLabel} API key:\x1b[0m `)).trim() || undefined;
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
- console.log("\x1b[33m No key set it later via /setup in Telegram.\x1b[0m");
230
+ p.log.warn("No key \u2014 set later via /setup in Telegram.");
411
231
  }
412
232
  let baseUrl = preset.baseUrl;
413
- if (serviceKey === "custom") {
414
- baseUrl = (await ask(rl, `\n\x1b[36m Base URL:\x1b[0m `)).trim() || undefined;
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, fall back to static presets
236
+ // Fetch live models for OpenRouter
417
237
  let models = [...preset.models];
418
- if (serviceKey === "openrouter" && apiKey) {
419
- const spinner = ora({ text: "Fetching models from OpenRouter...", indent: 2 }).start();
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
- spinner.succeed(`${models.length} models available`);
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
- const customOption = "(type a custom model name)";
432
- const choice = await pick(rl, "Which model?", [...models, customOption], true);
433
- model = choice === customOption ? (await ask(rl, `\n\x1b[36m Model name:\x1b[0m `)).trim() : choice;
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 ask(rl, `\n\x1b[36m Model name:\x1b[0m `)).trim();
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
- ora({ indent: 2 }).succeed("API configured");
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