thinyai 0.1.9 → 0.1.10

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 +203 -45
  2. package/package.json +6 -6
package/dist/bin.js CHANGED
@@ -4,9 +4,165 @@
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
167
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
12
168
  import { homedir as homedir2 } from "os";
@@ -2066,12 +2222,12 @@ async function createSkillPlugin(id, env) {
2066
2222
 
2067
2223
  // src/ui.ts
2068
2224
  import figlet from "figlet";
2069
- import chalk from "chalk";
2070
- var BRAND = chalk.cyan;
2071
- var DIM = chalk.dim;
2225
+ import chalk2 from "chalk";
2226
+ var BRAND = chalk2.cyan;
2227
+ var DIM = chalk2.dim;
2072
2228
  var AGENT_LABEL = BRAND.bold;
2073
- var ERROR_COLOR = chalk.red;
2074
- var SUCCESS_COLOR = chalk.green;
2229
+ var ERROR_COLOR = chalk2.red;
2230
+ var SUCCESS_COLOR = chalk2.green;
2075
2231
  function getWidth() {
2076
2232
  return process.stdout.columns || 80;
2077
2233
  }
@@ -2149,9 +2305,9 @@ function renderToolsAndSkills(tools, skills, opts) {
2149
2305
  process.stdout.write(BRAND("\u2514" + "\u2500".repeat(w - 2) + "\u2518") + "\n");
2150
2306
  }
2151
2307
  function renderHints(logFile) {
2152
- const logHint = logFile ? ` \xB7 ${DIM("logs \u2192")} ${chalk.dim(logFile)}` : "";
2308
+ const logHint = logFile ? ` \xB7 ${DIM("logs \u2192")} ${chalk2.dim(logFile)}` : "";
2153
2309
  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"
2310
+ "\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
2311
  );
2156
2312
  }
2157
2313
  function renderAgentLabel(name) {
@@ -2161,13 +2317,13 @@ function renderAgentDone() {
2161
2317
  process.stdout.write("\n");
2162
2318
  }
2163
2319
  function renderError(message) {
2164
- process.stdout.write("\n" + ERROR_COLOR("Error: ") + chalk.white(message) + "\n");
2320
+ process.stdout.write("\n" + ERROR_COLOR("Error: ") + chalk2.white(message) + "\n");
2165
2321
  }
2166
2322
  function renderInfo(message) {
2167
2323
  process.stdout.write(DIM(message) + "\n");
2168
2324
  }
2169
2325
  function renderWarning(message) {
2170
- process.stdout.write(chalk.yellow("\u26A0 ") + chalk.white(message) + "\n");
2326
+ process.stdout.write(chalk2.yellow("\u26A0 ") + chalk2.white(message) + "\n");
2171
2327
  }
2172
2328
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
2173
2329
  var Spinner = class {
@@ -2203,7 +2359,7 @@ function renderStatus(parts) {
2203
2359
  }
2204
2360
  function renderStored(label, links, backend = "Walrus") {
2205
2361
  process.stdout.write(
2206
- SUCCESS_COLOR(" \u2713 ") + DIM(`${label} on ${backend} \xB7 `) + chalk.dim.underline(links.blob) + "\n"
2362
+ SUCCESS_COLOR(" \u2713 ") + DIM(`${label} on ${backend} \xB7 `) + chalk2.dim.underline(links.blob) + "\n"
2207
2363
  );
2208
2364
  }
2209
2365
  function renderSaving(label, backend = "Walrus") {
@@ -2230,14 +2386,14 @@ function renderInline(s) {
2230
2386
  codes.push(c);
2231
2387
  return `\0${String(codes.length - 1)}\0`;
2232
2388
  });
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));
2389
+ s = s.replace(/\*\*([^*\n]+)\*\*/g, (_m, t) => chalk2.bold(t));
2390
+ s = s.replace(/\*([^*\n]+)\*/g, (_m, t) => chalk2.italic(t));
2391
+ s = s.replace(/~~([^~\n]+)~~/g, (_m, t) => chalk2.strikethrough(t));
2236
2392
  s = s.replace(
2237
2393
  /\[([^\]]+)\]\(([^)\s]+)\)/g,
2238
- (_m, text2, url) => OSC8(url, chalk.cyan.underline(text2))
2394
+ (_m, text2, url) => OSC8(url, chalk2.cyan.underline(text2))
2239
2395
  );
2240
- s = s.replace(/(\d+)/g, (_m, i) => chalk.cyan(codes[Number(i)] ?? ""));
2396
+ s = s.replace(/(\d+)/g, (_m, i) => chalk2.cyan(codes[Number(i)] ?? ""));
2241
2397
  return s;
2242
2398
  }
2243
2399
  function renderMarkdownLine(line, inCode, setCode) {
@@ -2247,16 +2403,16 @@ function renderMarkdownLine(line, inCode, setCode) {
2247
2403
  setCode(!inCode);
2248
2404
  return "";
2249
2405
  }
2250
- if (inCode) return chalk.cyan(line);
2406
+ if (inCode) return chalk2.cyan(line);
2251
2407
  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)));
2408
+ if (h) return ((h[1] ?? "").length <= 2 ? chalk2.bold.underline : chalk2.bold)(renderInline(h[2] ?? ""));
2409
+ if (/^(-{3,}|\*{3,}|_{3,})$/.test(trimmed)) return chalk2.dim("\u2500".repeat(Math.min(getWidth(), 50)));
2254
2410
  const q = /^>\s?(.*)$/.exec(trimmed);
2255
- if (q) return chalk.dim(`\u2502 ${renderInline(q[1] ?? "")}`);
2411
+ if (q) return chalk2.dim(`\u2502 ${renderInline(q[1] ?? "")}`);
2256
2412
  const b = /^[-*+]\s+(.*)$/.exec(trimmed);
2257
- if (b) return `${indent}${chalk.cyan("\u2022")} ${renderInline(b[1] ?? "")}`;
2413
+ if (b) return `${indent}${chalk2.cyan("\u2022")} ${renderInline(b[1] ?? "")}`;
2258
2414
  const n = /^(\d+)[.)]\s+(.*)$/.exec(trimmed);
2259
- if (n) return `${indent}${chalk.cyan(`${n[1] ?? ""}.`)} ${renderInline(n[2] ?? "")}`;
2415
+ if (n) return `${indent}${chalk2.cyan(`${n[1] ?? ""}.`)} ${renderInline(n[2] ?? "")}`;
2260
2416
  return renderInline(line);
2261
2417
  }
2262
2418
  function createMarkdownWriter(write) {
@@ -2271,7 +2427,7 @@ function createMarkdownWriter(write) {
2271
2427
  const emit = (text2, think) => {
2272
2428
  if (!text2) return;
2273
2429
  if (think) {
2274
- write(chalk.dim.italic(text2));
2430
+ write(chalk2.dim.italic(text2));
2275
2431
  return;
2276
2432
  }
2277
2433
  for (const ch of text2) {
@@ -2358,6 +2514,18 @@ function parseSkillArgs() {
2358
2514
  return (args[idx + 1] ?? "").split(",").map((s) => s.trim()).filter(Boolean);
2359
2515
  }
2360
2516
  var currentSessionId = `cli-${(/* @__PURE__ */ new Date()).getTime().toString()}`;
2517
+ var SLASH_COMMANDS = [
2518
+ { name: "/new", desc: "Start a new session (long-term memory carries over)" },
2519
+ { name: "/connect", desc: "Switch the LLM provider" },
2520
+ { name: "/models", desc: "Change the active provider's model" },
2521
+ { name: "/tools", desc: "List available tools" },
2522
+ { name: "/skills", desc: "List available skills" },
2523
+ { name: "/session", desc: "Show the current session id" },
2524
+ { name: "/stats", desc: "Session token + tool stats" },
2525
+ { name: "/verify", desc: "Replay a Walrus audit trail by blob id" },
2526
+ { name: "/clear", desc: "Clear the screen" },
2527
+ { name: "/help", desc: "Show help" }
2528
+ ];
2361
2529
  function isNewerVersion(latest, current) {
2362
2530
  const a = latest.split(".").map((n) => Number(n) || 0);
2363
2531
  const b = current.split(".").map((n) => Number(n) || 0);
@@ -2754,23 +2922,13 @@ YOUR TOOLS:
2754
2922
  `Web: fetch_url (any URL)${webSearchOn ? ` \xB7 web_search (${exaKey ? "Exa" : "Brave"})` : " \xB7 web_search off (set EXA_API_KEY)"}`
2755
2923
  );
2756
2924
  notifyIfUpdate(thinyDir);
2757
- const rl = createInterface({ input: stdin, output: stdout });
2758
- emitKeypressEvents(stdin);
2925
+ const PROMPT = "\x1B[36mYou\x1B[0m \x1B[2m\u203A\x1B[0m ";
2926
+ const prompt = new SlashPrompt(stdin, stdout, PROMPT, SLASH_COMMANDS);
2759
2927
  const spinner = new Spinner();
2760
2928
  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
2929
  deliverRef = (ref) => {
2772
- if (atPrompt) {
2773
- printAbovePrompt(() => {
2930
+ if (prompt.isReading()) {
2931
+ prompt.printAbove(() => {
2774
2932
  renderStored("memory saved", explorerLinks(ref, network), memBackend);
2775
2933
  });
2776
2934
  } else memoryRefs.push(ref);
@@ -2790,7 +2948,7 @@ YOUR TOOLS:
2790
2948
  ` ${String(i + 1)}. ${pr.label} (${pr.model})${pr.id === cfg.activeProviderId ? " \xB7 active" : ""}`
2791
2949
  );
2792
2950
  });
2793
- const ans = (await rl.question("Switch to (number, blank to cancel): ")).trim();
2951
+ const ans = (await prompt.readLine("Switch to (number, blank to cancel): ") ?? "").trim();
2794
2952
  if (!ans) return;
2795
2953
  const idx = Number(ans) - 1;
2796
2954
  const chosen = provs[idx];
@@ -2825,7 +2983,7 @@ Active: ${prov.label} \xB7 current model: ${prov.model}`);
2825
2983
  providersOf(cfg).forEach((pr) => {
2826
2984
  renderInfo(` \u2022 ${pr.label}: ${pr.model}`);
2827
2985
  });
2828
- modelId = (await rl.question("New model id for the active provider (blank to cancel): ")).trim();
2986
+ modelId = (await prompt.readLine("New model id for the active provider (blank to cancel): ") ?? "").trim();
2829
2987
  if (!modelId) return;
2830
2988
  }
2831
2989
  prov.model = modelId;
@@ -2850,9 +3008,8 @@ Active: ${prov.label} \xB7 current model: ${prov.model}`);
2850
3008
  for (const ref of memoryRefs.splice(0))
2851
3009
  renderStored("memory saved", explorerLinks(ref, network), memBackend);
2852
3010
  if (pendingWrites > 0) renderSaving("memory", memBackend);
2853
- atPrompt = true;
2854
- const input = await rl.question(PROMPT);
2855
- atPrompt = false;
3011
+ const input = await prompt.readLine();
3012
+ if (input === null) break;
2856
3013
  const trimmed = input.trim();
2857
3014
  if (!trimmed) continue;
2858
3015
  if (trimmed.startsWith("/")) {
@@ -3020,8 +3177,8 @@ Audit trail ${blobId}
3020
3177
  }).catch((err) => {
3021
3178
  if (pendingWrites > 0) pendingWrites -= 1;
3022
3179
  const m = `Walrus audit flush failed: ${err instanceof Error ? err.message : String(err)}`;
3023
- if (atPrompt) {
3024
- printAbovePrompt(() => {
3180
+ if (prompt.isReading()) {
3181
+ prompt.printAbove(() => {
3025
3182
  renderWarning(m);
3026
3183
  });
3027
3184
  } else renderWarning(m);
@@ -3040,6 +3197,7 @@ Audit trail ${blobId}
3040
3197
  }
3041
3198
  }
3042
3199
  } finally {
3200
+ prompt.close();
3043
3201
  if (flushMemory) await flushMemory().catch(() => void 0);
3044
3202
  }
3045
3203
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinyai",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
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
- "@thiny/logger-pino": "0.1.0",
43
- "@thiny/mcp": "0.1.0",
44
41
  "@thiny/walrus": "0.1.0",
45
42
  "@thiny/plugin-agents": "0.1.0",
46
43
  "@thiny/plugin-web-search": "0.1.0",
44
+ "@thiny/logger-pino": "0.1.0",
45
+ "@thiny/mcp": "0.1.0",
46
+ "@thiny/memory-memwal": "0.1.0",
47
47
  "@thiny/signer-sui": "0.1.0",
48
48
  "@thiny/plugin-sui": "0.1.0",
49
- "@thiny/skills": "0.1.0",
50
- "@thiny/memory-memwal": "0.1.0"
49
+ "@thiny/model-aisdk": "0.1.0",
50
+ "@thiny/skills": "0.1.0"
51
51
  },
52
52
  "author": "Thiny AI",
53
53
  "engines": {