thinyai 0.1.10 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin.js +92 -29
  2. package/package.json +7 -7
package/dist/bin.js CHANGED
@@ -164,7 +164,7 @@ var SlashPrompt = class {
164
164
 
165
165
  // src/main.ts
166
166
  import { stdin, stdout } from "process";
167
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
167
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
168
168
  import { homedir as homedir2 } from "os";
169
169
  import { join as join2 } from "path";
170
170
  import { z as z7 } from "zod";
@@ -1182,6 +1182,7 @@ function memwalFactsPlugin(opts) {
1182
1182
 
1183
1183
  // ../../packages/adapters/walrus/src/index.ts
1184
1184
  import { mkdir, readFile, writeFile } from "fs/promises";
1185
+ import { mkdirSync, readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2 } from "fs";
1185
1186
  import { dirname } from "path";
1186
1187
  import { z as z2 } from "zod";
1187
1188
  var DEFAULT_PUBLISHER = "https://publisher.walrus-testnet.walrus.space";
@@ -1319,6 +1320,13 @@ function walrusMemoryPlugin(opts) {
1319
1320
  let pending = Promise.resolve();
1320
1321
  async function load() {
1321
1322
  if (cache) return cache;
1323
+ if (opts.cacheFile && existsSync2(opts.cacheFile)) {
1324
+ try {
1325
+ cache = JSON.parse(readFileSync2(opts.cacheFile, "utf8"));
1326
+ return cache;
1327
+ } catch {
1328
+ }
1329
+ }
1322
1330
  loading ??= (async () => {
1323
1331
  try {
1324
1332
  const blobId = await opts.pointers.get(key);
@@ -1334,6 +1342,13 @@ function walrusMemoryPlugin(opts) {
1334
1342
  }
1335
1343
  function save(facts) {
1336
1344
  cache = facts;
1345
+ if (opts.cacheFile) {
1346
+ try {
1347
+ mkdirSync(dirname(opts.cacheFile), { recursive: true });
1348
+ writeFileSync(opts.cacheFile, JSON.stringify(facts, null, 2));
1349
+ } catch {
1350
+ }
1351
+ }
1337
1352
  opts.onStoreStart?.();
1338
1353
  pending = pending.then(async () => {
1339
1354
  try {
@@ -1520,18 +1535,13 @@ function explorerTxUrl(network, digest) {
1520
1535
  function suiPlugin(opts) {
1521
1536
  const sig = opts.signer;
1522
1537
  const resolve2 = typeof sig === "function" ? sig : () => sig;
1523
- const signerOrThrow = () => {
1524
- const s = resolve2();
1525
- if (!s) {
1526
- throw new Error(
1527
- "No Sui wallet configured yet. Call sui_setup to create or import one (or tell the user to run `thiny sui init`)."
1528
- );
1529
- }
1530
- return s;
1538
+ const SETUP_NEEDED = {
1539
+ ok: false,
1540
+ setupNeeded: true,
1541
+ message: "Sui isn't set up yet. Ask the user which network (testnet or mainnet) and which wallet (generate a new key, import a private key, or use a Rill agent wallet), then call sui_setup. Do NOT retry this tool until setup is complete."
1531
1542
  };
1532
1543
  const requireSimSuccess = opts.policy?.requireSimSuccess ?? true;
1533
- const executeTx = async (tx, toolName, approvalArgs, reason) => {
1534
- const signer = signerOrThrow();
1544
+ const executeTx = async (signer, tx, toolName, approvalArgs, reason) => {
1535
1545
  const sim = await signer.devInspect(tx);
1536
1546
  const status = sim.effects.status.status;
1537
1547
  if (requireSimSuccess && status !== "success") {
@@ -1561,10 +1571,11 @@ function suiPlugin(opts) {
1561
1571
  coinType: z4.string().optional().describe("Coin type, e.g. 0x2::sui::SUI (default: SUI).")
1562
1572
  }),
1563
1573
  execute: async ({ address, coinType }) => {
1564
- const signer = signerOrThrow();
1574
+ const signer = resolve2();
1575
+ if (!signer) return SETUP_NEEDED;
1565
1576
  const owner = address ?? signer.address;
1566
1577
  if (owner === void 0) {
1567
- throw new Error("sui_balance: no address given and the signer has no key/address.");
1578
+ return { ok: false, message: "No address given and no wallet is set up. Run sui_setup first." };
1568
1579
  }
1569
1580
  const bal = await signer.client.getBalance({ owner, ...coinType ? { coinType } : {} });
1570
1581
  return {
@@ -1580,7 +1591,8 @@ function suiPlugin(opts) {
1580
1591
  description: "Read a Sui object's type and fields by id.",
1581
1592
  parameters: z4.object({ objectId: z4.string().min(1).describe("The object id (0x\u2026).") }),
1582
1593
  execute: async ({ objectId }) => {
1583
- const signer = signerOrThrow();
1594
+ const signer = resolve2();
1595
+ if (!signer) return SETUP_NEEDED;
1584
1596
  const res = await signer.client.getObject({
1585
1597
  id: objectId,
1586
1598
  options: { showContent: true, showType: true, showOwner: true }
@@ -1602,8 +1614,10 @@ function suiPlugin(opts) {
1602
1614
  unsignedTx: z4.string().min(1).describe("Unsigned PTB \u2014 the JSON string from Transaction.toJSON() (no sender, no gas).")
1603
1615
  }),
1604
1616
  execute: async ({ unsignedTx }) => {
1617
+ const signer = resolve2();
1618
+ if (!signer) return SETUP_NEEDED;
1605
1619
  const tx = Transaction2.from(unsignedTx);
1606
- return await executeTx(tx, "sui_execute_ptb", { unsignedTx }, "sign and submit a Sui PTB");
1620
+ return await executeTx(signer, tx, "sui_execute_ptb", { unsignedTx }, "sign and submit a Sui PTB");
1607
1621
  }
1608
1622
  });
1609
1623
  const transfer = defineTool({
@@ -1616,7 +1630,8 @@ function suiPlugin(opts) {
1616
1630
  coinType: z4.string().optional().describe("Coin type (default 0x2::sui::SUI).")
1617
1631
  }),
1618
1632
  execute: async ({ recipient, amountMist, coinType }) => {
1619
- const signer = signerOrThrow();
1633
+ const signer = resolve2();
1634
+ if (!signer) return SETUP_NEEDED;
1620
1635
  const amount = BigInt(amountMist);
1621
1636
  const type = coinType ?? "0x2::sui::SUI";
1622
1637
  const tx = new Transaction2();
@@ -1625,7 +1640,7 @@ function suiPlugin(opts) {
1625
1640
  tx.transferObjects([coin], tx.pure.address(recipient));
1626
1641
  } else {
1627
1642
  const owner = signer.address;
1628
- if (owner === void 0) throw new Error("sui_transfer: signer has no address.");
1643
+ if (owner === void 0) return { ok: false, message: "Wallet has no address. Run sui_setup." };
1629
1644
  const { data } = await signer.client.getCoins({ owner, coinType: type });
1630
1645
  const [first, ...rest] = data;
1631
1646
  if (!first) throw new Error(`sui_transfer: no ${type} coins owned by ${owner}.`);
@@ -1637,6 +1652,7 @@ function suiPlugin(opts) {
1637
1652
  tx.transferObjects([coin], tx.pure.address(recipient));
1638
1653
  }
1639
1654
  return executeTx(
1655
+ signer,
1640
1656
  tx,
1641
1657
  "sui_transfer",
1642
1658
  { recipient, amountMist, coinType: type },
@@ -1660,6 +1676,8 @@ function suiPlugin(opts) {
1660
1676
  ).optional().describe("Ordered arguments to the function.")
1661
1677
  }),
1662
1678
  execute: async ({ target, typeArguments, args }) => {
1679
+ const signer = resolve2();
1680
+ if (!signer) return SETUP_NEEDED;
1663
1681
  const tx = new Transaction2();
1664
1682
  const built = (args ?? []).map((a) => {
1665
1683
  if (a.kind === "gas") return tx.gas;
@@ -1690,7 +1708,7 @@ function suiPlugin(opts) {
1690
1708
  }
1691
1709
  });
1692
1710
  tx.moveCall({ target, typeArguments: typeArguments ?? [], arguments: built });
1693
- return await executeTx(tx, "sui_move_call", { target, typeArguments, args }, `Move call ${target}`);
1711
+ return await executeTx(signer, tx, "sui_move_call", { target, typeArguments, args }, `Move call ${target}`);
1694
1712
  }
1695
1713
  });
1696
1714
  return { name: "sui", tools: [balance, object, executePtb, transfer, moveCall] };
@@ -1956,7 +1974,7 @@ var SkillRegistry = class {
1956
1974
  var defaultRegistry = new SkillRegistry();
1957
1975
 
1958
1976
  // src/onboarding.ts
1959
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync, chmodSync } from "fs";
1977
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, chmodSync } from "fs";
1960
1978
  import { homedir } from "os";
1961
1979
  import { join, dirname as dirname2 } from "path";
1962
1980
  import { fileURLToPath } from "url";
@@ -1966,18 +1984,18 @@ var CONFIG = join(THINY_DIR, "config.json");
1966
1984
  function version() {
1967
1985
  try {
1968
1986
  const pkg = join(dirname2(fileURLToPath(import.meta.url)), "../package.json");
1969
- return JSON.parse(readFileSync2(pkg, "utf8")).version ?? "0.0.0";
1987
+ return JSON.parse(readFileSync3(pkg, "utf8")).version ?? "0.0.0";
1970
1988
  } catch {
1971
1989
  return "0.0.0";
1972
1990
  }
1973
1991
  }
1974
1992
  function loadConfig() {
1975
- return existsSync2(CONFIG) ? JSON.parse(readFileSync2(CONFIG, "utf8")) : null;
1993
+ return existsSync3(CONFIG) ? JSON.parse(readFileSync3(CONFIG, "utf8")) : null;
1976
1994
  }
1977
1995
  function saveConfig(cfg) {
1978
- mkdirSync(THINY_DIR, { recursive: true });
1996
+ mkdirSync2(THINY_DIR, { recursive: true });
1979
1997
  chmodSync(THINY_DIR, 448);
1980
- writeFileSync(CONFIG, JSON.stringify(cfg, null, 2));
1998
+ writeFileSync2(CONFIG, JSON.stringify(cfg, null, 2));
1981
1999
  chmodSync(CONFIG, 384);
1982
2000
  }
1983
2001
  function applyConfig(cfg) {
@@ -2538,19 +2556,19 @@ function notifyIfUpdate(thinyDir) {
2538
2556
  const cur = version();
2539
2557
  const cacheFile = join2(thinyDir, "update-check.json");
2540
2558
  try {
2541
- const cached = JSON.parse(readFileSync3(cacheFile, "utf8"));
2559
+ const cached = JSON.parse(readFileSync4(cacheFile, "utf8"));
2542
2560
  if (cached.latest && isNewerVersion(cached.latest, cur)) {
2543
2561
  renderInfo(`Update available: ${cur} \u2192 ${cached.latest} \u2014 run \`thiny update\``);
2544
2562
  }
2545
2563
  } catch {
2546
2564
  }
2547
2565
  void fetch("https://registry.npmjs.org/thinyai/latest").then((r) => r.json()).then((j) => {
2548
- if (j.version) writeFileSync2(cacheFile, JSON.stringify({ latest: j.version, at: Date.now() }));
2566
+ if (j.version) writeFileSync3(cacheFile, JSON.stringify({ latest: j.version, at: Date.now() }));
2549
2567
  }).catch(() => void 0);
2550
2568
  }
2551
2569
  async function runCli() {
2552
2570
  const thinyDir = join2(homedir2(), ".thiny");
2553
- mkdirSync2(thinyDir, { recursive: true });
2571
+ mkdirSync3(thinyDir, { recursive: true });
2554
2572
  const envLogFile = process.env.THINY_LOG_FILE?.trim();
2555
2573
  const logFile = envLogFile && envLogFile.length > 0 ? envLogFile : join2(thinyDir, "cli.log");
2556
2574
  const fileLogger = pinoLogger({ level: process.env.LOG_LEVEL ?? "info", file: logFile });
@@ -2598,6 +2616,8 @@ async function runCli() {
2598
2616
  // directory `thiny` is launched from — a cwd-relative file would fragment per folder.
2599
2617
  pointers: filePointerStore(process.env.WALRUS_POINTERS ?? join2(thinyDir, "thiny-pointers.json")),
2600
2618
  userId,
2619
+ // Instant, reliable local mirror — cross-session memory no longer waits on the slow Walrus PUT.
2620
+ cacheFile: join2(thinyDir, `memory-${userId}.json`),
2601
2621
  onStoreStart: () => pendingWrites += 1,
2602
2622
  onStore: (ref) => {
2603
2623
  if (pendingWrites > 0) pendingWrites -= 1;
@@ -2779,12 +2799,55 @@ async function runCli() {
2779
2799
  return { activeAddress: address, note: `Now signing as ${address}.` };
2780
2800
  }
2781
2801
  });
2802
+ const suiBalancesTool = defineTool({
2803
+ name: "sui_balances",
2804
+ description: "Fetch ALL coin balances across ALL of the user's Sui addresses on a network. Use for 'what's my balance / what coins do I have'. Returns each address with its coins. SUI amounts are also given in whole SUI (1 SUI = 1e9 MIST).",
2805
+ parameters: z7.object({
2806
+ network: z7.enum(["testnet", "mainnet"]).optional().describe("Network (default: the active one)."),
2807
+ address: z7.string().optional().describe("Limit to one address (default: all the user's wallets).")
2808
+ }),
2809
+ execute: async ({ network: network2, address }) => {
2810
+ const net = network2 ?? suiNetwork;
2811
+ const client = net === suiNetwork && suiSignerRef ? suiSignerRef.client : suiSigner({ network: net }).client;
2812
+ const walletAddrs = suiWalletsOf(loadConfig()).map((w) => w.address);
2813
+ if (suiSignerRef?.address && !walletAddrs.includes(suiSignerRef.address)) {
2814
+ walletAddrs.push(suiSignerRef.address);
2815
+ }
2816
+ const addrs = address ? [address] : walletAddrs;
2817
+ if (addrs.length === 0) {
2818
+ return {
2819
+ ok: false,
2820
+ setupNeeded: true,
2821
+ message: "Sui isn't set up. Ask the user which network and wallet (generate / import / Rill), then call sui_setup. Don't retry until then."
2822
+ };
2823
+ }
2824
+ const addresses = [];
2825
+ for (const addr of addrs) {
2826
+ const balances = await client.getAllBalances({ owner: addr });
2827
+ addresses.push({
2828
+ address: addr,
2829
+ coins: balances.map((b) => {
2830
+ const symbol = b.coinType.split("::").pop() ?? b.coinType;
2831
+ const isSui = b.coinType.endsWith("::sui::SUI");
2832
+ return {
2833
+ symbol,
2834
+ coinType: b.coinType,
2835
+ balanceMist: b.totalBalance,
2836
+ ...isSui ? { sui: Number(b.totalBalance) / 1e9 } : {}
2837
+ };
2838
+ })
2839
+ });
2840
+ }
2841
+ return { network: net, addresses };
2842
+ }
2843
+ });
2782
2844
  const walletTools = [
2783
2845
  suiWalletsTool,
2784
2846
  suiCreateWalletTool,
2785
2847
  suiImportWalletTool,
2786
2848
  suiExportWalletTool,
2787
- suiUseWalletTool
2849
+ suiUseWalletTool,
2850
+ suiBalancesTool
2788
2851
  ];
2789
2852
  const fetchUrlTool = defineTool({
2790
2853
  name: "fetch_url",
@@ -2855,9 +2918,9 @@ async function runCli() {
2855
2918
  HOW TO ACT: When a request maps to one of your tools, CALL THE TOOL automatically \u2014 figure out the right tool yourself; do not ask the user which tool to run, do not ask permission for read-only actions, and never say you can't do something one of your tools covers. Chain tools when needed (e.g. web_search \u2192 fetch_url \u2192 act).
2856
2919
 
2857
2920
  YOUR TOOLS:
2858
- \u2022 Memory \u2014 remember_fact, recall_memory: durable memory across sessions (stored on Walrus). Known facts are injected each turn under \u201C[User Memory \u2026]\u201D. Immediately save anything durable the user shares (name, role, preferences, projects, goals). Answer \u201Cwhat do you remember\u201D from it. You DO remember across sessions \u2014 never say otherwise.
2921
+ \u2022 Memory \u2014 remember_fact: durable memory across sessions. Whenever the user shares anything durable about themselves (name, role, preferences, projects, goals), call remember_fact ONCE to save it. Your known facts are AUTO-INJECTED at the top of every conversation under \u201C[User Memory \u2026]\u201D, so answer \u201Cwhat do you remember / what's my name\u201D directly from that context \u2014 do NOT call recall_memory unless the injected memory is empty and you truly need to re-check. You DO remember across sessions; never say otherwise.
2859
2922
  \u2022 Links \u2014 fetch_url: read ANY URL the user shares (a skill.md, docs, JSON, an API/MCP endpoint). Always fetch shared links instead of saying you can't open URLs.
2860
- ` + (webSearchOn ? "\u2022 Web search \u2014 web_search: search the web for anything you don't know (news, prices, docs). web_search FINDS pages by query; fetch_url READS a specific URL \u2014 use them together.\n" : "") + "\u2022 Planning \u2014 update_plan (track multi-step work), delegate_task (hand a focused subtask to a sub-agent).\n\u2022 Sui blockchain \u2014 you transact yourself; NEVER tell the user to install a browser wallet. " + (suiSignerRef ? `The active wallet is on ${suiNetwork} at ${suiSignerRef.address ?? "?"}. ` : "No wallet yet \u2014 call sui_create_wallet (or sui_import_wallet) when the user wants Sui, then have them fund the address. ") + "Wallets: sui_wallets (list ALL the user's wallets + addresses \u2014 use this to answer 'what's my address / what wallets do I have'), sui_create_wallet (new key pair), sui_import_wallet (restore from a suiprivkey), sui_export_wallet (reveal a private key \u2014 only when asked), sui_use_wallet (switch the active wallet). On-chain: sui_balance & sui_object (read), sui_transfer (send SUI/any coin \u2014 amounts in MIST, 1 SUI = 1e9), sui_move_call (call ANY Move function), sui_execute_ptb (sign a builder/Rill PTB). Prefer sui_transfer for sends and sui_move_call for contract calls; confirm details before signing.",
2923
+ ` + (webSearchOn ? "\u2022 Web search \u2014 web_search: search the web for anything you don't know (news, prices, docs). web_search FINDS pages by query; fetch_url READS a specific URL \u2014 use them together.\n" : "") + "\u2022 Planning \u2014 update_plan (track multi-step work), delegate_task (hand a focused subtask to a sub-agent).\n\u2022 Sui blockchain \u2014 you transact yourself; NEVER tell the user to install a browser wallet. " + (suiSignerRef ? `Sui IS set up. The user's primary (active) wallet is on ${suiNetwork} at ${suiSignerRef.address ?? "?"}. ` : "Sui is NOT set up yet. When the user wants anything Sui-related, FIRST ask if they'd like you to set it up, and ask which network (testnet/mainnet) and which wallet option (generate a new key, import a suiprivkey, or use a Rill agent wallet). Then call sui_setup with their choices. Do not attempt other Sui tools until setup succeeds. ") + "Wallet tools: sui_wallets (list ALL wallets + which is primary/active \u2014 answer 'what's my address' from this), sui_balances (ALL coins across ALL addresses on a network \u2014 answer 'what's my balance' with this), sui_create_wallet, sui_import_wallet, sui_export_wallet (reveal a key only when asked), sui_use_wallet (switch primary). Never overwrite or replace an existing wallet \u2014 adding a wallet keeps the others. If it's unclear which wallet to use, ask the user which is their primary. On-chain: sui_balance & sui_object (read), sui_transfer (send SUI/any coin \u2014 amounts in MIST, 1 SUI = 1e9), sui_move_call (call ANY Move function), sui_execute_ptb (sign a builder/Rill PTB). Prefer sui_transfer for sends and sui_move_call for contract calls; confirm details before signing.\nWhen ANY Sui tool fails, do NOT paste raw JSON or stack traces \u2014 explain what went wrong in one plain sentence and what to do next. Present balances/results readably (SUI amounts in whole SUI, short addresses), and keep every answer brief and to the point.",
2861
2924
  tools: [echoTool, suiSetupTool, ...walletTools, fetchUrlTool, ...webTools],
2862
2925
  plugins: [
2863
2926
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinyai",
3
- "version": "0.1.10",
3
+ "version": "0.1.13",
4
4
  "description": "Thiny AI — a beautiful terminal agent: interactive chat, tools, Walrus memory, and Sui execution.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -38,16 +38,16 @@
38
38
  "tsup": "^8.5.1",
39
39
  "typescript": "^5.5.0",
40
40
  "@thiny/core": "0.1.0",
41
- "@thiny/walrus": "0.1.0",
42
- "@thiny/plugin-agents": "0.1.0",
43
- "@thiny/plugin-web-search": "0.1.0",
41
+ "@thiny/memory-memwal": "0.1.0",
44
42
  "@thiny/logger-pino": "0.1.0",
45
43
  "@thiny/mcp": "0.1.0",
46
- "@thiny/memory-memwal": "0.1.0",
47
- "@thiny/signer-sui": "0.1.0",
44
+ "@thiny/walrus": "0.1.0",
45
+ "@thiny/plugin-agents": "0.1.0",
48
46
  "@thiny/plugin-sui": "0.1.0",
47
+ "@thiny/plugin-web-search": "0.1.0",
49
48
  "@thiny/model-aisdk": "0.1.0",
50
- "@thiny/skills": "0.1.0"
49
+ "@thiny/skills": "0.1.0",
50
+ "@thiny/signer-sui": "0.1.0"
51
51
  },
52
52
  "author": "Thiny AI",
53
53
  "engines": {