teleton 0.8.3 → 0.8.4

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,908 @@
1
+ import {
2
+ ConfigSchema,
3
+ expandPath
4
+ } from "./chunk-NH2CNRKJ.js";
5
+ import {
6
+ COINGECKO_API_URL,
7
+ tonapiFetch
8
+ } from "./chunk-VFA7QMCZ.js";
9
+ import {
10
+ getSupportedProviders
11
+ } from "./chunk-6OOHHJ4N.js";
12
+ import {
13
+ fetchWithTimeout
14
+ } from "./chunk-XQUHC3JZ.js";
15
+ import {
16
+ TELETON_ROOT
17
+ } from "./chunk-EYWNOHMJ.js";
18
+ import {
19
+ createLogger
20
+ } from "./chunk-NQ6FZKCE.js";
21
+
22
+ // src/ton/endpoint.ts
23
+ var ENDPOINT_CACHE_TTL_MS = 6e4;
24
+ var ORBS_HOST = "ton.access.orbs.network";
25
+ var ORBS_TOPOLOGY_URL = `https://${ORBS_HOST}/mngr/nodes?npm_version=2.3.3`;
26
+ var TONCENTER_URL = `https://toncenter.com/api/v2/jsonRPC`;
27
+ var _cache = null;
28
+ var _toncenterApiKey;
29
+ function setToncenterApiKey(key) {
30
+ _toncenterApiKey = key;
31
+ }
32
+ function getToncenterApiKey() {
33
+ return _toncenterApiKey;
34
+ }
35
+ async function discoverOrbsEndpoint() {
36
+ const res = await fetch(ORBS_TOPOLOGY_URL, { signal: AbortSignal.timeout(5e3) });
37
+ const nodes = await res.json();
38
+ const healthy = nodes.filter(
39
+ (n) => n.Healthy === "1" && n.Weight > 0 && n.Mngr?.health?.["v2-mainnet"]
40
+ );
41
+ if (healthy.length === 0) throw new Error("no healthy orbs nodes");
42
+ const totalWeight = healthy.reduce((sum, n) => sum + n.Weight, 0);
43
+ let r = Math.floor(Math.random() * totalWeight);
44
+ let chosen = healthy[0];
45
+ for (const node of healthy) {
46
+ r -= node.Weight;
47
+ if (r < 0) {
48
+ chosen = node;
49
+ break;
50
+ }
51
+ }
52
+ return `https://${ORBS_HOST}/${chosen.NodeId}/1/mainnet/toncenter-api-v2/jsonRPC`;
53
+ }
54
+ async function getCachedHttpEndpoint() {
55
+ if (_cache && Date.now() - _cache.ts < ENDPOINT_CACHE_TTL_MS) {
56
+ return _cache.url;
57
+ }
58
+ let url;
59
+ if (_toncenterApiKey) {
60
+ try {
61
+ const testUrl = `https://toncenter.com/api/v2/getAddressInformation?address=EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c`;
62
+ const res = await fetch(testUrl, {
63
+ headers: { "X-API-Key": _toncenterApiKey },
64
+ signal: AbortSignal.timeout(5e3)
65
+ });
66
+ if (!res.ok) throw new Error(`TonCenter ${res.status}`);
67
+ url = TONCENTER_URL;
68
+ } catch {
69
+ try {
70
+ url = await discoverOrbsEndpoint();
71
+ } catch {
72
+ url = TONCENTER_URL;
73
+ }
74
+ }
75
+ } else {
76
+ try {
77
+ url = await discoverOrbsEndpoint();
78
+ } catch {
79
+ url = TONCENTER_URL;
80
+ }
81
+ }
82
+ _cache = { url, ts: Date.now() };
83
+ return url;
84
+ }
85
+ function invalidateEndpointCache() {
86
+ _cache = null;
87
+ }
88
+
89
+ // src/ton/wallet-service.ts
90
+ import { mnemonicNew, mnemonicToPrivateKey, mnemonicValidate } from "@ton/crypto";
91
+ import { WalletContractV5R1, TonClient, fromNano } from "@ton/ton";
92
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
93
+ import { join, dirname } from "path";
94
+ var log = createLogger("TON");
95
+ var WALLET_FILE = join(TELETON_ROOT, "wallet.json");
96
+ var _walletCache;
97
+ var _keyPairCache = null;
98
+ var _tonClientCache = null;
99
+ async function generateWallet() {
100
+ const mnemonic = await mnemonicNew(24);
101
+ const keyPair = await mnemonicToPrivateKey(mnemonic);
102
+ const wallet = WalletContractV5R1.create({
103
+ workchain: 0,
104
+ publicKey: keyPair.publicKey
105
+ });
106
+ const address = wallet.address.toString({ bounceable: true, testOnly: false });
107
+ return {
108
+ version: "w5r1",
109
+ address,
110
+ publicKey: keyPair.publicKey.toString("hex"),
111
+ mnemonic,
112
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
113
+ };
114
+ }
115
+ function saveWallet(wallet) {
116
+ const dir = dirname(WALLET_FILE);
117
+ if (!existsSync(dir)) {
118
+ mkdirSync(dir, { recursive: true });
119
+ }
120
+ writeFileSync(WALLET_FILE, JSON.stringify(wallet, null, 2), { encoding: "utf-8", mode: 384 });
121
+ _walletCache = void 0;
122
+ _keyPairCache = null;
123
+ }
124
+ function loadWallet() {
125
+ if (_walletCache !== void 0) return _walletCache;
126
+ if (!existsSync(WALLET_FILE)) {
127
+ _walletCache = null;
128
+ return null;
129
+ }
130
+ try {
131
+ const content = readFileSync(WALLET_FILE, "utf-8");
132
+ const parsed = JSON.parse(content);
133
+ if (!parsed.mnemonic || !Array.isArray(parsed.mnemonic) || parsed.mnemonic.length !== 24) {
134
+ throw new Error("Invalid wallet.json: mnemonic must be a 24-word array");
135
+ }
136
+ _walletCache = parsed;
137
+ return _walletCache;
138
+ } catch (error) {
139
+ log.error({ err: error }, "Failed to load wallet");
140
+ _walletCache = null;
141
+ return null;
142
+ }
143
+ }
144
+ function walletExists() {
145
+ return existsSync(WALLET_FILE);
146
+ }
147
+ async function importWallet(mnemonic) {
148
+ const valid = await mnemonicValidate(mnemonic);
149
+ if (!valid) {
150
+ throw new Error("Invalid mnemonic: words do not form a valid TON seed phrase");
151
+ }
152
+ const keyPair = await mnemonicToPrivateKey(mnemonic);
153
+ const wallet = WalletContractV5R1.create({
154
+ workchain: 0,
155
+ publicKey: keyPair.publicKey
156
+ });
157
+ const address = wallet.address.toString({ bounceable: true, testOnly: false });
158
+ return {
159
+ version: "w5r1",
160
+ address,
161
+ publicKey: keyPair.publicKey.toString("hex"),
162
+ mnemonic,
163
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
164
+ };
165
+ }
166
+ function getWalletAddress() {
167
+ const wallet = loadWallet();
168
+ return wallet?.address || null;
169
+ }
170
+ async function getCachedTonClient() {
171
+ const endpoint = await getCachedHttpEndpoint();
172
+ if (_tonClientCache && _tonClientCache.endpoint === endpoint) {
173
+ return _tonClientCache.client;
174
+ }
175
+ const apiKey = getToncenterApiKey();
176
+ const client = new TonClient({ endpoint, ...apiKey && { apiKey } });
177
+ _tonClientCache = { client, endpoint };
178
+ return client;
179
+ }
180
+ function invalidateTonClientCache() {
181
+ _tonClientCache = null;
182
+ invalidateEndpointCache();
183
+ }
184
+ async function getKeyPair() {
185
+ if (_keyPairCache) return _keyPairCache;
186
+ const wallet = loadWallet();
187
+ if (!wallet) return null;
188
+ _keyPairCache = await mnemonicToPrivateKey(wallet.mnemonic);
189
+ return _keyPairCache;
190
+ }
191
+ async function getWalletBalance(address) {
192
+ try {
193
+ const client = await getCachedTonClient();
194
+ const { Address } = await import("@ton/core");
195
+ const addressObj = Address.parse(address);
196
+ const balance = await client.getBalance(addressObj);
197
+ const balanceFormatted = fromNano(balance);
198
+ return {
199
+ balance: balanceFormatted,
200
+ balanceNano: balance.toString()
201
+ };
202
+ } catch (error) {
203
+ log.error({ err: error }, "Failed to get balance");
204
+ return null;
205
+ }
206
+ }
207
+ var TON_PRICE_CACHE_TTL_MS = 3e4;
208
+ var _tonPriceCache = null;
209
+ async function getTonPrice() {
210
+ if (_tonPriceCache && Date.now() - _tonPriceCache.timestamp < TON_PRICE_CACHE_TTL_MS) {
211
+ return { ..._tonPriceCache };
212
+ }
213
+ try {
214
+ const response = await tonapiFetch(`/rates?tokens=ton&currencies=usd`);
215
+ if (response.ok) {
216
+ const data = await response.json();
217
+ const price = data?.rates?.TON?.prices?.USD;
218
+ if (typeof price === "number" && price > 0) {
219
+ _tonPriceCache = { usd: price, source: "TonAPI", timestamp: Date.now() };
220
+ return _tonPriceCache;
221
+ }
222
+ }
223
+ } catch {
224
+ }
225
+ try {
226
+ const response = await fetchWithTimeout(
227
+ `${COINGECKO_API_URL}/simple/price?ids=the-open-network&vs_currencies=usd`
228
+ );
229
+ if (!response.ok) {
230
+ throw new Error(`CoinGecko API error: ${response.status}`);
231
+ }
232
+ const data = await response.json();
233
+ const price = data["the-open-network"]?.usd;
234
+ if (typeof price === "number" && price > 0) {
235
+ _tonPriceCache = { usd: price, source: "CoinGecko", timestamp: Date.now() };
236
+ return _tonPriceCache;
237
+ }
238
+ } catch (error) {
239
+ log.error({ err: error }, "Failed to get TON price");
240
+ }
241
+ return null;
242
+ }
243
+
244
+ // src/config/configurable-keys.ts
245
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
246
+ import { parse, stringify } from "yaml";
247
+ var noValidation = () => void 0;
248
+ var identity = (v) => v;
249
+ var nonEmpty = (v) => v.length > 0 ? void 0 : "Must not be empty";
250
+ function numberInRange(min, max) {
251
+ return (v) => {
252
+ const n = Number(v);
253
+ if (isNaN(n)) return "Must be a number";
254
+ if (n < min || n > max) return `Must be between ${min} and ${max}`;
255
+ return void 0;
256
+ };
257
+ }
258
+ function enumValidator(options) {
259
+ return (v) => options.includes(v) ? void 0 : `Must be one of: ${options.join(", ")}`;
260
+ }
261
+ function positiveInteger(v) {
262
+ const n = Number(v);
263
+ if (!Number.isInteger(n) || n <= 0) return "Must be a positive integer";
264
+ return void 0;
265
+ }
266
+ function validateUrl(v) {
267
+ if (v === "") return void 0;
268
+ if (v.startsWith("http://") || v.startsWith("https://")) return void 0;
269
+ return "Must be empty or start with http:// or https://";
270
+ }
271
+ var CONFIGURABLE_KEYS = {
272
+ // ─── API Keys ──────────────────────────────────────────────────────
273
+ "agent.api_key": {
274
+ type: "string",
275
+ category: "API Keys",
276
+ label: "LLM API Key",
277
+ description: "LLM provider API key",
278
+ sensitive: true,
279
+ hotReload: "instant",
280
+ validate: (v) => v.length >= 10 ? void 0 : "Must be at least 10 characters",
281
+ mask: (v) => v.slice(0, 8) + "****",
282
+ parse: identity
283
+ },
284
+ tavily_api_key: {
285
+ type: "string",
286
+ category: "API Keys",
287
+ label: "Tavily API Key",
288
+ description: "Tavily API key for web search",
289
+ sensitive: true,
290
+ hotReload: "instant",
291
+ validate: (v) => v.startsWith("tvly-") ? void 0 : "Must start with 'tvly-'",
292
+ mask: (v) => v.slice(0, 9) + "****",
293
+ parse: identity
294
+ },
295
+ tonapi_key: {
296
+ type: "string",
297
+ category: "API Keys",
298
+ label: "TonAPI Key",
299
+ description: "TonAPI key for higher rate limits",
300
+ sensitive: true,
301
+ hotReload: "instant",
302
+ validate: (v) => v.length >= 10 ? void 0 : "Must be at least 10 characters",
303
+ mask: (v) => v.slice(0, 10) + "****",
304
+ parse: identity
305
+ },
306
+ toncenter_api_key: {
307
+ type: "string",
308
+ category: "API Keys",
309
+ label: "TonCenter API Key",
310
+ description: "TonCenter API key for dedicated RPC endpoint (free at toncenter.com)",
311
+ sensitive: true,
312
+ hotReload: "instant",
313
+ validate: (v) => v.length >= 10 ? void 0 : "Must be at least 10 characters",
314
+ mask: (v) => v.slice(0, 10) + "****",
315
+ parse: identity
316
+ },
317
+ "telegram.bot_token": {
318
+ type: "string",
319
+ category: "API Keys",
320
+ label: "Bot Token",
321
+ description: "Bot token from @BotFather",
322
+ sensitive: true,
323
+ hotReload: "instant",
324
+ validate: (v) => v.includes(":") ? void 0 : "Must contain ':' (e.g., 123456:ABC...)",
325
+ mask: (v) => v.split(":")[0] + ":****",
326
+ parse: identity
327
+ },
328
+ // ─── Agent ─────────────────────────────────────────────────────────
329
+ "agent.provider": {
330
+ type: "enum",
331
+ category: "Agent",
332
+ label: "Provider",
333
+ description: "LLM provider",
334
+ sensitive: false,
335
+ hotReload: "instant",
336
+ options: getSupportedProviders().map((p) => p.id),
337
+ validate: enumValidator(getSupportedProviders().map((p) => p.id)),
338
+ mask: identity,
339
+ parse: identity
340
+ },
341
+ "agent.model": {
342
+ type: "string",
343
+ category: "Agent",
344
+ label: "Model",
345
+ description: "Main LLM model ID",
346
+ sensitive: false,
347
+ hotReload: "instant",
348
+ validate: nonEmpty,
349
+ mask: identity,
350
+ parse: identity
351
+ },
352
+ "agent.utility_model": {
353
+ type: "string",
354
+ category: "Agent",
355
+ label: "Utility Model",
356
+ description: "Cheap model for summarization (auto-detected if empty)",
357
+ sensitive: false,
358
+ hotReload: "instant",
359
+ validate: noValidation,
360
+ mask: identity,
361
+ parse: identity
362
+ },
363
+ "agent.temperature": {
364
+ type: "number",
365
+ category: "Agent",
366
+ label: "Temperature",
367
+ description: "Response creativity (0.0 = deterministic, 2.0 = max)",
368
+ sensitive: false,
369
+ hotReload: "instant",
370
+ validate: numberInRange(0, 2),
371
+ mask: identity,
372
+ parse: (v) => Number(v)
373
+ },
374
+ "agent.max_tokens": {
375
+ type: "number",
376
+ category: "Agent",
377
+ label: "Max Tokens",
378
+ description: "Maximum response length in tokens",
379
+ sensitive: false,
380
+ hotReload: "instant",
381
+ validate: numberInRange(256, 128e3),
382
+ mask: identity,
383
+ parse: (v) => Number(v)
384
+ },
385
+ "agent.max_agentic_iterations": {
386
+ type: "number",
387
+ category: "Agent",
388
+ label: "Max Iterations",
389
+ description: "Max tool-call loop iterations per message",
390
+ sensitive: false,
391
+ hotReload: "instant",
392
+ validate: numberInRange(1, 20),
393
+ mask: identity,
394
+ parse: (v) => Number(v)
395
+ },
396
+ "agent.base_url": {
397
+ type: "string",
398
+ category: "Agent",
399
+ label: "API Base URL",
400
+ description: "Base URL for local LLM server (requires restart)",
401
+ sensitive: false,
402
+ hotReload: "restart",
403
+ validate: validateUrl,
404
+ mask: identity,
405
+ parse: identity
406
+ },
407
+ "cocoon.port": {
408
+ type: "number",
409
+ category: "Agent",
410
+ label: "Cocoon Port",
411
+ description: "Cocoon proxy port (requires restart)",
412
+ sensitive: false,
413
+ hotReload: "restart",
414
+ validate: numberInRange(1, 65535),
415
+ mask: identity,
416
+ parse: (v) => Number(v)
417
+ },
418
+ // ─── Session ───────────────────────────────────────────────────
419
+ "agent.session_reset_policy.daily_reset_enabled": {
420
+ type: "boolean",
421
+ category: "Session",
422
+ label: "Daily Reset",
423
+ description: "Enable daily session reset at specified hour",
424
+ sensitive: false,
425
+ hotReload: "instant",
426
+ validate: enumValidator(["true", "false"]),
427
+ mask: identity,
428
+ parse: (v) => v === "true"
429
+ },
430
+ "agent.session_reset_policy.daily_reset_hour": {
431
+ type: "number",
432
+ category: "Session",
433
+ label: "Reset Hour",
434
+ description: "Hour (0-23 UTC) for daily session reset",
435
+ sensitive: false,
436
+ hotReload: "instant",
437
+ validate: numberInRange(0, 23),
438
+ mask: identity,
439
+ parse: (v) => Number(v)
440
+ },
441
+ "agent.session_reset_policy.idle_expiry_enabled": {
442
+ type: "boolean",
443
+ category: "Session",
444
+ label: "Idle Expiry",
445
+ description: "Enable automatic session expiry after idle period",
446
+ sensitive: false,
447
+ hotReload: "instant",
448
+ validate: enumValidator(["true", "false"]),
449
+ mask: identity,
450
+ parse: (v) => v === "true"
451
+ },
452
+ "agent.session_reset_policy.idle_expiry_minutes": {
453
+ type: "number",
454
+ category: "Session",
455
+ label: "Idle Minutes",
456
+ description: "Idle minutes before session expires (minimum 1)",
457
+ sensitive: false,
458
+ hotReload: "instant",
459
+ validate: numberInRange(1, Number.MAX_SAFE_INTEGER),
460
+ mask: identity,
461
+ parse: (v) => Number(v)
462
+ },
463
+ // ─── Telegram ──────────────────────────────────────────────────────
464
+ "telegram.bot_username": {
465
+ type: "string",
466
+ category: "Telegram",
467
+ label: "Bot Username",
468
+ description: "Bot username without @",
469
+ sensitive: false,
470
+ hotReload: "instant",
471
+ validate: (v) => v.length >= 3 ? void 0 : "Must be at least 3 characters",
472
+ mask: identity,
473
+ parse: identity
474
+ },
475
+ "telegram.dm_policy": {
476
+ type: "enum",
477
+ category: "Telegram",
478
+ label: "DM Policy",
479
+ description: "Who can message the bot in private",
480
+ sensitive: false,
481
+ hotReload: "instant",
482
+ options: ["admin-only", "allowlist", "open", "disabled"],
483
+ optionLabels: {
484
+ "admin-only": "Admin Only",
485
+ allowlist: "Allow Users",
486
+ open: "Open",
487
+ disabled: "Disabled"
488
+ },
489
+ validate: enumValidator(["open", "allowlist", "admin-only", "disabled"]),
490
+ mask: identity,
491
+ parse: identity
492
+ },
493
+ "telegram.group_policy": {
494
+ type: "enum",
495
+ category: "Telegram",
496
+ label: "Group Policy",
497
+ description: "Which groups the bot can respond in",
498
+ sensitive: false,
499
+ hotReload: "instant",
500
+ options: ["open", "allowlist", "admin-only", "disabled"],
501
+ optionLabels: {
502
+ open: "Open",
503
+ allowlist: "Allow Groups",
504
+ "admin-only": "Admin Only",
505
+ disabled: "Disabled"
506
+ },
507
+ validate: enumValidator(["open", "allowlist", "admin-only", "disabled"]),
508
+ mask: identity,
509
+ parse: identity
510
+ },
511
+ "telegram.require_mention": {
512
+ type: "boolean",
513
+ category: "Telegram",
514
+ label: "Require Mention",
515
+ description: "Require @mention in groups to respond",
516
+ sensitive: false,
517
+ hotReload: "instant",
518
+ validate: enumValidator(["true", "false"]),
519
+ mask: identity,
520
+ parse: (v) => v === "true"
521
+ },
522
+ "telegram.owner_name": {
523
+ type: "string",
524
+ category: "Telegram",
525
+ label: "Owner Name",
526
+ description: "Owner's first name (used in system prompt)",
527
+ sensitive: false,
528
+ hotReload: "instant",
529
+ validate: noValidation,
530
+ mask: identity,
531
+ parse: identity
532
+ },
533
+ "telegram.owner_username": {
534
+ type: "string",
535
+ category: "Telegram",
536
+ label: "Owner Username",
537
+ description: "Owner's Telegram username (without @)",
538
+ sensitive: false,
539
+ hotReload: "instant",
540
+ validate: noValidation,
541
+ mask: identity,
542
+ parse: identity
543
+ },
544
+ "telegram.debounce_ms": {
545
+ type: "number",
546
+ category: "Telegram",
547
+ label: "Debounce (ms)",
548
+ description: "Group message debounce delay in ms (0 = disabled)",
549
+ sensitive: false,
550
+ hotReload: "instant",
551
+ validate: numberInRange(0, 1e4),
552
+ mask: identity,
553
+ parse: (v) => Number(v)
554
+ },
555
+ "telegram.agent_channel": {
556
+ type: "string",
557
+ category: "Telegram",
558
+ label: "Agent Channel",
559
+ description: "Channel username for auto-publishing",
560
+ sensitive: false,
561
+ hotReload: "instant",
562
+ validate: noValidation,
563
+ mask: identity,
564
+ parse: identity
565
+ },
566
+ "telegram.typing_simulation": {
567
+ type: "boolean",
568
+ category: "Telegram",
569
+ label: "Typing Simulation",
570
+ description: "Simulate typing indicator before sending replies",
571
+ sensitive: false,
572
+ hotReload: "instant",
573
+ validate: enumValidator(["true", "false"]),
574
+ mask: identity,
575
+ parse: (v) => v === "true"
576
+ },
577
+ "telegram.owner_id": {
578
+ type: "number",
579
+ category: "Telegram",
580
+ label: "Admin ID",
581
+ description: "Primary admin Telegram user ID (auto-added to Admin IDs)",
582
+ sensitive: false,
583
+ hotReload: "instant",
584
+ validate: positiveInteger,
585
+ mask: identity,
586
+ parse: (v) => Number(v)
587
+ },
588
+ "telegram.max_message_length": {
589
+ type: "number",
590
+ category: "Telegram",
591
+ label: "Max Message Length",
592
+ description: "Maximum message length in characters",
593
+ sensitive: false,
594
+ hotReload: "instant",
595
+ validate: numberInRange(1, 32768),
596
+ mask: identity,
597
+ parse: (v) => Number(v)
598
+ },
599
+ "telegram.rate_limit_messages_per_second": {
600
+ type: "number",
601
+ category: "Telegram",
602
+ label: "Rate Limit \u2014 Messages/sec",
603
+ description: "Rate limit: messages per second (requires restart)",
604
+ sensitive: false,
605
+ hotReload: "restart",
606
+ validate: numberInRange(0.1, 10),
607
+ mask: identity,
608
+ parse: (v) => Number(v)
609
+ },
610
+ "telegram.rate_limit_groups_per_minute": {
611
+ type: "number",
612
+ category: "Telegram",
613
+ label: "Rate Limit \u2014 Groups/min",
614
+ description: "Rate limit: groups per minute (requires restart)",
615
+ sensitive: false,
616
+ hotReload: "restart",
617
+ validate: numberInRange(1, 60),
618
+ mask: identity,
619
+ parse: (v) => Number(v)
620
+ },
621
+ "telegram.admin_ids": {
622
+ type: "array",
623
+ itemType: "number",
624
+ category: "Telegram",
625
+ label: "Admin IDs",
626
+ description: "Admin user IDs with elevated access",
627
+ sensitive: false,
628
+ hotReload: "instant",
629
+ validate: positiveInteger,
630
+ mask: identity,
631
+ parse: (v) => Number(v)
632
+ },
633
+ "telegram.allow_from": {
634
+ type: "array",
635
+ itemType: "number",
636
+ category: "Telegram",
637
+ label: "Allowed Users",
638
+ description: "User IDs allowed for DM access",
639
+ sensitive: false,
640
+ hotReload: "instant",
641
+ validate: positiveInteger,
642
+ mask: identity,
643
+ parse: (v) => Number(v)
644
+ },
645
+ "telegram.group_allow_from": {
646
+ type: "array",
647
+ itemType: "number",
648
+ category: "Telegram",
649
+ label: "Allowed Groups",
650
+ description: "Group IDs allowed for group access",
651
+ sensitive: false,
652
+ hotReload: "instant",
653
+ validate: positiveInteger,
654
+ mask: identity,
655
+ parse: (v) => Number(v)
656
+ },
657
+ // ─── Embedding ─────────────────────────────────────────────────────
658
+ "embedding.provider": {
659
+ type: "enum",
660
+ category: "Embedding",
661
+ label: "Embedding Provider",
662
+ description: "Embedding provider for RAG",
663
+ sensitive: false,
664
+ hotReload: "instant",
665
+ options: ["local", "anthropic", "none"],
666
+ validate: enumValidator(["local", "anthropic", "none"]),
667
+ mask: identity,
668
+ parse: identity
669
+ },
670
+ "embedding.model": {
671
+ type: "string",
672
+ category: "Embedding",
673
+ label: "Embedding Model",
674
+ description: "Embedding model ID (requires restart)",
675
+ sensitive: false,
676
+ hotReload: "restart",
677
+ validate: noValidation,
678
+ mask: identity,
679
+ parse: identity
680
+ },
681
+ // ─── WebUI ─────────────────────────────────────────────────────────
682
+ "webui.port": {
683
+ type: "number",
684
+ category: "WebUI",
685
+ label: "WebUI Port",
686
+ description: "HTTP server port (requires restart)",
687
+ sensitive: false,
688
+ hotReload: "restart",
689
+ validate: numberInRange(1024, 65535),
690
+ mask: identity,
691
+ parse: (v) => Number(v)
692
+ },
693
+ "webui.log_requests": {
694
+ type: "boolean",
695
+ category: "WebUI",
696
+ label: "Log HTTP Requests",
697
+ description: "Log all HTTP requests to console",
698
+ sensitive: false,
699
+ hotReload: "instant",
700
+ validate: enumValidator(["true", "false"]),
701
+ mask: identity,
702
+ parse: (v) => v === "true"
703
+ },
704
+ // ─── Deals ─────────────────────────────────────────────────────────
705
+ "deals.enabled": {
706
+ type: "boolean",
707
+ category: "Deals",
708
+ label: "Deals Enabled",
709
+ description: "Enable the deals/escrow module",
710
+ sensitive: false,
711
+ hotReload: "instant",
712
+ validate: enumValidator(["true", "false"]),
713
+ mask: identity,
714
+ parse: (v) => v === "true"
715
+ },
716
+ "deals.expiry_seconds": {
717
+ type: "number",
718
+ category: "Deals",
719
+ label: "Deal Expiry",
720
+ description: "Deal expiry timeout in seconds",
721
+ sensitive: false,
722
+ hotReload: "instant",
723
+ validate: numberInRange(10, 3600),
724
+ mask: identity,
725
+ parse: (v) => Number(v)
726
+ },
727
+ "deals.buy_max_floor_percent": {
728
+ type: "number",
729
+ category: "Deals",
730
+ label: "Buy Max Floor %",
731
+ description: "Maximum floor % for buy deals",
732
+ sensitive: false,
733
+ hotReload: "instant",
734
+ validate: numberInRange(1, 100),
735
+ mask: identity,
736
+ parse: (v) => Number(v)
737
+ },
738
+ "deals.sell_min_floor_percent": {
739
+ type: "number",
740
+ category: "Deals",
741
+ label: "Sell Min Floor %",
742
+ description: "Minimum floor % for sell deals",
743
+ sensitive: false,
744
+ hotReload: "instant",
745
+ validate: numberInRange(100, 500),
746
+ mask: identity,
747
+ parse: (v) => Number(v)
748
+ },
749
+ // ─── TON Proxy ────────────────────────────────────────────────────
750
+ "ton_proxy.enabled": {
751
+ type: "boolean",
752
+ category: "TON Proxy",
753
+ label: "TON Proxy Enabled",
754
+ description: "Enable Tonutils-Proxy for .ton site access (auto-downloads binary on first run)",
755
+ sensitive: false,
756
+ hotReload: "instant",
757
+ validate: enumValidator(["true", "false"]),
758
+ mask: identity,
759
+ parse: (v) => v === "true"
760
+ },
761
+ "ton_proxy.port": {
762
+ type: "number",
763
+ category: "TON Proxy",
764
+ label: "Proxy Port",
765
+ description: "HTTP proxy port for .ton sites (default: 8080)",
766
+ sensitive: false,
767
+ hotReload: "restart",
768
+ validate: numberInRange(1, 65535),
769
+ mask: identity,
770
+ parse: (v) => Number(v)
771
+ },
772
+ "ton_proxy.binary_path": {
773
+ type: "string",
774
+ category: "TON Proxy",
775
+ label: "Binary Path",
776
+ description: "Custom path to tonutils-proxy-cli (leave empty for auto-download)",
777
+ sensitive: false,
778
+ hotReload: "restart",
779
+ validate: noValidation,
780
+ mask: identity,
781
+ parse: identity
782
+ },
783
+ // ─── Capabilities ──────────────────────────────────────────────────
784
+ "capabilities.exec.mode": {
785
+ type: "enum",
786
+ category: "Coding Agent",
787
+ label: "Exec Mode",
788
+ description: "System execution: off (disabled) or yolo (full system access)",
789
+ sensitive: false,
790
+ hotReload: "restart",
791
+ options: ["off", "yolo"],
792
+ optionLabels: { off: "Disabled", yolo: "YOLO" },
793
+ validate: enumValidator(["off", "yolo"]),
794
+ mask: identity,
795
+ parse: identity
796
+ },
797
+ "capabilities.exec.scope": {
798
+ type: "enum",
799
+ category: "Coding Agent",
800
+ label: "Exec Scope",
801
+ description: "Who can trigger exec tools",
802
+ sensitive: false,
803
+ hotReload: "restart",
804
+ options: ["admin-only", "allowlist", "all"],
805
+ optionLabels: { "admin-only": "Admin Only", allowlist: "Allowlist", all: "Everyone" },
806
+ validate: enumValidator(["admin-only", "allowlist", "all"]),
807
+ mask: identity,
808
+ parse: identity
809
+ },
810
+ // ─── Developer ─────────────────────────────────────────────────────
811
+ "dev.hot_reload": {
812
+ type: "boolean",
813
+ category: "Developer",
814
+ label: "Hot Reload",
815
+ description: "Watch ~/.teleton/plugins/ for live changes",
816
+ sensitive: false,
817
+ hotReload: "instant",
818
+ validate: enumValidator(["true", "false"]),
819
+ mask: identity,
820
+ parse: (v) => v === "true"
821
+ }
822
+ };
823
+ var FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
824
+ function assertSafePath(parts) {
825
+ if (parts.some((p) => FORBIDDEN_SEGMENTS.has(p))) {
826
+ throw new Error("Invalid config path: forbidden segment");
827
+ }
828
+ }
829
+ function getNestedValue(obj, path) {
830
+ const parts = path.split(".");
831
+ assertSafePath(parts);
832
+ let current = obj;
833
+ for (const part of parts) {
834
+ if (current == null || typeof current !== "object") return void 0;
835
+ current = current[part];
836
+ }
837
+ return current;
838
+ }
839
+ function setNestedValue(obj, path, value) {
840
+ const parts = path.split(".");
841
+ assertSafePath(parts);
842
+ let current = obj;
843
+ for (let i = 0; i < parts.length - 1; i++) {
844
+ if (current[parts[i]] == null || typeof current[parts[i]] !== "object") {
845
+ current[parts[i]] = {};
846
+ }
847
+ current = current[parts[i]];
848
+ }
849
+ current[parts[parts.length - 1]] = value;
850
+ }
851
+ function deleteNestedValue(obj, path) {
852
+ const parts = path.split(".");
853
+ assertSafePath(parts);
854
+ let current = obj;
855
+ for (let i = 0; i < parts.length - 1; i++) {
856
+ if (current == null || typeof current !== "object") return;
857
+ current = current[parts[i]];
858
+ }
859
+ if (current != null && typeof current === "object") {
860
+ delete current[parts[parts.length - 1]];
861
+ }
862
+ }
863
+ function readRawConfig(configPath) {
864
+ const fullPath = expandPath(configPath);
865
+ if (!existsSync2(fullPath)) {
866
+ throw new Error(`Config file not found: ${fullPath}
867
+ Run 'teleton setup' to create one.`);
868
+ }
869
+ const raw = parse(readFileSync2(fullPath, "utf-8"));
870
+ if (!raw || typeof raw !== "object") {
871
+ throw new Error(`Invalid config file: ${fullPath}`);
872
+ }
873
+ return raw;
874
+ }
875
+ function writeRawConfig(raw, configPath) {
876
+ const clone = { ...raw };
877
+ delete clone.market;
878
+ const result = ConfigSchema.safeParse(clone);
879
+ if (!result.success) {
880
+ throw new Error(`Refusing to save invalid config: ${result.error.message}`);
881
+ }
882
+ raw.meta = raw.meta ?? {};
883
+ raw.meta.last_modified_at = (/* @__PURE__ */ new Date()).toISOString();
884
+ const fullPath = expandPath(configPath);
885
+ writeFileSync2(fullPath, stringify(raw), { encoding: "utf-8", mode: 384 });
886
+ }
887
+
888
+ export {
889
+ setToncenterApiKey,
890
+ invalidateEndpointCache,
891
+ generateWallet,
892
+ saveWallet,
893
+ loadWallet,
894
+ walletExists,
895
+ importWallet,
896
+ getWalletAddress,
897
+ getCachedTonClient,
898
+ invalidateTonClientCache,
899
+ getKeyPair,
900
+ getWalletBalance,
901
+ getTonPrice,
902
+ CONFIGURABLE_KEYS,
903
+ getNestedValue,
904
+ setNestedValue,
905
+ deleteNestedValue,
906
+ readRawConfig,
907
+ writeRawConfig
908
+ };