thinyai 0.1.8 → 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 +347 -58
  2. package/package.json +8 -8
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";
@@ -1829,11 +1985,14 @@ function applyConfig(cfg) {
1829
1985
  const set = (k, v) => {
1830
1986
  if (v && !process.env[k]) process.env[k] = v;
1831
1987
  };
1832
- set("THINY_MODEL", cfg.model);
1833
- if (cfg.apiKey) {
1834
- set(cfg.model?.startsWith("anthropic") ? "THINY_ANTHROPIC_API_KEY" : "THINY_OPENAI_API_KEY", cfg.apiKey);
1988
+ const prov = activeProvider(cfg);
1989
+ if (prov) {
1990
+ set("THINY_MODEL", prov.model);
1991
+ if (prov.apiKey) {
1992
+ set(prov.model.startsWith("anthropic") ? "THINY_ANTHROPIC_API_KEY" : "THINY_OPENAI_API_KEY", prov.apiKey);
1993
+ }
1994
+ set("THINY_OPENAI_BASE_URL", prov.baseUrl);
1835
1995
  }
1836
- set("THINY_OPENAI_BASE_URL", cfg.baseUrl);
1837
1996
  set("THINY_PERSONA_NAME", cfg.agentName);
1838
1997
  set("THINY_USER_ID", cfg.userId);
1839
1998
  if (cfg.sui?.network) {
@@ -1872,6 +2031,37 @@ function saveSuiWallet(cfg, network, wallet, makeActive) {
1872
2031
  if (makeActive || !cfg.sui.activeAddress) cfg.sui.activeAddress = wallet.address;
1873
2032
  saveConfig(cfg);
1874
2033
  }
2034
+ function providersOf(cfg) {
2035
+ if (!cfg) return [];
2036
+ if (cfg.providers && cfg.providers.length > 0) return cfg.providers;
2037
+ if (cfg.model) {
2038
+ const id = cfg.model.startsWith("anthropic") ? "anthropic" : cfg.baseUrl ? "custom" : "openai";
2039
+ return [{ id, label: cfg.model, model: cfg.model, apiKey: cfg.apiKey, baseUrl: cfg.baseUrl }];
2040
+ }
2041
+ return [];
2042
+ }
2043
+ function activeProvider(cfg) {
2044
+ const all = providersOf(cfg);
2045
+ return all.find((x) => x.id === cfg?.activeProviderId) ?? all[0];
2046
+ }
2047
+ function saveProvider(cfg, provider, makeActive) {
2048
+ const all = providersOf(cfg).filter((x) => x.id !== provider.id);
2049
+ all.push(provider);
2050
+ cfg.providers = all;
2051
+ delete cfg.model;
2052
+ delete cfg.apiKey;
2053
+ delete cfg.baseUrl;
2054
+ if (makeActive || !cfg.activeProviderId) cfg.activeProviderId = provider.id;
2055
+ saveConfig(cfg);
2056
+ }
2057
+ function setActiveProvider(cfg, id) {
2058
+ const prov = providersOf(cfg).find((x) => x.id === id);
2059
+ if (!prov) return void 0;
2060
+ cfg.providers = providersOf(cfg);
2061
+ cfg.activeProviderId = id;
2062
+ saveConfig(cfg);
2063
+ return prov;
2064
+ }
1875
2065
  function bail(v) {
1876
2066
  if (p.isCancel(v)) {
1877
2067
  p.cancel("Cancelled.");
@@ -1897,25 +2087,28 @@ async function baseSetup() {
1897
2087
  );
1898
2088
  const pick = MODELS.find((m) => m.value === choice);
1899
2089
  if (!pick) throw new Error(`unknown model choice: ${choice}`);
1900
- const cfg = { agentName, userId: "default" };
2090
+ const cfg = loadConfig() ?? {};
2091
+ cfg.agentName = agentName;
2092
+ cfg.userId ??= "default";
2093
+ let provider;
1901
2094
  if (pick.custom) {
1902
- cfg.model = bail(
2095
+ const model = bail(
1903
2096
  await p.text({ message: "Model id", placeholder: "e.g. MiniMax-M3", validate: (v) => v ? void 0 : "Required" })
1904
2097
  );
1905
- cfg.baseUrl = bail(
2098
+ const baseUrl = bail(
1906
2099
  await p.text({
1907
2100
  message: "Base URL (OpenAI-compatible)",
1908
2101
  placeholder: "https://api.example.com/v1",
1909
2102
  validate: (v) => v && /^https?:\/\//.test(v) ? void 0 : "Must start with http(s)://"
1910
2103
  })
1911
2104
  );
1912
- cfg.apiKey = bail(await p.password({ message: "API key" }));
2105
+ const apiKey = bail(await p.password({ message: "API key" }));
2106
+ provider = { id: model, label: model, model, baseUrl, apiKey };
1913
2107
  } else {
1914
- cfg.model = pick.model;
1915
- if (pick.baseUrl) cfg.baseUrl = pick.baseUrl;
1916
- cfg.apiKey = pick.apiKey ?? (pick.needsKey ? bail(await p.password({ message: "API key" })) : void 0);
2108
+ const apiKey = pick.apiKey ?? (pick.needsKey ? bail(await p.password({ message: "API key" })) : void 0);
2109
+ provider = { id: pick.value, label: pick.label, model: pick.model ?? "", baseUrl: pick.baseUrl, apiKey };
1917
2110
  }
1918
- saveConfig(cfg);
2111
+ saveProvider(cfg, provider, true);
1919
2112
  p.outro(`Saved ${CONFIG} \u2014 run \`thiny\` to start, or \`thiny sui init\` for Sui.`);
1920
2113
  return cfg;
1921
2114
  }
@@ -2029,12 +2222,12 @@ async function createSkillPlugin(id, env) {
2029
2222
 
2030
2223
  // src/ui.ts
2031
2224
  import figlet from "figlet";
2032
- import chalk from "chalk";
2033
- var BRAND = chalk.cyan;
2034
- var DIM = chalk.dim;
2225
+ import chalk2 from "chalk";
2226
+ var BRAND = chalk2.cyan;
2227
+ var DIM = chalk2.dim;
2035
2228
  var AGENT_LABEL = BRAND.bold;
2036
- var ERROR_COLOR = chalk.red;
2037
- var SUCCESS_COLOR = chalk.green;
2229
+ var ERROR_COLOR = chalk2.red;
2230
+ var SUCCESS_COLOR = chalk2.green;
2038
2231
  function getWidth() {
2039
2232
  return process.stdout.columns || 80;
2040
2233
  }
@@ -2112,9 +2305,9 @@ function renderToolsAndSkills(tools, skills, opts) {
2112
2305
  process.stdout.write(BRAND("\u2514" + "\u2500".repeat(w - 2) + "\u2518") + "\n");
2113
2306
  }
2114
2307
  function renderHints(logFile) {
2115
- const logHint = logFile ? ` \xB7 ${DIM("logs \u2192")} ${chalk.dim(logFile)}` : "";
2308
+ const logHint = logFile ? ` \xB7 ${DIM("logs \u2192")} ${chalk2.dim(logFile)}` : "";
2116
2309
  process.stdout.write(
2117
- "\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"
2118
2311
  );
2119
2312
  }
2120
2313
  function renderAgentLabel(name) {
@@ -2124,13 +2317,13 @@ function renderAgentDone() {
2124
2317
  process.stdout.write("\n");
2125
2318
  }
2126
2319
  function renderError(message) {
2127
- process.stdout.write("\n" + ERROR_COLOR("Error: ") + chalk.white(message) + "\n");
2320
+ process.stdout.write("\n" + ERROR_COLOR("Error: ") + chalk2.white(message) + "\n");
2128
2321
  }
2129
2322
  function renderInfo(message) {
2130
2323
  process.stdout.write(DIM(message) + "\n");
2131
2324
  }
2132
2325
  function renderWarning(message) {
2133
- process.stdout.write(chalk.yellow("\u26A0 ") + chalk.white(message) + "\n");
2326
+ process.stdout.write(chalk2.yellow("\u26A0 ") + chalk2.white(message) + "\n");
2134
2327
  }
2135
2328
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
2136
2329
  var Spinner = class {
@@ -2166,7 +2359,7 @@ function renderStatus(parts) {
2166
2359
  }
2167
2360
  function renderStored(label, links, backend = "Walrus") {
2168
2361
  process.stdout.write(
2169
- 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"
2170
2363
  );
2171
2364
  }
2172
2365
  function renderSaving(label, backend = "Walrus") {
@@ -2193,14 +2386,14 @@ function renderInline(s) {
2193
2386
  codes.push(c);
2194
2387
  return `\0${String(codes.length - 1)}\0`;
2195
2388
  });
2196
- s = s.replace(/\*\*([^*\n]+)\*\*/g, (_m, t) => chalk.bold(t));
2197
- s = s.replace(/\*([^*\n]+)\*/g, (_m, t) => chalk.italic(t));
2198
- 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));
2199
2392
  s = s.replace(
2200
2393
  /\[([^\]]+)\]\(([^)\s]+)\)/g,
2201
- (_m, text2, url) => OSC8(url, chalk.cyan.underline(text2))
2394
+ (_m, text2, url) => OSC8(url, chalk2.cyan.underline(text2))
2202
2395
  );
2203
- 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)] ?? ""));
2204
2397
  return s;
2205
2398
  }
2206
2399
  function renderMarkdownLine(line, inCode, setCode) {
@@ -2210,16 +2403,16 @@ function renderMarkdownLine(line, inCode, setCode) {
2210
2403
  setCode(!inCode);
2211
2404
  return "";
2212
2405
  }
2213
- if (inCode) return chalk.cyan(line);
2406
+ if (inCode) return chalk2.cyan(line);
2214
2407
  const h = /^(#{1,6})\s+(.*)$/.exec(trimmed);
2215
- if (h) return ((h[1] ?? "").length <= 2 ? chalk.bold.underline : chalk.bold)(renderInline(h[2] ?? ""));
2216
- 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)));
2217
2410
  const q = /^>\s?(.*)$/.exec(trimmed);
2218
- if (q) return chalk.dim(`\u2502 ${renderInline(q[1] ?? "")}`);
2411
+ if (q) return chalk2.dim(`\u2502 ${renderInline(q[1] ?? "")}`);
2219
2412
  const b = /^[-*+]\s+(.*)$/.exec(trimmed);
2220
- if (b) return `${indent}${chalk.cyan("\u2022")} ${renderInline(b[1] ?? "")}`;
2413
+ if (b) return `${indent}${chalk2.cyan("\u2022")} ${renderInline(b[1] ?? "")}`;
2221
2414
  const n = /^(\d+)[.)]\s+(.*)$/.exec(trimmed);
2222
- if (n) return `${indent}${chalk.cyan(`${n[1] ?? ""}.`)} ${renderInline(n[2] ?? "")}`;
2415
+ if (n) return `${indent}${chalk2.cyan(`${n[1] ?? ""}.`)} ${renderInline(n[2] ?? "")}`;
2223
2416
  return renderInline(line);
2224
2417
  }
2225
2418
  function createMarkdownWriter(write) {
@@ -2234,7 +2427,7 @@ function createMarkdownWriter(write) {
2234
2427
  const emit = (text2, think) => {
2235
2428
  if (!text2) return;
2236
2429
  if (think) {
2237
- write(chalk.dim.italic(text2));
2430
+ write(chalk2.dim.italic(text2));
2238
2431
  return;
2239
2432
  }
2240
2433
  for (const ch of text2) {
@@ -2321,6 +2514,18 @@ function parseSkillArgs() {
2321
2514
  return (args[idx + 1] ?? "").split(",").map((s) => s.trim()).filter(Boolean);
2322
2515
  }
2323
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
+ ];
2324
2529
  function isNewerVersion(latest, current) {
2325
2530
  const a = latest.split(".").map((n) => Number(n) || 0);
2326
2531
  const b = current.split(".").map((n) => Number(n) || 0);
@@ -2352,9 +2557,22 @@ async function runCli() {
2352
2557
  const turn = { inputTokens: 0, outputTokens: 0, toolCalls: 0, modelCalls: 0 };
2353
2558
  const session = { inputTokens: 0, outputTokens: 0, toolCalls: 0, turns: 0 };
2354
2559
  const logger = captureStats(fileLogger, turn);
2355
- const activeModelName = process.env.THINY_MODEL ?? process.env.AGENT_MODEL ?? "openai:gpt-4o-mini";
2356
2560
  const personaName = process.env.THINY_PERSONA_NAME ?? "Thiny";
2357
- const model = loadThinyConfig();
2561
+ const buildModel = (p2) => aiSdkModel({
2562
+ model: p2.model,
2563
+ openai: { baseURL: p2.baseUrl, apiKey: p2.apiKey },
2564
+ anthropic: { apiKey: p2.apiKey }
2565
+ });
2566
+ const startProvider = activeProvider(loadConfig());
2567
+ let activeModel = startProvider ? buildModel(startProvider) : loadThinyConfig();
2568
+ let activeModelName = startProvider?.model ?? process.env.THINY_MODEL ?? process.env.AGENT_MODEL ?? "openai:gpt-4o-mini";
2569
+ const model = {
2570
+ generate: (m, t, s) => activeModel.generate(m, t, s),
2571
+ stream: (m, t, s) => {
2572
+ if (!activeModel.stream) throw new Error("active model has no streaming");
2573
+ return activeModel.stream(m, t, s);
2574
+ }
2575
+ };
2358
2576
  const network = process.env.WALRUS_NETWORK === "mainnet" ? "mainnet" : "testnet";
2359
2577
  const walrus = walrusClient({
2360
2578
  network,
@@ -2704,41 +2922,111 @@ YOUR TOOLS:
2704
2922
  `Web: fetch_url (any URL)${webSearchOn ? ` \xB7 web_search (${exaKey ? "Exa" : "Brave"})` : " \xB7 web_search off (set EXA_API_KEY)"}`
2705
2923
  );
2706
2924
  notifyIfUpdate(thinyDir);
2707
- const rl = createInterface({ input: stdin, output: stdout });
2708
- emitKeypressEvents(stdin);
2925
+ const PROMPT = "\x1B[36mYou\x1B[0m \x1B[2m\u203A\x1B[0m ";
2926
+ const prompt = new SlashPrompt(stdin, stdout, PROMPT, SLASH_COMMANDS);
2709
2927
  const spinner = new Spinner();
2710
2928
  const flushMemory = memoryPlugin.flush;
2711
- const PROMPT = "\x1B[36mYou\x1B[0m \x1B[2m\u203A\x1B[0m ";
2712
- let atPrompt = false;
2713
- const printAbovePrompt = (fn) => {
2714
- if (atPrompt) {
2715
- cursorTo(stdout, 0);
2716
- clearLine(stdout, 0);
2717
- }
2718
- fn();
2719
- if (atPrompt) rl.prompt(true);
2720
- };
2721
2929
  deliverRef = (ref) => {
2722
- if (atPrompt) {
2723
- printAbovePrompt(() => {
2930
+ if (prompt.isReading()) {
2931
+ prompt.printAbove(() => {
2724
2932
  renderStored("memory saved", explorerLinks(ref, network), memBackend);
2725
2933
  });
2726
2934
  } else memoryRefs.push(ref);
2727
2935
  };
2936
+ const handleConnect = async (arg) => {
2937
+ const cfg = loadConfig() ?? {};
2938
+ const provs = providersOf(cfg);
2939
+ if (provs.length === 0) {
2940
+ renderInfo("No providers configured \u2014 run `thiny init` to add one.");
2941
+ return;
2942
+ }
2943
+ let id = arg;
2944
+ if (!id) {
2945
+ renderInfo("\nProviders:");
2946
+ provs.forEach((pr, i) => {
2947
+ renderInfo(
2948
+ ` ${String(i + 1)}. ${pr.label} (${pr.model})${pr.id === cfg.activeProviderId ? " \xB7 active" : ""}`
2949
+ );
2950
+ });
2951
+ const ans = (await prompt.readLine("Switch to (number, blank to cancel): ") ?? "").trim();
2952
+ if (!ans) return;
2953
+ const idx = Number(ans) - 1;
2954
+ const chosen = provs[idx];
2955
+ if (!chosen) {
2956
+ renderWarning("Invalid choice.");
2957
+ return;
2958
+ }
2959
+ id = chosen.id;
2960
+ }
2961
+ const match = provs.find((pr) => pr.id === id || pr.label === id || pr.model === id);
2962
+ const prov = match ? setActiveProvider(cfg, match.id) : void 0;
2963
+ if (!prov) {
2964
+ renderWarning(`No provider "${id}".`);
2965
+ return;
2966
+ }
2967
+ activeModel = buildModel(prov);
2968
+ activeModelName = prov.model;
2969
+ renderInfo(`Connected: ${prov.label} \xB7 ${prov.model}`);
2970
+ };
2971
+ const handleModels = async (arg) => {
2972
+ const cfg = loadConfig() ?? {};
2973
+ const prov = activeProvider(cfg);
2974
+ if (!prov) {
2975
+ renderInfo("No provider configured \u2014 run `thiny init`.");
2976
+ return;
2977
+ }
2978
+ let modelId = arg;
2979
+ if (!modelId) {
2980
+ renderInfo(`
2981
+ Active: ${prov.label} \xB7 current model: ${prov.model}`);
2982
+ renderInfo("Configured providers:");
2983
+ providersOf(cfg).forEach((pr) => {
2984
+ renderInfo(` \u2022 ${pr.label}: ${pr.model}`);
2985
+ });
2986
+ modelId = (await prompt.readLine("New model id for the active provider (blank to cancel): ") ?? "").trim();
2987
+ if (!modelId) return;
2988
+ }
2989
+ prov.model = modelId;
2990
+ saveProvider(cfg, prov, true);
2991
+ activeModel = buildModel(prov);
2992
+ activeModelName = modelId;
2993
+ renderInfo(`Model set: ${modelId}`);
2994
+ };
2995
+ const showSlashMenu = () => {
2996
+ renderInfo(
2997
+ "\nCommands: /new \xB7 /connect \xB7 /models \xB7 /tools \xB7 /skills \xB7 /session \xB7 /stats \xB7 /verify <blobId> \xB7 /clear \xB7 /help"
2998
+ );
2999
+ renderInfo(`Tools: ${agent.registry.all().map((t) => t.name).join(", ")}`);
3000
+ const cats = [...defaultRegistry.byCategory()].map(
3001
+ ([cat, defs]) => `${cat}(${defs.map((d) => d.id).join(",")})`
3002
+ );
3003
+ renderInfo(`Skills: ${cats.join(" ")}
3004
+ `);
3005
+ };
2728
3006
  try {
2729
3007
  for (; ; ) {
2730
3008
  for (const ref of memoryRefs.splice(0))
2731
3009
  renderStored("memory saved", explorerLinks(ref, network), memBackend);
2732
3010
  if (pendingWrites > 0) renderSaving("memory", memBackend);
2733
- atPrompt = true;
2734
- const input = await rl.question(PROMPT);
2735
- atPrompt = false;
3011
+ const input = await prompt.readLine();
3012
+ if (input === null) break;
2736
3013
  const trimmed = input.trim();
2737
3014
  if (!trimmed) continue;
2738
3015
  if (trimmed.startsWith("/")) {
2739
3016
  const parts = trimmed.slice(1).split(" ");
2740
3017
  const cmd = parts[0];
3018
+ const arg = parts.slice(1).join(" ").trim() || void 0;
2741
3019
  switch (cmd) {
3020
+ case "":
3021
+ showSlashMenu();
3022
+ break;
3023
+ case "connect":
3024
+ await handleConnect(arg);
3025
+ break;
3026
+ case "models":
3027
+ case "model":
3028
+ await handleModels(arg);
3029
+ break;
2742
3030
  case "new": {
2743
3031
  currentSessionId = `cli-${(/* @__PURE__ */ new Date()).getTime().toString()}`;
2744
3032
  renderInfo("New session started \u2014 long-term memory carries over");
@@ -2809,7 +3097,7 @@ Audit trail ${blobId}
2809
3097
  break;
2810
3098
  case "help":
2811
3099
  renderInfo(
2812
- "\n/new \xB7 /tools \xB7 /skills \xB7 /stats \xB7 /session \xB7 /verify <blobId> \xB7 /clear \xB7 /help\n"
3100
+ "\n/new \xB7 /connect \xB7 /models \xB7 /tools \xB7 /skills \xB7 /stats \xB7 /session \xB7 /verify <blobId> \xB7 /clear \xB7 /help\n(type just `/` to see commands + all tools + skills)\n"
2813
3101
  );
2814
3102
  break;
2815
3103
  default:
@@ -2889,8 +3177,8 @@ Audit trail ${blobId}
2889
3177
  }).catch((err) => {
2890
3178
  if (pendingWrites > 0) pendingWrites -= 1;
2891
3179
  const m = `Walrus audit flush failed: ${err instanceof Error ? err.message : String(err)}`;
2892
- if (atPrompt) {
2893
- printAbovePrompt(() => {
3180
+ if (prompt.isReading()) {
3181
+ prompt.printAbove(() => {
2894
3182
  renderWarning(m);
2895
3183
  });
2896
3184
  } else renderWarning(m);
@@ -2909,6 +3197,7 @@ Audit trail ${blobId}
2909
3197
  }
2910
3198
  }
2911
3199
  } finally {
3200
+ prompt.close();
2912
3201
  if (flushMemory) await flushMemory().catch(() => void 0);
2913
3202
  }
2914
3203
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinyai",
3
- "version": "0.1.8",
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",
@@ -37,17 +37,17 @@
37
37
  "devDependencies": {
38
38
  "tsup": "^8.5.1",
39
39
  "typescript": "^5.5.0",
40
- "@thiny/memory-memwal": "0.1.0",
41
40
  "@thiny/core": "0.1.0",
42
- "@thiny/mcp": "0.1.0",
43
- "@thiny/logger-pino": "0.1.0",
44
- "@thiny/model-aisdk": "0.1.0",
41
+ "@thiny/walrus": "0.1.0",
45
42
  "@thiny/plugin-agents": "0.1.0",
46
43
  "@thiny/plugin-web-search": "0.1.0",
47
- "@thiny/walrus": "0.1.0",
44
+ "@thiny/logger-pino": "0.1.0",
45
+ "@thiny/mcp": "0.1.0",
46
+ "@thiny/memory-memwal": "0.1.0",
48
47
  "@thiny/signer-sui": "0.1.0",
49
- "@thiny/skills": "0.1.0",
50
- "@thiny/plugin-sui": "0.1.0"
48
+ "@thiny/plugin-sui": "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": {