thinyai 0.1.9 → 0.1.11

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 +230 -55
  2. package/package.json +6 -6
package/dist/bin.js CHANGED
@@ -4,11 +4,167 @@
4
4
  import { spawnSync } from "child_process";
5
5
  import { fileURLToPath as fileURLToPath2 } from "url";
6
6
 
7
+ // src/prompt.ts
8
+ import { emitKeypressEvents } from "readline";
9
+ import chalk from "chalk";
10
+ var ANSI = /\x1b\[[0-9;]*m/g;
11
+ var visLen = (s) => s.replace(ANSI, "").length;
12
+ var MAX_ROWS = 8;
13
+ var SlashPrompt = class {
14
+ constructor(stdin2, stdout2, basePrompt, commands) {
15
+ this.stdin = stdin2;
16
+ this.stdout = stdout2;
17
+ this.basePrompt = basePrompt;
18
+ this.commands = commands;
19
+ this.curPrompt = basePrompt;
20
+ emitKeypressEvents(stdin2);
21
+ }
22
+ stdin;
23
+ stdout;
24
+ basePrompt;
25
+ commands;
26
+ buf = "";
27
+ sel = 0;
28
+ rows = 0;
29
+ // menu rows currently drawn below the input line
30
+ onKey;
31
+ resolver;
32
+ curPrompt;
33
+ width() {
34
+ return this.stdout.columns || 80;
35
+ }
36
+ matches() {
37
+ if (!this.buf.startsWith("/") || this.buf.includes(" ")) return [];
38
+ const q = this.buf.slice(1).toLowerCase();
39
+ return this.commands.filter((c) => c.name.slice(1).toLowerCase().startsWith(q));
40
+ }
41
+ /** Redraw the input line + the dropdown, leaving the cursor at the end of the typed text. */
42
+ draw() {
43
+ const ms = this.matches();
44
+ if (this.sel >= ms.length) this.sel = Math.max(0, ms.length - 1);
45
+ this.stdout.write("\r\x1B[0J");
46
+ let out = this.curPrompt + this.buf;
47
+ const shown = ms.slice(0, MAX_ROWS);
48
+ const w = this.width();
49
+ for (let i = 0; i < shown.length; i++) {
50
+ const c = shown[i];
51
+ if (!c) continue;
52
+ const name = c.name.padEnd(18);
53
+ const descMax = Math.max(10, w - 24);
54
+ const desc = c.desc.length > descMax ? `${c.desc.slice(0, descMax - 1)}\u2026` : c.desc;
55
+ out += "\n" + (i === this.sel ? chalk.bgCyan.black(` ${name} `) + " " + chalk.dim(desc) : " " + chalk.cyan(name) + " " + chalk.dim(desc));
56
+ }
57
+ this.stdout.write(out);
58
+ this.rows = shown.length;
59
+ if (this.rows > 0) this.stdout.write(`\x1B[${String(this.rows)}A`);
60
+ const col = visLen(this.curPrompt) + this.buf.length;
61
+ this.stdout.write("\r" + (col > 0 ? `\x1B[${String(col)}C` : ""));
62
+ }
63
+ finish(value) {
64
+ if (this.onKey) this.stdin.off("keypress", this.onKey);
65
+ this.onKey = void 0;
66
+ if (this.rows > 0) this.stdout.write(`\x1B[${String(this.rows)}B`);
67
+ this.stdout.write("\r\x1B[0J\n");
68
+ this.rows = 0;
69
+ const r = this.resolver;
70
+ this.resolver = void 0;
71
+ r?.(value);
72
+ }
73
+ handle(str, key) {
74
+ const name = key?.name;
75
+ if (key?.ctrl && name === "c") {
76
+ this.stdout.write("\n");
77
+ process.exit(0);
78
+ }
79
+ if (key?.ctrl && name === "d") {
80
+ if (this.buf === "") this.finish(null);
81
+ return;
82
+ }
83
+ if (name === "return" || name === "enter") {
84
+ const ms = this.matches();
85
+ const menuOpen = this.buf.startsWith("/") && ms.length > 0;
86
+ const chosen = ms[this.sel];
87
+ this.finish(menuOpen && chosen ? chosen.name : this.buf);
88
+ return;
89
+ }
90
+ if (name === "escape") {
91
+ this.buf = "";
92
+ this.sel = 0;
93
+ this.draw();
94
+ return;
95
+ }
96
+ if (name === "backspace") {
97
+ this.buf = this.buf.slice(0, -1);
98
+ this.sel = 0;
99
+ this.draw();
100
+ return;
101
+ }
102
+ if (name === "up" || name === "down") {
103
+ const n = this.matches().length;
104
+ if (n > 0) {
105
+ this.sel = name === "up" ? (this.sel - 1 + n) % n : (this.sel + 1) % n;
106
+ this.draw();
107
+ }
108
+ return;
109
+ }
110
+ if (name === "tab") {
111
+ const chosen = this.matches()[this.sel];
112
+ if (chosen) {
113
+ this.buf = chosen.name;
114
+ this.draw();
115
+ }
116
+ return;
117
+ }
118
+ if (str && !key?.ctrl) {
119
+ const clean = str.replace(/[\r\n]/g, "");
120
+ if (clean && (clean.length > 1 || clean.charCodeAt(0) >= 32)) {
121
+ this.buf += clean;
122
+ this.sel = 0;
123
+ this.draw();
124
+ }
125
+ }
126
+ }
127
+ /** Read one line. `promptOverride` swaps the prompt text (for sub-questions); null = EOF (Ctrl-D). */
128
+ readLine(promptOverride) {
129
+ this.curPrompt = promptOverride ?? this.basePrompt;
130
+ this.buf = "";
131
+ this.sel = 0;
132
+ this.rows = 0;
133
+ if (this.stdin.isTTY) this.stdin.setRawMode(true);
134
+ this.stdin.resume();
135
+ this.draw();
136
+ return new Promise((resolve2) => {
137
+ this.resolver = resolve2;
138
+ const handler = (s, k) => {
139
+ this.handle(s, k);
140
+ };
141
+ this.onKey = handler;
142
+ this.stdin.on("keypress", handler);
143
+ });
144
+ }
145
+ /** True while actively awaiting a line (so callers can choose to print-above vs queue). */
146
+ isReading() {
147
+ return this.resolver !== void 0;
148
+ }
149
+ /** Print something above the live prompt (e.g. an async notice) without disturbing the input. */
150
+ printAbove(fn) {
151
+ if (!this.resolver) {
152
+ fn();
153
+ return;
154
+ }
155
+ this.stdout.write("\r\x1B[0J");
156
+ fn();
157
+ this.draw();
158
+ }
159
+ close() {
160
+ if (this.onKey) this.stdin.off("keypress", this.onKey);
161
+ if (this.stdin.isTTY) this.stdin.setRawMode(false);
162
+ }
163
+ };
164
+
7
165
  // src/main.ts
8
- import { createInterface } from "readline/promises";
9
- import { clearLine, cursorTo, emitKeypressEvents } from "readline";
10
166
  import { stdin, stdout } from "process";
11
- 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";
12
168
  import { homedir as homedir2 } from "os";
13
169
  import { join as join2 } from "path";
14
170
  import { z as z7 } from "zod";
@@ -1026,6 +1182,7 @@ function memwalFactsPlugin(opts) {
1026
1182
 
1027
1183
  // ../../packages/adapters/walrus/src/index.ts
1028
1184
  import { mkdir, readFile, writeFile } from "fs/promises";
1185
+ import { mkdirSync, readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2 } from "fs";
1029
1186
  import { dirname } from "path";
1030
1187
  import { z as z2 } from "zod";
1031
1188
  var DEFAULT_PUBLISHER = "https://publisher.walrus-testnet.walrus.space";
@@ -1163,6 +1320,13 @@ function walrusMemoryPlugin(opts) {
1163
1320
  let pending = Promise.resolve();
1164
1321
  async function load() {
1165
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
+ }
1166
1330
  loading ??= (async () => {
1167
1331
  try {
1168
1332
  const blobId = await opts.pointers.get(key);
@@ -1178,6 +1342,13 @@ function walrusMemoryPlugin(opts) {
1178
1342
  }
1179
1343
  function save(facts) {
1180
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
+ }
1181
1352
  opts.onStoreStart?.();
1182
1353
  pending = pending.then(async () => {
1183
1354
  try {
@@ -1800,7 +1971,7 @@ var SkillRegistry = class {
1800
1971
  var defaultRegistry = new SkillRegistry();
1801
1972
 
1802
1973
  // src/onboarding.ts
1803
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync, chmodSync } from "fs";
1974
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, chmodSync } from "fs";
1804
1975
  import { homedir } from "os";
1805
1976
  import { join, dirname as dirname2 } from "path";
1806
1977
  import { fileURLToPath } from "url";
@@ -1810,18 +1981,18 @@ var CONFIG = join(THINY_DIR, "config.json");
1810
1981
  function version() {
1811
1982
  try {
1812
1983
  const pkg = join(dirname2(fileURLToPath(import.meta.url)), "../package.json");
1813
- return JSON.parse(readFileSync2(pkg, "utf8")).version ?? "0.0.0";
1984
+ return JSON.parse(readFileSync3(pkg, "utf8")).version ?? "0.0.0";
1814
1985
  } catch {
1815
1986
  return "0.0.0";
1816
1987
  }
1817
1988
  }
1818
1989
  function loadConfig() {
1819
- return existsSync2(CONFIG) ? JSON.parse(readFileSync2(CONFIG, "utf8")) : null;
1990
+ return existsSync3(CONFIG) ? JSON.parse(readFileSync3(CONFIG, "utf8")) : null;
1820
1991
  }
1821
1992
  function saveConfig(cfg) {
1822
- mkdirSync(THINY_DIR, { recursive: true });
1993
+ mkdirSync2(THINY_DIR, { recursive: true });
1823
1994
  chmodSync(THINY_DIR, 448);
1824
- writeFileSync(CONFIG, JSON.stringify(cfg, null, 2));
1995
+ writeFileSync2(CONFIG, JSON.stringify(cfg, null, 2));
1825
1996
  chmodSync(CONFIG, 384);
1826
1997
  }
1827
1998
  function applyConfig(cfg) {
@@ -2066,12 +2237,12 @@ async function createSkillPlugin(id, env) {
2066
2237
 
2067
2238
  // src/ui.ts
2068
2239
  import figlet from "figlet";
2069
- import chalk from "chalk";
2070
- var BRAND = chalk.cyan;
2071
- var DIM = chalk.dim;
2240
+ import chalk2 from "chalk";
2241
+ var BRAND = chalk2.cyan;
2242
+ var DIM = chalk2.dim;
2072
2243
  var AGENT_LABEL = BRAND.bold;
2073
- var ERROR_COLOR = chalk.red;
2074
- var SUCCESS_COLOR = chalk.green;
2244
+ var ERROR_COLOR = chalk2.red;
2245
+ var SUCCESS_COLOR = chalk2.green;
2075
2246
  function getWidth() {
2076
2247
  return process.stdout.columns || 80;
2077
2248
  }
@@ -2149,9 +2320,9 @@ function renderToolsAndSkills(tools, skills, opts) {
2149
2320
  process.stdout.write(BRAND("\u2514" + "\u2500".repeat(w - 2) + "\u2518") + "\n");
2150
2321
  }
2151
2322
  function renderHints(logFile) {
2152
- const logHint = logFile ? ` \xB7 ${DIM("logs \u2192")} ${chalk.dim(logFile)}` : "";
2323
+ const logHint = logFile ? ` \xB7 ${DIM("logs \u2192")} ${chalk2.dim(logFile)}` : "";
2153
2324
  process.stdout.write(
2154
- "\n" + DIM("Type a message \xB7 ") + DIM("/new") + chalk.dim(" new session \xB7 ") + DIM("/skills") + chalk.dim(" list skills \xB7 ") + DIM("/tools") + chalk.dim(" list tools \xB7 ") + DIM("Ctrl+C") + chalk.dim(" quit") + logHint + "\n\n"
2325
+ "\n" + DIM("Type a message \xB7 ") + DIM("/new") + chalk2.dim(" new session \xB7 ") + DIM("/skills") + chalk2.dim(" list skills \xB7 ") + DIM("/tools") + chalk2.dim(" list tools \xB7 ") + DIM("Ctrl+C") + chalk2.dim(" quit") + logHint + "\n\n"
2155
2326
  );
2156
2327
  }
2157
2328
  function renderAgentLabel(name) {
@@ -2161,13 +2332,13 @@ function renderAgentDone() {
2161
2332
  process.stdout.write("\n");
2162
2333
  }
2163
2334
  function renderError(message) {
2164
- process.stdout.write("\n" + ERROR_COLOR("Error: ") + chalk.white(message) + "\n");
2335
+ process.stdout.write("\n" + ERROR_COLOR("Error: ") + chalk2.white(message) + "\n");
2165
2336
  }
2166
2337
  function renderInfo(message) {
2167
2338
  process.stdout.write(DIM(message) + "\n");
2168
2339
  }
2169
2340
  function renderWarning(message) {
2170
- process.stdout.write(chalk.yellow("\u26A0 ") + chalk.white(message) + "\n");
2341
+ process.stdout.write(chalk2.yellow("\u26A0 ") + chalk2.white(message) + "\n");
2171
2342
  }
2172
2343
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
2173
2344
  var Spinner = class {
@@ -2203,7 +2374,7 @@ function renderStatus(parts) {
2203
2374
  }
2204
2375
  function renderStored(label, links, backend = "Walrus") {
2205
2376
  process.stdout.write(
2206
- SUCCESS_COLOR(" \u2713 ") + DIM(`${label} on ${backend} \xB7 `) + chalk.dim.underline(links.blob) + "\n"
2377
+ SUCCESS_COLOR(" \u2713 ") + DIM(`${label} on ${backend} \xB7 `) + chalk2.dim.underline(links.blob) + "\n"
2207
2378
  );
2208
2379
  }
2209
2380
  function renderSaving(label, backend = "Walrus") {
@@ -2230,14 +2401,14 @@ function renderInline(s) {
2230
2401
  codes.push(c);
2231
2402
  return `\0${String(codes.length - 1)}\0`;
2232
2403
  });
2233
- s = s.replace(/\*\*([^*\n]+)\*\*/g, (_m, t) => chalk.bold(t));
2234
- s = s.replace(/\*([^*\n]+)\*/g, (_m, t) => chalk.italic(t));
2235
- s = s.replace(/~~([^~\n]+)~~/g, (_m, t) => chalk.strikethrough(t));
2404
+ s = s.replace(/\*\*([^*\n]+)\*\*/g, (_m, t) => chalk2.bold(t));
2405
+ s = s.replace(/\*([^*\n]+)\*/g, (_m, t) => chalk2.italic(t));
2406
+ s = s.replace(/~~([^~\n]+)~~/g, (_m, t) => chalk2.strikethrough(t));
2236
2407
  s = s.replace(
2237
2408
  /\[([^\]]+)\]\(([^)\s]+)\)/g,
2238
- (_m, text2, url) => OSC8(url, chalk.cyan.underline(text2))
2409
+ (_m, text2, url) => OSC8(url, chalk2.cyan.underline(text2))
2239
2410
  );
2240
- s = s.replace(/(\d+)/g, (_m, i) => chalk.cyan(codes[Number(i)] ?? ""));
2411
+ s = s.replace(/(\d+)/g, (_m, i) => chalk2.cyan(codes[Number(i)] ?? ""));
2241
2412
  return s;
2242
2413
  }
2243
2414
  function renderMarkdownLine(line, inCode, setCode) {
@@ -2247,16 +2418,16 @@ function renderMarkdownLine(line, inCode, setCode) {
2247
2418
  setCode(!inCode);
2248
2419
  return "";
2249
2420
  }
2250
- if (inCode) return chalk.cyan(line);
2421
+ if (inCode) return chalk2.cyan(line);
2251
2422
  const h = /^(#{1,6})\s+(.*)$/.exec(trimmed);
2252
- if (h) return ((h[1] ?? "").length <= 2 ? chalk.bold.underline : chalk.bold)(renderInline(h[2] ?? ""));
2253
- if (/^(-{3,}|\*{3,}|_{3,})$/.test(trimmed)) return chalk.dim("\u2500".repeat(Math.min(getWidth(), 50)));
2423
+ if (h) return ((h[1] ?? "").length <= 2 ? chalk2.bold.underline : chalk2.bold)(renderInline(h[2] ?? ""));
2424
+ if (/^(-{3,}|\*{3,}|_{3,})$/.test(trimmed)) return chalk2.dim("\u2500".repeat(Math.min(getWidth(), 50)));
2254
2425
  const q = /^>\s?(.*)$/.exec(trimmed);
2255
- if (q) return chalk.dim(`\u2502 ${renderInline(q[1] ?? "")}`);
2426
+ if (q) return chalk2.dim(`\u2502 ${renderInline(q[1] ?? "")}`);
2256
2427
  const b = /^[-*+]\s+(.*)$/.exec(trimmed);
2257
- if (b) return `${indent}${chalk.cyan("\u2022")} ${renderInline(b[1] ?? "")}`;
2428
+ if (b) return `${indent}${chalk2.cyan("\u2022")} ${renderInline(b[1] ?? "")}`;
2258
2429
  const n = /^(\d+)[.)]\s+(.*)$/.exec(trimmed);
2259
- if (n) return `${indent}${chalk.cyan(`${n[1] ?? ""}.`)} ${renderInline(n[2] ?? "")}`;
2430
+ if (n) return `${indent}${chalk2.cyan(`${n[1] ?? ""}.`)} ${renderInline(n[2] ?? "")}`;
2260
2431
  return renderInline(line);
2261
2432
  }
2262
2433
  function createMarkdownWriter(write) {
@@ -2271,7 +2442,7 @@ function createMarkdownWriter(write) {
2271
2442
  const emit = (text2, think) => {
2272
2443
  if (!text2) return;
2273
2444
  if (think) {
2274
- write(chalk.dim.italic(text2));
2445
+ write(chalk2.dim.italic(text2));
2275
2446
  return;
2276
2447
  }
2277
2448
  for (const ch of text2) {
@@ -2358,6 +2529,18 @@ function parseSkillArgs() {
2358
2529
  return (args[idx + 1] ?? "").split(",").map((s) => s.trim()).filter(Boolean);
2359
2530
  }
2360
2531
  var currentSessionId = `cli-${(/* @__PURE__ */ new Date()).getTime().toString()}`;
2532
+ var SLASH_COMMANDS = [
2533
+ { name: "/new", desc: "Start a new session (long-term memory carries over)" },
2534
+ { name: "/connect", desc: "Switch the LLM provider" },
2535
+ { name: "/models", desc: "Change the active provider's model" },
2536
+ { name: "/tools", desc: "List available tools" },
2537
+ { name: "/skills", desc: "List available skills" },
2538
+ { name: "/session", desc: "Show the current session id" },
2539
+ { name: "/stats", desc: "Session token + tool stats" },
2540
+ { name: "/verify", desc: "Replay a Walrus audit trail by blob id" },
2541
+ { name: "/clear", desc: "Clear the screen" },
2542
+ { name: "/help", desc: "Show help" }
2543
+ ];
2361
2544
  function isNewerVersion(latest, current) {
2362
2545
  const a = latest.split(".").map((n) => Number(n) || 0);
2363
2546
  const b = current.split(".").map((n) => Number(n) || 0);
@@ -2370,19 +2553,19 @@ function notifyIfUpdate(thinyDir) {
2370
2553
  const cur = version();
2371
2554
  const cacheFile = join2(thinyDir, "update-check.json");
2372
2555
  try {
2373
- const cached = JSON.parse(readFileSync3(cacheFile, "utf8"));
2556
+ const cached = JSON.parse(readFileSync4(cacheFile, "utf8"));
2374
2557
  if (cached.latest && isNewerVersion(cached.latest, cur)) {
2375
2558
  renderInfo(`Update available: ${cur} \u2192 ${cached.latest} \u2014 run \`thiny update\``);
2376
2559
  }
2377
2560
  } catch {
2378
2561
  }
2379
2562
  void fetch("https://registry.npmjs.org/thinyai/latest").then((r) => r.json()).then((j) => {
2380
- if (j.version) writeFileSync2(cacheFile, JSON.stringify({ latest: j.version, at: Date.now() }));
2563
+ if (j.version) writeFileSync3(cacheFile, JSON.stringify({ latest: j.version, at: Date.now() }));
2381
2564
  }).catch(() => void 0);
2382
2565
  }
2383
2566
  async function runCli() {
2384
2567
  const thinyDir = join2(homedir2(), ".thiny");
2385
- mkdirSync2(thinyDir, { recursive: true });
2568
+ mkdirSync3(thinyDir, { recursive: true });
2386
2569
  const envLogFile = process.env.THINY_LOG_FILE?.trim();
2387
2570
  const logFile = envLogFile && envLogFile.length > 0 ? envLogFile : join2(thinyDir, "cli.log");
2388
2571
  const fileLogger = pinoLogger({ level: process.env.LOG_LEVEL ?? "info", file: logFile });
@@ -2430,6 +2613,8 @@ async function runCli() {
2430
2613
  // directory `thiny` is launched from — a cwd-relative file would fragment per folder.
2431
2614
  pointers: filePointerStore(process.env.WALRUS_POINTERS ?? join2(thinyDir, "thiny-pointers.json")),
2432
2615
  userId,
2616
+ // Instant, reliable local mirror — cross-session memory no longer waits on the slow Walrus PUT.
2617
+ cacheFile: join2(thinyDir, `memory-${userId}.json`),
2433
2618
  onStoreStart: () => pendingWrites += 1,
2434
2619
  onStore: (ref) => {
2435
2620
  if (pendingWrites > 0) pendingWrites -= 1;
@@ -2687,7 +2872,7 @@ async function runCli() {
2687
2872
  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).
2688
2873
 
2689
2874
  YOUR TOOLS:
2690
- \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.
2875
+ \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.
2691
2876
  \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.
2692
2877
  ` + (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.",
2693
2878
  tools: [echoTool, suiSetupTool, ...walletTools, fetchUrlTool, ...webTools],
@@ -2754,23 +2939,13 @@ YOUR TOOLS:
2754
2939
  `Web: fetch_url (any URL)${webSearchOn ? ` \xB7 web_search (${exaKey ? "Exa" : "Brave"})` : " \xB7 web_search off (set EXA_API_KEY)"}`
2755
2940
  );
2756
2941
  notifyIfUpdate(thinyDir);
2757
- const rl = createInterface({ input: stdin, output: stdout });
2758
- emitKeypressEvents(stdin);
2942
+ const PROMPT = "\x1B[36mYou\x1B[0m \x1B[2m\u203A\x1B[0m ";
2943
+ const prompt = new SlashPrompt(stdin, stdout, PROMPT, SLASH_COMMANDS);
2759
2944
  const spinner = new Spinner();
2760
2945
  const flushMemory = memoryPlugin.flush;
2761
- const PROMPT = "\x1B[36mYou\x1B[0m \x1B[2m\u203A\x1B[0m ";
2762
- let atPrompt = false;
2763
- const printAbovePrompt = (fn) => {
2764
- if (atPrompt) {
2765
- cursorTo(stdout, 0);
2766
- clearLine(stdout, 0);
2767
- }
2768
- fn();
2769
- if (atPrompt) rl.prompt(true);
2770
- };
2771
2946
  deliverRef = (ref) => {
2772
- if (atPrompt) {
2773
- printAbovePrompt(() => {
2947
+ if (prompt.isReading()) {
2948
+ prompt.printAbove(() => {
2774
2949
  renderStored("memory saved", explorerLinks(ref, network), memBackend);
2775
2950
  });
2776
2951
  } else memoryRefs.push(ref);
@@ -2790,7 +2965,7 @@ YOUR TOOLS:
2790
2965
  ` ${String(i + 1)}. ${pr.label} (${pr.model})${pr.id === cfg.activeProviderId ? " \xB7 active" : ""}`
2791
2966
  );
2792
2967
  });
2793
- const ans = (await rl.question("Switch to (number, blank to cancel): ")).trim();
2968
+ const ans = (await prompt.readLine("Switch to (number, blank to cancel): ") ?? "").trim();
2794
2969
  if (!ans) return;
2795
2970
  const idx = Number(ans) - 1;
2796
2971
  const chosen = provs[idx];
@@ -2825,7 +3000,7 @@ Active: ${prov.label} \xB7 current model: ${prov.model}`);
2825
3000
  providersOf(cfg).forEach((pr) => {
2826
3001
  renderInfo(` \u2022 ${pr.label}: ${pr.model}`);
2827
3002
  });
2828
- modelId = (await rl.question("New model id for the active provider (blank to cancel): ")).trim();
3003
+ modelId = (await prompt.readLine("New model id for the active provider (blank to cancel): ") ?? "").trim();
2829
3004
  if (!modelId) return;
2830
3005
  }
2831
3006
  prov.model = modelId;
@@ -2850,9 +3025,8 @@ Active: ${prov.label} \xB7 current model: ${prov.model}`);
2850
3025
  for (const ref of memoryRefs.splice(0))
2851
3026
  renderStored("memory saved", explorerLinks(ref, network), memBackend);
2852
3027
  if (pendingWrites > 0) renderSaving("memory", memBackend);
2853
- atPrompt = true;
2854
- const input = await rl.question(PROMPT);
2855
- atPrompt = false;
3028
+ const input = await prompt.readLine();
3029
+ if (input === null) break;
2856
3030
  const trimmed = input.trim();
2857
3031
  if (!trimmed) continue;
2858
3032
  if (trimmed.startsWith("/")) {
@@ -3020,8 +3194,8 @@ Audit trail ${blobId}
3020
3194
  }).catch((err) => {
3021
3195
  if (pendingWrites > 0) pendingWrites -= 1;
3022
3196
  const m = `Walrus audit flush failed: ${err instanceof Error ? err.message : String(err)}`;
3023
- if (atPrompt) {
3024
- printAbovePrompt(() => {
3197
+ if (prompt.isReading()) {
3198
+ prompt.printAbove(() => {
3025
3199
  renderWarning(m);
3026
3200
  });
3027
3201
  } else renderWarning(m);
@@ -3040,6 +3214,7 @@ Audit trail ${blobId}
3040
3214
  }
3041
3215
  }
3042
3216
  } finally {
3217
+ prompt.close();
3043
3218
  if (flushMemory) await flushMemory().catch(() => void 0);
3044
3219
  }
3045
3220
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinyai",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
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/model-aisdk": "0.1.0",
42
41
  "@thiny/logger-pino": "0.1.0",
42
+ "@thiny/memory-memwal": "0.1.0",
43
+ "@thiny/model-aisdk": "0.1.0",
43
44
  "@thiny/mcp": "0.1.0",
44
- "@thiny/walrus": "0.1.0",
45
+ "@thiny/plugin-sui": "0.1.0",
45
46
  "@thiny/plugin-agents": "0.1.0",
46
47
  "@thiny/plugin-web-search": "0.1.0",
48
+ "@thiny/walrus": "0.1.0",
47
49
  "@thiny/signer-sui": "0.1.0",
48
- "@thiny/plugin-sui": "0.1.0",
49
- "@thiny/skills": "0.1.0",
50
- "@thiny/memory-memwal": "0.1.0"
50
+ "@thiny/skills": "0.1.0"
51
51
  },
52
52
  "author": "Thiny AI",
53
53
  "engines": {