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.
- package/dist/bin.js +230 -55
- 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
|
|
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
|
|
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(
|
|
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
|
|
1990
|
+
return existsSync3(CONFIG) ? JSON.parse(readFileSync3(CONFIG, "utf8")) : null;
|
|
1820
1991
|
}
|
|
1821
1992
|
function saveConfig(cfg) {
|
|
1822
|
-
|
|
1993
|
+
mkdirSync2(THINY_DIR, { recursive: true });
|
|
1823
1994
|
chmodSync(THINY_DIR, 448);
|
|
1824
|
-
|
|
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
|
|
2070
|
-
var BRAND =
|
|
2071
|
-
var 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 =
|
|
2074
|
-
var SUCCESS_COLOR =
|
|
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")} ${
|
|
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") +
|
|
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: ") +
|
|
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(
|
|
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 `) +
|
|
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) =>
|
|
2234
|
-
s = s.replace(/\*([^*\n]+)\*/g, (_m, t) =>
|
|
2235
|
-
s = s.replace(/~~([^~\n]+)~~/g, (_m, 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,
|
|
2409
|
+
(_m, text2, url) => OSC8(url, chalk2.cyan.underline(text2))
|
|
2239
2410
|
);
|
|
2240
|
-
s = s.replace(/(\d+)/g, (_m, 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
|
|
2421
|
+
if (inCode) return chalk2.cyan(line);
|
|
2251
2422
|
const h = /^(#{1,6})\s+(.*)$/.exec(trimmed);
|
|
2252
|
-
if (h) return ((h[1] ?? "").length <= 2 ?
|
|
2253
|
-
if (/^(-{3,}|\*{3,}|_{3,})$/.test(trimmed)) return
|
|
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
|
|
2426
|
+
if (q) return chalk2.dim(`\u2502 ${renderInline(q[1] ?? "")}`);
|
|
2256
2427
|
const b = /^[-*+]\s+(.*)$/.exec(trimmed);
|
|
2257
|
-
if (b) return `${indent}${
|
|
2428
|
+
if (b) return `${indent}${chalk2.cyan("\u2022")} ${renderInline(b[1] ?? "")}`;
|
|
2258
2429
|
const n = /^(\d+)[.)]\s+(.*)$/.exec(trimmed);
|
|
2259
|
-
if (n) return `${indent}${
|
|
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(
|
|
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(
|
|
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)
|
|
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
|
-
|
|
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
|
|
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
|
|
2758
|
-
|
|
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 (
|
|
2773
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
2854
|
-
|
|
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 (
|
|
3024
|
-
|
|
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.
|
|
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/
|
|
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/
|
|
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": {
|