@t2000/cli 0.18.4 → 0.18.6

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.
@@ -1,7 +1,7 @@
1
- import { createRequire as __createRequire } from 'module'; const require = __createRequire(import.meta.url);
1
+ import { createRequire as __createRequire } from 'module'; import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const require = __createRequire(import.meta.url); const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename);
2
2
  import {
3
3
  __export
4
- } from "./chunk-2P7CF3JO.js";
4
+ } from "./chunk-YPWSCLE3.js";
5
5
 
6
6
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
7
7
  var external_exports = {};
@@ -5334,4 +5334,4 @@ export {
5334
5334
  external_exports,
5335
5335
  zodToJsonSchema
5336
5336
  };
5337
- //# sourceMappingURL=chunk-XIQ5PRLT.js.map
5337
+ //# sourceMappingURL=chunk-5TJJMPLT.js.map
@@ -1,4 +1,4 @@
1
- import { createRequire as __createRequire } from 'module'; const require = __createRequire(import.meta.url);
1
+ import { createRequire as __createRequire } from 'module'; import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const require = __createRequire(import.meta.url); const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename);
2
2
 
3
3
  // ../../node_modules/.pnpm/web-streams-polyfill@4.0.0-beta.3/node_modules/web-streams-polyfill/dist/ponyfill.mjs
4
4
  var e = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? Symbol : (e2) => `Symbol(${e2})`;
@@ -2052,4 +2052,4 @@ formdata-node/lib/esm/blobHelpers.js:
2052
2052
  formdata-node/lib/esm/Blob.js:
2053
2053
  (*! Based on fetch-blob. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> & David Frank *)
2054
2054
  */
2055
- //# sourceMappingURL=chunk-UKFUFU5T.js.map
2055
+ //# sourceMappingURL=chunk-7KJXKMEM.js.map
@@ -1,4 +1,4 @@
1
- import { createRequire as __createRequire } from 'module'; const require = __createRequire(import.meta.url);
1
+ import { createRequire as __createRequire } from 'module'; import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const require = __createRequire(import.meta.url); const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename);
2
2
  var __create = Object.create;
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -47,4 +47,4 @@ export {
47
47
  __toESM,
48
48
  __toCommonJS
49
49
  };
50
- //# sourceMappingURL=chunk-2P7CF3JO.js.map
50
+ //# sourceMappingURL=chunk-YPWSCLE3.js.map
@@ -1,14 +1,14 @@
1
- import { createRequire as __createRequire } from 'module'; const require = __createRequire(import.meta.url);
1
+ import { createRequire as __createRequire } from 'module'; import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const require = __createRequire(import.meta.url); const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename);
2
2
  import {
3
3
  external_exports,
4
4
  zodToJsonSchema
5
- } from "./chunk-XIQ5PRLT.js";
5
+ } from "./chunk-5TJJMPLT.js";
6
6
  import {
7
7
  Blob,
8
8
  File,
9
9
  isFile,
10
10
  isFunction
11
- } from "./chunk-UKFUFU5T.js";
11
+ } from "./chunk-7KJXKMEM.js";
12
12
  import {
13
13
  __commonJS,
14
14
  __esm,
@@ -16,7 +16,7 @@ import {
16
16
  __require,
17
17
  __toCommonJS,
18
18
  __toESM
19
- } from "./chunk-2P7CF3JO.js";
19
+ } from "./chunk-YPWSCLE3.js";
20
20
 
21
21
  // ../../node_modules/.pnpm/webidl-conversions@3.0.1/node_modules/webidl-conversions/lib/index.js
22
22
  var require_lib = __commonJS({
@@ -12650,7 +12650,7 @@ var require_keyboard = __commonJS({
12650
12650
  }
12651
12651
  };
12652
12652
  exports.Keyboard = Keyboard;
12653
- var InlineKeyboard = class _InlineKeyboard {
12653
+ var InlineKeyboard2 = class _InlineKeyboard {
12654
12654
  /**
12655
12655
  * Initialize a new `InlineKeyboard` with an optional two-dimensional array
12656
12656
  * of `InlineKeyboardButton` objects. This is the nested array that holds
@@ -13162,7 +13162,7 @@ var require_keyboard = __commonJS({
13162
13162
  return new _InlineKeyboard(source.map((row) => row.slice()));
13163
13163
  }
13164
13164
  };
13165
- exports.InlineKeyboard = InlineKeyboard;
13165
+ exports.InlineKeyboard = InlineKeyboard2;
13166
13166
  function transpose(grid) {
13167
13167
  var _a3;
13168
13168
  const transposed = [];
@@ -14987,6 +14987,9 @@ var require_node_cron = __commonJS({
14987
14987
 
14988
14988
  // ../gateway/dist/index.js
14989
14989
  import { createRequire } from "module";
14990
+ import { fileURLToPath } from "url";
14991
+ import { dirname, resolve, join } from "path";
14992
+ import { T2000Error, INVESTMENT_ASSETS } from "@t2000/sdk";
14990
14993
 
14991
14994
  // ../../node_modules/.pnpm/@anthropic-ai+sdk@0.39.0/node_modules/@anthropic-ai/sdk/version.mjs
14992
14995
  var VERSION = "0.39.0";
@@ -15358,7 +15361,7 @@ var MultipartBody = class {
15358
15361
  import { ReadableStream as ReadableStream2 } from "stream/web";
15359
15362
  var fileFromPathWarned = false;
15360
15363
  async function fileFromPath2(path, ...args) {
15361
- const { fileFromPath: _fileFromPath } = await import("./fileFromPath-5M6WXDSO.js");
15364
+ const { fileFromPath: _fileFromPath } = await import("./fileFromPath-TJOXOKGY.js");
15362
15365
  if (!fileFromPathWarned) {
15363
15366
  console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path)}) instead`);
15364
15367
  fileFromPathWarned = true;
@@ -19008,7 +19011,7 @@ var MultipartBody2 = class {
19008
19011
  import { ReadableStream as ReadableStream4 } from "stream/web";
19009
19012
  var fileFromPathWarned2 = false;
19010
19013
  async function fileFromPath4(path, ...args) {
19011
- const { fileFromPath: _fileFromPath } = await import("./fileFromPath-5M6WXDSO.js");
19014
+ const { fileFromPath: _fileFromPath } = await import("./fileFromPath-TJOXOKGY.js");
19012
19015
  if (!fileFromPathWarned2) {
19013
19016
  console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path)}) instead`);
19014
19017
  fileFromPathWarned2 = true;
@@ -24861,12 +24864,12 @@ var import_grammy = __toESM(require_mod2(), 1);
24861
24864
  import { Hono } from "hono";
24862
24865
  import { serve } from "@hono/node-server";
24863
24866
  var import_node_cron = __toESM(require_node_cron(), 1);
24864
- import { INVESTMENT_ASSETS } from "@t2000/sdk";
24865
24867
  import { readFile, writeFile } from "fs/promises";
24866
- import { resolve, join } from "path";
24867
24868
  import { homedir } from "os";
24868
24869
  import { existsSync, mkdirSync, appendFileSync, statSync, readdirSync, unlinkSync } from "fs";
24869
24870
  var require$1 = createRequire(import.meta.url);
24871
+ var __filename$1 = fileURLToPath(import.meta.url);
24872
+ dirname(__filename$1);
24870
24873
  var __require2 = /* @__PURE__ */ ((x) => typeof require$1 !== "undefined" ? require$1 : typeof Proxy !== "undefined" ? new Proxy(x, {
24871
24874
  get: (a, b) => (typeof require$1 !== "undefined" ? require$1 : a)[b]
24872
24875
  }) : x)(function(x) {
@@ -25235,8 +25238,8 @@ var WebChatChannel = class {
25235
25238
  try {
25236
25239
  const { readFile: readFile2 } = await import("fs/promises");
25237
25240
  const { resolve: resolve2, join: join2 } = await import("path");
25238
- const { fileURLToPath } = await import("url");
25239
- const __dirname2 = resolve2(fileURLToPath(import.meta.url), "..");
25241
+ const { fileURLToPath: fileURLToPath2 } = await import("url");
25242
+ const __dirname2 = resolve2(fileURLToPath2(import.meta.url), "..");
25240
25243
  const distPath = join2(__dirname2, "..", "web", "dist");
25241
25244
  const filePath = join2(distPath, path);
25242
25245
  if (!filePath.startsWith(distPath)) return c.text("Forbidden", 403);
@@ -25291,6 +25294,17 @@ function getInlineHTML() {
25291
25294
  .confirm-bar .accept { background: var(--green); color: white; }
25292
25295
  .confirm-bar .cancel { background: var(--surface); color: var(--text-muted); border: 1px solid var(--border); }
25293
25296
  .typing { align-self: flex-start; color: var(--text-muted); font-size: 13px; padding: 8px 16px; }
25297
+ #welcome { padding: 40px 20px; text-align: center; flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 16px; }
25298
+ #welcome h2 { font-size: 18px; font-weight: 600; color: var(--text); }
25299
+ #welcome p { font-size: 14px; color: var(--text-muted); max-width: 320px; }
25300
+ .quick-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; max-width: 380px; }
25301
+ .quick-btn { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 10px 16px; color: var(--text); font-size: 13px; cursor: pointer; transition: border-color 0.15s; }
25302
+ .quick-btn:hover { border-color: var(--accent); }
25303
+ .msg a { color: var(--accent); text-decoration: none; }
25304
+ .msg a:hover { text-decoration: underline; }
25305
+ .msg code { background: rgba(255,255,255,0.08); padding: 1px 5px; border-radius: 4px; font-family: 'SF Mono', Monaco, monospace; font-size: 0.9em; }
25306
+ .msg ul, .msg ol { margin: 4px 0; padding-left: 20px; }
25307
+ .msg li { margin: 2px 0; }
25294
25308
  footer { padding: 16px 20px; border-top: 1px solid var(--border); }
25295
25309
  #input-form { display: flex; gap: 8px; }
25296
25310
  #input { flex: 1; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 12px 16px; color: var(--text); font-size: 14px; outline: none; font-family: inherit; }
@@ -25305,7 +25319,17 @@ function getInlineHTML() {
25305
25319
  <div class="status" id="status"></div>
25306
25320
  <h1>t2000</h1>
25307
25321
  </header>
25308
- <div id="messages"></div>
25322
+ <div id="welcome">
25323
+ <h2>t2000</h2>
25324
+ <p>Your AI financial advisor. Ask me anything about your accounts.</p>
25325
+ <div class="quick-actions">
25326
+ <button class="quick-btn" onclick="quickAction('What\\'s my balance?')">\u{1F4B0} Balance</button>
25327
+ <button class="quick-btn" onclick="quickAction('Show my portfolio')">\u{1F4CA} Portfolio</button>
25328
+ <button class="quick-btn" onclick="quickAction('What are the best rates?')">\u{1F4C8} Rates</button>
25329
+ <button class="quick-btn" onclick="quickAction('Show recent transactions')">\u{1F4B8} Transactions</button>
25330
+ </div>
25331
+ </div>
25332
+ <div id="messages" style="display:none"></div>
25309
25333
  <footer>
25310
25334
  <form id="input-form">
25311
25335
  <input id="input" placeholder="Message your AI financial advisor..." autocomplete="off" />
@@ -25314,11 +25338,25 @@ function getInlineHTML() {
25314
25338
  </footer>
25315
25339
  <script>
25316
25340
  const messages = document.getElementById('messages');
25341
+ const welcome = document.getElementById('welcome');
25317
25342
  const input = document.getElementById('input');
25318
25343
  const form = document.getElementById('input-form');
25319
25344
  const statusDot = document.getElementById('status');
25320
25345
  let currentAssistantMsg = null;
25321
25346
  let connected = false;
25347
+ let welcomeVisible = true;
25348
+
25349
+ function hideWelcome() {
25350
+ if (!welcomeVisible) return;
25351
+ welcomeVisible = false;
25352
+ welcome.style.display = 'none';
25353
+ messages.style.display = 'flex';
25354
+ }
25355
+
25356
+ function quickAction(text) {
25357
+ hideWelcome();
25358
+ sendMsg(text);
25359
+ }
25322
25360
 
25323
25361
  function connect() {
25324
25362
  const es = new EventSource('/api/events');
@@ -25327,12 +25365,14 @@ function connect() {
25327
25365
  es.onmessage = (e) => {
25328
25366
  const data = JSON.parse(e.data);
25329
25367
  if (data.type === 'token') {
25368
+ hideWelcome();
25330
25369
  if (!currentAssistantMsg) {
25331
25370
  currentAssistantMsg = addMessage('', 'assistant');
25332
25371
  }
25333
25372
  currentAssistantMsg.textContent += data.text;
25334
25373
  scrollToBottom();
25335
25374
  } else if (data.type === 'message') {
25375
+ hideWelcome();
25336
25376
  if (currentAssistantMsg) {
25337
25377
  currentAssistantMsg.innerHTML = renderMarkdown(data.text);
25338
25378
  } else {
@@ -25341,6 +25381,7 @@ function connect() {
25341
25381
  }
25342
25382
  currentAssistantMsg = null;
25343
25383
  } else if (data.type === 'tool_call') {
25384
+ hideWelcome();
25344
25385
  if (!currentAssistantMsg) currentAssistantMsg = addMessage('', 'assistant');
25345
25386
  const badge = document.createElement('span');
25346
25387
  badge.className = 'tool-badge';
@@ -25369,20 +25410,30 @@ function addMessage(text, role) {
25369
25410
  function scrollToBottom() { messages.scrollTop = messages.scrollHeight; }
25370
25411
 
25371
25412
  function renderMarkdown(text) {
25372
- return text
25373
- .replace(/\\|(.+?)\\|/g, (match) => {
25374
- const rows = match.trim().split('\\n').filter(r => r.trim());
25375
- if (rows.length < 2) return match;
25376
- const headers = rows[0].split('|').filter(c => c.trim()).map(c => '<th>' + c.trim() + '</th>');
25377
- const body = rows.slice(2).map(r => '<tr>' + r.split('|').filter(c => c.trim()).map(c => '<td>' + c.trim() + '</td>').join('') + '</tr>');
25378
- return '<table><tr>' + headers.join('') + '</tr>' + body.join('') + '</table>';
25379
- })
25413
+ let html = text
25414
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
25415
+ .replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>')
25380
25416
  .replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')
25381
- .replace(/\\n/g, '<br>');
25417
+ .replace(/\`([^\`]+)\`/g, '<code>$1</code>');
25418
+ const lines = html.split('\\n');
25419
+ let result = [];
25420
+ let inList = false;
25421
+ for (const line of lines) {
25422
+ if (line.match(/^\\s*[-*]\\s+/)) {
25423
+ if (!inList) { result.push('<ul>'); inList = true; }
25424
+ result.push('<li>' + line.replace(/^\\s*[-*]\\s+/, '') + '</li>');
25425
+ } else {
25426
+ if (inList) { result.push('</ul>'); inList = false; }
25427
+ result.push(line);
25428
+ }
25429
+ }
25430
+ if (inList) result.push('</ul>');
25431
+ return result.join('<br>').replace(/<br><ul>/g, '<ul>').replace(/<\\/ul><br>/g, '</ul>');
25382
25432
  }
25383
25433
 
25384
25434
  function sendMsg(text) {
25385
25435
  if (!text.trim()) return;
25436
+ hideWelcome();
25386
25437
  addMessage(text, 'user');
25387
25438
  input.value = '';
25388
25439
  currentAssistantMsg = null;
@@ -25399,6 +25450,7 @@ connect();
25399
25450
  </html>`;
25400
25451
  }
25401
25452
  var TELEGRAM_MAX_LENGTH = 4096;
25453
+ var TYPING_INTERVAL_MS = 4e3;
25402
25454
  var TelegramChannel = class {
25403
25455
  id = "telegram";
25404
25456
  name = "Telegram";
@@ -25406,7 +25458,9 @@ var TelegramChannel = class {
25406
25458
  allowedUsers;
25407
25459
  messageHandler = null;
25408
25460
  pinUnlockHandler = null;
25461
+ startHandler = null;
25409
25462
  awaitingPin = /* @__PURE__ */ new Set();
25463
+ typingIntervals = /* @__PURE__ */ new Map();
25410
25464
  constructor(config) {
25411
25465
  this.bot = new import_grammy.Bot(config.botToken);
25412
25466
  this.allowedUsers = new Set(config.allowedUsers);
@@ -25424,25 +25478,101 @@ var TelegramChannel = class {
25424
25478
  }
25425
25479
  async send(userId, message) {
25426
25480
  const chatId = parseInt(userId, 10);
25427
- const chunks = splitMessage(message);
25481
+ const html = markdownToTelegramHTML(message);
25482
+ const chunks = splitMessage(html);
25428
25483
  for (const chunk of chunks) {
25429
25484
  try {
25430
- await this.bot.api.sendMessage(chatId, chunk, { parse_mode: "Markdown" });
25485
+ await this.bot.api.sendMessage(chatId, chunk, { parse_mode: "HTML" });
25431
25486
  } catch {
25432
- await this.bot.api.sendMessage(chatId, chunk);
25487
+ await this.bot.api.sendMessage(chatId, stripHtml(chunk));
25433
25488
  }
25434
25489
  }
25435
25490
  }
25491
+ async sendWithConfirmation(userId, message) {
25492
+ const chatId = parseInt(userId, 10);
25493
+ const html = markdownToTelegramHTML(message);
25494
+ const keyboard = new import_grammy.InlineKeyboard().text("\u2705 Confirm", "confirm:yes").text("\u274C Cancel", "confirm:no");
25495
+ try {
25496
+ await this.bot.api.sendMessage(chatId, html, { parse_mode: "HTML", reply_markup: keyboard });
25497
+ } catch {
25498
+ await this.bot.api.sendMessage(chatId, stripHtml(html), { reply_markup: keyboard });
25499
+ }
25500
+ }
25436
25501
  onMessage(handler) {
25437
25502
  this.messageHandler = handler;
25438
25503
  }
25439
25504
  onPinUnlock(handler) {
25440
25505
  this.pinUnlockHandler = handler;
25441
25506
  }
25507
+ onStart(handler) {
25508
+ this.startHandler = handler;
25509
+ }
25442
25510
  requestPin(userId) {
25443
25511
  this.awaitingPin.add(userId);
25444
25512
  }
25513
+ startTyping(userId) {
25514
+ const chatId = parseInt(userId, 10);
25515
+ this.bot.api.sendChatAction(chatId, "typing").catch(() => {
25516
+ });
25517
+ const interval = setInterval(() => {
25518
+ this.bot.api.sendChatAction(chatId, "typing").catch(() => {
25519
+ });
25520
+ }, TYPING_INTERVAL_MS);
25521
+ this.typingIntervals.set(userId, interval);
25522
+ }
25523
+ stopTyping(userId) {
25524
+ const interval = this.typingIntervals.get(userId);
25525
+ if (interval) {
25526
+ clearInterval(interval);
25527
+ this.typingIntervals.delete(userId);
25528
+ }
25529
+ }
25445
25530
  setupHandlers() {
25531
+ this.bot.command("start", async (ctx) => {
25532
+ if (!this.isAllowed(ctx)) {
25533
+ await ctx.reply("This is a private financial agent. Access is restricted to the account owner.");
25534
+ return;
25535
+ }
25536
+ const userId = ctx.from.id.toString();
25537
+ let welcomeText = "Welcome to t2000 \u2014 your AI financial advisor.\n\nAsk me anything about your accounts.";
25538
+ if (this.startHandler) {
25539
+ try {
25540
+ welcomeText = await this.startHandler(userId);
25541
+ } catch {
25542
+ }
25543
+ }
25544
+ const keyboard = new import_grammy.InlineKeyboard().text("\u{1F4B0} Balance", "quick:What's my balance?").text("\u{1F4CA} Portfolio", "quick:Show my portfolio").row().text("\u{1F4C8} Rates", "quick:What are the best rates?").text("\u2753 Help", "quick:What can you do?");
25545
+ await ctx.reply(welcomeText, { reply_markup: keyboard });
25546
+ });
25547
+ this.bot.on("callback_query:data", async (ctx) => {
25548
+ if (!this.isAllowed(ctx)) {
25549
+ await ctx.answerCallbackQuery({ text: "Unauthorized" });
25550
+ return;
25551
+ }
25552
+ const data = ctx.callbackQuery.data;
25553
+ const userId = ctx.from.id.toString();
25554
+ if (data.startsWith("confirm:")) {
25555
+ const answer = data.slice(8);
25556
+ await ctx.answerCallbackQuery();
25557
+ try {
25558
+ await ctx.editMessageReplyMarkup({ reply_markup: void 0 });
25559
+ } catch {
25560
+ }
25561
+ if (this.messageHandler) {
25562
+ await this.messageHandler({ channelId: this.id, userId, text: answer });
25563
+ }
25564
+ return;
25565
+ }
25566
+ if (data.startsWith("quick:")) {
25567
+ await ctx.answerCallbackQuery();
25568
+ if (this.messageHandler) {
25569
+ const text = data.slice(6);
25570
+ await this.messageHandler({ channelId: this.id, userId, text });
25571
+ }
25572
+ return;
25573
+ }
25574
+ await ctx.answerCallbackQuery({ text: "Unknown action" });
25575
+ });
25446
25576
  this.bot.on(["message:photo", "message:video", "message:voice", "message:sticker", "message:document"], async (ctx) => {
25447
25577
  if (!this.isAllowed(ctx)) return;
25448
25578
  await ctx.reply("I can only process text messages. How can I help with your finances?");
@@ -25509,6 +25639,19 @@ function splitMessage(text) {
25509
25639
  }
25510
25640
  return chunks;
25511
25641
  }
25642
+ function escapeHtml(text) {
25643
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
25644
+ }
25645
+ function markdownToTelegramHTML(text) {
25646
+ let result = escapeHtml(text);
25647
+ result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
25648
+ result = result.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
25649
+ result = result.replace(/`([^`]+)`/g, "<code>$1</code>");
25650
+ return result;
25651
+ }
25652
+ function stripHtml(text) {
25653
+ return text.replace(/<[^>]+>/g, "");
25654
+ }
25512
25655
  var investAssets = Object.keys(INVESTMENT_ASSETS);
25513
25656
  function createToolRegistry() {
25514
25657
  return [
@@ -26011,23 +26154,66 @@ You manage their bank accounts on the Sui blockchain:
26011
26154
  - Exchange (swap tokens via Cetus DEX)
26012
26155
  - Investment (crypto & commodities \u2014 BTC, ETH, SUI, GOLD)
26013
26156
 
26014
- You have access to ${toolCount} tools. Use them to check balances, execute transactions, manage investments, and optimize yield.
26157
+ You have ${toolCount} tools. Use them to check balances, execute transactions, manage investments, and optimize yield.
26015
26158
 
26016
26159
  RULES:
26017
- - Always confirm before executing state-changing actions (send, save, invest, etc.)
26018
- - Show a clear summary of what you're about to do and ask "should I proceed?"
26019
- - For read-only queries (balance, rates, portfolio), respond immediately
26020
- - Be concise. Numbers matter. Skip fluff.
26021
- - Format currency with proper decimals. Show percentages for APY.
26022
- - If the user asks something outside finance, politely redirect.
26023
- - When presenting tables, use markdown format.
26024
- - Always include transaction links (suiscan.xyz) after successful transactions.
26160
+ - Always confirm before state-changing actions (send, save, invest, borrow, etc.)
26161
+ - Show what you'll do and ask "should I proceed?"
26162
+ - For read-only queries (balance, rates, portfolio), respond immediately \u2014 no confirmation needed
26163
+ - If the user asks something outside finance, briefly redirect
26164
+
26165
+ FORMATTING \u2014 follow these exactly:
26166
+ - Use standard markdown only: **bold**, \`code\`, [links](url), - lists
26167
+ - NEVER use markdown tables \u2014 they render poorly on mobile
26168
+ - One data point per line with emoji prefix for scannability
26169
+ - Currency: always 2 decimal places ($52.67, not $52.6723)
26170
+ - APY: always show % with 1-2 decimals (4.2%, not 0.0423)
26171
+ - Addresses: wrap in backticks \`0xabc...def\`
26172
+ - Transaction links: [View on explorer](https://suiscan.xyz/testnet/tx/DIGEST)
26173
+ - No Unicode box-drawing characters (\u2500, \u2502, \u250C, etc.) \u2014 they render inconsistently
26174
+ - No column-aligned text with spaces \u2014 breaks on proportional fonts
26025
26175
 
26026
26176
  PERSONALITY:
26027
- - Professional but approachable
26028
- - Brief \u2014 this is a chat, not an essay
26029
- - Proactive \u2014 suggest optimizations when you see them
26030
- - When you notice a better yield rate, mention it`;
26177
+ - You are a knowledgeable financial advisor, not a chatbot
26178
+ - Brief \u2014 3-5 lines for simple queries, never an essay
26179
+ - Lead with the number \u2014 users scan for amounts
26180
+ - Be opinionated \u2014 if you notice idle funds, bad rates, or risky positions, say so
26181
+ - Suggest next actions only when something is genuinely actionable (idle funds, rate changes, better yield, risky health factor). Do NOT add a suggestion to every response
26182
+ - Never start with "Sure!" or "Of course!" \u2014 just answer
26183
+
26184
+ RESPONSE EXAMPLES \u2014 match this style:
26185
+
26186
+ When showing balances:
26187
+ \u{1F4B3} Checking: **$52.67**
26188
+ \u{1F3E6} Savings: **$19.24** (earning 4.2%)
26189
+ \u{1F4B8} Debt: **-$2.01**
26190
+ \u{1F4C8} Investment: **$0.05**
26191
+
26192
+ Net: **$70.95**
26193
+
26194
+ Your debt ($2.01) costs more than it earns. Pay it off from checking? Just say "repay all."
26195
+
26196
+ When showing a transaction receipt:
26197
+ \u2705 Saved **$80.00**
26198
+
26199
+ Protocol: NAVI
26200
+ APY: 5.57%
26201
+ Monthly yield: ~$3.71
26202
+ [View on explorer](https://suiscan.xyz/testnet/tx/abc123)
26203
+
26204
+ Savings balance: **$99.24** (+$80.00)
26205
+
26206
+ When showing portfolio:
26207
+ Your portfolio: **$152.30** (+2.3%)
26208
+
26209
+ \u{1F4C8} **SUI** \u2014 45.2 tokens ($48.00, +3.1%) \u2014 earning 2.6% on Suilend
26210
+ \u{1F4C8} **BTC** \u2014 0.0012 ($89.30, +1.8%)
26211
+ \u{1F4C9} **ETH** \u2014 0.025 ($15.00, -0.5%)
26212
+
26213
+ \u{1F4A1} ETH is the only position losing. Rebalance into SUI?
26214
+
26215
+ When showing an error:
26216
+ \u274C Not enough funds. You have **$12.50** available but need $50.00. Try a smaller amount?`;
26031
26217
  }
26032
26218
  async function buildContextInjection(agent) {
26033
26219
  try {
@@ -26563,7 +26749,7 @@ var Logger = class _Logger {
26563
26749
  if (!existsSync(this.logDir)) mkdirSync(this.logDir, { recursive: true });
26564
26750
  this.logPath = join(this.logDir, LOG_FILE);
26565
26751
  this.level = opts?.level ?? "info";
26566
- this.toConsole = opts?.verbose ?? false;
26752
+ this.toConsole = true;
26567
26753
  }
26568
26754
  debug(msg, data) {
26569
26755
  this.write("debug", msg, data);
@@ -26749,13 +26935,32 @@ var Gateway = class _Gateway {
26749
26935
  allowedUsers: telegramConfig.allowedUsers ?? []
26750
26936
  });
26751
26937
  const loop = new AgentLoop({ agent: this.agent, llm: this.llm, tools, toolDefinitions: toolDefs });
26938
+ telegram.onStart(async (_userId) => {
26939
+ try {
26940
+ const balance = await this.agent.balance();
26941
+ return [
26942
+ "Welcome to t2000 \u2014 your AI financial advisor.\n",
26943
+ `\u{1F4B3} Checking: $${balance.available.toFixed(2)}`,
26944
+ `\u{1F3E6} Savings: $${balance.savings.toFixed(2)}`,
26945
+ `Net: $${(balance.available + balance.savings - balance.debt).toFixed(2)}`,
26946
+ "\nAsk me anything, or tap a button below."
26947
+ ].join("\n");
26948
+ } catch {
26949
+ return "Welcome to t2000 \u2014 your AI financial advisor.\n\nAsk me anything about your accounts.";
26950
+ }
26951
+ });
26752
26952
  telegram.onMessage(async (msg) => {
26753
26953
  if (this.agent.enforcer.getConfig().locked) {
26754
26954
  telegram.requestPin(msg.userId);
26755
26955
  await telegram.send(msg.userId, "Agent is locked. Enter your PIN to unlock.");
26756
26956
  return;
26757
26957
  }
26758
- await this.handleMessage(msg, loop, telegram);
26958
+ telegram.startTyping(msg.userId);
26959
+ try {
26960
+ await this.handleMessage(msg, loop, telegram);
26961
+ } finally {
26962
+ telegram.stopTyping(msg.userId);
26963
+ }
26759
26964
  });
26760
26965
  telegram.onPinUnlock(async (_pin) => {
26761
26966
  try {
@@ -26802,6 +27007,9 @@ var Gateway = class _Gateway {
26802
27007
  }
26803
27008
  async handleMessage(msg, loop, channel) {
26804
27009
  const isWebChat = channel instanceof WebChatChannel;
27010
+ const isTelegram = channel instanceof TelegramChannel;
27011
+ const startTime = Date.now();
27012
+ const queryPreview = msg.text.length > 40 ? msg.text.slice(0, 40) + "..." : msg.text;
26805
27013
  try {
26806
27014
  const response = await loop.processMessage(msg.text, {
26807
27015
  stream: isWebChat,
@@ -26815,30 +27023,58 @@ var Gateway = class _Gateway {
26815
27023
  channel.sendConfirmation(response.needsConfirmation.preview);
26816
27024
  }
26817
27025
  }
26818
- await channel.send(msg.userId, response.text);
26819
- const cost = this.estimateCost(response.usage);
26820
- this.logger.debug(`Message handled`, {
26821
- channel: channel.name,
26822
- tools: response.toolCalls.length,
26823
- inputTokens: response.usage.inputTokens,
26824
- outputTokens: response.usage.outputTokens,
26825
- cost: `$${cost.toFixed(4)}`
26826
- });
26827
- } catch (err) {
26828
- const errorMsg = err instanceof Error ? err.message : "Internal error";
26829
- if (this.isLLMError(err)) {
26830
- this.logger.error("LLM call failed", { error: errorMsg });
26831
- await channel.send(msg.userId, "AI is temporarily unavailable. Please try again in a moment.");
27026
+ if (isTelegram && response.needsConfirmation) {
27027
+ await channel.sendWithConfirmation(msg.userId, response.text);
26832
27028
  } else {
26833
- this.logger.error(`Message error: ${errorMsg}`);
26834
- await channel.send(msg.userId, `Sorry, something went wrong: ${errorMsg}`);
27029
+ await channel.send(msg.userId, response.text);
27030
+ }
27031
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
27032
+ const toolCount = response.toolCalls.length;
27033
+ const suffix = response.needsConfirmation ? "confirmation pending" : `${toolCount} tool${toolCount !== 1 ? "s" : ""}, ${elapsed}s`;
27034
+ this.logger.info(`${channel.id} \xB7 "${queryPreview}" \u2192 ${suffix}`);
27035
+ if (this.options.verbose) {
27036
+ const cost = this.estimateCost(response.usage);
27037
+ this.logger.debug(` tokens: ${response.usage.inputTokens}in/${response.usage.outputTokens}out, ~$${cost.toFixed(4)}`);
26835
27038
  }
27039
+ } catch (err) {
27040
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
27041
+ const friendlyMsg = this.friendlyError(err);
27042
+ this.logger.error(`${channel.id} \xB7 "${queryPreview}" \u2192 error (${elapsed}s): ${err instanceof Error ? err.message : String(err)}`);
27043
+ await channel.send(msg.userId, friendlyMsg);
26836
27044
  }
26837
27045
  }
26838
- isLLMError(err) {
26839
- if (!(err instanceof Error)) return false;
27046
+ friendlyError(err) {
27047
+ if (!(err instanceof Error)) return "Something went wrong. Try again?";
26840
27048
  const msg = err.message.toLowerCase();
26841
- return msg.includes("api") || msg.includes("rate limit") || msg.includes("429") || msg.includes("500") || msg.includes("503") || msg.includes("overloaded") || msg.includes("timeout");
27049
+ if (msg.includes("rate limit") || msg.includes("429") || msg.includes("overloaded")) {
27050
+ return "AI is busy. Try again in a moment.";
27051
+ }
27052
+ if (msg.includes("api") || msg.includes("500") || msg.includes("503") || msg.includes("timeout")) {
27053
+ return "AI is temporarily unavailable. Please try again in a moment.";
27054
+ }
27055
+ if (err instanceof T2000Error) {
27056
+ switch (err.code) {
27057
+ case "INSUFFICIENT_BALANCE":
27058
+ case "INSUFFICIENT_GAS":
27059
+ return `Not enough funds. ${err.message}`;
27060
+ case "SAFEGUARD_BLOCKED":
27061
+ return `${err.message}`;
27062
+ case "HEALTH_FACTOR_TOO_LOW":
27063
+ case "WITHDRAW_WOULD_LIQUIDATE":
27064
+ return "That would put your health factor below safe levels. Try a smaller amount.";
27065
+ case "SLIPPAGE_EXCEEDED":
27066
+ return "Price moved too much during the swap. Try again or increase slippage.";
27067
+ case "PROTOCOL_PAUSED":
27068
+ return "The protocol is temporarily paused. Try again later.";
27069
+ case "INVALID_ADDRESS":
27070
+ return "That address doesn't look right. Check it and try again.";
27071
+ case "INVALID_AMOUNT":
27072
+ return "Invalid amount. Please enter a positive number.";
27073
+ default:
27074
+ return `${err.message}`;
27075
+ }
27076
+ }
27077
+ return `Something went wrong: ${err.message}`;
26842
27078
  }
26843
27079
  estimateCost(usage) {
26844
27080
  if (this.llm.id === "anthropic") {
@@ -26876,4 +27112,4 @@ humanize-ms/index.js:
26876
27112
  * MIT Licensed
26877
27113
  *)
26878
27114
  */
26879
- //# sourceMappingURL=dist-PSN4YXQP.js.map
27115
+ //# sourceMappingURL=dist-ALKOTWPF.js.map