teleton 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1055 @@
1
+ import {
2
+ CasinoConfigSchema,
3
+ ConfigSchema,
4
+ DealsConfigSchema,
5
+ MarketConfigSchema,
6
+ configExists,
7
+ ensureWorkspace,
8
+ generateWallet,
9
+ getDefaultConfigPath,
10
+ getProviderMetadata,
11
+ getSupportedProviders,
12
+ isNewWorkspace,
13
+ main,
14
+ saveWallet,
15
+ validateApiKeyFormat
16
+ } from "../chunk-WXVHT6CI.js";
17
+ import "../chunk-XBGUNXF2.js";
18
+ import "../chunk-WDUHRPGA.js";
19
+ import "../chunk-UR2LQEKR.js";
20
+ import {
21
+ TELETON_ROOT
22
+ } from "../chunk-7NJ46ZIX.js";
23
+
24
+ // src/cli/index.ts
25
+ import { Command } from "commander";
26
+
27
+ // src/cli/prompts.ts
28
+ import * as clack from "@clack/prompts";
29
+ var ClackPrompter = class {
30
+ /**
31
+ * Display intro banner
32
+ */
33
+ async intro(title) {
34
+ clack.intro(title);
35
+ }
36
+ /**
37
+ * Display outro message
38
+ */
39
+ async outro(message) {
40
+ clack.outro(message);
41
+ }
42
+ /**
43
+ * Display note/info
44
+ */
45
+ async note(message, title) {
46
+ clack.note(message, title);
47
+ }
48
+ /**
49
+ * Text input prompt
50
+ */
51
+ async text(options) {
52
+ const result = await clack.text({
53
+ message: options.message,
54
+ placeholder: options.placeholder,
55
+ initialValue: options.initialValue,
56
+ validate: options.validate
57
+ });
58
+ if (clack.isCancel(result)) {
59
+ throw new CancelledError();
60
+ }
61
+ return result;
62
+ }
63
+ /**
64
+ * Password input (hidden)
65
+ */
66
+ 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;
75
+ }
76
+ /**
77
+ * Select prompt (single choice)
78
+ */
79
+ async select(options) {
80
+ const result = await clack.select({
81
+ message: options.message,
82
+ options: options.options.map((opt) => {
83
+ const mapped = {
84
+ value: opt.value,
85
+ label: opt.label
86
+ };
87
+ if (opt.hint) {
88
+ mapped.hint = opt.hint;
89
+ }
90
+ return mapped;
91
+ }),
92
+ initialValue: options.initialValue
93
+ });
94
+ if (clack.isCancel(result)) {
95
+ throw new CancelledError();
96
+ }
97
+ return result;
98
+ }
99
+ /**
100
+ * Confirm (yes/no)
101
+ */
102
+ async confirm(options) {
103
+ const result = await clack.confirm({
104
+ message: options.message,
105
+ initialValue: options.initialValue ?? false
106
+ });
107
+ if (clack.isCancel(result)) {
108
+ throw new CancelledError();
109
+ }
110
+ return result;
111
+ }
112
+ /**
113
+ * Multi-select prompt
114
+ */
115
+ async multiselect(options) {
116
+ const result = await clack.multiselect({
117
+ message: options.message,
118
+ options: options.options.map((opt) => {
119
+ const mapped = {
120
+ value: opt.value,
121
+ label: opt.label
122
+ };
123
+ if (opt.hint) {
124
+ mapped.hint = opt.hint;
125
+ }
126
+ return mapped;
127
+ }),
128
+ required: options.required
129
+ });
130
+ if (clack.isCancel(result)) {
131
+ throw new CancelledError();
132
+ }
133
+ return result;
134
+ }
135
+ /**
136
+ * Spinner for long operations
137
+ */
138
+ spinner() {
139
+ const s = clack.spinner();
140
+ return {
141
+ start: (message) => s.start(message),
142
+ stop: (message) => s.stop(message),
143
+ message: (message) => s.message(message)
144
+ };
145
+ }
146
+ /**
147
+ * Log message
148
+ */
149
+ log(message) {
150
+ clack.log.message(message);
151
+ }
152
+ /**
153
+ * Log warning
154
+ */
155
+ warn(message) {
156
+ clack.log.warn(message);
157
+ }
158
+ /**
159
+ * Log error
160
+ */
161
+ error(message) {
162
+ clack.log.error(message);
163
+ }
164
+ /**
165
+ * Log success
166
+ */
167
+ success(message) {
168
+ clack.log.success(message);
169
+ }
170
+ };
171
+ var CancelledError = class extends Error {
172
+ constructor() {
173
+ super("Operation cancelled by user");
174
+ this.name = "CancelledError";
175
+ }
176
+ };
177
+ function createPrompter() {
178
+ return new ClackPrompter();
179
+ }
180
+
181
+ // src/cli/commands/onboard.ts
182
+ import { writeFileSync } from "fs";
183
+ import { execSync } from "child_process";
184
+ import YAML from "yaml";
185
+ async function onboardCommand(options = {}) {
186
+ const prompter = createPrompter();
187
+ try {
188
+ if (options.nonInteractive) {
189
+ await runNonInteractiveOnboarding(options, prompter);
190
+ } else {
191
+ await runInteractiveOnboarding(options, prompter);
192
+ }
193
+ } catch (err) {
194
+ if (err instanceof CancelledError) {
195
+ prompter.outro("Onboarding cancelled");
196
+ process.exit(0);
197
+ }
198
+ throw err;
199
+ }
200
+ }
201
+ async function runInteractiveOnboarding(options, prompter) {
202
+ const blue2 = "\x1B[34m";
203
+ const reset2 = "\x1B[0m";
204
+ console.log(`
205
+ ${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
206
+ \u2502 \u2502
207
+ \u2502 ______________ ________________ _ __ ___ _____________ ________ \u2502
208
+ \u2502 /_ __/ ____/ / / ____/_ __/ __ \\/ | / / / | / ____/ ____/ | / /_ __/ \u2502
209
+ \u2502 / / / __/ / / / __/ / / / / / / |/ / / /| |/ / __/ __/ / |/ / / / \u2502
210
+ \u2502 / / / /___/ /___/ /___ / / / /_/ / /| / / ___ / /_/ / /___/ /| / / / \u2502
211
+ \u2502 /_/ /_____/_____/_____/ /_/ \\____/_/ |_/ /_/ |_\\____/_____/_/ |_/ /_/ \u2502
212
+ \u2502 \u2502
213
+ \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}
214
+
215
+ Need help? Join @ResistanceForum on Telegram
216
+ `);
217
+ prompter.note(
218
+ "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.",
219
+ "Security Warning"
220
+ );
221
+ const acceptRisk = await prompter.confirm({
222
+ message: "I understand the risks and want to continue",
223
+ initialValue: false
224
+ });
225
+ if (!acceptRisk) {
226
+ prompter.outro("Setup cancelled - you must accept the risks to continue");
227
+ process.exit(1);
228
+ }
229
+ const spinner2 = prompter.spinner();
230
+ spinner2.start("Creating workspace...");
231
+ const workspace = await ensureWorkspace({
232
+ workspaceDir: options.workspace,
233
+ ensureTemplates: true
234
+ });
235
+ const isNew = isNewWorkspace(workspace);
236
+ spinner2.stop(`\u2713 Workspace: ${workspace.root}`);
237
+ if (!isNew) {
238
+ prompter.warn("Existing configuration detected");
239
+ const shouldOverwrite = await prompter.confirm({
240
+ message: "Overwrite existing configuration?",
241
+ initialValue: false
242
+ });
243
+ if (!shouldOverwrite) {
244
+ prompter.outro("Setup cancelled - existing configuration preserved");
245
+ return;
246
+ }
247
+ }
248
+ const flow = await prompter.select({
249
+ message: "Installation mode",
250
+ options: [
251
+ { value: "quick", label: "QuickStart", hint: "Minimal configuration (recommended)" },
252
+ { value: "advanced", label: "Advanced", hint: "Detailed configuration" }
253
+ ],
254
+ initialValue: "quick"
255
+ });
256
+ const providers = getSupportedProviders();
257
+ const selectedProvider = await prompter.select({
258
+ message: "AI Provider",
259
+ options: providers.map((p) => ({
260
+ value: p.id,
261
+ label: p.displayName,
262
+ hint: p.toolLimit !== null ? `${p.defaultModel} (max ${p.toolLimit} tools)` : `${p.defaultModel}`
263
+ })),
264
+ initialValue: "anthropic"
265
+ });
266
+ const providerMeta = getProviderMetadata(selectedProvider);
267
+ if (providerMeta.toolLimit !== null) {
268
+ prompter.note(
269
+ `${providerMeta.displayName} supports max ${providerMeta.toolLimit} tools.
270
+ Tonnet currently has ~121 tools. If more tools are added,
271
+ some may be truncated.`,
272
+ "Tool Limit"
273
+ );
274
+ }
275
+ prompter.note(
276
+ "You need Telegram credentials from https://my.telegram.org/apps\nCreate an application and note the API ID and API Hash",
277
+ "Telegram"
278
+ );
279
+ const apiIdStr = options.apiId ? options.apiId.toString() : await prompter.text({
280
+ message: "API ID (from my.telegram.org)",
281
+ validate: (value) => {
282
+ if (!value || isNaN(parseInt(value))) return "Invalid API ID (must be a number)";
283
+ }
284
+ });
285
+ const apiId = parseInt(apiIdStr);
286
+ const apiHash = options.apiHash ? options.apiHash : await prompter.text({
287
+ message: "API Hash (from my.telegram.org)",
288
+ validate: (value) => {
289
+ if (!value || value.length < 10) return "Invalid API Hash";
290
+ }
291
+ });
292
+ const phone = options.phone ? options.phone : await prompter.text({
293
+ message: "Phone number (international format, e.g. +1234567890)",
294
+ placeholder: "+1234567890",
295
+ validate: (value) => {
296
+ if (!value || !value.startsWith("+")) return "Invalid format (must start with +)";
297
+ }
298
+ });
299
+ prompter.note(
300
+ "To get your Telegram User ID:\n1. Open @userinfobot on Telegram\n2. Send /start\n3. Note the ID displayed",
301
+ "User ID"
302
+ );
303
+ const userIdStr = options.userId ? options.userId.toString() : await prompter.text({
304
+ message: "Your Telegram User ID (for admin rights)",
305
+ validate: (value) => {
306
+ if (!value || isNaN(parseInt(value))) return "Invalid User ID";
307
+ }
308
+ });
309
+ const userId = parseInt(userIdStr);
310
+ prompter.note(
311
+ `${providerMeta.displayName} API key required.
312
+ Get it at: ${providerMeta.consoleUrl}`,
313
+ "API Key"
314
+ );
315
+ const apiKey = options.apiKey ? options.apiKey : await prompter.password({
316
+ message: `${providerMeta.displayName} API Key (${providerMeta.keyHint})`,
317
+ validate: (value) => validateApiKeyFormat(selectedProvider, value)
318
+ });
319
+ let selectedModel = providerMeta.defaultModel;
320
+ if (flow === "advanced") {
321
+ const customModel = await prompter.text({
322
+ message: `Model ID (default: ${providerMeta.defaultModel})`,
323
+ placeholder: providerMeta.defaultModel,
324
+ initialValue: providerMeta.defaultModel
325
+ });
326
+ if (customModel && customModel.trim()) {
327
+ selectedModel = customModel.trim();
328
+ }
329
+ }
330
+ let dmPolicy = "open";
331
+ let groupPolicy = "open";
332
+ let requireMention = true;
333
+ if (flow === "advanced") {
334
+ dmPolicy = await prompter.select({
335
+ message: "DM policy (private messages)",
336
+ options: [
337
+ { value: "open", label: "Open", hint: "Reply to everyone" },
338
+ { value: "allowlist", label: "Allowlist", hint: "Only specific users" },
339
+ { value: "disabled", label: "Disabled", hint: "No DM replies" }
340
+ ],
341
+ initialValue: "open"
342
+ });
343
+ groupPolicy = await prompter.select({
344
+ message: "Group policy",
345
+ options: [
346
+ { value: "open", label: "Open", hint: "Reply in all groups" },
347
+ { value: "allowlist", label: "Allowlist", hint: "Only specific groups" },
348
+ { value: "disabled", label: "Disabled", hint: "No group replies" }
349
+ ],
350
+ initialValue: "open"
351
+ });
352
+ requireMention = await prompter.confirm({
353
+ message: "Require @mention in groups?",
354
+ initialValue: true
355
+ });
356
+ }
357
+ const config = {
358
+ meta: {
359
+ version: "1.0.0",
360
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
361
+ onboard_command: "teleton setup"
362
+ },
363
+ agent: {
364
+ provider: selectedProvider,
365
+ api_key: apiKey,
366
+ model: selectedModel,
367
+ max_tokens: 4096,
368
+ temperature: 0.7,
369
+ system_prompt: null,
370
+ max_agentic_iterations: 5,
371
+ session_reset_policy: {
372
+ daily_reset_enabled: true,
373
+ daily_reset_hour: 4,
374
+ idle_expiry_enabled: true,
375
+ idle_expiry_minutes: 1440
376
+ }
377
+ },
378
+ telegram: {
379
+ api_id: apiId,
380
+ api_hash: apiHash,
381
+ phone,
382
+ session_name: "tonnet_session",
383
+ session_path: workspace.sessionPath,
384
+ dm_policy: dmPolicy,
385
+ allow_from: [],
386
+ group_policy: groupPolicy,
387
+ group_allow_from: [],
388
+ require_mention: requireMention,
389
+ max_message_length: 4096,
390
+ typing_simulation: true,
391
+ rate_limit_messages_per_second: 1,
392
+ rate_limit_groups_per_minute: 20,
393
+ admin_ids: [userId],
394
+ agent_channel: null,
395
+ debounce_ms: 1500,
396
+ bot_token: "YOUR_BOT_TOKEN_FROM_BOTFATHER",
397
+ bot_username: "your_deals_bot"
398
+ },
399
+ storage: {
400
+ sessions_file: `${workspace.root}/sessions.json`,
401
+ pairing_file: `${workspace.root}/pairing.json`,
402
+ memory_file: `${workspace.root}/memory.json`,
403
+ history_limit: 100
404
+ },
405
+ casino: CasinoConfigSchema.parse({}),
406
+ deals: DealsConfigSchema.parse({}),
407
+ market: MarketConfigSchema.parse({})
408
+ };
409
+ spinner2.start("Saving configuration...");
410
+ const configYaml = YAML.stringify(config);
411
+ writeFileSync(workspace.configPath, configYaml, "utf-8");
412
+ spinner2.stop("\u2713 Configuration saved");
413
+ spinner2.start("Generating TON wallet...");
414
+ const wallet = await generateWallet();
415
+ saveWallet(wallet);
416
+ spinner2.stop("\u2713 TON wallet generated");
417
+ spinner2.start("Installing browser for market data...");
418
+ try {
419
+ execSync("npx playwright install chromium", {
420
+ stdio: "pipe",
421
+ timeout: 12e4
422
+ // 2 minutes timeout
423
+ });
424
+ spinner2.stop("\u2713 Browser installed");
425
+ } catch (err) {
426
+ spinner2.stop(
427
+ "\u26A0 Browser install failed (can be done later with: npx playwright install chromium)"
428
+ );
429
+ }
430
+ prompter.note(
431
+ "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.",
432
+ "Mnemonic Seed (24 words)"
433
+ );
434
+ prompter.note(
435
+ `Workspace: ${workspace.root}
436
+ Config: ${workspace.configPath}
437
+ Templates: SOUL.md, MEMORY.md, IDENTITY.md, USER.md
438
+ Telegram: ${phone} (API ID: ${apiId})
439
+ Admin: User ID ${userId}
440
+ Provider: ${providerMeta.displayName}
441
+ Model: ${selectedModel}
442
+ TON Wallet: ${wallet.address}`,
443
+ "Setup complete"
444
+ );
445
+ prompter.note(
446
+ "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",
447
+ "Ready"
448
+ );
449
+ prompter.outro("Good luck!");
450
+ }
451
+ async function runNonInteractiveOnboarding(options, prompter) {
452
+ if (!options.apiId || !options.apiHash || !options.phone || !options.apiKey || !options.userId) {
453
+ prompter.error(
454
+ "Non-interactive mode requires: --api-id, --api-hash, --phone, --api-key, --user-id"
455
+ );
456
+ process.exit(1);
457
+ }
458
+ const workspace = await ensureWorkspace({
459
+ workspaceDir: options.workspace,
460
+ ensureTemplates: true
461
+ });
462
+ const selectedProvider = options.provider || "anthropic";
463
+ const providerMeta = getProviderMetadata(selectedProvider);
464
+ const config = {
465
+ meta: {
466
+ version: "1.0.0",
467
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
468
+ onboard_command: "teleton setup"
469
+ },
470
+ agent: {
471
+ provider: selectedProvider,
472
+ api_key: options.apiKey,
473
+ model: providerMeta.defaultModel,
474
+ max_tokens: 4096,
475
+ temperature: 0.7,
476
+ system_prompt: null,
477
+ max_agentic_iterations: 5,
478
+ session_reset_policy: {
479
+ daily_reset_enabled: true,
480
+ daily_reset_hour: 4,
481
+ idle_expiry_enabled: true,
482
+ idle_expiry_minutes: 1440
483
+ }
484
+ },
485
+ telegram: {
486
+ api_id: options.apiId,
487
+ api_hash: options.apiHash,
488
+ phone: options.phone,
489
+ session_name: "tonnet_session",
490
+ session_path: workspace.sessionPath,
491
+ dm_policy: "open",
492
+ allow_from: [],
493
+ group_policy: "open",
494
+ group_allow_from: [],
495
+ require_mention: true,
496
+ max_message_length: 4096,
497
+ typing_simulation: true,
498
+ rate_limit_messages_per_second: 1,
499
+ rate_limit_groups_per_minute: 20,
500
+ admin_ids: [options.userId],
501
+ agent_channel: null,
502
+ debounce_ms: 1500,
503
+ bot_token: "YOUR_BOT_TOKEN_FROM_BOTFATHER",
504
+ bot_username: "your_deals_bot"
505
+ },
506
+ storage: {
507
+ sessions_file: `${workspace.root}/sessions.json`,
508
+ pairing_file: `${workspace.root}/pairing.json`,
509
+ memory_file: `${workspace.root}/memory.json`,
510
+ history_limit: 100
511
+ },
512
+ casino: CasinoConfigSchema.parse({}),
513
+ deals: DealsConfigSchema.parse({}),
514
+ market: MarketConfigSchema.parse({})
515
+ };
516
+ const configYaml = YAML.stringify(config);
517
+ writeFileSync(workspace.configPath, configYaml, "utf-8");
518
+ prompter.success(`\u2713 Configuration created: ${workspace.configPath}`);
519
+ }
520
+
521
+ // src/cli/commands/doctor.ts
522
+ import { existsSync, readFileSync, statSync } from "fs";
523
+ import { join } from "path";
524
+ import { homedir } from "os";
525
+ import { parse } from "yaml";
526
+ var green = "\x1B[32m";
527
+ var yellow = "\x1B[33m";
528
+ var red = "\x1B[31m";
529
+ var reset = "\x1B[0m";
530
+ var blue = "\x1B[34m";
531
+ function formatResult(result) {
532
+ const icon = result.status === "ok" ? `${green}\u2713${reset}` : result.status === "warn" ? `${yellow}\u26A0${reset}` : `${red}\u2717${reset}`;
533
+ return `${icon} ${result.name}: ${result.message}`;
534
+ }
535
+ async function checkConfig(workspaceDir) {
536
+ const configPath = join(workspaceDir, "config.yaml");
537
+ if (!existsSync(configPath)) {
538
+ return {
539
+ name: "Config file",
540
+ status: "error",
541
+ message: `Not found at ${configPath}`
542
+ };
543
+ }
544
+ try {
545
+ const content = readFileSync(configPath, "utf-8");
546
+ const raw = parse(content);
547
+ const result = ConfigSchema.safeParse(raw);
548
+ if (!result.success) {
549
+ return {
550
+ name: "Config file",
551
+ status: "error",
552
+ message: `Invalid: ${result.error.issues[0]?.message || "Unknown error"}`
553
+ };
554
+ }
555
+ return {
556
+ name: "Config file",
557
+ status: "ok",
558
+ message: "Valid"
559
+ };
560
+ } catch (err) {
561
+ return {
562
+ name: "Config file",
563
+ status: "error",
564
+ message: `Parse error: ${err instanceof Error ? err.message : String(err)}`
565
+ };
566
+ }
567
+ }
568
+ async function checkTelegramCredentials(workspaceDir) {
569
+ const configPath = join(workspaceDir, "config.yaml");
570
+ if (!existsSync(configPath)) {
571
+ return {
572
+ name: "Telegram credentials",
573
+ status: "error",
574
+ message: "Config not found"
575
+ };
576
+ }
577
+ try {
578
+ const content = readFileSync(configPath, "utf-8");
579
+ const config = parse(content);
580
+ if (!config.telegram?.api_id || !config.telegram?.api_hash) {
581
+ return {
582
+ name: "Telegram credentials",
583
+ status: "error",
584
+ message: "Missing API ID or API Hash"
585
+ };
586
+ }
587
+ if (!config.telegram?.phone) {
588
+ return {
589
+ name: "Telegram credentials",
590
+ status: "error",
591
+ message: "Missing phone number"
592
+ };
593
+ }
594
+ return {
595
+ name: "Telegram credentials",
596
+ status: "ok",
597
+ message: `Phone: ${config.telegram.phone}`
598
+ };
599
+ } catch {
600
+ return {
601
+ name: "Telegram credentials",
602
+ status: "error",
603
+ message: "Could not read config"
604
+ };
605
+ }
606
+ }
607
+ async function checkApiKey(workspaceDir) {
608
+ const configPath = join(workspaceDir, "config.yaml");
609
+ if (!existsSync(configPath)) {
610
+ return {
611
+ name: "API key",
612
+ status: "error",
613
+ message: "Config not found"
614
+ };
615
+ }
616
+ try {
617
+ const content = readFileSync(configPath, "utf-8");
618
+ const config = parse(content);
619
+ const provider = config.agent?.provider || "anthropic";
620
+ const apiKey = config.agent?.api_key;
621
+ let meta;
622
+ try {
623
+ meta = getProviderMetadata(provider);
624
+ } catch {
625
+ return {
626
+ name: "API key",
627
+ status: "error",
628
+ message: `Unknown provider: ${provider}`
629
+ };
630
+ }
631
+ if (!apiKey) {
632
+ return {
633
+ name: `${meta.displayName} API key`,
634
+ status: "error",
635
+ message: "Not configured"
636
+ };
637
+ }
638
+ const validationError = validateApiKeyFormat(provider, apiKey);
639
+ if (validationError) {
640
+ return {
641
+ name: `${meta.displayName} API key`,
642
+ status: "warn",
643
+ message: validationError
644
+ };
645
+ }
646
+ const maskLen = Math.min(10, apiKey.length - 4);
647
+ const masked = apiKey.substring(0, maskLen) + "..." + apiKey.substring(apiKey.length - 4);
648
+ return {
649
+ name: `${meta.displayName} API key`,
650
+ status: "ok",
651
+ message: masked
652
+ };
653
+ } catch {
654
+ return {
655
+ name: "API key",
656
+ status: "error",
657
+ message: "Could not read config"
658
+ };
659
+ }
660
+ }
661
+ async function checkWallet(workspaceDir) {
662
+ const walletPath = join(workspaceDir, "wallet.json");
663
+ if (!existsSync(walletPath)) {
664
+ return {
665
+ name: "TON wallet",
666
+ status: "warn",
667
+ message: "Not found (run teleton setup to generate)"
668
+ };
669
+ }
670
+ try {
671
+ const content = readFileSync(walletPath, "utf-8");
672
+ const wallet = JSON.parse(content);
673
+ if (!wallet.address) {
674
+ return {
675
+ name: "TON wallet",
676
+ status: "error",
677
+ message: "Invalid wallet file (no address)"
678
+ };
679
+ }
680
+ const shortAddr = wallet.address.substring(0, 8) + "..." + wallet.address.substring(wallet.address.length - 6);
681
+ return {
682
+ name: "TON wallet",
683
+ status: "ok",
684
+ message: shortAddr
685
+ };
686
+ } catch {
687
+ return {
688
+ name: "TON wallet",
689
+ status: "error",
690
+ message: "Could not read wallet file"
691
+ };
692
+ }
693
+ }
694
+ async function checkSoul(workspaceDir) {
695
+ const soulPath = join(workspaceDir, "SOUL.md");
696
+ if (!existsSync(soulPath)) {
697
+ return {
698
+ name: "SOUL.md",
699
+ status: "warn",
700
+ message: "Not found (agent will use defaults)"
701
+ };
702
+ }
703
+ try {
704
+ const stats = statSync(soulPath);
705
+ const sizeKb = (stats.size / 1024).toFixed(1);
706
+ return {
707
+ name: "SOUL.md",
708
+ status: "ok",
709
+ message: `${sizeKb} KB`
710
+ };
711
+ } catch {
712
+ return {
713
+ name: "SOUL.md",
714
+ status: "error",
715
+ message: "Could not read file"
716
+ };
717
+ }
718
+ }
719
+ async function checkDatabase(workspaceDir) {
720
+ const dbPath = join(workspaceDir, "memory.db");
721
+ if (!existsSync(dbPath)) {
722
+ return {
723
+ name: "Memory database",
724
+ status: "warn",
725
+ message: "Not found (will be created on first start)"
726
+ };
727
+ }
728
+ try {
729
+ const stats = statSync(dbPath);
730
+ const sizeMb = (stats.size / 1024 / 1024).toFixed(2);
731
+ return {
732
+ name: "Memory database",
733
+ status: "ok",
734
+ message: `${sizeMb} MB`
735
+ };
736
+ } catch {
737
+ return {
738
+ name: "Memory database",
739
+ status: "error",
740
+ message: "Could not read database"
741
+ };
742
+ }
743
+ }
744
+ async function checkTelegramSession(workspaceDir) {
745
+ const sessionPath = join(workspaceDir, "telegram_session.txt");
746
+ if (!existsSync(sessionPath)) {
747
+ return {
748
+ name: "Telegram session",
749
+ status: "warn",
750
+ message: "Not found (will prompt for login on first start)"
751
+ };
752
+ }
753
+ try {
754
+ const stats = statSync(sessionPath);
755
+ const age = Date.now() - stats.mtimeMs;
756
+ const daysAgo = Math.floor(age / (1e3 * 60 * 60 * 24));
757
+ if (daysAgo > 30) {
758
+ return {
759
+ name: "Telegram session",
760
+ status: "warn",
761
+ message: `Last updated ${daysAgo} days ago (may need re-auth)`
762
+ };
763
+ }
764
+ return {
765
+ name: "Telegram session",
766
+ status: "ok",
767
+ message: daysAgo === 0 ? "Active (today)" : `Active (${daysAgo} days ago)`
768
+ };
769
+ } catch {
770
+ return {
771
+ name: "Telegram session",
772
+ status: "error",
773
+ message: "Could not read session"
774
+ };
775
+ }
776
+ }
777
+ async function checkMarketData(workspaceDir) {
778
+ const dbPath = join(workspaceDir, "gifts.db");
779
+ if (!existsSync(dbPath)) {
780
+ return {
781
+ name: "Gift market data",
782
+ status: "warn",
783
+ message: "Not found (will fetch on first start)"
784
+ };
785
+ }
786
+ try {
787
+ const Database = (await import("better-sqlite3")).default;
788
+ const db = new Database(dbPath, { readonly: true });
789
+ const collections = db.prepare("SELECT COUNT(*) as count FROM gift_collections").get();
790
+ const models = db.prepare("SELECT COUNT(*) as count FROM gift_models").get();
791
+ const lastUpdate = db.prepare("SELECT MAX(updated_at) as last FROM gift_collections").get();
792
+ db.close();
793
+ if (!lastUpdate.last) {
794
+ return {
795
+ name: "Gift market data",
796
+ status: "warn",
797
+ message: "Database empty (no data yet)"
798
+ };
799
+ }
800
+ const lastUpdateTime = (/* @__PURE__ */ new Date(lastUpdate.last + "Z")).getTime();
801
+ const age = Date.now() - lastUpdateTime;
802
+ const hoursAgo = Math.floor(age / (1e3 * 60 * 60));
803
+ const daysAgo = Math.floor(hoursAgo / 24);
804
+ const dataInfo = `${collections.count} collections, ${models.count} models`;
805
+ if (daysAgo > 7) {
806
+ return {
807
+ name: "Gift market data",
808
+ status: "warn",
809
+ message: `Stale (${daysAgo} days old) - ${dataInfo}`
810
+ };
811
+ }
812
+ if (hoursAgo > 24) {
813
+ return {
814
+ name: "Gift market data",
815
+ status: "ok",
816
+ message: `${daysAgo} day${daysAgo > 1 ? "s" : ""} old - ${dataInfo}`
817
+ };
818
+ }
819
+ return {
820
+ name: "Gift market data",
821
+ status: "ok",
822
+ message: hoursAgo === 0 ? `Fresh (< 1h) - ${dataInfo}` : `${hoursAgo}h old - ${dataInfo}`
823
+ };
824
+ } catch (err) {
825
+ return {
826
+ name: "Gift market data",
827
+ status: "error",
828
+ message: `Database error: ${err instanceof Error ? err.message : String(err)}`
829
+ };
830
+ }
831
+ }
832
+ async function checkModel(workspaceDir) {
833
+ const configPath = join(workspaceDir, "config.yaml");
834
+ if (!existsSync(configPath)) {
835
+ return {
836
+ name: "AI Model",
837
+ status: "error",
838
+ message: "Config not found"
839
+ };
840
+ }
841
+ try {
842
+ const content = readFileSync(configPath, "utf-8");
843
+ const config = parse(content);
844
+ const provider = config.agent?.provider || "anthropic";
845
+ let model = config.agent?.model;
846
+ if (!model) {
847
+ try {
848
+ model = getProviderMetadata(provider).defaultModel;
849
+ } catch {
850
+ model = "unknown";
851
+ }
852
+ }
853
+ return {
854
+ name: "AI Model",
855
+ status: "ok",
856
+ message: `${provider}/${model}`
857
+ };
858
+ } catch {
859
+ return {
860
+ name: "AI Model",
861
+ status: "error",
862
+ message: "Could not read config"
863
+ };
864
+ }
865
+ }
866
+ async function checkAdmins(workspaceDir) {
867
+ const configPath = join(workspaceDir, "config.yaml");
868
+ if (!existsSync(configPath)) {
869
+ return {
870
+ name: "Admin users",
871
+ status: "error",
872
+ message: "Config not found"
873
+ };
874
+ }
875
+ try {
876
+ const content = readFileSync(configPath, "utf-8");
877
+ const config = parse(content);
878
+ const admins = config.telegram?.admin_ids || [];
879
+ if (admins.length === 0) {
880
+ return {
881
+ name: "Admin users",
882
+ status: "warn",
883
+ message: "None configured (no admin commands available)"
884
+ };
885
+ }
886
+ return {
887
+ name: "Admin users",
888
+ status: "ok",
889
+ message: `${admins.length} user${admins.length > 1 ? "s" : ""}: ${admins.join(", ")}`
890
+ };
891
+ } catch {
892
+ return {
893
+ name: "Admin users",
894
+ status: "error",
895
+ message: "Could not read config"
896
+ };
897
+ }
898
+ }
899
+ async function checkNodeVersion() {
900
+ const version = process.version;
901
+ const major = parseInt(version.slice(1).split(".")[0]);
902
+ if (major < 20) {
903
+ return {
904
+ name: "Node.js",
905
+ status: "error",
906
+ message: `${version} (requires >= 20.0.0)`
907
+ };
908
+ }
909
+ return {
910
+ name: "Node.js",
911
+ status: "ok",
912
+ message: version
913
+ };
914
+ }
915
+ async function checkPlaywrightBrowser() {
916
+ const homeDir = homedir();
917
+ const browserPaths = [
918
+ join(homeDir, ".cache", "ms-playwright", "chromium-*"),
919
+ join(homeDir, ".cache", "ms-playwright"),
920
+ join(homeDir, "Library", "Caches", "ms-playwright"),
921
+ join(homeDir, "AppData", "Local", "ms-playwright")
922
+ ];
923
+ for (const basePath of browserPaths) {
924
+ const checkPath = basePath.replace("/chromium-*", "");
925
+ if (existsSync(checkPath)) {
926
+ try {
927
+ const { readdirSync } = await import("fs");
928
+ const contents = readdirSync(checkPath);
929
+ const hasChromium = contents.some((f) => f.startsWith("chromium"));
930
+ if (hasChromium) {
931
+ return {
932
+ name: "Playwright browser",
933
+ status: "ok",
934
+ message: "Chromium installed"
935
+ };
936
+ }
937
+ } catch {
938
+ return {
939
+ name: "Playwright browser",
940
+ status: "ok",
941
+ message: "Cache found"
942
+ };
943
+ }
944
+ }
945
+ }
946
+ return {
947
+ name: "Playwright browser",
948
+ status: "warn",
949
+ message: "Not found (run: npx playwright install chromium)"
950
+ };
951
+ }
952
+ async function doctorCommand() {
953
+ const workspaceDir = TELETON_ROOT;
954
+ console.log(`
955
+ ${blue} \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\u2510
956
+ \u2502 TELETON DOCTOR - System Health Check \u2502
957
+ \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\u2518${reset}
958
+ `);
959
+ console.log(` Workspace: ${workspaceDir}
960
+ `);
961
+ const results = [];
962
+ console.log(" Running checks...\n");
963
+ results.push(await checkNodeVersion());
964
+ results.push(await checkPlaywrightBrowser());
965
+ results.push(await checkConfig(workspaceDir));
966
+ results.push(await checkTelegramCredentials(workspaceDir));
967
+ results.push(await checkApiKey(workspaceDir));
968
+ results.push(await checkTelegramSession(workspaceDir));
969
+ results.push(await checkWallet(workspaceDir));
970
+ results.push(await checkSoul(workspaceDir));
971
+ results.push(await checkDatabase(workspaceDir));
972
+ results.push(await checkMarketData(workspaceDir));
973
+ results.push(await checkModel(workspaceDir));
974
+ results.push(await checkAdmins(workspaceDir));
975
+ for (const result of results) {
976
+ console.log(` ${formatResult(result)}`);
977
+ }
978
+ const errors = results.filter((r) => r.status === "error").length;
979
+ const warnings = results.filter((r) => r.status === "warn").length;
980
+ const ok = results.filter((r) => r.status === "ok").length;
981
+ console.log("");
982
+ if (errors > 0) {
983
+ console.log(
984
+ `${red} \u2717 ${errors} error${errors > 1 ? "s" : ""} found - run 'teleton setup' to fix${reset}`
985
+ );
986
+ } else if (warnings > 0) {
987
+ console.log(
988
+ `${yellow} \u26A0 ${warnings} warning${warnings > 1 ? "s" : ""} - agent may work with limited features${reset}`
989
+ );
990
+ } else {
991
+ console.log(`${green} \u2713 All ${ok} checks passed - system ready${reset}`);
992
+ }
993
+ console.log("");
994
+ }
995
+
996
+ // src/cli/index.ts
997
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
998
+ import { dirname, join as join2 } from "path";
999
+ import { fileURLToPath } from "url";
1000
+ function findPackageJson() {
1001
+ let dir = dirname(fileURLToPath(import.meta.url));
1002
+ for (let i = 0; i < 10; i++) {
1003
+ const candidate = join2(dir, "package.json");
1004
+ if (existsSync2(candidate)) {
1005
+ return JSON.parse(readFileSync2(candidate, "utf-8"));
1006
+ }
1007
+ dir = dirname(dir);
1008
+ }
1009
+ return { version: "0.0.0" };
1010
+ }
1011
+ var packageJson = findPackageJson();
1012
+ var program = new Command();
1013
+ program.name("teleton").description("Teleton Agent - Personal AI Agent for Telegram").version(packageJson.version);
1014
+ program.command("setup").description("Interactive wizard to set up Teleton").option("--workspace <dir>", "Workspace directory (default: ~/.teleton)").option("--non-interactive", "Non-interactive mode (requires all options)").option("--api-id <id>", "Telegram API ID").option("--api-hash <hash>", "Telegram API Hash").option("--phone <number>", "Phone number (international format)").option("--api-key <key>", "Anthropic API key").option("--user-id <id>", "Your Telegram User ID (for admin)").action(async (options) => {
1015
+ try {
1016
+ await onboardCommand({
1017
+ workspace: options.workspace,
1018
+ nonInteractive: options.nonInteractive,
1019
+ apiId: options.apiId ? parseInt(options.apiId) : void 0,
1020
+ apiHash: options.apiHash,
1021
+ phone: options.phone,
1022
+ apiKey: options.apiKey,
1023
+ userId: options.userId ? parseInt(options.userId) : void 0
1024
+ });
1025
+ } catch (error) {
1026
+ console.error("Erreur:", error instanceof Error ? error.message : String(error));
1027
+ process.exit(1);
1028
+ }
1029
+ });
1030
+ program.command("start").description("Start the Teleton agent").option("-c, --config <path>", "Config file path", getDefaultConfigPath()).action(async (options) => {
1031
+ try {
1032
+ if (!configExists(options.config)) {
1033
+ console.error("\u274C Configuration non trouv\xE9e");
1034
+ console.error(` Fichier attendu: ${options.config}`);
1035
+ console.error("\n\u{1F4A1} Lance d'abord: teleton setup");
1036
+ process.exit(1);
1037
+ }
1038
+ await main(options.config);
1039
+ } catch (error) {
1040
+ console.error("Erreur:", error instanceof Error ? error.message : String(error));
1041
+ process.exit(1);
1042
+ }
1043
+ });
1044
+ program.command("doctor").description("Run system health checks").action(async () => {
1045
+ try {
1046
+ await doctorCommand();
1047
+ } catch (error) {
1048
+ console.error("Error:", error instanceof Error ? error.message : String(error));
1049
+ process.exit(1);
1050
+ }
1051
+ });
1052
+ program.action(() => {
1053
+ program.help();
1054
+ });
1055
+ program.parse(process.argv);