thinyai 0.1.4 → 0.1.6
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 +119 -15
- package/package.json +7 -6
package/dist/bin.js
CHANGED
|
@@ -8,10 +8,10 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
8
8
|
import { createInterface } from "readline/promises";
|
|
9
9
|
import { clearLine, cursorTo, emitKeypressEvents } from "readline";
|
|
10
10
|
import { stdin, stdout } from "process";
|
|
11
|
-
import { mkdirSync as mkdirSync2 } from "fs";
|
|
11
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
12
12
|
import { homedir as homedir2 } from "os";
|
|
13
13
|
import { join as join2 } from "path";
|
|
14
|
-
import { z as
|
|
14
|
+
import { z as z7 } from "zod";
|
|
15
15
|
|
|
16
16
|
// ../../packages/core/src/domain/messages.ts
|
|
17
17
|
var systemMessage = (content) => ({ role: "system", content });
|
|
@@ -1440,14 +1440,14 @@ function suiPlugin(opts) {
|
|
|
1440
1440
|
});
|
|
1441
1441
|
const executePtb = defineTool({
|
|
1442
1442
|
name: "sui_execute_ptb",
|
|
1443
|
-
description: "Sign and submit an unsigned Sui programmable transaction (PTB)
|
|
1443
|
+
description: "Sign and submit an unsigned Sui programmable transaction (PTB) produced by an external builder/MCP (e.g. Rill). Re-simulates \u2192 soft policy \u2192 approval gate \u2192 sign + submit. Pass the builder's `unsignedTx`: the JSON string from `Transaction.toJSON()`, built with NO sender and NO gas (the signer fills both). On-chain caps may still abort it. For your OWN transfers/calls use sui_transfer / sui_move_call instead.",
|
|
1444
1444
|
sensitive: true,
|
|
1445
1445
|
parameters: z4.object({
|
|
1446
|
-
|
|
1446
|
+
unsignedTx: z4.string().min(1).describe("Unsigned PTB \u2014 the JSON string from Transaction.toJSON() (no sender, no gas).")
|
|
1447
1447
|
}),
|
|
1448
|
-
execute: async ({
|
|
1449
|
-
const tx = Transaction2.from(
|
|
1450
|
-
return await executeTx(tx, "sui_execute_ptb", {
|
|
1448
|
+
execute: async ({ unsignedTx }) => {
|
|
1449
|
+
const tx = Transaction2.from(unsignedTx);
|
|
1450
|
+
return await executeTx(tx, "sui_execute_ptb", { unsignedTx }, "sign and submit a Sui PTB");
|
|
1451
1451
|
}
|
|
1452
1452
|
});
|
|
1453
1453
|
const transfer = defineTool({
|
|
@@ -1608,6 +1608,61 @@ async function mcpHttpPlugin(opts) {
|
|
|
1608
1608
|
return buildMcpPlugin(transport, opts.name ?? "mcp");
|
|
1609
1609
|
}
|
|
1610
1610
|
|
|
1611
|
+
// ../../packages/plugins/web-search/src/index.ts
|
|
1612
|
+
import { z as z6 } from "zod";
|
|
1613
|
+
function describeHttpError(status) {
|
|
1614
|
+
if (status === 401) return "Authentication failed \u2014 verify your BRAVE_API_KEY is correct.";
|
|
1615
|
+
if (status === 403) return "Access forbidden \u2014 check your API subscription level.";
|
|
1616
|
+
if (status === 422) return "Invalid query \u2014 the search request was malformed.";
|
|
1617
|
+
if (status === 429) return "Rate limit exceeded \u2014 slow down requests or upgrade your plan.";
|
|
1618
|
+
if (status >= 500) return "Brave Search server error \u2014 try again later.";
|
|
1619
|
+
return `Unexpected error \u2014 see Brave Search API docs for HTTP ${String(status)}.`;
|
|
1620
|
+
}
|
|
1621
|
+
function webSearchPlugin(opts) {
|
|
1622
|
+
if (!opts.apiKey.trim()) {
|
|
1623
|
+
throw new Error(
|
|
1624
|
+
"webSearchPlugin: apiKey is required. Obtain a key at https://brave.com/search/api/ and set BRAVE_API_KEY in .env."
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
const endpoint = opts.endpoint ?? "https://api.search.brave.com/res/v1/web/search";
|
|
1628
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
1629
|
+
return {
|
|
1630
|
+
name: "web-search",
|
|
1631
|
+
tools: [
|
|
1632
|
+
defineTool({
|
|
1633
|
+
name: "web_search",
|
|
1634
|
+
description: "Search the public web and return the top results (title, url, snippet). Use when asked about current events, recent facts, or any topic you are not certain about.",
|
|
1635
|
+
parameters: z6.object({
|
|
1636
|
+
query: z6.string().min(1).describe("The search query."),
|
|
1637
|
+
count: z6.number().int().min(1).max(10).default(5).describe("Number of results to return (1\u201310).")
|
|
1638
|
+
}),
|
|
1639
|
+
execute: async ({ query, count }) => {
|
|
1640
|
+
const url = `${endpoint}?q=${encodeURIComponent(query)}&count=${String(count)}`;
|
|
1641
|
+
const response = await fetchImpl(url, {
|
|
1642
|
+
headers: {
|
|
1643
|
+
Accept: "application/json",
|
|
1644
|
+
"X-Subscription-Token": opts.apiKey
|
|
1645
|
+
}
|
|
1646
|
+
});
|
|
1647
|
+
if (!response.ok) {
|
|
1648
|
+
throw new Error(
|
|
1649
|
+
`web_search failed: HTTP ${String(response.status)} \u2014 ${describeHttpError(response.status)}`
|
|
1650
|
+
);
|
|
1651
|
+
}
|
|
1652
|
+
const data = await response.json();
|
|
1653
|
+
return {
|
|
1654
|
+
results: (data.web?.results ?? []).map((result) => ({
|
|
1655
|
+
title: result.title,
|
|
1656
|
+
url: result.url,
|
|
1657
|
+
snippet: result.description
|
|
1658
|
+
}))
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
})
|
|
1662
|
+
]
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1611
1666
|
// ../../packages/skills/src/catalog.ts
|
|
1612
1667
|
var BUILTIN_SKILLS = [
|
|
1613
1668
|
{
|
|
@@ -2228,7 +2283,7 @@ function captureStats(base, turn) {
|
|
|
2228
2283
|
var echoTool = defineTool({
|
|
2229
2284
|
name: "echo",
|
|
2230
2285
|
description: "Echo text back verbatim. Use when asked to repeat or echo something.",
|
|
2231
|
-
parameters:
|
|
2286
|
+
parameters: z7.object({ text: z7.string().describe("the text to echo") }),
|
|
2232
2287
|
execute: ({ text: text2 }) => Promise.resolve({ echoed: text2 })
|
|
2233
2288
|
});
|
|
2234
2289
|
function parseSkillArgs() {
|
|
@@ -2238,6 +2293,28 @@ function parseSkillArgs() {
|
|
|
2238
2293
|
return (args[idx + 1] ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
2239
2294
|
}
|
|
2240
2295
|
var currentSessionId = `cli-${(/* @__PURE__ */ new Date()).getTime().toString()}`;
|
|
2296
|
+
function isNewerVersion(latest, current) {
|
|
2297
|
+
const a = latest.split(".").map((n) => Number(n) || 0);
|
|
2298
|
+
const b = current.split(".").map((n) => Number(n) || 0);
|
|
2299
|
+
for (let i = 0; i < 3; i++) {
|
|
2300
|
+
if ((a[i] ?? 0) !== (b[i] ?? 0)) return (a[i] ?? 0) > (b[i] ?? 0);
|
|
2301
|
+
}
|
|
2302
|
+
return false;
|
|
2303
|
+
}
|
|
2304
|
+
function notifyIfUpdate(thinyDir) {
|
|
2305
|
+
const cur = version();
|
|
2306
|
+
const cacheFile = join2(thinyDir, "update-check.json");
|
|
2307
|
+
try {
|
|
2308
|
+
const cached = JSON.parse(readFileSync3(cacheFile, "utf8"));
|
|
2309
|
+
if (cached.latest && isNewerVersion(cached.latest, cur)) {
|
|
2310
|
+
renderInfo(`Update available: ${cur} \u2192 ${cached.latest} \u2014 run \`thiny update\``);
|
|
2311
|
+
}
|
|
2312
|
+
} catch {
|
|
2313
|
+
}
|
|
2314
|
+
void fetch("https://registry.npmjs.org/thinyai/latest").then((r) => r.json()).then((j) => {
|
|
2315
|
+
if (j.version) writeFileSync2(cacheFile, JSON.stringify({ latest: j.version, at: Date.now() }));
|
|
2316
|
+
}).catch(() => void 0);
|
|
2317
|
+
}
|
|
2241
2318
|
async function runCli() {
|
|
2242
2319
|
const thinyDir = join2(homedir2(), ".thiny");
|
|
2243
2320
|
mkdirSync2(thinyDir, { recursive: true });
|
|
@@ -2305,11 +2382,11 @@ async function runCli() {
|
|
|
2305
2382
|
name: "sui_setup",
|
|
2306
2383
|
description: "Set up or change the agent's Sui wallet so it can read balances and sign transactions. Use when the user asks to enable Sui, create/import a wallet, or switch network. Modes: generate (new local key), import (a suiprivkey\u2026), rill (use a Rill MCP signer URL). Takes effect immediately; Rill's builder tools connect on the next start. Always remind the user to fund the returned address.",
|
|
2307
2384
|
sensitive: true,
|
|
2308
|
-
parameters:
|
|
2309
|
-
network:
|
|
2310
|
-
wallet:
|
|
2311
|
-
secretKey:
|
|
2312
|
-
rillMcpUrl:
|
|
2385
|
+
parameters: z7.object({
|
|
2386
|
+
network: z7.enum(["testnet", "mainnet"]).default("testnet"),
|
|
2387
|
+
wallet: z7.enum(["generate", "import", "rill"]).describe("generate a new key, import a suiprivkey\u2026, or use a Rill MCP signer"),
|
|
2388
|
+
secretKey: z7.string().optional().describe("suiprivkey\u2026 \u2014 required when wallet=import"),
|
|
2389
|
+
rillMcpUrl: z7.string().optional().describe("Rill MCP URL \u2014 used when wallet=rill")
|
|
2313
2390
|
}),
|
|
2314
2391
|
execute: async ({ network: net, wallet, secretKey, rillMcpUrl }) => {
|
|
2315
2392
|
const network2 = net === "mainnet" ? "mainnet" : "testnet";
|
|
@@ -2344,13 +2421,37 @@ async function runCli() {
|
|
|
2344
2421
|
};
|
|
2345
2422
|
}
|
|
2346
2423
|
});
|
|
2424
|
+
const fetchUrlTool = defineTool({
|
|
2425
|
+
name: "fetch_url",
|
|
2426
|
+
description: "Fetch the contents of an http(s) URL (markdown, text, JSON, HTML). ALWAYS use this when the user shares a link \u2014 e.g. a skill.md, docs page, or an API/MCP endpoint \u2014 instead of saying you can't open URLs. Returns the response text (truncated if very large).",
|
|
2427
|
+
parameters: z7.object({
|
|
2428
|
+
url: z7.string().url().describe("The http(s) URL to fetch."),
|
|
2429
|
+
maxChars: z7.number().int().positive().optional().describe("Max characters of body to return (default 20000).")
|
|
2430
|
+
}),
|
|
2431
|
+
execute: async ({ url, maxChars }) => {
|
|
2432
|
+
const limit = maxChars ?? 2e4;
|
|
2433
|
+
const res = await fetch(url, {
|
|
2434
|
+
headers: { "user-agent": "thiny-cli", accept: "*/*" },
|
|
2435
|
+
signal: AbortSignal.timeout(15e3)
|
|
2436
|
+
});
|
|
2437
|
+
const body = await res.text();
|
|
2438
|
+
return {
|
|
2439
|
+
url,
|
|
2440
|
+
status: res.status,
|
|
2441
|
+
contentType: res.headers.get("content-type") ?? "",
|
|
2442
|
+
truncated: body.length > limit,
|
|
2443
|
+
content: body.slice(0, limit)
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
});
|
|
2447
|
+
const webPlugins = process.env.BRAVE_API_KEY ? [webSearchPlugin({ apiKey: process.env.BRAVE_API_KEY })] : [];
|
|
2347
2448
|
const budget = budgetMiddleware({ maxCalls: 50, logger });
|
|
2348
2449
|
const agent = await createAgent({
|
|
2349
2450
|
model,
|
|
2350
2451
|
logger: agentLogger,
|
|
2351
2452
|
persona,
|
|
2352
|
-
systemPrompt: "You are a helpful AI assistant. Use tools when they help you answer better. Be concise.\n\nMEMORY: You have persistent long-term memory across sessions, stored on Walrus. What you already know about the user is injected automatically at the start of each conversation under \u201C[User Memory \u2026]\u201D. When the user shares anything durable about themselves \u2014 their name, role, preferences, projects, or goals, even casually \u2014 immediately call remember_fact to save it. If asked what you remember, answer from the injected user memory (or call recall_memory). You DO remember across sessions \u2014 never say you lack memory or that each session starts fresh.\n\nFor multi-step work, call update_plan to track steps and delegate_task to hand focused sub-problems to a sub-agent.\n\
|
|
2353
|
-
tools: [echoTool, suiSetupTool],
|
|
2453
|
+
systemPrompt: "You are a helpful AI assistant. Use tools when they help you answer better. Be concise.\n\nMEMORY: You have persistent long-term memory across sessions, stored on Walrus. What you already know about the user is injected automatically at the start of each conversation under \u201C[User Memory \u2026]\u201D. When the user shares anything durable about themselves \u2014 their name, role, preferences, projects, or goals, even casually \u2014 immediately call remember_fact to save it. If asked what you remember, answer from the injected user memory (or call recall_memory). You DO remember across sessions \u2014 never say you lack memory or that each session starts fresh.\n\nFor multi-step work, call update_plan to track steps and delegate_task to hand focused sub-problems to a sub-agent.\n\nLINKS: When the user shares any URL (a skill.md, docs, an API or MCP endpoint, etc.), call fetch_url to read it \u2014 never say you can't open links" + (webPlugins.length > 0 ? ". Use web_search to look things up on the web.\n\n" : ".\n\n") + "SUI: You can operate on the Sui blockchain directly with your own tools \u2014 do NOT tell the user to install a browser wallet extension. " + (suiSignerRef ? `A wallet IS configured on ${suiNetwork} at ${suiSignerRef.address ?? "(unknown)"}. ` : "No wallet is set up yet \u2014 when the user wants Sui, call sui_setup (generate / import / rill) to create or connect one, then tell them to fund the returned address. ") + "Your Sui tools: sui_setup (create/import a wallet), sui_balance and sui_object (read), sui_transfer (send SUI or any coin to an address \u2014 amounts in MIST, 1 SUI = 1e9), sui_move_call (call ANY Move function on any package \u2014 the general way to run any on-chain action), and sui_execute_ptb (sign a PTB an external builder/Rill produced). Prefer sui_transfer for sends and sui_move_call for contract calls. Always confirm details and remind the user to fund the wallet. Never claim you cannot transact on Sui \u2014 you can, via these tools.",
|
|
2454
|
+
tools: [echoTool, suiSetupTool, fetchUrlTool],
|
|
2354
2455
|
plugins: [
|
|
2355
2456
|
{
|
|
2356
2457
|
name: "observability",
|
|
@@ -2363,6 +2464,8 @@ async function runCli() {
|
|
|
2363
2464
|
// always present; tools guide setup if no wallet
|
|
2364
2465
|
...suiPlugins,
|
|
2365
2466
|
// Rill MCP builder tools (if connected)
|
|
2467
|
+
...webPlugins,
|
|
2468
|
+
// web_search when BRAVE_API_KEY is set
|
|
2366
2469
|
...skillPlugins
|
|
2367
2470
|
]
|
|
2368
2471
|
});
|
|
@@ -2408,6 +2511,7 @@ async function runCli() {
|
|
|
2408
2511
|
`Sui: ${suiNetwork} \xB7 ${suiSignerRef.address ?? "?"}${process.env.MCP_URL ? " \xB7 Rill MCP connected" : ""}`
|
|
2409
2512
|
);
|
|
2410
2513
|
else renderInfo("Sui: no wallet \u2014 ask the agent to set one up, or run `thiny sui init`");
|
|
2514
|
+
notifyIfUpdate(thinyDir);
|
|
2411
2515
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
2412
2516
|
emitKeypressEvents(stdin);
|
|
2413
2517
|
const spinner = new Spinner();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thinyai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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,16 +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
|
+
"@thiny/walrus": "0.1.0",
|
|
42
|
+
"@thiny/plugin-agents": "0.1.0",
|
|
40
43
|
"@thiny/core": "0.1.0",
|
|
41
44
|
"@thiny/model-aisdk": "0.1.0",
|
|
42
|
-
"@thiny/walrus": "0.1.0",
|
|
43
45
|
"@thiny/logger-pino": "0.1.0",
|
|
44
|
-
"@thiny/
|
|
45
|
-
"@thiny/skills": "0.1.0",
|
|
46
|
-
"@thiny/plugin-agents": "0.1.0",
|
|
46
|
+
"@thiny/mcp": "0.1.0",
|
|
47
47
|
"@thiny/signer-sui": "0.1.0",
|
|
48
|
+
"@thiny/plugin-web-search": "0.1.0",
|
|
48
49
|
"@thiny/plugin-sui": "0.1.0",
|
|
49
|
-
"@thiny/
|
|
50
|
+
"@thiny/skills": "0.1.0"
|
|
50
51
|
},
|
|
51
52
|
"author": "Thiny AI",
|
|
52
53
|
"engines": {
|