teleton 0.5.2 → 0.7.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.
Files changed (40) hide show
  1. package/README.md +243 -105
  2. package/dist/chunk-FNV5FF35.js +331 -0
  3. package/dist/chunk-LRCPA7SC.js +149 -0
  4. package/dist/{chunk-WUTMT6DW.js → chunk-N3F7E7DR.js} +114 -566
  5. package/dist/chunk-ND2X5FWB.js +368 -0
  6. package/dist/chunk-NERLQY2H.js +421 -0
  7. package/dist/{chunk-YBA6IBGT.js → chunk-OCLG5GKI.js} +24 -24
  8. package/dist/{chunk-WOXBZOQX.js → chunk-OGIG552S.js} +2152 -4488
  9. package/dist/chunk-RCMD3U65.js +141 -0
  10. package/dist/{chunk-O4R7V5Y2.js → chunk-RO62LO6Z.js} +11 -1
  11. package/dist/chunk-TCD4NZDA.js +3226 -0
  12. package/dist/chunk-UCN6TI25.js +143 -0
  13. package/dist/{chunk-WL2Q3VRD.js → chunk-UDD7FYOU.js} +12 -4
  14. package/dist/chunk-VAUJSSD3.js +20 -0
  15. package/dist/chunk-XBE4JB7C.js +8 -0
  16. package/dist/chunk-XBKSS6DM.js +58 -0
  17. package/dist/cli/index.js +1179 -412
  18. package/dist/client-3VWE7NC4.js +29 -0
  19. package/dist/{get-my-gifts-KVULMBJ3.js → get-my-gifts-RI7FAXAL.js} +3 -1
  20. package/dist/index.js +17 -8
  21. package/dist/{memory-Y5J7CXAR.js → memory-RD7ZSTRV.js} +16 -10
  22. package/dist/{migrate-UEQCDWL2.js → migrate-GO4NOBT7.js} +14 -6
  23. package/dist/{server-BQY7CM2N.js → server-OWVEZTR3.js} +869 -93
  24. package/dist/setup-server-C7ZTPHD5.js +934 -0
  25. package/dist/{task-dependency-resolver-TRPILAHM.js → task-dependency-resolver-WKZWJLLM.js} +19 -15
  26. package/dist/{task-executor-N7XNVK5N.js → task-executor-PD3H4MLO.js} +5 -2
  27. package/dist/tool-adapter-Y3TCEQOC.js +145 -0
  28. package/dist/tool-index-MIVK3D7H.js +250 -0
  29. package/dist/{transcript-7V4UNID4.js → transcript-UDJZP6NK.js} +2 -1
  30. package/dist/web/assets/complete-fZLnb5Ot.js +1 -0
  31. package/dist/web/assets/index-B_FcaX5D.css +1 -0
  32. package/dist/web/assets/index-CbeAP4_n.js +67 -0
  33. package/dist/web/assets/index.es-oXiZF7Hc.js +11 -0
  34. package/dist/web/assets/login-telegram-BP7CJDmx.js +1 -0
  35. package/dist/web/assets/run-DOrDowjK.js +1 -0
  36. package/dist/web/index.html +2 -2
  37. package/package.json +21 -15
  38. package/dist/chunk-5WWR4CU3.js +0 -124
  39. package/dist/web/assets/index-CDMbujHf.css +0 -1
  40. package/dist/web/assets/index-DDX8oQ2z.js +0 -67
package/dist/cli/index.js CHANGED
@@ -1,164 +1,519 @@
1
1
  import {
2
- ConfigSchema,
3
- DealsConfigSchema,
4
2
  TelegramUserClient,
3
+ main
4
+ } from "../chunk-OGIG552S.js";
5
+ import "../chunk-UDD7FYOU.js";
6
+ import "../chunk-EHEV7FJ7.js";
7
+ import "../chunk-U7FQYCBQ.js";
8
+ import {
9
+ CONFIGURABLE_KEYS,
5
10
  configExists,
11
+ deleteNestedValue,
12
+ getDefaultConfigPath,
13
+ getNestedValue,
14
+ readRawConfig,
15
+ setNestedValue,
16
+ writeRawConfig
17
+ } from "../chunk-TCD4NZDA.js";
18
+ import {
19
+ ConfigSchema,
20
+ DealsConfigSchema,
6
21
  ensureWorkspace,
7
22
  generateWallet,
8
- getDefaultConfigPath,
9
- getProviderMetadata,
10
- getSupportedProviders,
11
23
  importWallet,
12
24
  isNewWorkspace,
13
25
  loadWallet,
14
- main,
15
26
  saveWallet,
16
- validateApiKeyFormat,
17
27
  walletExists
18
- } from "../chunk-WOXBZOQX.js";
19
- import "../chunk-WL2Q3VRD.js";
20
- import "../chunk-EHEV7FJ7.js";
21
- import "../chunk-U7FQYCBQ.js";
22
- import "../chunk-5WWR4CU3.js";
23
- import "../chunk-YBA6IBGT.js";
28
+ } from "../chunk-NERLQY2H.js";
29
+ import "../chunk-QUAPFI2N.js";
30
+ import "../chunk-TSKJCWQQ.js";
24
31
  import {
25
- fetchWithTimeout
26
- } from "../chunk-WUTMT6DW.js";
27
- import "../chunk-4DU3C27M.js";
32
+ getErrorMessage
33
+ } from "../chunk-XBE4JB7C.js";
34
+ import "../chunk-ND2X5FWB.js";
35
+ import {
36
+ getProviderMetadata,
37
+ getSupportedProviders,
38
+ validateApiKeyFormat
39
+ } from "../chunk-LRCPA7SC.js";
40
+ import "../chunk-OCLG5GKI.js";
41
+ import "../chunk-N3F7E7DR.js";
42
+ import "../chunk-FNV5FF35.js";
43
+ import "../chunk-UCN6TI25.js";
44
+ import "../chunk-XBKSS6DM.js";
28
45
  import {
29
46
  TELEGRAM_MAX_MESSAGE_LENGTH
30
- } from "../chunk-O4R7V5Y2.js";
47
+ } from "../chunk-RO62LO6Z.js";
48
+ import {
49
+ fetchWithTimeout
50
+ } from "../chunk-VAUJSSD3.js";
51
+ import "../chunk-4DU3C27M.js";
31
52
  import {
32
53
  TELETON_ROOT
33
54
  } from "../chunk-EYWNOHMJ.js";
55
+ import "../chunk-RCMD3U65.js";
34
56
  import "../chunk-NUGDTPE4.js";
35
- import "../chunk-QUAPFI2N.js";
36
- import "../chunk-TSKJCWQQ.js";
37
57
  import "../chunk-QGM4M3NI.js";
38
58
 
39
59
  // src/cli/index.ts
40
60
  import { Command } from "commander";
41
61
 
42
62
  // src/cli/prompts.ts
43
- import * as clack from "@clack/prompts";
44
- var ClackPrompter = class {
63
+ import { input, select, checkbox, confirm, password } from "@inquirer/prompts";
64
+ import chalk from "chalk";
65
+ import ora from "ora";
66
+ var TON = chalk.hex("#0098EA");
67
+ var GREEN = chalk.green;
68
+ var CYAN = chalk.cyan;
69
+ var DIM = chalk.dim;
70
+ var BOLD = chalk.bold;
71
+ var RED = chalk.red;
72
+ var YELLOW = chalk.yellow;
73
+ var WHITE = chalk.white;
74
+ var inquirerTheme = {
75
+ prefix: { idle: TON("\u203A"), done: GREEN("\u2714") },
76
+ style: {
77
+ answer: (t) => CYAN.bold(t),
78
+ message: (t, status) => status === "done" ? DIM(t) : BOLD(t),
79
+ error: (t) => RED.bold(` \u2718 ${t}`),
80
+ help: (t) => DIM(t),
81
+ highlight: (t) => TON.bold(t),
82
+ description: (t) => DIM.italic(t)
83
+ },
84
+ icon: { cursor: TON("\u276F") }
85
+ };
86
+ function stripAnsi(s) {
87
+ return s.replace(/\x1B\[[0-9;]*m/g, "");
88
+ }
89
+ function padRight(s, len) {
90
+ return s + " ".repeat(Math.max(0, len - s.length));
91
+ }
92
+ function padRightAnsi(s, len) {
93
+ const visible = stripAnsi(s).length;
94
+ return s + " ".repeat(Math.max(0, len - visible));
95
+ }
96
+ function centerIn(text, width) {
97
+ const vis = stripAnsi(text).length;
98
+ const pad = width - vis;
99
+ const left = Math.floor(pad / 2);
100
+ const right = pad - left;
101
+ return " ".repeat(Math.max(0, left)) + text + " ".repeat(Math.max(0, right));
102
+ }
103
+ var BOX_WIDTH = 56;
104
+ var ASCII_ART_RAW = [
105
+ " ______ __ __ ___ __",
106
+ " /_ __/__ / /__ / /_____ ____ / | ____ ____ ____ / /_",
107
+ " / / / _ \\/ / _ \\/ __/ __ \\/ __ \\ / /| |/ __ `/ _ \\/ __ \\/ __/",
108
+ " / / / __/ / __/ /_/ /_/ / / / / / ___ / /_/ / __/ / / / /_",
109
+ " /_/ \\___/_/\\___/\\__/\\____/_/ /_/ /_/ |_\\__, /\\___/_/ /_/\\__/",
110
+ " /____/"
111
+ ];
112
+ var ART_WIDTH = Math.max(...ASCII_ART_RAW.map((l) => l.length));
113
+ var ASCII_ART = ASCII_ART_RAW.map((l) => l + " ".repeat(ART_WIDTH - l.length));
114
+ var FRAME_WIDTH = Math.max(BOX_WIDTH, ART_WIDTH + 4);
115
+ function frameRow(content, border = TON) {
116
+ const pad = FRAME_WIDTH - stripAnsi(content).length;
117
+ return ` ${border("\u2551")}${content}${" ".repeat(Math.max(0, pad))}${border("\u2551")}`;
118
+ }
119
+ function emptyRow(border = TON) {
120
+ return ` ${border("\u2551")}${" ".repeat(FRAME_WIDTH)}${border("\u2551")}`;
121
+ }
122
+ function wizardFrame(currentStep, steps) {
123
+ const W = FRAME_WIDTH;
124
+ const out = [];
125
+ out.push(` ${TON("\u2554" + "\u2550".repeat(W) + "\u2557")}`);
126
+ out.push(emptyRow());
127
+ for (const line of ASCII_ART) {
128
+ out.push(frameRow(TON.bold(centerIn(line, W))));
129
+ }
130
+ out.push(emptyRow());
131
+ const subtitle = "Autonomous AI agent on Telegram with native TON blockchain integration";
132
+ out.push(frameRow(DIM(centerIn(subtitle, W))));
133
+ out.push(frameRow(DIM(centerIn("t.me/TeletonAgents | v0.7.0", W))));
134
+ out.push(` ${TON("\u2560" + "\u2550".repeat(W) + "\u2563")}`);
135
+ out.push(emptyRow());
136
+ const labelWidth = 14;
137
+ for (let i = 0; i < steps.length; i++) {
138
+ const s = steps[i];
139
+ let line;
140
+ if (i < currentStep) {
141
+ const val = s.value ?? "";
142
+ line = ` ${GREEN("\u2714")} ${WHITE(padRight(s.label, labelWidth))}${CYAN(val)}`;
143
+ } else if (i === currentStep) {
144
+ line = ` ${TON.bold("\u25B8")} ${TON.bold(padRight(s.label, labelWidth))}${DIM(s.desc)}`;
145
+ } else {
146
+ line = ` ${DIM("\u25CB")} ${DIM(padRight(s.label, labelWidth))}${DIM(s.desc)}`;
147
+ }
148
+ out.push(frameRow(padRightAnsi(line, W)));
149
+ }
150
+ out.push(emptyRow());
151
+ const pct = Math.round(currentStep / steps.length * 100);
152
+ const barLen = Math.max(10, W - 36);
153
+ const filled = Math.round(currentStep / steps.length * barLen);
154
+ const bar = TON("\u2588".repeat(filled)) + DIM("\u2591".repeat(barLen - filled));
155
+ const footer = ` ${bar} ${DIM(`${pct}% \xB7 Step ${currentStep + 1} of ${steps.length}`)}`;
156
+ out.push(frameRow(padRightAnsi(footer, W)));
157
+ out.push(emptyRow());
158
+ out.push(` ${TON("\u255A" + "\u2550".repeat(W) + "\u255D")}`);
159
+ return out.join("\n");
160
+ }
161
+ function noteBox(text, title, color = YELLOW) {
162
+ const W = FRAME_WIDTH;
163
+ const titleStr = ` ${title} `;
164
+ const titlePad = W - stripAnsi(titleStr).length - 1;
165
+ console.log(
166
+ ` ${color("\u250C\u2500")}${color.bold(titleStr)}${color("\u2500".repeat(Math.max(0, titlePad)) + "\u2510")}`
167
+ );
168
+ const lines = text.split("\n");
169
+ for (const line of lines) {
170
+ const pad = W - stripAnsi(line).length - 2;
171
+ console.log(` ${color("\u2502")} ${line}${" ".repeat(Math.max(0, pad))}${color("\u2502")}`);
172
+ }
173
+ console.log(` ${color("\u2514" + "\u2500".repeat(W) + "\u2518")}`);
174
+ console.log();
175
+ }
176
+ function finalSummaryBox(steps, connected) {
177
+ const W = FRAME_WIDTH;
178
+ const B = GREEN;
179
+ const gRow = (content) => {
180
+ const pad = W - stripAnsi(content).length;
181
+ return ` ${B("\u2551")}${content}${" ".repeat(Math.max(0, pad))}${B("\u2551")}`;
182
+ };
183
+ const gEmpty = () => ` ${B("\u2551")}${" ".repeat(W)}${B("\u2551")}`;
184
+ const out = [];
185
+ const t1 = " Configuration Summary ";
186
+ const t1Pad = W - stripAnsi(t1).length - 1;
187
+ out.push(` ${B("\u2554\u2550" + B.bold(t1) + "\u2550".repeat(Math.max(0, t1Pad)) + "\u2557")}`);
188
+ out.push(gEmpty());
189
+ for (const s of steps) {
190
+ const val = s.value ?? DIM("not set");
191
+ const entry = ` ${GREEN("\u2714")} ${WHITE(padRight(s.label, 14))}${CYAN(val)}`;
192
+ out.push(gRow(entry));
193
+ }
194
+ const t2 = " Next Steps ";
195
+ const t2Pad = W - stripAnsi(t2).length - 1;
196
+ out.push(gEmpty());
197
+ out.push(` ${B("\u2560\u2550" + B.bold(t2) + "\u2550".repeat(Math.max(0, t2Pad)) + "\u2563")}`);
198
+ out.push(gEmpty());
199
+ const items = connected ? [
200
+ `${TON("1.")} Start the agent`,
201
+ ` ${CYAN("$ teleton start")}`,
202
+ "",
203
+ `${TON("2.")} Send ${CYAN("/boot")} as your first message`,
204
+ ` to bootstrap the agent's personality`,
205
+ "",
206
+ `${TON("3.")} Customize ${DIM("~/.teleton/workspace/SOUL.md")}`,
207
+ ` to shape your agent's behavior`,
208
+ "",
209
+ `${TON("4.")} Read the docs`,
210
+ ` ${TON.underline("https://github.com/TONresistor/teleton-agent")}`
211
+ ] : [
212
+ `${TON("1.")} Start the agent`,
213
+ ` ${CYAN("$ teleton start")}`,
214
+ "",
215
+ `${TON("2.")} On first launch, you will be asked for:`,
216
+ ` - Telegram verification code`,
217
+ ` - 2FA password (if enabled)`,
218
+ "",
219
+ `${TON("3.")} Send a message to your Telegram account to test`
220
+ ];
221
+ for (const item of items) {
222
+ const pad = W - stripAnsi(item).length - 2;
223
+ out.push(` ${B("\u2551")} ${item}${" ".repeat(Math.max(0, pad))}${B("\u2551")}`);
224
+ }
225
+ out.push(gEmpty());
226
+ out.push(` ${B("\u255A" + "\u2550".repeat(W) + "\u255D")}`);
227
+ return out.join("\n");
228
+ }
229
+ var CancelledError = class extends Error {
230
+ constructor() {
231
+ super("Operation cancelled by user");
232
+ this.name = "CancelledError";
233
+ }
234
+ };
235
+ function wrapExitPromptError(promise) {
236
+ return promise.catch((err) => {
237
+ if (err?.name === "ExitPromptError") throw new CancelledError();
238
+ throw err;
239
+ });
240
+ }
241
+ function adaptValidate(fn) {
242
+ if (!fn) return void 0;
243
+ return (value) => {
244
+ const result = fn(value);
245
+ if (result === void 0) return true;
246
+ if (result instanceof Error) return result.message;
247
+ return result;
248
+ };
249
+ }
250
+ var InquirerPrompter = class {
45
251
  async intro(title) {
46
- clack.intro(title);
252
+ console.log(`
253
+ ${TON.bold(title)}
254
+ `);
47
255
  }
48
256
  async outro(message) {
49
- clack.outro(message);
257
+ console.log(`
258
+ ${DIM(message)}
259
+ `);
50
260
  }
51
261
  async note(message, title) {
52
- clack.note(message, title);
262
+ noteBox(message, title ?? "Note", YELLOW);
53
263
  }
54
264
  async text(options) {
55
- const result = await clack.text({
56
- message: options.message,
57
- placeholder: options.placeholder,
58
- initialValue: options.initialValue,
59
- validate: options.validate
60
- });
61
- if (clack.isCancel(result)) {
62
- throw new CancelledError();
63
- }
64
- return result;
265
+ return wrapExitPromptError(
266
+ input({
267
+ message: options.message,
268
+ default: options.initialValue,
269
+ theme: inquirerTheme,
270
+ validate: adaptValidate(options.validate)
271
+ })
272
+ );
65
273
  }
66
274
  async password(options) {
67
- const result = await clack.password({
68
- message: options.message,
69
- validate: options.validate
70
- });
71
- if (clack.isCancel(result)) {
72
- throw new CancelledError();
73
- }
74
- return result;
275
+ return wrapExitPromptError(
276
+ password({
277
+ message: options.message,
278
+ theme: inquirerTheme,
279
+ validate: adaptValidate(options.validate)
280
+ })
281
+ );
75
282
  }
76
283
  async select(options) {
77
- const result = await clack.select({
78
- message: options.message,
79
- options: options.options.map((opt) => {
80
- const mapped = {
284
+ return wrapExitPromptError(
285
+ select({
286
+ message: options.message,
287
+ default: options.initialValue,
288
+ theme: inquirerTheme,
289
+ choices: options.options.map((opt) => ({
81
290
  value: opt.value,
82
- label: opt.label
83
- };
84
- if (opt.hint) {
85
- mapped.hint = opt.hint;
86
- }
87
- return mapped;
88
- }),
89
- initialValue: options.initialValue
90
- });
91
- if (clack.isCancel(result)) {
92
- throw new CancelledError();
93
- }
94
- return result;
291
+ name: opt.label,
292
+ description: opt.hint
293
+ }))
294
+ })
295
+ );
95
296
  }
96
297
  async confirm(options) {
97
- const result = await clack.confirm({
98
- message: options.message,
99
- initialValue: options.initialValue ?? false
100
- });
101
- if (clack.isCancel(result)) {
102
- throw new CancelledError();
103
- }
104
- return result;
298
+ return wrapExitPromptError(
299
+ confirm({
300
+ message: options.message,
301
+ default: options.initialValue ?? false,
302
+ theme: inquirerTheme
303
+ })
304
+ );
105
305
  }
106
306
  async multiselect(options) {
107
- const result = await clack.multiselect({
108
- message: options.message,
109
- options: options.options.map((opt) => {
110
- const mapped = {
307
+ return wrapExitPromptError(
308
+ checkbox({
309
+ message: options.message,
310
+ theme: {
311
+ ...inquirerTheme,
312
+ icon: { cursor: TON("\u276F"), checked: GREEN("\u2714"), unchecked: DIM("\u25CB") }
313
+ },
314
+ choices: options.options.map((opt) => ({
111
315
  value: opt.value,
112
- label: opt.label
113
- };
114
- if (opt.hint) {
115
- mapped.hint = opt.hint;
116
- }
117
- return mapped;
118
- }),
119
- required: options.required
120
- });
121
- if (clack.isCancel(result)) {
122
- throw new CancelledError();
123
- }
124
- return result;
316
+ name: opt.label,
317
+ description: opt.hint
318
+ })),
319
+ required: options.required
320
+ })
321
+ );
125
322
  }
126
323
  spinner() {
127
- const s = clack.spinner();
324
+ let s = null;
128
325
  return {
129
- start: (message) => s.start(message),
130
- stop: (message) => s.stop(message),
131
- message: (message) => s.message(message)
326
+ start: (message) => {
327
+ s = ora({ text: DIM(message), color: "cyan" }).start();
328
+ },
329
+ stop: (message) => {
330
+ if (s) s.succeed(DIM(message));
331
+ s = null;
332
+ },
333
+ message: (message) => {
334
+ if (s) s.text = DIM(message);
335
+ }
132
336
  };
133
337
  }
134
338
  log(message) {
135
- clack.log.message(message);
339
+ console.log(` ${DIM("\u25CB")} ${message}`);
136
340
  }
137
341
  warn(message) {
138
- clack.log.warn(message);
342
+ console.log(` ${YELLOW("\u26A0")} ${YELLOW(message)}`);
139
343
  }
140
344
  error(message) {
141
- clack.log.error(message);
345
+ console.log(` ${RED("\u2717")} ${RED(message)}`);
142
346
  }
143
347
  success(message) {
144
- clack.log.success(message);
145
- }
146
- };
147
- var CancelledError = class extends Error {
148
- constructor() {
149
- super("Operation cancelled by user");
150
- this.name = "CancelledError";
348
+ console.log(` ${GREEN("\u2713")} ${GREEN(message)}`);
151
349
  }
152
350
  };
153
351
  function createPrompter() {
154
- return new ClackPrompter();
352
+ return new InquirerPrompter();
155
353
  }
156
354
 
157
355
  // src/cli/commands/onboard.ts
158
- import { writeFileSync, readFileSync, existsSync, chmodSync } from "fs";
356
+ import { writeFileSync, readFileSync, existsSync } from "fs";
159
357
  import { join } from "path";
160
358
  import YAML from "yaml";
359
+ import ora2 from "ora";
360
+ var STEPS = [
361
+ { label: "Agent", desc: "Name & mode" },
362
+ { label: "Provider", desc: "LLM & API key" },
363
+ { label: "Telegram", desc: "Credentials" },
364
+ { label: "Config", desc: "Model & policies" },
365
+ { label: "Modules", desc: "Optional features" },
366
+ { label: "Wallet", desc: "TON blockchain" },
367
+ { label: "Connect", desc: "Telegram auth" }
368
+ ];
369
+ function redraw(currentStep) {
370
+ console.clear();
371
+ console.log();
372
+ console.log(wizardFrame(currentStep, STEPS));
373
+ console.log();
374
+ }
375
+ function sleep(ms) {
376
+ return new Promise((r) => setTimeout(r, ms));
377
+ }
378
+ var MODEL_OPTIONS = {
379
+ anthropic: [
380
+ {
381
+ value: "claude-opus-4-5-20251101",
382
+ name: "Claude Opus 4.5",
383
+ description: "Most capable, $5/M"
384
+ },
385
+ { value: "claude-sonnet-4-0", name: "Claude Sonnet 4", description: "Balanced, $3/M" },
386
+ {
387
+ value: "claude-haiku-4-5-20251001",
388
+ name: "Claude Haiku 4.5",
389
+ description: "Fast & cheap, $1/M"
390
+ },
391
+ {
392
+ value: "claude-3-5-haiku-20241022",
393
+ name: "Claude 3.5 Haiku",
394
+ description: "Cheapest, $0.80/M"
395
+ }
396
+ ],
397
+ openai: [
398
+ { value: "gpt-5", name: "GPT-5", description: "Most capable, 400K ctx, $1.25/M" },
399
+ { value: "gpt-4o", name: "GPT-4o", description: "Balanced, 128K ctx, $2.50/M" },
400
+ { value: "gpt-4.1", name: "GPT-4.1", description: "1M ctx, $2/M" },
401
+ { value: "gpt-4.1-mini", name: "GPT-4.1 Mini", description: "1M ctx, cheap, $0.40/M" },
402
+ { value: "o3", name: "o3", description: "Reasoning, 200K ctx, $2/M" }
403
+ ],
404
+ google: [
405
+ { value: "gemini-2.5-flash", name: "Gemini 2.5 Flash", description: "Fast, 1M ctx, $0.30/M" },
406
+ {
407
+ value: "gemini-2.5-pro",
408
+ name: "Gemini 2.5 Pro",
409
+ description: "Most capable, 1M ctx, $1.25/M"
410
+ },
411
+ { value: "gemini-2.0-flash", name: "Gemini 2.0 Flash", description: "Cheap, 1M ctx, $0.10/M" }
412
+ ],
413
+ xai: [
414
+ { value: "grok-4-fast", name: "Grok 4 Fast", description: "Vision, 2M ctx, $0.20/M" },
415
+ { value: "grok-4", name: "Grok 4", description: "Reasoning, 256K ctx, $3/M" },
416
+ { value: "grok-3", name: "Grok 3", description: "Stable, 131K ctx, $3/M" }
417
+ ],
418
+ groq: [
419
+ {
420
+ value: "meta-llama/llama-4-maverick-17b-128e-instruct",
421
+ name: "Llama 4 Maverick",
422
+ description: "Vision, 131K ctx, $0.20/M"
423
+ },
424
+ { value: "qwen/qwen3-32b", name: "Qwen3 32B", description: "Reasoning, 131K ctx, $0.29/M" },
425
+ {
426
+ value: "deepseek-r1-distill-llama-70b",
427
+ name: "DeepSeek R1 70B",
428
+ description: "Reasoning, 131K ctx, $0.75/M"
429
+ },
430
+ {
431
+ value: "llama-3.3-70b-versatile",
432
+ name: "Llama 3.3 70B",
433
+ description: "General purpose, 131K ctx, $0.59/M"
434
+ }
435
+ ],
436
+ openrouter: [
437
+ { value: "anthropic/claude-opus-4.5", name: "Claude Opus 4.5", description: "200K ctx, $5/M" },
438
+ { value: "openai/gpt-5", name: "GPT-5", description: "400K ctx, $1.25/M" },
439
+ { value: "google/gemini-2.5-flash", name: "Gemini 2.5 Flash", description: "1M ctx, $0.30/M" },
440
+ {
441
+ value: "deepseek/deepseek-r1",
442
+ name: "DeepSeek R1",
443
+ description: "Reasoning, 64K ctx, $0.70/M"
444
+ },
445
+ { value: "x-ai/grok-4", name: "Grok 4", description: "256K ctx, $3/M" }
446
+ ],
447
+ moonshot: [
448
+ { value: "kimi-k2.5", name: "Kimi K2.5", description: "Free, 256K ctx, multimodal" },
449
+ {
450
+ value: "kimi-k2-thinking",
451
+ name: "Kimi K2 Thinking",
452
+ description: "Free, 256K ctx, reasoning"
453
+ }
454
+ ],
455
+ mistral: [
456
+ {
457
+ value: "devstral-small-2507",
458
+ name: "Devstral Small",
459
+ description: "Coding, 128K ctx, $0.10/M"
460
+ },
461
+ {
462
+ value: "devstral-medium-latest",
463
+ name: "Devstral Medium",
464
+ description: "Coding, 262K ctx, $0.40/M"
465
+ },
466
+ {
467
+ value: "mistral-large-latest",
468
+ name: "Mistral Large",
469
+ description: "General, 128K ctx, $2/M"
470
+ },
471
+ {
472
+ value: "magistral-small",
473
+ name: "Magistral Small",
474
+ description: "Reasoning, 128K ctx, $0.50/M"
475
+ }
476
+ ]
477
+ };
161
478
  async function onboardCommand(options = {}) {
479
+ if (options.ui) {
480
+ const { SetupServer } = await import("../setup-server-C7ZTPHD5.js");
481
+ const port = parseInt(options.uiPort || "7777") || 7777;
482
+ const url = `http://localhost:${port}/setup`;
483
+ const blue2 = "\x1B[34m";
484
+ const reset2 = "\x1B[0m";
485
+ const dim = "\x1B[2m";
486
+ console.log(`
487
+ ${blue2} \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
488
+ \u2502 \u2502
489
+ \u2502 ______________ ________________ _ __ ___ _____________ ________ \u2502
490
+ \u2502 /_ __/ ____/ / / ____/_ __/ __ \\/ | / / / | / ____/ ____/ | / /_ __/ \u2502
491
+ \u2502 / / / __/ / / / __/ / / / / / / |/ / / /| |/ / __/ __/ / |/ / / / \u2502
492
+ \u2502 / / / /___/ /___/ /___ / / / /_/ / /| / / ___ / /_/ / /___/ /| / / / \u2502
493
+ \u2502 /_/ /_____/_____/_____/ /_/ \\____/_/ |_/ /_/ |_\\____/_____/_/ |_/ /_/ \u2502
494
+ \u2502 \u2502
495
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 DEV: ZKPROOF.T.ME \u2500\u2500\u2518${reset2}
496
+
497
+ ${dim}Setup wizard running at${reset2} ${url}
498
+ ${dim}Opening in your default browser...${reset2}
499
+ ${dim}Press Ctrl+C to cancel.${reset2}
500
+ `);
501
+ const server = new SetupServer(port);
502
+ await server.start();
503
+ process.on("SIGINT", async () => {
504
+ await server.stop();
505
+ process.exit(0);
506
+ });
507
+ await server.waitForLaunch();
508
+ console.log("\n Launch signal received \u2014 stopping setup server");
509
+ await server.stop();
510
+ console.log(" Starting TonnetApp...\n");
511
+ const { TeletonApp } = await import("../index.js");
512
+ const configPath = join(TELETON_ROOT, "config.yaml");
513
+ const app = new TeletonApp(configPath);
514
+ await app.start();
515
+ return;
516
+ }
162
517
  const prompter = createPrompter();
163
518
  try {
164
519
  if (options.nonInteractive) {
@@ -168,368 +523,515 @@ async function onboardCommand(options = {}) {
168
523
  }
169
524
  } catch (err) {
170
525
  if (err instanceof CancelledError) {
171
- prompter.outro("Onboarding cancelled");
526
+ console.log(`
527
+ ${DIM("Setup cancelled. No changes were made.")}
528
+ `);
172
529
  process.exit(0);
173
530
  }
174
531
  throw err;
175
532
  }
176
533
  }
177
534
  async function runInteractiveOnboarding(options, prompter) {
178
- const blue2 = "\x1B[34m";
179
- const reset2 = "\x1B[0m";
180
- console.log(`
181
- ${blue2} \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
182
- \u2502 \u2502
183
- \u2502 ______________ ________________ _ __ ___ _____________ ________ \u2502
184
- \u2502 /_ __/ ____/ / / ____/_ __/ __ \\/ | / / / | / ____/ ____/ | / /_ __/ \u2502
185
- \u2502 / / / __/ / / / __/ / / / / / / |/ / / /| |/ / __/ __/ / |/ / / / \u2502
186
- \u2502 / / / /___/ /___/ /___ / / / /_/ / /| / / ___ / /_/ / /___/ /| / / / \u2502
187
- \u2502 /_/ /_____/_____/_____/ /_/ \\____/_/ |_/ /_/ |_\\____/_____/_/ |_/ /_/ \u2502
188
- \u2502 \u2502
189
- \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 SETUP \u2500\u2500\u2518${reset2}
190
-
191
- Need help? Join @ResistanceForum on Telegram
192
- `);
193
- prompter.note(
194
- "Your Teleton agent will have FULL CONTROL over:\n\n\u2022 TELEGRAM: Read, send, and delete messages on your behalf\n\u2022 TON WALLET: A new wallet will be generated that the agent\n can use to send transactions autonomously\n\nWe strongly recommend using a dedicated Telegram account.\nOnly fund the generated wallet with amounts you're comfortable\nletting the agent manage.",
195
- "Security Warning"
535
+ let selectedFlow = "quick";
536
+ let selectedProvider = "anthropic";
537
+ const dealsEnabled = true;
538
+ let selectedModel = "";
539
+ let apiKey = "";
540
+ let apiId = 0;
541
+ let apiHash = "";
542
+ let phone = "";
543
+ let userId = 0;
544
+ let tonapiKey;
545
+ let tavilyApiKey;
546
+ let botToken;
547
+ let botUsername;
548
+ let dmPolicy = "open";
549
+ let groupPolicy = "open";
550
+ let requireMention = true;
551
+ let maxAgenticIterations = "5";
552
+ let cocoonInstance = 1e4;
553
+ let buyMaxFloorPercent = 100;
554
+ let sellMinFloorPercent = 105;
555
+ console.clear();
556
+ console.log();
557
+ console.log(wizardFrame(0, STEPS));
558
+ console.log();
559
+ await sleep(800);
560
+ redraw(0);
561
+ noteBox(
562
+ "Your Teleton agent will have FULL CONTROL over:\n\n \u2022 TELEGRAM: Read, send, and delete messages on your behalf\n \u2022 TON WALLET: A new wallet will be generated that the agent\n can use to send transactions autonomously\n\nWe strongly recommend using a dedicated Telegram account.\nOnly fund the generated wallet with amounts you're comfortable\nletting the agent manage.",
563
+ "Security Warning",
564
+ RED
196
565
  );
197
- const acceptRisk = await prompter.confirm({
566
+ const acceptRisk = await confirm({
198
567
  message: "I understand the risks and want to continue",
199
- initialValue: false
568
+ default: false,
569
+ theme: inquirerTheme
200
570
  });
201
571
  if (!acceptRisk) {
202
- prompter.outro("Setup cancelled - you must accept the risks to continue");
572
+ console.log(`
573
+ ${DIM("Setup cancelled \u2014 you must accept the risks to continue.")}
574
+ `);
203
575
  process.exit(1);
204
576
  }
205
- const spinner2 = prompter.spinner();
206
- spinner2.start("Creating workspace...");
577
+ const spinner = ora2({ color: "cyan" });
578
+ spinner.start(DIM("Creating workspace..."));
207
579
  const workspace = await ensureWorkspace({
208
580
  workspaceDir: options.workspace,
209
581
  ensureTemplates: true
210
582
  });
211
583
  const isNew = isNewWorkspace(workspace);
212
- spinner2.stop(`\u2713 Workspace: ${workspace.root}`);
584
+ spinner.succeed(DIM(`Workspace: ${workspace.root}`));
213
585
  if (!isNew) {
214
586
  prompter.warn("Existing configuration detected");
215
- const shouldOverwrite = await prompter.confirm({
587
+ const shouldOverwrite = await confirm({
216
588
  message: "Overwrite existing configuration?",
217
- initialValue: false
589
+ default: false,
590
+ theme: inquirerTheme
218
591
  });
219
592
  if (!shouldOverwrite) {
220
- prompter.outro("Setup cancelled - existing configuration preserved");
593
+ console.log(`
594
+ ${DIM("Setup cancelled \u2014 existing configuration preserved.")}
595
+ `);
221
596
  return;
222
597
  }
223
598
  }
224
- const agentName = await prompter.text({
599
+ const agentName = await input({
225
600
  message: "Give your agent a name (optional)",
226
- placeholder: "e.g. Nova, Kai, Echo..."
601
+ default: "Nova",
602
+ theme: inquirerTheme
227
603
  });
228
604
  if (agentName && agentName.trim() && existsSync(workspace.identityPath)) {
229
605
  const identity = readFileSync(workspace.identityPath, "utf-8");
230
606
  const updated = identity.replace("[Your name - pick one or ask your human]", agentName.trim());
231
607
  writeFileSync(workspace.identityPath, updated, "utf-8");
232
608
  }
233
- const flow = await prompter.select({
609
+ selectedFlow = await select({
234
610
  message: "Installation mode",
235
- options: [
236
- { value: "quick", label: "QuickStart", hint: "Minimal configuration (recommended)" },
237
- { value: "advanced", label: "Advanced", hint: "Detailed configuration" }
238
- ],
239
- initialValue: "quick"
240
- });
241
- const enabledModules = await prompter.multiselect({
242
- message: "Enable optional modules (Space to toggle, Enter to confirm)",
243
- options: [
611
+ default: "quick",
612
+ theme: inquirerTheme,
613
+ choices: [
244
614
  {
245
- value: "deals",
246
- label: "Gifts & Deals",
247
- hint: "Gift/TON trading with escrow system"
248
- }
249
- ],
250
- required: false
615
+ value: "quick",
616
+ name: "\u26A1 QuickStart",
617
+ description: "Minimal configuration (recommended)"
618
+ },
619
+ { value: "advanced", name: "\u2699 Advanced", description: "Detailed configuration" }
620
+ ]
251
621
  });
252
- const dealsEnabled = enabledModules.includes("deals");
622
+ STEPS[0].value = `${agentName} (${selectedFlow})`;
623
+ redraw(1);
253
624
  const providers = getSupportedProviders();
254
- const selectedProvider = await prompter.select({
625
+ selectedProvider = await select({
255
626
  message: "AI Provider",
256
- options: providers.map((p) => ({
627
+ default: "anthropic",
628
+ theme: inquirerTheme,
629
+ choices: providers.map((p) => ({
257
630
  value: p.id,
258
- label: p.displayName,
259
- hint: p.toolLimit !== null ? `${p.defaultModel} (max ${p.toolLimit} tools)` : `${p.defaultModel}`
260
- })),
261
- initialValue: "anthropic"
631
+ name: p.displayName,
632
+ description: p.toolLimit !== null ? `${p.defaultModel} (max ${p.toolLimit} tools)` : `${p.defaultModel}`
633
+ }))
262
634
  });
263
635
  const providerMeta = getProviderMetadata(selectedProvider);
264
636
  if (providerMeta.toolLimit !== null) {
265
- prompter.note(
637
+ noteBox(
266
638
  `${providerMeta.displayName} supports max ${providerMeta.toolLimit} tools.
267
639
  Teleton currently has ~116 tools. If more tools are added,
268
640
  some may be truncated.`,
269
641
  "Tool Limit"
270
642
  );
271
643
  }
272
- prompter.note(
644
+ let localBaseUrl = "";
645
+ if (selectedProvider === "cocoon") {
646
+ apiKey = "";
647
+ const cocoonPort = await input({
648
+ message: "Cocoon proxy HTTP port",
649
+ default: "10000",
650
+ theme: inquirerTheme,
651
+ validate: (value = "") => {
652
+ const n = parseInt(value.trim(), 10);
653
+ return n >= 1 && n <= 65535 ? true : "Must be a port number (1-65535)";
654
+ }
655
+ });
656
+ cocoonInstance = parseInt(cocoonPort.trim(), 10);
657
+ noteBox(
658
+ `Cocoon Network \u2014 Decentralized LLM on TON
659
+ No API key needed. Requires cocoon-cli running externally.
660
+ Teleton will connect to http://localhost:${cocoonInstance}/v1/`,
661
+ "Cocoon Network",
662
+ TON
663
+ );
664
+ STEPS[1].value = `${providerMeta.displayName} ${DIM(`port ${cocoonInstance}`)}`;
665
+ } else if (selectedProvider === "local") {
666
+ apiKey = "";
667
+ localBaseUrl = await input({
668
+ message: "Local LLM server URL",
669
+ default: "http://localhost:11434/v1",
670
+ theme: inquirerTheme,
671
+ validate: (value = "") => {
672
+ try {
673
+ new URL(value.trim());
674
+ return true;
675
+ } catch {
676
+ return "Must be a valid URL (e.g. http://localhost:11434/v1)";
677
+ }
678
+ }
679
+ });
680
+ localBaseUrl = localBaseUrl.trim();
681
+ noteBox(
682
+ `Local LLM \u2014 OpenAI-compatible server
683
+ No API key needed. Models auto-discovered at startup.
684
+ Teleton will connect to ${localBaseUrl}`,
685
+ "Local LLM",
686
+ TON
687
+ );
688
+ STEPS[1].value = `${providerMeta.displayName} ${DIM(localBaseUrl)}`;
689
+ } else {
690
+ const envApiKey = process.env.TELETON_API_KEY;
691
+ if (options.apiKey) {
692
+ apiKey = options.apiKey;
693
+ } else if (envApiKey) {
694
+ const validationError = validateApiKeyFormat(selectedProvider, envApiKey);
695
+ if (validationError) {
696
+ prompter.warn(`TELETON_API_KEY env var found but invalid: ${validationError}`);
697
+ apiKey = await password({
698
+ message: `${providerMeta.displayName} API Key (${providerMeta.keyHint})`,
699
+ theme: inquirerTheme,
700
+ validate: (value = "") => validateApiKeyFormat(selectedProvider, value) ?? true
701
+ });
702
+ } else {
703
+ prompter.log(`Using API key from TELETON_API_KEY env var`);
704
+ apiKey = envApiKey;
705
+ }
706
+ } else {
707
+ noteBox(
708
+ `${providerMeta.displayName} API key required.
709
+ Get it at: ${providerMeta.consoleUrl}`,
710
+ "API Key",
711
+ TON
712
+ );
713
+ apiKey = await password({
714
+ message: `${providerMeta.displayName} API Key (${providerMeta.keyHint})`,
715
+ theme: inquirerTheme,
716
+ validate: (value = "") => validateApiKeyFormat(selectedProvider, value) ?? true
717
+ });
718
+ }
719
+ const maskedKey = apiKey.length > 10 ? apiKey.slice(0, 6) + "..." + apiKey.slice(-4) : "***";
720
+ STEPS[1].value = `${providerMeta.displayName} ${DIM(maskedKey)}`;
721
+ }
722
+ redraw(2);
723
+ noteBox(
273
724
  "You need Telegram credentials from https://my.telegram.org/apps\nCreate an application and note the API ID and API Hash",
274
- "Telegram"
725
+ "Telegram",
726
+ TON
275
727
  );
276
728
  const envApiId = process.env.TELETON_TG_API_ID;
277
729
  const envApiHash = process.env.TELETON_TG_API_HASH;
278
730
  const envPhone = process.env.TELETON_TG_PHONE;
279
- const envApiKey = process.env.TELETON_API_KEY;
280
- const apiIdStr = options.apiId ? options.apiId.toString() : await prompter.text({
731
+ const apiIdStr = options.apiId ? options.apiId.toString() : await input({
281
732
  message: envApiId ? "API ID (from env)" : "API ID (from my.telegram.org)",
282
- initialValue: envApiId,
733
+ default: envApiId,
734
+ theme: inquirerTheme,
283
735
  validate: (value) => {
284
736
  if (!value || isNaN(parseInt(value))) return "Invalid API ID (must be a number)";
737
+ return true;
285
738
  }
286
739
  });
287
- const apiId = parseInt(apiIdStr);
288
- const apiHash = options.apiHash ? options.apiHash : await prompter.text({
740
+ apiId = parseInt(apiIdStr);
741
+ apiHash = options.apiHash ? options.apiHash : await input({
289
742
  message: envApiHash ? "API Hash (from env)" : "API Hash (from my.telegram.org)",
290
- initialValue: envApiHash,
743
+ default: envApiHash,
744
+ theme: inquirerTheme,
291
745
  validate: (value) => {
292
746
  if (!value || value.length < 10) return "Invalid API Hash";
747
+ return true;
293
748
  }
294
749
  });
295
- const phone = options.phone ? options.phone : await prompter.text({
296
- message: envPhone ? "Phone number (from env)" : "Phone number (international format, e.g. +1234567890)",
297
- placeholder: "+1234567890",
298
- initialValue: envPhone,
750
+ phone = options.phone ? options.phone : await input({
751
+ message: envPhone ? "Phone number (from env)" : "Phone number (international format)",
752
+ default: envPhone,
753
+ theme: inquirerTheme,
299
754
  validate: (value) => {
300
- if (!value || !value.startsWith("+")) return "Invalid format (must start with +)";
755
+ if (!value || !value.startsWith("+")) return "Must start with +";
756
+ return true;
301
757
  }
302
758
  });
303
- prompter.note(
759
+ noteBox(
304
760
  "To get your Telegram User ID:\n1. Open @userinfobot on Telegram\n2. Send /start\n3. Note the ID displayed",
305
- "User ID"
761
+ "User ID",
762
+ TON
306
763
  );
307
- const userIdStr = options.userId ? options.userId.toString() : await prompter.text({
764
+ const userIdStr = options.userId ? options.userId.toString() : await input({
308
765
  message: "Your Telegram User ID (for admin rights)",
766
+ theme: inquirerTheme,
309
767
  validate: (value) => {
310
768
  if (!value || isNaN(parseInt(value))) return "Invalid User ID";
769
+ return true;
311
770
  }
312
771
  });
313
- const userId = parseInt(userIdStr);
314
- prompter.note(
315
- `${providerMeta.displayName} API key required.
316
- Get it at: ${providerMeta.consoleUrl}`,
317
- "API Key"
318
- );
319
- let apiKey;
320
- if (options.apiKey) {
321
- apiKey = options.apiKey;
322
- } else if (envApiKey) {
323
- const validationError = validateApiKeyFormat(selectedProvider, envApiKey);
324
- if (validationError) {
325
- prompter.warn(`TELETON_API_KEY env var found but invalid: ${validationError}`);
326
- apiKey = await prompter.password({
327
- message: `${providerMeta.displayName} API Key (${providerMeta.keyHint})`,
328
- validate: (value) => validateApiKeyFormat(selectedProvider, value)
329
- });
330
- } else {
331
- prompter.log(`Using API key from TELETON_API_KEY env var`);
332
- apiKey = envApiKey;
333
- }
334
- } else {
335
- apiKey = await prompter.password({
336
- message: `${providerMeta.displayName} API Key (${providerMeta.keyHint})`,
337
- validate: (value) => validateApiKeyFormat(selectedProvider, value)
338
- });
339
- }
340
- const MODEL_OPTIONS = {
341
- anthropic: [
342
- { value: "claude-opus-4-5-20251101", label: "Claude Opus 4.5", hint: "Most capable, $5/M" },
343
- { value: "claude-sonnet-4-0", label: "Claude Sonnet 4", hint: "Balanced, $3/M" },
344
- { value: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", hint: "Fast & cheap, $1/M" },
345
- { value: "claude-3-5-haiku-20241022", label: "Claude 3.5 Haiku", hint: "Cheapest, $0.80/M" }
346
- ],
347
- openai: [
348
- { value: "gpt-5", label: "GPT-5", hint: "Most capable, 400K ctx, $1.25/M" },
349
- { value: "gpt-4o", label: "GPT-4o", hint: "Balanced, 128K ctx, $2.50/M" },
350
- { value: "gpt-4.1", label: "GPT-4.1", hint: "1M ctx, $2/M" },
351
- { value: "gpt-4.1-mini", label: "GPT-4.1 Mini", hint: "1M ctx, cheap, $0.40/M" },
352
- { value: "o3", label: "o3", hint: "Reasoning, 200K ctx, $2/M" }
353
- ],
354
- google: [
355
- { value: "gemini-2.5-flash", label: "Gemini 2.5 Flash", hint: "Fast, 1M ctx, $0.30/M" },
356
- { value: "gemini-2.5-pro", label: "Gemini 2.5 Pro", hint: "Most capable, 1M ctx, $1.25/M" },
357
- { value: "gemini-2.0-flash", label: "Gemini 2.0 Flash", hint: "Cheap, 1M ctx, $0.10/M" }
358
- ],
359
- xai: [
360
- { value: "grok-4-fast", label: "Grok 4 Fast", hint: "Vision, 2M ctx, $0.20/M" },
361
- { value: "grok-4", label: "Grok 4", hint: "Reasoning, 256K ctx, $3/M" },
362
- { value: "grok-3", label: "Grok 3", hint: "Stable, 131K ctx, $3/M" }
363
- ],
364
- groq: [
365
- {
366
- value: "meta-llama/llama-4-maverick-17b-128e-instruct",
367
- label: "Llama 4 Maverick",
368
- hint: "Vision, 131K ctx, $0.20/M"
369
- },
370
- { value: "qwen/qwen3-32b", label: "Qwen3 32B", hint: "Reasoning, 131K ctx, $0.29/M" },
371
- {
372
- value: "deepseek-r1-distill-llama-70b",
373
- label: "DeepSeek R1 70B",
374
- hint: "Reasoning, 131K ctx, $0.75/M"
375
- },
376
- {
377
- value: "llama-3.3-70b-versatile",
378
- label: "Llama 3.3 70B",
379
- hint: "General purpose, 131K ctx, $0.59/M"
380
- }
381
- ],
382
- openrouter: [
383
- { value: "anthropic/claude-opus-4.5", label: "Claude Opus 4.5", hint: "200K ctx, $5/M" },
384
- { value: "openai/gpt-5", label: "GPT-5", hint: "400K ctx, $1.25/M" },
385
- { value: "google/gemini-2.5-flash", label: "Gemini 2.5 Flash", hint: "1M ctx, $0.30/M" },
386
- { value: "deepseek/deepseek-r1", label: "DeepSeek R1", hint: "Reasoning, 64K ctx, $0.70/M" },
387
- { value: "x-ai/grok-4", label: "Grok 4", hint: "256K ctx, $3/M" }
388
- ]
389
- };
390
- let selectedModel = providerMeta.defaultModel;
391
- if (flow === "advanced") {
772
+ userId = parseInt(userIdStr);
773
+ STEPS[2].value = `${phone} (ID: ${userId})`;
774
+ redraw(3);
775
+ selectedModel = providerMeta.defaultModel;
776
+ if (selectedFlow === "advanced" && selectedProvider !== "cocoon" && selectedProvider !== "local") {
392
777
  const providerModels = MODEL_OPTIONS[selectedProvider] || [];
393
- const modelOptions = [
778
+ const modelChoices = [
394
779
  ...providerModels,
395
- { value: "__custom__", label: "Custom", hint: "Enter a model ID manually" }
780
+ { value: "__custom__", name: "Custom", description: "Enter a model ID manually" }
396
781
  ];
397
- const modelChoice = await prompter.select({
782
+ const modelChoice = await select({
398
783
  message: "Model",
399
- options: modelOptions,
400
- initialValue: providerMeta.defaultModel
784
+ default: providerMeta.defaultModel,
785
+ theme: inquirerTheme,
786
+ choices: modelChoices
401
787
  });
402
788
  if (modelChoice === "__custom__") {
403
- const customModel = await prompter.text({
789
+ const customModel = await input({
404
790
  message: "Model ID",
405
- placeholder: providerMeta.defaultModel,
406
- initialValue: providerMeta.defaultModel
791
+ default: providerMeta.defaultModel,
792
+ theme: inquirerTheme
407
793
  });
408
- if (customModel && customModel.trim()) {
409
- selectedModel = customModel.trim();
410
- }
794
+ if (customModel?.trim()) selectedModel = customModel.trim();
411
795
  } else {
412
796
  selectedModel = modelChoice;
413
797
  }
414
- }
415
- let dmPolicy = "open";
416
- let groupPolicy = "open";
417
- let requireMention = true;
418
- let maxAgenticIterations = "5";
419
- if (flow === "advanced") {
420
- dmPolicy = await prompter.select({
798
+ dmPolicy = await select({
421
799
  message: "DM policy (private messages)",
422
- options: [
423
- { value: "open", label: "Open", hint: "Reply to everyone" },
424
- { value: "allowlist", label: "Allowlist", hint: "Only specific users" },
425
- { value: "disabled", label: "Disabled", hint: "No DM replies" }
426
- ],
427
- initialValue: "open"
800
+ default: "open",
801
+ theme: inquirerTheme,
802
+ choices: [
803
+ { value: "open", name: "Open", description: "Reply to everyone" },
804
+ { value: "allowlist", name: "Allowlist", description: "Only specific users" },
805
+ { value: "disabled", name: "Disabled", description: "No DM replies" }
806
+ ]
428
807
  });
429
- groupPolicy = await prompter.select({
808
+ groupPolicy = await select({
430
809
  message: "Group policy",
431
- options: [
432
- { value: "open", label: "Open", hint: "Reply in all groups" },
433
- { value: "allowlist", label: "Allowlist", hint: "Only specific groups" },
434
- { value: "disabled", label: "Disabled", hint: "No group replies" }
435
- ],
436
- initialValue: "open"
810
+ default: "open",
811
+ theme: inquirerTheme,
812
+ choices: [
813
+ { value: "open", name: "Open", description: "Reply in all groups" },
814
+ { value: "allowlist", name: "Allowlist", description: "Only specific groups" },
815
+ { value: "disabled", name: "Disabled", description: "No group replies" }
816
+ ]
437
817
  });
438
- requireMention = await prompter.confirm({
818
+ requireMention = await confirm({
439
819
  message: "Require @mention in groups?",
440
- initialValue: true
820
+ default: true,
821
+ theme: inquirerTheme
441
822
  });
442
- maxAgenticIterations = await prompter.text({
823
+ maxAgenticIterations = await input({
443
824
  message: "Max agentic iterations (tool call loops per message)",
444
- initialValue: "5",
825
+ default: "5",
826
+ theme: inquirerTheme,
445
827
  validate: (v) => {
446
828
  const n = parseInt(v, 10);
447
- if (isNaN(n) || n < 1 || n > 50) return "Must be a number between 1 and 50";
829
+ return !isNaN(n) && n >= 1 && n <= 50 ? true : "Must be 1\u201350";
448
830
  }
449
831
  });
832
+ const modelLabel = providerModels.find((m) => m.value === selectedModel)?.name ?? selectedModel;
833
+ STEPS[3].value = `${modelLabel}, ${dmPolicy}/${groupPolicy}`;
834
+ } else {
835
+ STEPS[3].value = `${selectedModel} (defaults)`;
450
836
  }
451
- let botToken;
452
- let botUsername;
453
- let buyMaxFloorPercent = 100;
454
- let sellMinFloorPercent = 105;
837
+ redraw(4);
838
+ const extras = [];
455
839
  if (dealsEnabled) {
456
- const customizeStrategy = await prompter.confirm({
457
- message: "Customize trading thresholds? (default: buy \u2264 floor, sell \u2265 floor +5%)",
458
- initialValue: false
840
+ const customizeStrategy = await confirm({
841
+ message: `Customize trading thresholds? ${DIM("(default: buy \u2264 floor, sell \u2265 floor +5%)")}`,
842
+ default: false,
843
+ theme: inquirerTheme
459
844
  });
460
845
  if (customizeStrategy) {
461
- const buyInput = await prompter.text({
846
+ const buyInput = await input({
462
847
  message: "Max buy price (% of floor price)",
463
- initialValue: "100",
848
+ default: "100",
849
+ theme: inquirerTheme,
464
850
  validate: (v) => {
465
851
  const n = parseInt(v, 10);
466
- if (isNaN(n) || n < 50 || n > 150) return "Must be between 50 and 150";
852
+ return !isNaN(n) && n >= 50 && n <= 150 ? true : "Must be 50\u2013150";
467
853
  }
468
854
  });
469
855
  buyMaxFloorPercent = parseInt(buyInput, 10);
470
- const sellInput = await prompter.text({
856
+ const sellInput = await input({
471
857
  message: "Min sell price (% of floor price)",
472
- initialValue: "105",
858
+ default: "105",
859
+ theme: inquirerTheme,
473
860
  validate: (v) => {
474
861
  const n = parseInt(v, 10);
475
- if (isNaN(n) || n < 100 || n > 200) return "Must be between 100 and 200";
862
+ return !isNaN(n) && n >= 100 && n <= 200 ? true : "Must be 100\u2013200";
476
863
  }
477
864
  });
478
865
  sellMinFloorPercent = parseInt(sellInput, 10);
479
866
  }
480
- }
481
- if (dealsEnabled) {
482
- prompter.note(
867
+ noteBox(
483
868
  "Create a bot with @BotFather on Telegram:\n1. Send /newbot and follow the instructions\n2. Copy the bot token\n3. Enable inline mode: /setinline on the bot",
484
- "Deals Bot"
869
+ "Deals Bot",
870
+ TON
485
871
  );
486
- const tokenInput = await prompter.password({
872
+ const tokenInput = await password({
487
873
  message: "Bot token (from @BotFather)",
874
+ theme: inquirerTheme,
488
875
  validate: (value) => {
489
- if (!value || !value.includes(":")) return "Invalid bot token format (expected id:hash)";
876
+ if (!value || !value.includes(":")) return "Invalid format (expected id:hash)";
877
+ return true;
490
878
  }
491
879
  });
492
- spinner2.start("Validating bot token...");
880
+ spinner.start(DIM("Validating bot token..."));
493
881
  try {
494
882
  const res = await fetchWithTimeout(`https://api.telegram.org/bot${tokenInput}/getMe`);
495
883
  const data = await res.json();
496
884
  if (!data.ok) {
497
- spinner2.stop("\u26A0 Bot token is invalid - skipping bot setup");
885
+ spinner.warn(DIM("Bot token is invalid \u2014 skipping bot setup"));
498
886
  } else {
499
887
  botToken = tokenInput;
500
888
  botUsername = data.result.username;
501
- spinner2.stop(`\u2713 Bot verified: @${botUsername}`);
889
+ spinner.succeed(DIM(`Bot verified: @${botUsername}`));
502
890
  }
503
891
  } catch {
504
- spinner2.stop("\u26A0 Could not validate bot token (network error) - saving anyway");
892
+ spinner.warn(DIM("Could not validate bot token (network error) \u2014 saving anyway"));
505
893
  botToken = tokenInput;
506
- const usernameInput = await prompter.text({
894
+ const usernameInput = await input({
507
895
  message: "Bot username (without @)",
896
+ theme: inquirerTheme,
508
897
  validate: (value) => {
509
898
  if (!value || value.length < 3) return "Username too short";
899
+ return true;
510
900
  }
511
901
  });
512
902
  botUsername = usernameInput;
513
903
  }
904
+ extras.push("Deals");
514
905
  }
515
- let tonapiKey;
516
- const setupTonapi = await prompter.confirm({
517
- message: "Add a TonAPI key? (optional, recommended)",
518
- initialValue: false
906
+ const setupTonapi = await confirm({
907
+ message: `Add a TonAPI key? ${DIM("(optional, recommended for 10x rate limits)")}`,
908
+ default: false,
909
+ theme: inquirerTheme
519
910
  });
520
911
  if (setupTonapi) {
521
- prompter.note(
912
+ noteBox(
522
913
  "Without key: 1 req/s (you will hit rate limits)\nWith free key: 10 req/s (recommended)\n\nOpen @tonapibot on Telegram \u2192 tap the mini app \u2192 generate a server key",
523
- "TonAPI"
914
+ "TonAPI",
915
+ TON
524
916
  );
525
- const keyInput = await prompter.text({
917
+ const keyInput = await input({
526
918
  message: "TonAPI key",
919
+ theme: inquirerTheme,
527
920
  validate: (v) => {
528
921
  if (!v || v.length < 10) return "Key too short";
922
+ return true;
529
923
  }
530
924
  });
531
925
  tonapiKey = keyInput;
926
+ extras.push("TonAPI");
927
+ }
928
+ const setupTavily = await confirm({
929
+ message: `Enable web search? ${DIM("(free Tavily key \u2014 1,000 req/month)")}`,
930
+ default: false,
931
+ theme: inquirerTheme
932
+ });
933
+ if (setupTavily) {
934
+ noteBox(
935
+ "Web search lets your agent search the internet and read web pages.\n\nTo get your free API key (takes 30 seconds):\n\n 1. Go to https://app.tavily.com/sign-in\n 2. Create an account (email or Google/GitHub)\n 3. Your API key is displayed on the dashboard\n (starts with tvly-)\n\nFree plan: 1,000 requests/month \u2014 no credit card required.",
936
+ "Tavily \u2014 Web Search API",
937
+ TON
938
+ );
939
+ const keyInput = await input({
940
+ message: "Tavily API key (starts with tvly-)",
941
+ theme: inquirerTheme,
942
+ validate: (v) => {
943
+ if (!v || !v.startsWith("tvly-")) return "Should start with tvly-";
944
+ return true;
945
+ }
946
+ });
947
+ tavilyApiKey = keyInput;
948
+ extras.push("Tavily");
949
+ }
950
+ STEPS[4].value = extras.length ? extras.join(", ") : "defaults";
951
+ redraw(5);
952
+ let wallet;
953
+ const existingWallet = walletExists() ? loadWallet() : null;
954
+ if (existingWallet) {
955
+ noteBox(`Existing wallet found: ${existingWallet.address}`, "TON Wallet", TON);
956
+ const walletAction = await select({
957
+ message: "A TON wallet already exists. What do you want to do?",
958
+ default: "keep",
959
+ theme: inquirerTheme,
960
+ choices: [
961
+ { value: "keep", name: "Keep existing", description: existingWallet.address },
962
+ {
963
+ value: "regenerate",
964
+ name: "Generate new",
965
+ description: "WARNING: old wallet will be lost"
966
+ },
967
+ { value: "import", name: "Import mnemonic", description: "Restore from 24-word seed" }
968
+ ]
969
+ });
970
+ if (walletAction === "keep") {
971
+ wallet = existingWallet;
972
+ } else if (walletAction === "import") {
973
+ const mnemonicInput = await input({
974
+ message: "Enter your 24-word mnemonic (space-separated)",
975
+ theme: inquirerTheme,
976
+ validate: (value = "") => {
977
+ const words = value.trim().split(/\s+/);
978
+ return words.length === 24 ? true : `Expected 24 words, got ${words.length}`;
979
+ }
980
+ });
981
+ spinner.start(DIM("Importing wallet..."));
982
+ wallet = await importWallet(mnemonicInput.trim().split(/\s+/));
983
+ saveWallet(wallet);
984
+ spinner.succeed(DIM(`Wallet imported: ${wallet.address}`));
985
+ } else {
986
+ spinner.start(DIM("Generating new TON wallet..."));
987
+ wallet = await generateWallet();
988
+ saveWallet(wallet);
989
+ spinner.succeed(DIM("New TON wallet generated"));
990
+ }
991
+ } else {
992
+ spinner.start(DIM("Generating TON wallet..."));
993
+ wallet = await generateWallet();
994
+ saveWallet(wallet);
995
+ spinner.succeed(DIM("TON wallet generated"));
996
+ }
997
+ if (!existingWallet || wallet !== existingWallet) {
998
+ const W = FRAME_WIDTH;
999
+ const mnTitle = " \u26A0 BACKUP REQUIRED \u2014 WRITE DOWN THESE 24 WORDS";
1000
+ console.log();
1001
+ console.log(RED(` \u250C${"\u2500".repeat(W)}\u2510`));
1002
+ console.log(RED(" \u2502") + RED.bold(padRight(mnTitle, W)) + RED("\u2502"));
1003
+ console.log(RED(` \u251C${"\u2500".repeat(W)}\u2524`));
1004
+ console.log(RED(" \u2502") + " ".repeat(W) + RED("\u2502"));
1005
+ const cols = 4;
1006
+ const wordWidth = Math.max(10, Math.floor((W - 8) / cols) - 5);
1007
+ const words = wallet.mnemonic;
1008
+ for (let r = 0; r < 6; r++) {
1009
+ const parts = [];
1010
+ for (let c = 0; c < cols; c++) {
1011
+ const idx = r * cols + c;
1012
+ const num = String(idx + 1).padStart(2, " ");
1013
+ parts.push(`${DIM(num + ".")} ${WHITE(padRight(words[idx], wordWidth))}`);
1014
+ }
1015
+ const line = ` ${parts.join(" ")}`;
1016
+ const visPad = W - stripAnsi(line).length;
1017
+ console.log(RED(" \u2502") + line + " ".repeat(Math.max(0, visPad)) + RED("\u2502"));
1018
+ }
1019
+ console.log(RED(" \u2502") + " ".repeat(W) + RED("\u2502"));
1020
+ console.log(
1021
+ RED(" \u2502") + padRightAnsi(DIM(" These words allow you to recover your wallet."), W) + RED("\u2502")
1022
+ );
1023
+ console.log(
1024
+ RED(" \u2502") + padRightAnsi(DIM(" Without them, you will lose access to your TON."), W) + RED("\u2502")
1025
+ );
1026
+ console.log(
1027
+ RED(" \u2502") + padRightAnsi(DIM(" Write them on paper and keep them safe."), W) + RED("\u2502")
1028
+ );
1029
+ console.log(RED(" \u2502") + " ".repeat(W) + RED("\u2502"));
1030
+ console.log(RED(` \u2514${"\u2500".repeat(W)}\u2518`));
1031
+ console.log();
532
1032
  }
1033
+ STEPS[5].value = `${wallet.address.slice(0, 8)}...${wallet.address.slice(-4)}`;
1034
+ redraw(6);
533
1035
  const config = {
534
1036
  meta: {
535
1037
  version: "1.0.0",
@@ -539,6 +1041,7 @@ Get it at: ${providerMeta.consoleUrl}`,
539
1041
  agent: {
540
1042
  provider: selectedProvider,
541
1043
  api_key: apiKey,
1044
+ ...selectedProvider === "local" && localBaseUrl ? { base_url: localBaseUrl } : {},
542
1045
  model: selectedModel,
543
1046
  max_tokens: 4096,
544
1047
  temperature: 0.7,
@@ -593,66 +1096,42 @@ Get it at: ${providerMeta.consoleUrl}`,
593
1096
  log_requests: false
594
1097
  },
595
1098
  dev: { hot_reload: false },
1099
+ tool_rag: {
1100
+ enabled: true,
1101
+ top_k: 25,
1102
+ always_include: [
1103
+ "telegram_send_message",
1104
+ "telegram_reply_message",
1105
+ "telegram_send_photo",
1106
+ "telegram_send_document",
1107
+ "journal_*",
1108
+ "workspace_*",
1109
+ "web_*"
1110
+ ],
1111
+ skip_unlimited_providers: false
1112
+ },
1113
+ logging: { level: "info", pretty: true },
1114
+ mcp: { servers: {} },
596
1115
  plugins: {},
597
- tonapi_key: tonapiKey
1116
+ ...selectedProvider === "cocoon" ? { cocoon: { port: cocoonInstance } } : {},
1117
+ tonapi_key: tonapiKey,
1118
+ tavily_api_key: tavilyApiKey
598
1119
  };
599
- spinner2.start("Saving configuration...");
1120
+ spinner.start(DIM("Saving configuration..."));
600
1121
  const configYaml = YAML.stringify(config);
601
- writeFileSync(workspace.configPath, configYaml, "utf-8");
602
- chmodSync(workspace.configPath, 384);
603
- spinner2.stop("\u2713 Configuration saved");
604
- let wallet;
605
- const existingWallet = walletExists() ? loadWallet() : null;
606
- if (existingWallet) {
607
- prompter.note(`Existing wallet found: ${existingWallet.address}`, "TON Wallet");
608
- const walletAction = await prompter.select({
609
- message: "A TON wallet already exists. What do you want to do?",
610
- options: [
611
- { value: "keep", label: "Keep existing", hint: `${existingWallet.address}` },
612
- { value: "regenerate", label: "Generate new", hint: "WARNING: old wallet will be lost" },
613
- { value: "import", label: "Import mnemonic", hint: "Restore from 24-word seed" }
614
- ],
615
- initialValue: "keep"
616
- });
617
- if (walletAction === "keep") {
618
- wallet = existingWallet;
619
- } else if (walletAction === "import") {
620
- const mnemonicInput = await prompter.text({
621
- message: "Enter your 24-word mnemonic (space-separated)",
622
- validate: (value) => {
623
- const words = value.trim().split(/\s+/);
624
- if (words.length !== 24) return `Expected 24 words, got ${words.length}`;
625
- }
626
- });
627
- spinner2.start("Importing wallet...");
628
- wallet = await importWallet(mnemonicInput.trim().split(/\s+/));
629
- saveWallet(wallet);
630
- spinner2.stop(`\u2713 Wallet imported: ${wallet.address}`);
631
- } else {
632
- spinner2.start("Generating new TON wallet...");
633
- wallet = await generateWallet();
634
- saveWallet(wallet);
635
- spinner2.stop("\u2713 New TON wallet generated");
636
- }
637
- } else {
638
- spinner2.start("Generating TON wallet...");
639
- wallet = await generateWallet();
640
- saveWallet(wallet);
641
- spinner2.stop("\u2713 TON wallet generated");
642
- }
643
- if (!existingWallet || wallet !== existingWallet) {
644
- prompter.note(
645
- "BACKUP REQUIRED - WRITE DOWN THESE 24 WORDS:\n\n" + wallet.mnemonic.join(" ") + "\n\nThese words allow you to recover your wallet.\nWithout them, you will lose access to your TON.\nWrite them on paper and keep them safe.",
646
- "Mnemonic Seed (24 words)"
647
- );
648
- }
1122
+ writeFileSync(workspace.configPath, configYaml, { encoding: "utf-8", mode: 384 });
1123
+ spinner.succeed(DIM(`Configuration saved: ${workspace.configPath}`));
649
1124
  let telegramConnected = false;
650
- const connectNow = await prompter.confirm({
651
- message: "Connect to Telegram now? (you'll need the verification code sent to your phone)",
652
- initialValue: true
1125
+ const connectNow = await confirm({
1126
+ message: `Connect to Telegram now? ${DIM("(verification code will be sent to your phone)")}`,
1127
+ default: true,
1128
+ theme: inquirerTheme
653
1129
  });
654
1130
  if (connectNow) {
655
- prompter.log("Connecting to Telegram... Check your phone for the verification code.");
1131
+ console.log(
1132
+ `
1133
+ ${DIM("Connecting to Telegram... Check your phone for the verification code.")}`
1134
+ );
656
1135
  try {
657
1136
  const sessionPath = join(TELETON_ROOT, "telegram_session.txt");
658
1137
  const client = new TelegramUserClient({
@@ -665,52 +1144,54 @@ Get it at: ${providerMeta.consoleUrl}`,
665
1144
  const me = client.getMe();
666
1145
  await client.disconnect();
667
1146
  telegramConnected = true;
668
- prompter.success(
669
- `\u2713 Telegram connected as ${me?.firstName || ""}${me?.username ? ` (@${me.username})` : ""}`
670
- );
1147
+ const displayName = `${me?.firstName || ""}${me?.username ? ` (@${me.username})` : ""}`;
1148
+ console.log(` ${GREEN("\u2713")} ${DIM("Telegram connected as")} ${CYAN(displayName)}
1149
+ `);
1150
+ STEPS[6].value = `Connected${me?.username ? ` (@${me.username})` : ""}`;
671
1151
  } catch (err) {
672
1152
  prompter.warn(
673
1153
  `Telegram connection failed: ${err instanceof Error ? err.message : String(err)}
674
1154
  You can authenticate later when running: teleton start`
675
1155
  );
1156
+ STEPS[6].value = "Auth on first start";
676
1157
  }
677
- }
678
- prompter.note(
679
- `Workspace: ${workspace.root}
680
- Config: ${workspace.configPath}
681
- Templates: SOUL.md, MEMORY.md, IDENTITY.md, USER.md
682
- Telegram: ${phone} (API ID: ${apiId})${telegramConnected ? " \u2713 connected" : ""}
683
- Admin: User ID ${userId}
684
- Provider: ${providerMeta.displayName}
685
- Model: ${selectedModel}
686
- TON Wallet: ${wallet.address}`,
687
- "Setup complete"
688
- );
689
- if (telegramConnected) {
690
- prompter.note(
691
- "Next steps:\n\n1. Start the agent:\n $ teleton start\n\n2. Send /boot as your first message to bootstrap\n the agent's personality and get to know each other",
692
- "Ready"
693
- );
694
1158
  } else {
695
- prompter.note(
696
- "Next steps:\n\n1. Start the agent:\n $ teleton start\n\n2. On first launch, you will be asked for:\n - Telegram verification code\n - 2FA password (if enabled)\n\n3. Send a message to your Telegram account to test",
697
- "Ready"
698
- );
1159
+ console.log(`
1160
+ ${DIM("You can authenticate later when running: teleton start")}
1161
+ `);
1162
+ STEPS[6].value = "Auth on first start";
699
1163
  }
700
- prompter.outro("Good luck!");
1164
+ console.clear();
1165
+ console.log();
1166
+ console.log(wizardFrame(STEPS.length, STEPS));
1167
+ console.log();
1168
+ console.log(finalSummaryBox(STEPS, telegramConnected));
1169
+ console.log();
1170
+ console.log(
1171
+ ` ${GREEN.bold("\u2714")} ${GREEN.bold("Setup complete!")} ${DIM(`Config saved to ${workspace.configPath}`)}`
1172
+ );
1173
+ console.log(` ${TON.bold("\u26A1")} Good luck!
1174
+ `);
701
1175
  }
702
1176
  async function runNonInteractiveOnboarding(options, prompter) {
703
- if (!options.apiId || !options.apiHash || !options.phone || !options.apiKey || !options.userId) {
704
- prompter.error(
705
- "Non-interactive mode requires: --api-id, --api-hash, --phone, --api-key, --user-id"
706
- );
1177
+ const selectedProvider = options.provider || "anthropic";
1178
+ const needsApiKey = selectedProvider !== "cocoon" && selectedProvider !== "local";
1179
+ if (!options.apiId || !options.apiHash || !options.phone || !options.userId) {
1180
+ prompter.error("Non-interactive mode requires: --api-id, --api-hash, --phone, --user-id");
1181
+ process.exit(1);
1182
+ }
1183
+ if (needsApiKey && !options.apiKey) {
1184
+ prompter.error(`Non-interactive mode requires --api-key for provider "${selectedProvider}"`);
1185
+ process.exit(1);
1186
+ }
1187
+ if (selectedProvider === "local" && !options.baseUrl) {
1188
+ prompter.error("Non-interactive mode requires --base-url for local provider");
707
1189
  process.exit(1);
708
1190
  }
709
1191
  const workspace = await ensureWorkspace({
710
1192
  workspaceDir: options.workspace,
711
1193
  ensureTemplates: true
712
1194
  });
713
- const selectedProvider = options.provider || "anthropic";
714
1195
  const providerMeta = getProviderMetadata(selectedProvider);
715
1196
  const config = {
716
1197
  meta: {
@@ -720,7 +1201,8 @@ async function runNonInteractiveOnboarding(options, prompter) {
720
1201
  },
721
1202
  agent: {
722
1203
  provider: selectedProvider,
723
- api_key: options.apiKey,
1204
+ api_key: options.apiKey || "",
1205
+ ...options.baseUrl ? { base_url: options.baseUrl } : {},
724
1206
  model: providerMeta.defaultModel,
725
1207
  max_tokens: 4096,
726
1208
  temperature: 0.7,
@@ -771,12 +1253,28 @@ async function runNonInteractiveOnboarding(options, prompter) {
771
1253
  log_requests: false
772
1254
  },
773
1255
  dev: { hot_reload: false },
774
- plugins: {}
1256
+ tool_rag: {
1257
+ enabled: true,
1258
+ top_k: 25,
1259
+ always_include: [
1260
+ "telegram_send_message",
1261
+ "telegram_reply_message",
1262
+ "telegram_send_photo",
1263
+ "telegram_send_document",
1264
+ "journal_*",
1265
+ "workspace_*",
1266
+ "web_*"
1267
+ ],
1268
+ skip_unlimited_providers: false
1269
+ },
1270
+ logging: { level: "info", pretty: true },
1271
+ mcp: { servers: {} },
1272
+ plugins: {},
1273
+ tavily_api_key: options.tavilyApiKey
775
1274
  };
776
1275
  const configYaml = YAML.stringify(config);
777
- writeFileSync(workspace.configPath, configYaml, "utf-8");
778
- chmodSync(workspace.configPath, 384);
779
- prompter.success(`\u2713 Configuration created: ${workspace.configPath}`);
1276
+ writeFileSync(workspace.configPath, configYaml, { encoding: "utf-8", mode: 384 });
1277
+ prompter.success(`Configuration created: ${workspace.configPath}`);
780
1278
  }
781
1279
 
782
1280
  // src/cli/commands/doctor.ts
@@ -888,6 +1386,13 @@ async function checkApiKey(workspaceDir) {
888
1386
  message: `Unknown provider: ${provider}`
889
1387
  };
890
1388
  }
1389
+ if (provider === "cocoon" || provider === "local") {
1390
+ return {
1391
+ name: `${meta.displayName}`,
1392
+ status: "ok",
1393
+ message: "No API key needed"
1394
+ };
1395
+ }
891
1396
  if (!apiKey) {
892
1397
  return {
893
1398
  name: `${meta.displayName} API key`,
@@ -903,8 +1408,8 @@ async function checkApiKey(workspaceDir) {
903
1408
  message: validationError
904
1409
  };
905
1410
  }
906
- const maskLen = Math.min(10, apiKey.length - 4);
907
- const masked = apiKey.substring(0, maskLen) + "..." + apiKey.substring(apiKey.length - 4);
1411
+ const maskLen = Math.min(4, Math.max(0, apiKey.length - 4));
1412
+ const masked = apiKey.substring(0, maskLen) + "****" + apiKey.substring(apiKey.length - 4);
908
1413
  return {
909
1414
  name: `${meta.displayName} API key`,
910
1415
  status: "ok",
@@ -1159,6 +1664,224 @@ ${blue} \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u250
1159
1664
  console.log("");
1160
1665
  }
1161
1666
 
1667
+ // src/cli/commands/mcp.ts
1668
+ function ensureMcpSection(raw) {
1669
+ if (!raw.mcp || typeof raw.mcp !== "object") {
1670
+ raw.mcp = { servers: {} };
1671
+ }
1672
+ const mcp2 = raw.mcp;
1673
+ if (!mcp2.servers || typeof mcp2.servers !== "object") {
1674
+ mcp2.servers = {};
1675
+ }
1676
+ return mcp2.servers;
1677
+ }
1678
+ function deriveServerName(pkg) {
1679
+ const unscoped = pkg.includes("/") ? pkg.split("/").pop() : pkg;
1680
+ return unscoped.replace(/^server-/, "").replace(/^mcp-server-/, "").replace(/^mcp-/, "");
1681
+ }
1682
+ async function mcpAddCommand(pkg, extraArgs, options) {
1683
+ const configPath = options.config || getDefaultConfigPath();
1684
+ const raw = readRawConfig(configPath);
1685
+ const servers = ensureMcpSection(raw);
1686
+ const serverName = options.name || deriveServerName(pkg);
1687
+ if (servers[serverName]) {
1688
+ console.error(`\u274C MCP server "${serverName}" already exists. Remove it first or use --name.`);
1689
+ process.exit(1);
1690
+ }
1691
+ const entry = {};
1692
+ if (options.url) {
1693
+ entry.url = pkg;
1694
+ } else {
1695
+ entry.command = "npx";
1696
+ entry.args = ["-y", pkg, ...extraArgs];
1697
+ }
1698
+ if (options.scope && options.scope !== "always") {
1699
+ entry.scope = options.scope;
1700
+ }
1701
+ if (options.env && options.env.length > 0) {
1702
+ const envMap = {};
1703
+ for (const pair of options.env) {
1704
+ const eq = pair.indexOf("=");
1705
+ if (eq === -1) {
1706
+ console.error(`\u274C Invalid --env format: "${pair}" (expected KEY=VALUE)`);
1707
+ process.exit(1);
1708
+ }
1709
+ envMap[pair.slice(0, eq)] = pair.slice(eq + 1);
1710
+ }
1711
+ entry.env = envMap;
1712
+ }
1713
+ servers[serverName] = entry;
1714
+ writeRawConfig(raw, configPath);
1715
+ console.log(`\u2705 Added MCP server "${serverName}"`);
1716
+ if (entry.command) {
1717
+ console.log(` Command: ${entry.command}`);
1718
+ } else {
1719
+ console.log(` URL: ${entry.url}`);
1720
+ }
1721
+ console.log(`
1722
+ Restart teleton to connect: teleton start`);
1723
+ }
1724
+ async function mcpRemoveCommand(name, options) {
1725
+ const configPath = options.config || getDefaultConfigPath();
1726
+ const raw = readRawConfig(configPath);
1727
+ const servers = ensureMcpSection(raw);
1728
+ if (!servers[name]) {
1729
+ console.error(`\u274C MCP server "${name}" not found.`);
1730
+ const names = Object.keys(servers);
1731
+ if (names.length > 0) {
1732
+ console.error(` Available: ${names.join(", ")}`);
1733
+ }
1734
+ process.exit(1);
1735
+ }
1736
+ delete servers[name];
1737
+ writeRawConfig(raw, configPath);
1738
+ console.log(`\u2705 Removed MCP server "${name}"`);
1739
+ }
1740
+ async function mcpListCommand(options) {
1741
+ const configPath = options.config || getDefaultConfigPath();
1742
+ const raw = readRawConfig(configPath);
1743
+ const servers = ensureMcpSection(raw);
1744
+ const entries = Object.entries(servers);
1745
+ if (entries.length === 0) {
1746
+ console.log("No MCP servers configured.");
1747
+ console.log("\n Add one: teleton mcp add @modelcontextprotocol/server-filesystem /tmp");
1748
+ return;
1749
+ }
1750
+ console.log(`MCP servers (${entries.length}):
1751
+ `);
1752
+ for (const [name, cfg] of entries) {
1753
+ const type = cfg.command ? "stdio" : "sse";
1754
+ const target = cfg.command || cfg.url || "?";
1755
+ const scope = cfg.scope || "always";
1756
+ const enabled = cfg.enabled !== false ? "\u2713" : "\u2717";
1757
+ console.log(` ${enabled} ${name} (${type}, ${scope})`);
1758
+ console.log(` ${target}`);
1759
+ if (cfg.env && typeof cfg.env === "object") {
1760
+ const keys = Object.keys(cfg.env);
1761
+ console.log(` env: ${keys.join(", ")}`);
1762
+ }
1763
+ }
1764
+ }
1765
+
1766
+ // src/cli/commands/config.ts
1767
+ function requireWhitelisted(key) {
1768
+ const meta = CONFIGURABLE_KEYS[key];
1769
+ if (!meta) {
1770
+ const allowed = Object.keys(CONFIGURABLE_KEYS).join(", ");
1771
+ console.error(`Key "${key}" is not configurable.
1772
+ Allowed keys: ${allowed}`);
1773
+ process.exit(1);
1774
+ }
1775
+ return meta;
1776
+ }
1777
+ async function actionSet(key, value, configPath) {
1778
+ const meta = requireWhitelisted(key);
1779
+ if (!value) {
1780
+ const prompter = createPrompter();
1781
+ try {
1782
+ if (meta.sensitive) {
1783
+ value = await prompter.password({
1784
+ message: `Enter value for ${key}:`,
1785
+ validate: (v) => {
1786
+ if (!v) return "Value is required";
1787
+ const err2 = meta.validate(v);
1788
+ return err2 ? new Error(err2) : void 0;
1789
+ }
1790
+ });
1791
+ } else {
1792
+ value = await prompter.text({
1793
+ message: `Enter value for ${key}:`,
1794
+ validate: (v) => {
1795
+ if (!v) return "Value is required";
1796
+ const err2 = meta.validate(v);
1797
+ return err2 ? new Error(err2) : void 0;
1798
+ }
1799
+ });
1800
+ }
1801
+ } catch (e) {
1802
+ if (e instanceof CancelledError) {
1803
+ console.log("Cancelled.");
1804
+ return;
1805
+ }
1806
+ throw e;
1807
+ }
1808
+ }
1809
+ const err = meta.validate(value);
1810
+ if (err) {
1811
+ console.error(`Invalid value for ${key}: ${err}`);
1812
+ process.exit(1);
1813
+ }
1814
+ const raw = readRawConfig(configPath);
1815
+ setNestedValue(raw, key, meta.parse(value));
1816
+ writeRawConfig(raw, configPath);
1817
+ console.log(`\u2713 ${key} = ${meta.mask(value)}`);
1818
+ }
1819
+ function actionGet(key, configPath) {
1820
+ const meta = requireWhitelisted(key);
1821
+ const raw = readRawConfig(configPath);
1822
+ const value = getNestedValue(raw, key);
1823
+ if (value == null || value === "") {
1824
+ console.log(`\u2717 ${key} (not set)`);
1825
+ } else {
1826
+ const display = meta.sensitive ? meta.mask(String(value)) : String(value);
1827
+ console.log(`\u2713 ${key} = ${display}`);
1828
+ }
1829
+ }
1830
+ function actionList(configPath) {
1831
+ const raw = readRawConfig(configPath);
1832
+ console.log("\nConfigurable keys:\n");
1833
+ for (const [key, meta] of Object.entries(CONFIGURABLE_KEYS)) {
1834
+ const value = getNestedValue(raw, key);
1835
+ if (value != null && value !== "") {
1836
+ const display = meta.sensitive ? meta.mask(String(value)) : String(value);
1837
+ console.log(` \u2713 ${key.padEnd(24)} = ${display}`);
1838
+ } else {
1839
+ console.log(` \u2717 ${key.padEnd(24)} (not set)`);
1840
+ }
1841
+ }
1842
+ console.log();
1843
+ }
1844
+ function actionUnset(key, configPath) {
1845
+ requireWhitelisted(key);
1846
+ const raw = readRawConfig(configPath);
1847
+ deleteNestedValue(raw, key);
1848
+ writeRawConfig(raw, configPath);
1849
+ console.log(`\u2713 ${key} unset`);
1850
+ }
1851
+ async function configCommand(action, key, value, options) {
1852
+ const configPath = options.config ?? getDefaultConfigPath();
1853
+ switch (action) {
1854
+ case "list":
1855
+ actionList(configPath);
1856
+ break;
1857
+ case "get":
1858
+ if (!key) {
1859
+ console.error("Usage: teleton config get <key>");
1860
+ process.exit(1);
1861
+ }
1862
+ actionGet(key, configPath);
1863
+ break;
1864
+ case "set":
1865
+ if (!key) {
1866
+ console.error("Usage: teleton config set <key> [value]");
1867
+ process.exit(1);
1868
+ }
1869
+ await actionSet(key, value, configPath);
1870
+ break;
1871
+ case "unset":
1872
+ if (!key) {
1873
+ console.error("Usage: teleton config unset <key>");
1874
+ process.exit(1);
1875
+ }
1876
+ actionUnset(key, configPath);
1877
+ break;
1878
+ default:
1879
+ console.error(`Unknown action: ${action}
1880
+ Available: set, get, list, unset`);
1881
+ process.exit(1);
1882
+ }
1883
+ }
1884
+
1162
1885
  // src/cli/index.ts
1163
1886
  import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
1164
1887
  import { dirname, join as join3 } from "path";
@@ -1177,19 +1900,23 @@ function findPackageJson() {
1177
1900
  var packageJson = findPackageJson();
1178
1901
  var program = new Command();
1179
1902
  program.name("teleton").description("Teleton Agent - Personal AI Agent for Telegram").version(packageJson.version);
1180
- program.command("setup").description("Interactive wizard to set up Teleton").option("--workspace <dir>", "Workspace directory").option("--non-interactive", "Non-interactive mode").option("--api-id <id>", "Telegram API ID").option("--api-hash <hash>", "Telegram API Hash").option("--phone <number>", "Phone number").option("--api-key <key>", "Anthropic API key").option("--user-id <id>", "Telegram User ID").action(async (options) => {
1903
+ program.command("setup").description("Interactive wizard to set up Teleton").option("--workspace <dir>", "Workspace directory").option("--non-interactive", "Non-interactive mode").option("--ui", "Launch web-based setup wizard").option("--ui-port <port>", "Port for setup WebUI", "7777").option("--api-id <id>", "Telegram API ID").option("--api-hash <hash>", "Telegram API Hash").option("--phone <number>", "Phone number").option("--api-key <key>", "LLM provider API key").option("--base-url <url>", "Base URL for local LLM server").option("--user-id <id>", "Telegram User ID").option("--tavily-api-key <key>", "Tavily API key for web search").action(async (options) => {
1181
1904
  try {
1182
1905
  await onboardCommand({
1183
1906
  workspace: options.workspace,
1184
1907
  nonInteractive: options.nonInteractive,
1908
+ ui: options.ui,
1909
+ uiPort: options.uiPort,
1185
1910
  apiId: options.apiId ? parseInt(options.apiId) : void 0,
1186
1911
  apiHash: options.apiHash,
1187
1912
  phone: options.phone,
1188
1913
  apiKey: options.apiKey,
1189
- userId: options.userId ? parseInt(options.userId) : void 0
1914
+ baseUrl: options.baseUrl,
1915
+ userId: options.userId ? parseInt(options.userId) : void 0,
1916
+ tavilyApiKey: options.tavilyApiKey
1190
1917
  });
1191
1918
  } catch (error) {
1192
- console.error("Error:", error instanceof Error ? error.message : String(error));
1919
+ console.error("Error:", getErrorMessage(error));
1193
1920
  process.exit(1);
1194
1921
  }
1195
1922
  });
@@ -1209,7 +1936,7 @@ program.command("start").description("Start the Teleton agent").option("-c, --co
1209
1936
  }
1210
1937
  await main(options.config);
1211
1938
  } catch (error) {
1212
- console.error("Error:", error instanceof Error ? error.message : String(error));
1939
+ console.error("Error:", getErrorMessage(error));
1213
1940
  process.exit(1);
1214
1941
  }
1215
1942
  });
@@ -1217,7 +1944,47 @@ program.command("doctor").description("Run system health checks").action(async (
1217
1944
  try {
1218
1945
  await doctorCommand();
1219
1946
  } catch (error) {
1220
- console.error("Error:", error instanceof Error ? error.message : String(error));
1947
+ console.error("Error:", getErrorMessage(error));
1948
+ process.exit(1);
1949
+ }
1950
+ });
1951
+ var mcp = program.command("mcp").description("Manage MCP (Model Context Protocol) servers");
1952
+ mcp.command("add <package> [args...]").description(
1953
+ "Add an MCP server (e.g. teleton mcp add @modelcontextprotocol/server-filesystem /tmp)"
1954
+ ).option("-n, --name <name>", "Server name (auto-derived from package if omitted)").option("-s, --scope <scope>", "Tool scope: always | dm-only | group-only | admin-only", "always").option(
1955
+ "-e, --env <KEY=VALUE...>",
1956
+ "Environment variables (repeatable)",
1957
+ (v, prev) => [...prev, v],
1958
+ []
1959
+ ).option("--url", "Treat <package> as an SSE/HTTP URL instead of an npx package").option("-c, --config <path>", "Config file path").action(async (pkg, args, options) => {
1960
+ try {
1961
+ await mcpAddCommand(pkg, args, options);
1962
+ } catch (error) {
1963
+ console.error("Error:", getErrorMessage(error));
1964
+ process.exit(1);
1965
+ }
1966
+ });
1967
+ mcp.command("remove <name>").description("Remove an MCP server by name").option("-c, --config <path>", "Config file path").action(async (name, options) => {
1968
+ try {
1969
+ await mcpRemoveCommand(name, options);
1970
+ } catch (error) {
1971
+ console.error("Error:", getErrorMessage(error));
1972
+ process.exit(1);
1973
+ }
1974
+ });
1975
+ mcp.command("list").description("List configured MCP servers").option("-c, --config <path>", "Config file path").action(async (options) => {
1976
+ try {
1977
+ await mcpListCommand(options);
1978
+ } catch (error) {
1979
+ console.error("Error:", getErrorMessage(error));
1980
+ process.exit(1);
1981
+ }
1982
+ });
1983
+ program.command("config").description("Manage configuration keys (set, get, list, unset)").argument("<action>", "set | get | list | unset").argument("[key]", "Config key (e.g., tavily_api_key, telegram.bot_token)").argument("[value]", "Value to set (prompts interactively if omitted)").option("-c, --config <path>", "Config file path").action(async (action, key, value, options) => {
1984
+ try {
1985
+ await configCommand(action, key, value, options);
1986
+ } catch (error) {
1987
+ console.error("Error:", getErrorMessage(error));
1221
1988
  process.exit(1);
1222
1989
  }
1223
1990
  });