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.
- package/dist/bin.js +347 -58
- 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
|
-
|
|
1833
|
-
if (
|
|
1834
|
-
set(
|
|
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 =
|
|
2090
|
+
const cfg = loadConfig() ?? {};
|
|
2091
|
+
cfg.agentName = agentName;
|
|
2092
|
+
cfg.userId ??= "default";
|
|
2093
|
+
let provider;
|
|
1901
2094
|
if (pick.custom) {
|
|
1902
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2105
|
+
const apiKey = bail(await p.password({ message: "API key" }));
|
|
2106
|
+
provider = { id: model, label: model, model, baseUrl, apiKey };
|
|
1913
2107
|
} else {
|
|
1914
|
-
|
|
1915
|
-
|
|
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
|
-
|
|
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
|
|
2033
|
-
var BRAND =
|
|
2034
|
-
var 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 =
|
|
2037
|
-
var SUCCESS_COLOR =
|
|
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")} ${
|
|
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") +
|
|
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: ") +
|
|
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(
|
|
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 `) +
|
|
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) =>
|
|
2197
|
-
s = s.replace(/\*([^*\n]+)\*/g, (_m, t) =>
|
|
2198
|
-
s = s.replace(/~~([^~\n]+)~~/g, (_m, 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,
|
|
2394
|
+
(_m, text2, url) => OSC8(url, chalk2.cyan.underline(text2))
|
|
2202
2395
|
);
|
|
2203
|
-
s = s.replace(/(\d+)/g, (_m, 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
|
|
2406
|
+
if (inCode) return chalk2.cyan(line);
|
|
2214
2407
|
const h = /^(#{1,6})\s+(.*)$/.exec(trimmed);
|
|
2215
|
-
if (h) return ((h[1] ?? "").length <= 2 ?
|
|
2216
|
-
if (/^(-{3,}|\*{3,}|_{3,})$/.test(trimmed)) return
|
|
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
|
|
2411
|
+
if (q) return chalk2.dim(`\u2502 ${renderInline(q[1] ?? "")}`);
|
|
2219
2412
|
const b = /^[-*+]\s+(.*)$/.exec(trimmed);
|
|
2220
|
-
if (b) return `${indent}${
|
|
2413
|
+
if (b) return `${indent}${chalk2.cyan("\u2022")} ${renderInline(b[1] ?? "")}`;
|
|
2221
2414
|
const n = /^(\d+)[.)]\s+(.*)$/.exec(trimmed);
|
|
2222
|
-
if (n) return `${indent}${
|
|
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(
|
|
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
|
|
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
|
|
2708
|
-
|
|
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 (
|
|
2723
|
-
|
|
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
|
-
|
|
2734
|
-
|
|
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 (
|
|
2893
|
-
|
|
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.
|
|
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/
|
|
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/
|
|
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/
|
|
50
|
-
"@thiny/
|
|
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": {
|