jawere 1.5.1 → 1.6.1
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/README.md +8 -16
- package/dist/index.js +537 -231
- package/package.json +2 -9
package/README.md
CHANGED
|
@@ -4,12 +4,10 @@ Terminal-based AI coding assistant powered by DeepSeek. Runs as an interactive R
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **Autonomous Agent Loop** — Tool-calling loop (up to 500 turns) with
|
|
7
|
+
- **Autonomous Agent Loop** — Tool-calling loop (up to 500 turns) with filesystem tools
|
|
8
8
|
- **Codebase Scanner** — Pre-scans the project on startup, generating `.codebase/tree.yaml` and `.codebase/meta.json` so the LLM has structural context before the first prompt
|
|
9
|
-
- **Interactive REPL** — Multiline input (Shift+Enter), session resumption, and persistent conversation history
|
|
9
|
+
- **Interactive REPL** — Multiline input (Shift+Enter), session resumption, and persistent conversation history
|
|
10
10
|
- **Encrypted API Key** — AES-256-GCM encrypted key storage at `~/.jawere/key.enc`
|
|
11
|
-
- **Session Persistence** — Full message history, tool calls, and token usage stored in Convex
|
|
12
|
-
- **Dual Environment** — Dev/prod modes with separate Convex deployments
|
|
13
11
|
|
|
14
12
|
## Quick Start
|
|
15
13
|
|
|
@@ -33,8 +31,6 @@ npm start
|
|
|
33
31
|
| Command | Description |
|
|
34
32
|
|---------|-------------|
|
|
35
33
|
| `/help` | Show available commands |
|
|
36
|
-
| `/sessions` | List recent Convex sessions |
|
|
37
|
-
| `/load <id>` | Resume a previous session |
|
|
38
34
|
| `/key` | Show API key status |
|
|
39
35
|
| `/setup` | Re-enter and save API key |
|
|
40
36
|
| `/clear` | Clear screen and start fresh |
|
|
@@ -42,7 +38,7 @@ npm start
|
|
|
42
38
|
|
|
43
39
|
## Tools
|
|
44
40
|
|
|
45
|
-
The LLM has access to
|
|
41
|
+
The LLM has access to filesystem and web tools:
|
|
46
42
|
|
|
47
43
|
- `bash` — Execute shell commands with configurable timeout (max 300s). Output truncated at 2000 lines / 50KB.
|
|
48
44
|
- `read` — Read file contents with line offset/limit for large files.
|
|
@@ -51,6 +47,10 @@ The LLM has access to seven filesystem tools:
|
|
|
51
47
|
- `ls` — List directory contents with sizes.
|
|
52
48
|
- `find` — Find files by fuzzy name or glob pattern. Skips hidden dirs and node_modules.
|
|
53
49
|
- `grep` — Search file contents with regex. Supports file glob filtering.
|
|
50
|
+
- `stat` — Get file or directory metadata.
|
|
51
|
+
- `diff` — Show git diff of working changes.
|
|
52
|
+
- `web_search` — Search the web for up-to-date information.
|
|
53
|
+
- `docs` — Search library/package documentation.
|
|
54
54
|
|
|
55
55
|
## Configuration
|
|
56
56
|
|
|
@@ -59,8 +59,6 @@ The LLM has access to seven filesystem tools:
|
|
|
59
59
|
| `DEEPSEEK_API_KEY` | — | DeepSeek API key |
|
|
60
60
|
| `DEEPSEEK_MODEL` | `deepseek-v4-pro` | Model name |
|
|
61
61
|
| `WORK_DIR` | `process.cwd()` | Working directory |
|
|
62
|
-
| `CONVEX_URL` | auto | Convex deployment URL |
|
|
63
|
-
| `NODE_ENV` | auto-detected | `development` or `production` |
|
|
64
62
|
|
|
65
63
|
## Project Structure
|
|
66
64
|
|
|
@@ -71,12 +69,11 @@ The LLM has access to seven filesystem tools:
|
|
|
71
69
|
│ ├── agent.ts # Agent loop: LLM calls + tool execution
|
|
72
70
|
│ ├── config.ts # Configuration (env, key, mode)
|
|
73
71
|
│ ├── tools.ts # Tool definitions & implementations
|
|
74
|
-
│ ├── convex-client.ts # HTTP client for Convex backend
|
|
75
72
|
│ ├── crypto.ts # Encrypted API key storage (AES-256-GCM)
|
|
76
73
|
│ ├── scanner.ts # Codebase pre-scanner (.codebase/)
|
|
77
74
|
│ ├── spinner.ts # Terminal spinner animations
|
|
75
|
+
│ ├── prompt.ts # Interactive prompt (multiline)
|
|
78
76
|
│ └── system-prompt.ts # System prompt template
|
|
79
|
-
├── convex/ # Convex backend (schema, mutations, queries)
|
|
80
77
|
├── scripts/build.js # esbuild bundler
|
|
81
78
|
├── dist/ # Compiled output
|
|
82
79
|
├── package.json
|
|
@@ -90,11 +87,6 @@ The LLM has access to seven filesystem tools:
|
|
|
90
87
|
| `npm start` | Run in dev mode |
|
|
91
88
|
| `npm run start:prod` | Run in prod mode |
|
|
92
89
|
| `npm run build` | Bundle with esbuild |
|
|
93
|
-
| `npm run dev` | Run `convex dev` for backend |
|
|
94
|
-
| `npm run deploy:dev` | Deploy Convex backend (dev) |
|
|
95
|
-
| `npm run deploy:prod` | Deploy Convex backend (prod) |
|
|
96
|
-
| `npm run seed:dev` | Seed dev database |
|
|
97
|
-
| `npm run seed:prod` | Seed prod database |
|
|
98
90
|
|
|
99
91
|
## License
|
|
100
92
|
|
package/dist/index.js
CHANGED
|
@@ -2801,12 +2801,12 @@ var require_lib2 = __commonJS({
|
|
|
2801
2801
|
const dest = new URL$1(destination).protocol;
|
|
2802
2802
|
return orig === dest;
|
|
2803
2803
|
};
|
|
2804
|
-
function
|
|
2805
|
-
if (!
|
|
2804
|
+
function fetch2(url, opts) {
|
|
2805
|
+
if (!fetch2.Promise) {
|
|
2806
2806
|
throw new Error("native promise missing, set fetch.Promise to your favorite alternative");
|
|
2807
2807
|
}
|
|
2808
|
-
Body.Promise =
|
|
2809
|
-
return new
|
|
2808
|
+
Body.Promise = fetch2.Promise;
|
|
2809
|
+
return new fetch2.Promise(function(resolve4, reject) {
|
|
2810
2810
|
const request = new Request3(url, opts);
|
|
2811
2811
|
const options = getNodeRequestOptions(request);
|
|
2812
2812
|
const send = (options.protocol === "https:" ? https : http).request;
|
|
@@ -2877,7 +2877,7 @@ var require_lib2 = __commonJS({
|
|
|
2877
2877
|
req.on("response", function(res) {
|
|
2878
2878
|
clearTimeout(reqTimeout);
|
|
2879
2879
|
const headers = createHeadersLenient(res.headers);
|
|
2880
|
-
if (
|
|
2880
|
+
if (fetch2.isRedirect(res.statusCode)) {
|
|
2881
2881
|
const location = headers.get("Location");
|
|
2882
2882
|
let locationURL = null;
|
|
2883
2883
|
try {
|
|
@@ -2939,7 +2939,7 @@ var require_lib2 = __commonJS({
|
|
|
2939
2939
|
requestOpts.body = void 0;
|
|
2940
2940
|
requestOpts.headers.delete("content-length");
|
|
2941
2941
|
}
|
|
2942
|
-
resolve4(
|
|
2942
|
+
resolve4(fetch2(new Request3(locationURL, requestOpts)));
|
|
2943
2943
|
finalize();
|
|
2944
2944
|
return;
|
|
2945
2945
|
}
|
|
@@ -3031,11 +3031,11 @@ var require_lib2 = __commonJS({
|
|
|
3031
3031
|
stream.end();
|
|
3032
3032
|
}
|
|
3033
3033
|
}
|
|
3034
|
-
|
|
3034
|
+
fetch2.isRedirect = function(code) {
|
|
3035
3035
|
return code === 301 || code === 302 || code === 303 || code === 307 || code === 308;
|
|
3036
3036
|
};
|
|
3037
|
-
|
|
3038
|
-
module.exports = exports =
|
|
3037
|
+
fetch2.Promise = global.Promise;
|
|
3038
|
+
module.exports = exports = fetch2;
|
|
3039
3039
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3040
3040
|
exports.default = exports;
|
|
3041
3041
|
exports.Headers = Headers3;
|
|
@@ -6817,7 +6817,7 @@ var VERSION = "4.104.0";
|
|
|
6817
6817
|
// node_modules/openai/_shims/registry.mjs
|
|
6818
6818
|
var auto = false;
|
|
6819
6819
|
var kind = void 0;
|
|
6820
|
-
var
|
|
6820
|
+
var fetch = void 0;
|
|
6821
6821
|
var Request = void 0;
|
|
6822
6822
|
var Response = void 0;
|
|
6823
6823
|
var Headers = void 0;
|
|
@@ -6838,7 +6838,7 @@ function setShims(shims, options = { auto: false }) {
|
|
|
6838
6838
|
}
|
|
6839
6839
|
auto = options.auto;
|
|
6840
6840
|
kind = shims.kind;
|
|
6841
|
-
|
|
6841
|
+
fetch = shims.fetch;
|
|
6842
6842
|
Request = shims.Request;
|
|
6843
6843
|
Response = shims.Response;
|
|
6844
6844
|
Headers = shims.Headers;
|
|
@@ -7950,7 +7950,7 @@ var APIClient = class {
|
|
|
7950
7950
|
this.maxRetries = validatePositiveInteger("maxRetries", maxRetries);
|
|
7951
7951
|
this.timeout = validatePositiveInteger("timeout", timeout);
|
|
7952
7952
|
this.httpAgent = httpAgent;
|
|
7953
|
-
this.fetch = overriddenFetch ??
|
|
7953
|
+
this.fetch = overriddenFetch ?? fetch;
|
|
7954
7954
|
}
|
|
7955
7955
|
authHeaders(opts) {
|
|
7956
7956
|
return {};
|
|
@@ -13101,6 +13101,14 @@ You have access to a set of tools that let you interact with the filesystem:
|
|
|
13101
13101
|
Skips hidden dirs and node_modules, .git, dist, etc.
|
|
13102
13102
|
grep Search file contents with regex. Returns file paths with line numbers.
|
|
13103
13103
|
Skips binary files and files over 500KB.
|
|
13104
|
+
web_search Search the web for general information using DuckDuckGo (free).
|
|
13105
|
+
Returns abstracts, answers, definitions, related topics, web links.
|
|
13106
|
+
Use for general knowledge, news, current events \u2014 NOT for code docs.
|
|
13107
|
+
docs Search library/framework/API documentation specifically. Uses site-scoped
|
|
13108
|
+
DuckDuckGo queries targeting official doc sites (MDN, nodejs.org, docs.rs,
|
|
13109
|
+
react.dev, python.org, etc.). Optional library param narrows the search.
|
|
13110
|
+
Use this for API references, method signatures, config options, examples.
|
|
13111
|
+
Prefer this over web_search for any programming documentation lookup.
|
|
13104
13112
|
|
|
13105
13113
|
\u2500\u2500 Parallel Tool Execution \u2500\u2500
|
|
13106
13114
|
|
|
@@ -13116,6 +13124,9 @@ independent of each other. This is faster and saves tokens \u2014 use it wheneve
|
|
|
13116
13124
|
GOOD \u2014 read a file AND list a directory at the same time:
|
|
13117
13125
|
\u2022 Call read(config.ts) + ls(src/) together
|
|
13118
13126
|
|
|
13127
|
+
GOOD \u2014 search docs/web while also reading files:
|
|
13128
|
+
\u2022 Call docs(query, library) + web_search(query) + read(file) all at once
|
|
13129
|
+
|
|
13119
13130
|
BAD \u2014 these depend on each other, so must be sequential:
|
|
13120
13131
|
\u2022 grep then read (grep finds a file, then you read it)
|
|
13121
13132
|
\u2022 bash then read (you run a command, then read its output file)
|
|
@@ -13182,6 +13193,35 @@ import { exec } from "child_process";
|
|
|
13182
13193
|
import { readFile, writeFile, mkdir, access, stat as fsStat } from "fs/promises";
|
|
13183
13194
|
import { constants } from "fs";
|
|
13184
13195
|
import { resolve, dirname } from "path";
|
|
13196
|
+
import { get as httpsGet } from "https";
|
|
13197
|
+
function safeResolve(workDir, userPath) {
|
|
13198
|
+
const resolved = resolve(workDir, userPath);
|
|
13199
|
+
const normalizedWd = resolve(workDir).replace(/\/+$/, "") + "/";
|
|
13200
|
+
const normalizedResolved = resolved.replace(/\/+$/, "") + "/";
|
|
13201
|
+
if (!normalizedResolved.startsWith(normalizedWd)) {
|
|
13202
|
+
throw new Error(
|
|
13203
|
+
`Path traversal blocked: "${userPath}" escapes the working directory. All file operations must stay within ${workDir}.`
|
|
13204
|
+
);
|
|
13205
|
+
}
|
|
13206
|
+
return resolved;
|
|
13207
|
+
}
|
|
13208
|
+
var DANGEROUS_COMMANDS = [
|
|
13209
|
+
{ pattern: /(?:^|[;&|])\s*rm\s.*-rf\s*(?:\/|\/\*|\s+\/)/, reason: "rm -rf on root filesystem" },
|
|
13210
|
+
{ pattern: /(?:^|[;&|])\s*sudo\s/, reason: "sudo escalation" },
|
|
13211
|
+
{ pattern: /:.*\(.*\)\s*\{\s*:\|:.*&\s*\}\s*;\s*:/, reason: "fork bomb" },
|
|
13212
|
+
{ pattern: /(?:^|[;&|])\s*(?:dd|mkfs|fdisk|parted)\s.*if=\/dev\/|of=\/dev\//, reason: "raw device write" },
|
|
13213
|
+
{ pattern: /(?:^|[;&|])\s*chmod\s.*-R\s*(?:777|o\+rwx|a\+rwx)\s*(?:\/|\/etc|\/usr|\/var)/, reason: "recursive world-writable on system dirs" },
|
|
13214
|
+
{ pattern: /(?:^|[;&|])\s*(?:curl|wget)\s.*\|\s*(?:ba)?sh/, reason: "curl-pipe-shell \u2014 use safer methods" },
|
|
13215
|
+
{ pattern: /(?:^|[;&|])\s*git\s+push\s+--force.*main|master/, reason: "force push to main/master" }
|
|
13216
|
+
];
|
|
13217
|
+
function checkDangerousCommand(command) {
|
|
13218
|
+
for (const { pattern, reason } of DANGEROUS_COMMANDS) {
|
|
13219
|
+
if (pattern.test(command)) {
|
|
13220
|
+
return `Blocked dangerous command (${reason}). If you need this, run it manually in your terminal.`;
|
|
13221
|
+
}
|
|
13222
|
+
}
|
|
13223
|
+
return null;
|
|
13224
|
+
}
|
|
13185
13225
|
var TOOL_DEFS = [
|
|
13186
13226
|
{
|
|
13187
13227
|
type: "function",
|
|
@@ -13387,6 +13427,52 @@ var TOOL_DEFS = [
|
|
|
13387
13427
|
required: ["pattern"]
|
|
13388
13428
|
}
|
|
13389
13429
|
}
|
|
13430
|
+
},
|
|
13431
|
+
{
|
|
13432
|
+
type: "function",
|
|
13433
|
+
function: {
|
|
13434
|
+
name: "web_search",
|
|
13435
|
+
description: "Search the web for information. Uses DuckDuckGo (free, no API key needed). Returns relevant results including abstracts, answers, related topics, and web links. Use this for general knowledge, news, current events, or broad information not found locally.",
|
|
13436
|
+
parameters: {
|
|
13437
|
+
type: "object",
|
|
13438
|
+
properties: {
|
|
13439
|
+
query: {
|
|
13440
|
+
type: "string",
|
|
13441
|
+
description: "Search query string"
|
|
13442
|
+
},
|
|
13443
|
+
count: {
|
|
13444
|
+
type: "number",
|
|
13445
|
+
description: "Maximum number of results to return (default 5, max 10)"
|
|
13446
|
+
}
|
|
13447
|
+
},
|
|
13448
|
+
required: ["query"]
|
|
13449
|
+
}
|
|
13450
|
+
}
|
|
13451
|
+
},
|
|
13452
|
+
{
|
|
13453
|
+
type: "function",
|
|
13454
|
+
function: {
|
|
13455
|
+
name: "docs",
|
|
13456
|
+
description: "Search library, framework, and API documentation. Uses DuckDuckGo site-targeted queries to search official documentation sources (MDN, Node.js docs, npm packages, Rust docs, Python docs, Go docs, etc.). Free \u2014 no API key needed. Use this for API signatures, method references, configuration options, package usage examples, or any programming documentation lookup.",
|
|
13457
|
+
parameters: {
|
|
13458
|
+
type: "object",
|
|
13459
|
+
properties: {
|
|
13460
|
+
query: {
|
|
13461
|
+
type: "string",
|
|
13462
|
+
description: 'Documentation search query (e.g. "fs.readFile options", "React useEffect cleanup")'
|
|
13463
|
+
},
|
|
13464
|
+
library: {
|
|
13465
|
+
type: "string",
|
|
13466
|
+
description: 'Optional library/package name to narrow search (e.g. "react", "typescript", "express")'
|
|
13467
|
+
},
|
|
13468
|
+
count: {
|
|
13469
|
+
type: "number",
|
|
13470
|
+
description: "Maximum results (default 5, max 8)"
|
|
13471
|
+
}
|
|
13472
|
+
},
|
|
13473
|
+
required: ["query"]
|
|
13474
|
+
}
|
|
13475
|
+
}
|
|
13390
13476
|
}
|
|
13391
13477
|
];
|
|
13392
13478
|
var MAX_OUTPUT_LINES = 2e3;
|
|
@@ -13420,6 +13506,8 @@ function truncateOutput(text) {
|
|
|
13420
13506
|
return { text: truncated, totalLines, totalBytes, linesShown, truncated: true };
|
|
13421
13507
|
}
|
|
13422
13508
|
async function execBash(command, workDir, timeoutSec) {
|
|
13509
|
+
const blocked = checkDangerousCommand(command);
|
|
13510
|
+
if (blocked) return blocked;
|
|
13423
13511
|
const timeout = Math.min(timeoutSec ?? 120, 300) * 1e3;
|
|
13424
13512
|
return new Promise((resolve4) => {
|
|
13425
13513
|
const child = exec(
|
|
@@ -13453,7 +13541,12 @@ async function execBash(command, workDir, timeoutSec) {
|
|
|
13453
13541
|
});
|
|
13454
13542
|
}
|
|
13455
13543
|
async function readFileTool(path, workDir, offset, limit2) {
|
|
13456
|
-
|
|
13544
|
+
let fullPath;
|
|
13545
|
+
try {
|
|
13546
|
+
fullPath = safeResolve(workDir, path);
|
|
13547
|
+
} catch (err) {
|
|
13548
|
+
return `Error: ${err.message}`;
|
|
13549
|
+
}
|
|
13457
13550
|
try {
|
|
13458
13551
|
await access(fullPath, constants.R_OK);
|
|
13459
13552
|
} catch {
|
|
@@ -13488,7 +13581,12 @@ function diffSummary(before, after) {
|
|
|
13488
13581
|
return `(${linePart} lines, ${bytePart})`;
|
|
13489
13582
|
}
|
|
13490
13583
|
async function editFileTool(path, edits, workDir) {
|
|
13491
|
-
|
|
13584
|
+
let fullPath;
|
|
13585
|
+
try {
|
|
13586
|
+
fullPath = safeResolve(workDir, path);
|
|
13587
|
+
} catch (err) {
|
|
13588
|
+
return `Error: ${err.message}`;
|
|
13589
|
+
}
|
|
13492
13590
|
let content;
|
|
13493
13591
|
try {
|
|
13494
13592
|
content = await readFile(fullPath, "utf-8");
|
|
@@ -13537,13 +13635,23 @@ ${errors.map((e2) => ` - ${e2}`).join("\n")}`;
|
|
|
13537
13635
|
return parts.join("\n");
|
|
13538
13636
|
}
|
|
13539
13637
|
async function writeFileTool(path, content, workDir) {
|
|
13540
|
-
|
|
13638
|
+
let fullPath;
|
|
13639
|
+
try {
|
|
13640
|
+
fullPath = safeResolve(workDir, path);
|
|
13641
|
+
} catch (err) {
|
|
13642
|
+
return `Error: ${err.message}`;
|
|
13643
|
+
}
|
|
13541
13644
|
await mkdir(dirname(fullPath), { recursive: true });
|
|
13542
13645
|
await writeFile(fullPath, content, "utf-8");
|
|
13543
13646
|
return `Successfully wrote ${Buffer.byteLength(content, "utf-8")} bytes to ${fullPath}`;
|
|
13544
13647
|
}
|
|
13545
13648
|
async function lsTool(path, workDir) {
|
|
13546
|
-
|
|
13649
|
+
let dir;
|
|
13650
|
+
try {
|
|
13651
|
+
dir = safeResolve(workDir, path || ".");
|
|
13652
|
+
} catch (err) {
|
|
13653
|
+
return `Error: ${err.message}`;
|
|
13654
|
+
}
|
|
13547
13655
|
const { readdir: readdir2, stat } = await import("fs/promises");
|
|
13548
13656
|
try {
|
|
13549
13657
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
@@ -13583,7 +13691,12 @@ ${lines.join("\n")}`).text;
|
|
|
13583
13691
|
}
|
|
13584
13692
|
}
|
|
13585
13693
|
async function findTool(pattern, searchPath, workDir) {
|
|
13586
|
-
|
|
13694
|
+
let base;
|
|
13695
|
+
try {
|
|
13696
|
+
base = safeResolve(workDir, searchPath || ".");
|
|
13697
|
+
} catch (err) {
|
|
13698
|
+
return `Error: ${err.message}`;
|
|
13699
|
+
}
|
|
13587
13700
|
const SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
13588
13701
|
"node_modules",
|
|
13589
13702
|
".git",
|
|
@@ -13649,7 +13762,12 @@ ${results.join("\n")}`
|
|
|
13649
13762
|
).text;
|
|
13650
13763
|
}
|
|
13651
13764
|
async function grepTool(pattern, searchPath, include, workDir) {
|
|
13652
|
-
|
|
13765
|
+
let base;
|
|
13766
|
+
try {
|
|
13767
|
+
base = safeResolve(workDir, searchPath || ".");
|
|
13768
|
+
} catch (err) {
|
|
13769
|
+
return `Error: ${err.message}`;
|
|
13770
|
+
}
|
|
13653
13771
|
const SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
13654
13772
|
"node_modules",
|
|
13655
13773
|
".git",
|
|
@@ -13740,7 +13858,12 @@ ${results.join("\n")}`
|
|
|
13740
13858
|
).text;
|
|
13741
13859
|
}
|
|
13742
13860
|
async function statTool(path, workDir) {
|
|
13743
|
-
|
|
13861
|
+
let fullPath;
|
|
13862
|
+
try {
|
|
13863
|
+
fullPath = safeResolve(workDir, path);
|
|
13864
|
+
} catch (err) {
|
|
13865
|
+
return `Error: ${err.message}`;
|
|
13866
|
+
}
|
|
13744
13867
|
try {
|
|
13745
13868
|
const s2 = await fsStat(fullPath);
|
|
13746
13869
|
const parts = [`${fullPath}:`];
|
|
@@ -13789,6 +13912,285 @@ async function diffTool(path, staged, base, workDir) {
|
|
|
13789
13912
|
else args.push("--", ".");
|
|
13790
13913
|
return execBash(`git ${args.join(" ")}`, workDir, 30);
|
|
13791
13914
|
}
|
|
13915
|
+
function httpsGetJSON(url, timeout = 8e3) {
|
|
13916
|
+
return new Promise((resolve4, reject) => {
|
|
13917
|
+
const req = httpsGet(url, { timeout }, (res) => {
|
|
13918
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
13919
|
+
httpsGetJSON(res.headers.location, timeout).then(resolve4).catch(reject);
|
|
13920
|
+
return;
|
|
13921
|
+
}
|
|
13922
|
+
let data = "";
|
|
13923
|
+
res.on("data", (chunk) => {
|
|
13924
|
+
data += chunk.toString();
|
|
13925
|
+
if (data.length > 5e5) {
|
|
13926
|
+
res.destroy();
|
|
13927
|
+
reject(new Error("Response too large"));
|
|
13928
|
+
}
|
|
13929
|
+
});
|
|
13930
|
+
res.on("end", () => {
|
|
13931
|
+
try {
|
|
13932
|
+
resolve4(JSON.parse(data));
|
|
13933
|
+
} catch {
|
|
13934
|
+
reject(new Error(`Failed to parse JSON (status ${res.statusCode})`));
|
|
13935
|
+
}
|
|
13936
|
+
});
|
|
13937
|
+
});
|
|
13938
|
+
req.on("error", reject);
|
|
13939
|
+
req.on("timeout", () => {
|
|
13940
|
+
req.destroy();
|
|
13941
|
+
reject(new Error("Request timed out"));
|
|
13942
|
+
});
|
|
13943
|
+
});
|
|
13944
|
+
}
|
|
13945
|
+
async function webSearchTool(query, count) {
|
|
13946
|
+
const maxResults = Math.min(Math.max(count ?? 5, 1), 10);
|
|
13947
|
+
const encoded = encodeURIComponent(query);
|
|
13948
|
+
const ddgUrl = `https://api.duckduckgo.com/?q=${encoded}&format=json&no_html=1&skip_disambig=1`;
|
|
13949
|
+
let json;
|
|
13950
|
+
try {
|
|
13951
|
+
json = await httpsGetJSON(ddgUrl, 1e4);
|
|
13952
|
+
} catch (err) {
|
|
13953
|
+
return `Error: Web search failed \u2014 ${err.message || err}`;
|
|
13954
|
+
}
|
|
13955
|
+
const parts = [];
|
|
13956
|
+
if (json.Heading && json.Heading !== query) {
|
|
13957
|
+
parts.push(`Topic: ${json.Heading}`);
|
|
13958
|
+
}
|
|
13959
|
+
if (json.Answer) {
|
|
13960
|
+
parts.push(`Answer: ${json.Answer}`);
|
|
13961
|
+
}
|
|
13962
|
+
if (json.AbstractText && json.AbstractText.trim()) {
|
|
13963
|
+
let abstract = json.AbstractText.trim();
|
|
13964
|
+
if (json.AbstractSource) {
|
|
13965
|
+
abstract += ` [Source: ${json.AbstractSource}]`;
|
|
13966
|
+
}
|
|
13967
|
+
if (json.AbstractURL) {
|
|
13968
|
+
abstract += ` \u2014 ${json.AbstractURL}`;
|
|
13969
|
+
}
|
|
13970
|
+
parts.push(`Summary: ${abstract}`);
|
|
13971
|
+
} else if (json.Abstract && json.Abstract.trim()) {
|
|
13972
|
+
parts.push(`Summary: ${json.Abstract.trim()}`);
|
|
13973
|
+
}
|
|
13974
|
+
if (json.Definition) {
|
|
13975
|
+
let def = `Definition: ${json.Definition}`;
|
|
13976
|
+
if (json.DefinitionSource) def += ` [Source: ${json.DefinitionSource}]`;
|
|
13977
|
+
parts.push(def);
|
|
13978
|
+
}
|
|
13979
|
+
if (json.Infobox?.content) {
|
|
13980
|
+
const fields = json.Infobox.content.filter((c2) => c2.label && c2.value).map((c2) => ` ${c2.label}: ${c2.value}`).slice(0, 8);
|
|
13981
|
+
if (fields.length > 0) {
|
|
13982
|
+
parts.push(`Details:
|
|
13983
|
+
${fields.join("\n")}`);
|
|
13984
|
+
}
|
|
13985
|
+
}
|
|
13986
|
+
if (json.RelatedTopics && json.RelatedTopics.length > 0) {
|
|
13987
|
+
const topics = [];
|
|
13988
|
+
for (const t2 of json.RelatedTopics) {
|
|
13989
|
+
if (topics.length >= maxResults) break;
|
|
13990
|
+
if (t2.Text && t2.FirstURL) {
|
|
13991
|
+
const cleanText = t2.Text.replace(/<[^>]+>/g, "").trim();
|
|
13992
|
+
topics.push(` - ${cleanText.slice(0, 200)}
|
|
13993
|
+
${t2.FirstURL}`);
|
|
13994
|
+
} else if (t2.Text) {
|
|
13995
|
+
topics.push(` - ${t2.Text.replace(/<[^>]+>/g, "").trim().slice(0, 200)}`);
|
|
13996
|
+
}
|
|
13997
|
+
}
|
|
13998
|
+
if (topics.length > 0) {
|
|
13999
|
+
parts.push(`Related:
|
|
14000
|
+
${topics.join("\n")}`);
|
|
14001
|
+
}
|
|
14002
|
+
}
|
|
14003
|
+
if (json.Results && json.Results.length > 0) {
|
|
14004
|
+
const results = [];
|
|
14005
|
+
for (const r2 of json.Results) {
|
|
14006
|
+
if (results.length >= maxResults) break;
|
|
14007
|
+
if (r2.Text && r2.FirstURL) {
|
|
14008
|
+
results.push(` - ${r2.Text.replace(/<[^>]+>/g, "").trim().slice(0, 200)}
|
|
14009
|
+
${r2.FirstURL}`);
|
|
14010
|
+
}
|
|
14011
|
+
}
|
|
14012
|
+
if (results.length > 0) {
|
|
14013
|
+
parts.push(`Web Results:
|
|
14014
|
+
${results.join("\n")}`);
|
|
14015
|
+
}
|
|
14016
|
+
}
|
|
14017
|
+
if (parts.length === 0) {
|
|
14018
|
+
return `No results found for "${query}". Try different keywords or be more specific.`;
|
|
14019
|
+
}
|
|
14020
|
+
return truncateOutput(parts.join("\n\n")).text;
|
|
14021
|
+
}
|
|
14022
|
+
var KNOWN_DOC_SITES = [
|
|
14023
|
+
// JavaScript/TypeScript ecosystem
|
|
14024
|
+
{ patterns: ["node", "nodejs", "node.js"], sites: ["nodejs.org", "nodejs.dev"] },
|
|
14025
|
+
{ patterns: ["javascript", "js", "mozilla", "mdn"], sites: ["developer.mozilla.org"] },
|
|
14026
|
+
{ patterns: ["typescript", "ts"], sites: ["typescriptlang.org"] },
|
|
14027
|
+
{ patterns: ["react", "reactjs", "react.js"], sites: ["react.dev", "reactjs.org", "legacy.reactjs.org"] },
|
|
14028
|
+
{ patterns: ["express", "expressjs", "express.js"], sites: ["expressjs.com"] },
|
|
14029
|
+
{ patterns: ["next", "nextjs", "next.js"], sites: ["nextjs.org"] },
|
|
14030
|
+
{ patterns: ["vue", "vuejs", "vue.js"], sites: ["vuejs.org"] },
|
|
14031
|
+
{ patterns: ["svelte", "sveltekit"], sites: ["svelte.dev", "kit.svelte.dev"] },
|
|
14032
|
+
{ patterns: ["tailwind", "tailwindcss"], sites: ["tailwindcss.com"] },
|
|
14033
|
+
{ patterns: ["prisma"], sites: ["prisma.io"] },
|
|
14034
|
+
{ patterns: ["vite"], sites: ["vitejs.dev"] },
|
|
14035
|
+
{ patterns: ["esbuild"], sites: ["esbuild.github.io"] },
|
|
14036
|
+
{ patterns: ["webpack"], sites: ["webpack.js.org"] },
|
|
14037
|
+
{ patterns: ["babel"], sites: ["babeljs.io"] },
|
|
14038
|
+
{ patterns: ["eslint"], sites: ["eslint.org"] },
|
|
14039
|
+
{ patterns: ["prettier"], sites: ["prettier.io"] },
|
|
14040
|
+
{ patterns: ["jest"], sites: ["jestjs.io"] },
|
|
14041
|
+
{ patterns: ["vitest"], sites: ["vitest.dev"] },
|
|
14042
|
+
{ patterns: ["playwright"], sites: ["playwright.dev"] },
|
|
14043
|
+
{ patterns: ["cypress"], sites: ["cypress.io", "docs.cypress.io"] },
|
|
14044
|
+
{ patterns: ["graphql"], sites: ["graphql.org"] },
|
|
14045
|
+
{ patterns: ["apollo"], sites: ["apollographql.com"] },
|
|
14046
|
+
{ patterns: ["trpc"], sites: ["trpc.io"] },
|
|
14047
|
+
{ patterns: ["zod"], sites: ["github.com/colinhacks/zod", "zod.dev"] },
|
|
14048
|
+
{ patterns: ["axios"], sites: ["axios-http.com"] },
|
|
14049
|
+
{ patterns: ["npm"], sites: ["npmjs.com", "docs.npmjs.com"] },
|
|
14050
|
+
{ patterns: ["pnpm"], sites: ["pnpm.io"] },
|
|
14051
|
+
{ patterns: ["yarn"], sites: ["yarnpkg.com"] },
|
|
14052
|
+
{ patterns: ["deno"], sites: ["deno.com", "deno.land", "docs.deno.com"] },
|
|
14053
|
+
{ patterns: ["bun"], sites: ["bun.sh"] },
|
|
14054
|
+
// Python
|
|
14055
|
+
{ patterns: ["python", "py"], sites: ["docs.python.org"] },
|
|
14056
|
+
{ patterns: ["django"], sites: ["djangoproject.com", "docs.djangoproject.com"] },
|
|
14057
|
+
{ patterns: ["flask"], sites: ["flask.palletsprojects.com"] },
|
|
14058
|
+
{ patterns: ["fastapi"], sites: ["fastapi.tiangolo.com"] },
|
|
14059
|
+
{ patterns: ["pydantic"], sites: ["docs.pydantic.dev"] },
|
|
14060
|
+
{ patterns: ["pytest"], sites: ["docs.pytest.org"] },
|
|
14061
|
+
{ patterns: ["poetry"], sites: ["python-poetry.org"] },
|
|
14062
|
+
{ patterns: ["numpy"], sites: ["numpy.org"] },
|
|
14063
|
+
{ patterns: ["pandas"], sites: ["pandas.pydata.org"] },
|
|
14064
|
+
{ patterns: ["sqlalchemy"], sites: ["docs.sqlalchemy.org"] },
|
|
14065
|
+
{ patterns: ["alembic"], sites: ["alembic.sqlalchemy.org"] },
|
|
14066
|
+
// Rust
|
|
14067
|
+
{ patterns: ["rust", "cargo", "rustc"], sites: ["doc.rust-lang.org", "docs.rs"] },
|
|
14068
|
+
{ patterns: ["tokio"], sites: ["docs.rs/tokio", "tokio.rs"] },
|
|
14069
|
+
{ patterns: ["serde"], sites: ["docs.rs/serde", "serde.rs"] },
|
|
14070
|
+
{ patterns: ["actix"], sites: ["actix.rs", "docs.rs/actix-web"] },
|
|
14071
|
+
{ patterns: ["axum"], sites: ["docs.rs/axum"] },
|
|
14072
|
+
{ patterns: ["bevy"], sites: ["bevyengine.org", "docs.rs/bevy"] },
|
|
14073
|
+
// Go
|
|
14074
|
+
{ patterns: ["go", "golang"], sites: ["pkg.go.dev", "go.dev"] },
|
|
14075
|
+
{ patterns: ["gin"], sites: ["gin-gonic.com", "pkg.go.dev/github.com/gin-gonic"] },
|
|
14076
|
+
{ patterns: ["echo"], sites: ["echo.labstack.com", "pkg.go.dev/github.com/labstack/echo"] },
|
|
14077
|
+
{ patterns: ["fiber"], sites: ["docs.gofiber.io", "pkg.go.dev/github.com/gofiber/fiber"] },
|
|
14078
|
+
// Ruby
|
|
14079
|
+
{ patterns: ["ruby", "rubygems"], sites: ["ruby-doc.org", "rubygems.org"] },
|
|
14080
|
+
{ patterns: ["rails", "ruby on rails"], sites: ["guides.rubyonrails.org", "api.rubyonrails.org"] },
|
|
14081
|
+
{ patterns: ["rspec"], sites: ["rspec.info", "rubydoc.info"] },
|
|
14082
|
+
// Other
|
|
14083
|
+
{ patterns: ["linux", "man"], sites: ["man7.org", "linux.die.net"] },
|
|
14084
|
+
{ patterns: ["git"], sites: ["git-scm.com"] },
|
|
14085
|
+
{ patterns: ["docker"], sites: ["docs.docker.com"] },
|
|
14086
|
+
{ patterns: ["kubernetes", "k8s"], sites: ["kubernetes.io"] },
|
|
14087
|
+
{ patterns: ["nginx"], sites: ["nginx.org", "nginx.com"] },
|
|
14088
|
+
{ patterns: ["postgresql", "postgres", "pg"], sites: ["postgresql.org"] },
|
|
14089
|
+
{ patterns: ["mysql"], sites: ["dev.mysql.com"] },
|
|
14090
|
+
{ patterns: ["redis"], sites: ["redis.io"] },
|
|
14091
|
+
{ patterns: ["mongodb", "mongo"], sites: ["mongodb.com", "docs.mongodb.com"] },
|
|
14092
|
+
{ patterns: ["sqlite"], sites: ["sqlite.org"] },
|
|
14093
|
+
{ patterns: ["aws"], sites: ["docs.aws.amazon.com"] }
|
|
14094
|
+
// Add more as needed — this list guides the agent to official docs
|
|
14095
|
+
];
|
|
14096
|
+
function resolveDocSites(query, library) {
|
|
14097
|
+
const searchTerms = (library ? `${library} ${query}` : query).toLowerCase();
|
|
14098
|
+
const sites = [];
|
|
14099
|
+
for (const entry of KNOWN_DOC_SITES) {
|
|
14100
|
+
for (const pat of entry.patterns) {
|
|
14101
|
+
if (searchTerms.includes(pat) && !sites.some((s2) => entry.sites.includes(s2))) {
|
|
14102
|
+
sites.push(...entry.sites);
|
|
14103
|
+
break;
|
|
14104
|
+
}
|
|
14105
|
+
}
|
|
14106
|
+
}
|
|
14107
|
+
return sites;
|
|
14108
|
+
}
|
|
14109
|
+
async function docsSearchTool(query, library, count, useSites = true) {
|
|
14110
|
+
const maxResults = Math.min(Math.max(count ?? 5, 1), 8);
|
|
14111
|
+
const sites = useSites ? resolveDocSites(query, library) : [];
|
|
14112
|
+
let searchQuery;
|
|
14113
|
+
if (library) {
|
|
14114
|
+
searchQuery = `${library} ${query}`;
|
|
14115
|
+
} else {
|
|
14116
|
+
searchQuery = query;
|
|
14117
|
+
}
|
|
14118
|
+
if (sites.length > 0) {
|
|
14119
|
+
const siteFilters = sites.map((s2) => `site:${s2}`).join(" OR ");
|
|
14120
|
+
searchQuery = `${searchQuery} (${siteFilters})`;
|
|
14121
|
+
} else {
|
|
14122
|
+
searchQuery = `${searchQuery} documentation`;
|
|
14123
|
+
}
|
|
14124
|
+
const ddgUrl = `https://api.duckduckgo.com/?q=${encodeURIComponent(searchQuery)}&format=json&no_html=1&skip_disambig=1`;
|
|
14125
|
+
let json;
|
|
14126
|
+
try {
|
|
14127
|
+
json = await httpsGetJSON(ddgUrl, 1e4);
|
|
14128
|
+
} catch (err) {
|
|
14129
|
+
return `Error: Docs search failed \u2014 ${err.message || err}`;
|
|
14130
|
+
}
|
|
14131
|
+
const parts = [];
|
|
14132
|
+
const headerParts = [];
|
|
14133
|
+
if (library) headerParts.push(`Library: ${library}`);
|
|
14134
|
+
if (sites.length > 0) headerParts.push(`Sources: ${sites.join(", ")}`);
|
|
14135
|
+
if (headerParts.length > 0) {
|
|
14136
|
+
parts.push(headerParts.join(" | "));
|
|
14137
|
+
}
|
|
14138
|
+
if (json.Answer) {
|
|
14139
|
+
parts.push(`Answer: ${json.Answer}`);
|
|
14140
|
+
}
|
|
14141
|
+
if (json.AbstractText && json.AbstractText.trim()) {
|
|
14142
|
+
const abstract = json.AbstractText.trim();
|
|
14143
|
+
const source = json.AbstractSource ? ` [${json.AbstractSource}]` : "";
|
|
14144
|
+
const url = json.AbstractURL ? ` \u2014 ${json.AbstractURL}` : "";
|
|
14145
|
+
parts.push(`Summary: ${abstract}${source}${url}`);
|
|
14146
|
+
} else if (json.Abstract && json.Abstract.trim()) {
|
|
14147
|
+
parts.push(`Summary: ${json.Abstract.trim()}`);
|
|
14148
|
+
}
|
|
14149
|
+
if (json.Definition) {
|
|
14150
|
+
let def = `Definition: ${json.Definition}`;
|
|
14151
|
+
if (json.DefinitionSource) def += ` [Source: ${json.DefinitionSource}]`;
|
|
14152
|
+
parts.push(def);
|
|
14153
|
+
}
|
|
14154
|
+
if (json.RelatedTopics && json.RelatedTopics.length > 0) {
|
|
14155
|
+
const topics = [];
|
|
14156
|
+
for (const t2 of json.RelatedTopics) {
|
|
14157
|
+
if (topics.length >= maxResults) break;
|
|
14158
|
+
if (t2.Text && t2.FirstURL) {
|
|
14159
|
+
const cleanText = t2.Text.replace(/<[^>]+>/g, "").trim();
|
|
14160
|
+
topics.push(` - ${cleanText.slice(0, 250)}
|
|
14161
|
+
${t2.FirstURL}`);
|
|
14162
|
+
} else if (t2.Text) {
|
|
14163
|
+
topics.push(` - ${t2.Text.replace(/<[^>]+>/g, "").trim().slice(0, 250)}`);
|
|
14164
|
+
}
|
|
14165
|
+
}
|
|
14166
|
+
if (topics.length > 0) {
|
|
14167
|
+
parts.push(`Documentation Results:
|
|
14168
|
+
${topics.join("\n")}`);
|
|
14169
|
+
}
|
|
14170
|
+
}
|
|
14171
|
+
if (json.Results && json.Results.length > 0) {
|
|
14172
|
+
const results = [];
|
|
14173
|
+
for (const r2 of json.Results) {
|
|
14174
|
+
if (results.length >= maxResults) break;
|
|
14175
|
+
if (r2.Text && r2.FirstURL) {
|
|
14176
|
+
results.push(` - ${r2.Text.replace(/<[^>]+>/g, "").trim().slice(0, 200)}
|
|
14177
|
+
${r2.FirstURL}`);
|
|
14178
|
+
}
|
|
14179
|
+
}
|
|
14180
|
+
if (results.length > 0) {
|
|
14181
|
+
parts.push(`More:
|
|
14182
|
+
${results.join("\n")}`);
|
|
14183
|
+
}
|
|
14184
|
+
}
|
|
14185
|
+
const hasContent = parts.length > (headerParts.length > 0 ? 1 : 0);
|
|
14186
|
+
if (!hasContent && sites.length > 0) {
|
|
14187
|
+
return docsSearchTool(query, library, count, false);
|
|
14188
|
+
}
|
|
14189
|
+
if (!hasContent) {
|
|
14190
|
+
return `No documentation results for "${query}". Try different terms or use web_search for broader results.`;
|
|
14191
|
+
}
|
|
14192
|
+
return truncateOutput(parts.join("\n\n")).text;
|
|
14193
|
+
}
|
|
13792
14194
|
async function executeTool(call, workDir) {
|
|
13793
14195
|
const { id, function: fn } = call;
|
|
13794
14196
|
let args;
|
|
@@ -13802,6 +14204,60 @@ async function executeTool(call, workDir) {
|
|
|
13802
14204
|
};
|
|
13803
14205
|
}
|
|
13804
14206
|
let result;
|
|
14207
|
+
try {
|
|
14208
|
+
switch (fn.name) {
|
|
14209
|
+
case "bash":
|
|
14210
|
+
if (typeof args.command !== "string" || args.command.length === 0) throw new Error('bash requires a non-empty "command" (string)');
|
|
14211
|
+
if (args.timeout !== void 0 && typeof args.timeout !== "number") throw new Error('bash "timeout" must be a number');
|
|
14212
|
+
break;
|
|
14213
|
+
case "read":
|
|
14214
|
+
if (typeof args.path !== "string" || args.path.length === 0) throw new Error('read requires a non-empty "path" (string)');
|
|
14215
|
+
if (args.offset !== void 0 && typeof args.offset !== "number") throw new Error('read "offset" must be a number');
|
|
14216
|
+
if (args.limit !== void 0 && typeof args.limit !== "number") throw new Error('read "limit" must be a number');
|
|
14217
|
+
break;
|
|
14218
|
+
case "edit":
|
|
14219
|
+
if (typeof args.path !== "string" || args.path.length === 0) throw new Error('edit requires a non-empty "path" (string)');
|
|
14220
|
+
if (!Array.isArray(args.edits)) throw new Error('edit "edits" must be an array');
|
|
14221
|
+
if (args.edits.length === 0) throw new Error('edit "edits" must be non-empty');
|
|
14222
|
+
for (let i2 = 0; i2 < args.edits.length; i2++) {
|
|
14223
|
+
const e2 = args.edits[i2];
|
|
14224
|
+
if (typeof e2.oldText !== "string") throw new Error(`edit "edits[${i2}].oldText" must be a string`);
|
|
14225
|
+
if (typeof e2.newText !== "string") throw new Error(`edit "edits[${i2}].newText" must be a string`);
|
|
14226
|
+
}
|
|
14227
|
+
break;
|
|
14228
|
+
case "write":
|
|
14229
|
+
if (typeof args.path !== "string" || args.path.length === 0) throw new Error('write requires a non-empty "path" (string)');
|
|
14230
|
+
if (typeof args.content !== "string") throw new Error('write requires "content" (string)');
|
|
14231
|
+
break;
|
|
14232
|
+
case "find":
|
|
14233
|
+
if (typeof args.pattern !== "string" || args.pattern.length === 0) throw new Error('find requires a non-empty "pattern" (string)');
|
|
14234
|
+
if (args.path !== void 0 && typeof args.path !== "string") throw new Error('find "path" must be a string');
|
|
14235
|
+
break;
|
|
14236
|
+
case "grep":
|
|
14237
|
+
if (typeof args.pattern !== "string" || args.pattern.length === 0) throw new Error('grep requires a non-empty "pattern" (string)');
|
|
14238
|
+
if (args.path !== void 0 && typeof args.path !== "string") throw new Error('grep "path" must be a string');
|
|
14239
|
+
if (args.include !== void 0 && typeof args.include !== "string") throw new Error('grep "include" must be a string');
|
|
14240
|
+
break;
|
|
14241
|
+
case "stat":
|
|
14242
|
+
if (typeof args.path !== "string" || args.path.length === 0) throw new Error('stat requires a non-empty "path" (string)');
|
|
14243
|
+
break;
|
|
14244
|
+
case "web_search":
|
|
14245
|
+
if (typeof args.query !== "string" || args.query.length === 0) throw new Error('web_search requires a non-empty "query" (string)');
|
|
14246
|
+
if (args.count !== void 0 && typeof args.count !== "number") throw new Error('web_search "count" must be a number');
|
|
14247
|
+
break;
|
|
14248
|
+
case "docs":
|
|
14249
|
+
if (typeof args.query !== "string" || args.query.length === 0) throw new Error('docs requires a non-empty "query" (string)');
|
|
14250
|
+
if (args.count !== void 0 && typeof args.count !== "number") throw new Error('docs "count" must be a number');
|
|
14251
|
+
if (args.library !== void 0 && typeof args.library !== "string") throw new Error('docs "library" must be a string');
|
|
14252
|
+
break;
|
|
14253
|
+
}
|
|
14254
|
+
} catch (validationErr) {
|
|
14255
|
+
return {
|
|
14256
|
+
tool_call_id: id,
|
|
14257
|
+
role: "tool",
|
|
14258
|
+
content: `Error: ${validationErr.message}`
|
|
14259
|
+
};
|
|
14260
|
+
}
|
|
13805
14261
|
try {
|
|
13806
14262
|
switch (fn.name) {
|
|
13807
14263
|
case "bash":
|
|
@@ -13850,6 +14306,19 @@ async function executeTool(call, workDir) {
|
|
|
13850
14306
|
case "stat":
|
|
13851
14307
|
result = await statTool(args.path, workDir);
|
|
13852
14308
|
break;
|
|
14309
|
+
case "web_search":
|
|
14310
|
+
result = await webSearchTool(
|
|
14311
|
+
args.query,
|
|
14312
|
+
args.count
|
|
14313
|
+
);
|
|
14314
|
+
break;
|
|
14315
|
+
case "docs":
|
|
14316
|
+
result = await docsSearchTool(
|
|
14317
|
+
args.query,
|
|
14318
|
+
args.library,
|
|
14319
|
+
args.count
|
|
14320
|
+
);
|
|
14321
|
+
break;
|
|
13853
14322
|
default:
|
|
13854
14323
|
result = `Error: Unknown tool: ${fn.name}`;
|
|
13855
14324
|
}
|
|
@@ -13865,7 +14334,7 @@ async function executeTool(call, workDir) {
|
|
|
13865
14334
|
|
|
13866
14335
|
// src/crypto.ts
|
|
13867
14336
|
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
|
|
13868
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, access as access2 } from "fs/promises";
|
|
14337
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, access as access2, chmod } from "fs/promises";
|
|
13869
14338
|
import { constants as constants2 } from "fs";
|
|
13870
14339
|
import { homedir, hostname, userInfo } from "os";
|
|
13871
14340
|
import { join } from "path";
|
|
@@ -13873,21 +14342,39 @@ var ALGORITHM = "aes-256-gcm";
|
|
|
13873
14342
|
var IV_LENGTH = 16;
|
|
13874
14343
|
var TAG_LENGTH = 16;
|
|
13875
14344
|
var SALT = "jawere-agent-key-vault-2026";
|
|
13876
|
-
|
|
14345
|
+
var PEPPER_FILE = join(homedir(), ".jawere", ".pepper");
|
|
14346
|
+
async function loadOrCreatePepper() {
|
|
14347
|
+
try {
|
|
14348
|
+
await access2(PEPPER_FILE, constants2.R_OK);
|
|
14349
|
+
return await readFile2(PEPPER_FILE);
|
|
14350
|
+
} catch {
|
|
14351
|
+
const pepper = randomBytes(32);
|
|
14352
|
+
await mkdir2(join(homedir(), ".jawere"), { recursive: true });
|
|
14353
|
+
await writeFile2(PEPPER_FILE, pepper);
|
|
14354
|
+
try {
|
|
14355
|
+
await chmod(PEPPER_FILE, 384);
|
|
14356
|
+
} catch {
|
|
14357
|
+
}
|
|
14358
|
+
return pepper;
|
|
14359
|
+
}
|
|
14360
|
+
}
|
|
14361
|
+
var _pepper = null;
|
|
14362
|
+
async function deriveKey() {
|
|
14363
|
+
if (!_pepper) _pepper = await loadOrCreatePepper();
|
|
13877
14364
|
const machineId = `${hostname()}-${userInfo().username}-jawere`;
|
|
13878
|
-
return scryptSync(machineId, SALT, 32);
|
|
14365
|
+
return scryptSync(machineId, Buffer.concat([Buffer.from(SALT), _pepper]), 32);
|
|
13879
14366
|
}
|
|
13880
|
-
function encrypt(plaintext) {
|
|
13881
|
-
const key = deriveKey();
|
|
14367
|
+
async function encrypt(plaintext) {
|
|
14368
|
+
const key = await deriveKey();
|
|
13882
14369
|
const iv = randomBytes(IV_LENGTH);
|
|
13883
14370
|
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
13884
14371
|
const encrypted = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
|
|
13885
14372
|
const tag = cipher.getAuthTag();
|
|
13886
14373
|
return Buffer.concat([iv, tag, encrypted]).toString("base64");
|
|
13887
14374
|
}
|
|
13888
|
-
function decrypt(encoded) {
|
|
14375
|
+
async function decrypt(encoded) {
|
|
13889
14376
|
try {
|
|
13890
|
-
const key = deriveKey();
|
|
14377
|
+
const key = await deriveKey();
|
|
13891
14378
|
const buf = Buffer.from(encoded, "base64");
|
|
13892
14379
|
const iv = buf.subarray(0, IV_LENGTH);
|
|
13893
14380
|
const tag = buf.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
|
|
@@ -13903,14 +14390,22 @@ var KEY_FILE = join(homedir(), ".jawere", "key.enc");
|
|
|
13903
14390
|
async function saveKey(apiKey) {
|
|
13904
14391
|
const dir = join(homedir(), ".jawere");
|
|
13905
14392
|
await mkdir2(dir, { recursive: true });
|
|
13906
|
-
const encrypted = encrypt(apiKey.trim());
|
|
14393
|
+
const encrypted = await encrypt(apiKey.trim());
|
|
13907
14394
|
await writeFile2(KEY_FILE, encrypted, "utf-8");
|
|
14395
|
+
try {
|
|
14396
|
+
await chmod(KEY_FILE, 384);
|
|
14397
|
+
} catch {
|
|
14398
|
+
}
|
|
14399
|
+
try {
|
|
14400
|
+
await chmod(dir, 448);
|
|
14401
|
+
} catch {
|
|
14402
|
+
}
|
|
13908
14403
|
}
|
|
13909
14404
|
async function loadKey() {
|
|
13910
14405
|
try {
|
|
13911
14406
|
await access2(KEY_FILE, constants2.R_OK);
|
|
13912
14407
|
const encrypted = await readFile2(KEY_FILE, "utf-8");
|
|
13913
|
-
return decrypt(encrypted.trim());
|
|
14408
|
+
return await decrypt(encrypted.trim());
|
|
13914
14409
|
} catch {
|
|
13915
14410
|
return null;
|
|
13916
14411
|
}
|
|
@@ -13925,8 +14420,6 @@ async function hasKey() {
|
|
|
13925
14420
|
}
|
|
13926
14421
|
|
|
13927
14422
|
// src/config.ts
|
|
13928
|
-
var CONVEX_DEV = "https://dazzling-jackal-33.convex.cloud";
|
|
13929
|
-
var CONVEX_PROD = "https://friendly-pigeon-624.convex.cloud";
|
|
13930
14423
|
function isDevMode() {
|
|
13931
14424
|
if (process.env.NODE_ENV === "production") return false;
|
|
13932
14425
|
if (process.env.NODE_ENV === "development") return true;
|
|
@@ -13955,7 +14448,6 @@ async function loadConfig() {
|
|
|
13955
14448
|
apiKey,
|
|
13956
14449
|
model: process.env.DEEPSEEK_MODEL || "deepseek-v4-pro",
|
|
13957
14450
|
workDir: process.env.WORK_DIR || process.cwd(),
|
|
13958
|
-
convexUrl: process.env.CONVEX_URL || (isDev ? CONVEX_DEV : CONVEX_PROD),
|
|
13959
14451
|
keyFromFile,
|
|
13960
14452
|
isDev
|
|
13961
14453
|
};
|
|
@@ -14218,93 +14710,6 @@ function createSpinner() {
|
|
|
14218
14710
|
return self;
|
|
14219
14711
|
}
|
|
14220
14712
|
|
|
14221
|
-
// src/convex-client.ts
|
|
14222
|
-
async function convexMutation(convexUrl, name, args) {
|
|
14223
|
-
const res = await fetch(`${convexUrl}/api/mutation`, {
|
|
14224
|
-
method: "POST",
|
|
14225
|
-
headers: { "content-type": "application/json" },
|
|
14226
|
-
body: JSON.stringify({ path: name, format: "json", args })
|
|
14227
|
-
});
|
|
14228
|
-
if (!res.ok) {
|
|
14229
|
-
const err = await res.text();
|
|
14230
|
-
throw new Error(`Convex mutation ${name} failed (${res.status}): ${err}`);
|
|
14231
|
-
}
|
|
14232
|
-
const json = await res.json();
|
|
14233
|
-
if (json.status === "error") {
|
|
14234
|
-
throw new Error(`Convex mutation ${name} error: ${json.errorMessage || JSON.stringify(json)}`);
|
|
14235
|
-
}
|
|
14236
|
-
return json.value;
|
|
14237
|
-
}
|
|
14238
|
-
async function convexQuery(convexUrl, name, args) {
|
|
14239
|
-
const res = await fetch(`${convexUrl}/api/query`, {
|
|
14240
|
-
method: "POST",
|
|
14241
|
-
headers: { "content-type": "application/json" },
|
|
14242
|
-
body: JSON.stringify({ path: name, format: "json", args })
|
|
14243
|
-
});
|
|
14244
|
-
if (!res.ok) {
|
|
14245
|
-
const err = await res.text();
|
|
14246
|
-
throw new Error(`Convex query ${name} failed (${res.status}): ${err}`);
|
|
14247
|
-
}
|
|
14248
|
-
const json = await res.json();
|
|
14249
|
-
if (json.status === "error") {
|
|
14250
|
-
throw new Error(`Convex query ${name} error: ${json.errorMessage || JSON.stringify(json)}`);
|
|
14251
|
-
}
|
|
14252
|
-
return json.value;
|
|
14253
|
-
}
|
|
14254
|
-
async function createSession(convexUrl, title, model, systemPrompt, toolNames) {
|
|
14255
|
-
return convexMutation(convexUrl, "sessions:create", {
|
|
14256
|
-
title,
|
|
14257
|
-
model,
|
|
14258
|
-
systemPrompt,
|
|
14259
|
-
toolNames
|
|
14260
|
-
});
|
|
14261
|
-
}
|
|
14262
|
-
async function appendUserMessage(convexUrl, sessionId, text) {
|
|
14263
|
-
return convexMutation(convexUrl, "sessions:appendMessage", {
|
|
14264
|
-
sessionId,
|
|
14265
|
-
role: "user",
|
|
14266
|
-
content: [{ type: "text", text }],
|
|
14267
|
-
timestamp: Date.now()
|
|
14268
|
-
});
|
|
14269
|
-
}
|
|
14270
|
-
async function appendAssistantMessage(convexUrl, sessionId, text, toolCalls, usage) {
|
|
14271
|
-
const content = [];
|
|
14272
|
-
if (text) {
|
|
14273
|
-
content.push({ type: "text", text });
|
|
14274
|
-
}
|
|
14275
|
-
if (toolCalls) {
|
|
14276
|
-
for (const tc of toolCalls) {
|
|
14277
|
-
content.push({
|
|
14278
|
-
type: "tool_call",
|
|
14279
|
-
id: tc.id,
|
|
14280
|
-
name: tc.name,
|
|
14281
|
-
arguments: tc.arguments
|
|
14282
|
-
});
|
|
14283
|
-
}
|
|
14284
|
-
}
|
|
14285
|
-
return convexMutation(convexUrl, "sessions:appendMessage", {
|
|
14286
|
-
sessionId,
|
|
14287
|
-
role: "assistant",
|
|
14288
|
-
content,
|
|
14289
|
-
timestamp: Date.now(),
|
|
14290
|
-
usage
|
|
14291
|
-
});
|
|
14292
|
-
}
|
|
14293
|
-
async function appendToolResult(convexUrl, sessionId, toolCallId, toolName, result, isError) {
|
|
14294
|
-
return convexMutation(convexUrl, "sessions:appendMessage", {
|
|
14295
|
-
sessionId,
|
|
14296
|
-
role: "toolResult",
|
|
14297
|
-
content: [{ type: "tool_result", toolCallId, content: result }],
|
|
14298
|
-
toolCallId,
|
|
14299
|
-
toolName,
|
|
14300
|
-
isError,
|
|
14301
|
-
timestamp: Date.now()
|
|
14302
|
-
});
|
|
14303
|
-
}
|
|
14304
|
-
async function listSessions(convexUrl) {
|
|
14305
|
-
return convexQuery(convexUrl, "sessions:list", {});
|
|
14306
|
-
}
|
|
14307
|
-
|
|
14308
14713
|
// src/agent.ts
|
|
14309
14714
|
var MAX_TURNS = 500;
|
|
14310
14715
|
var MAX_OUTPUT_TOKENS = 393216;
|
|
@@ -14312,11 +14717,20 @@ var COL = () => process.stdout.columns || 80;
|
|
|
14312
14717
|
var GRUVBOX_GREEN = "\x1B[38;2;184;187;3m";
|
|
14313
14718
|
var GRUVBOX_GRAY = "\x1B[38;2;146;131;116m";
|
|
14314
14719
|
var GRUVBOX_RED = "\x1B[38;2;251;73;52m";
|
|
14720
|
+
var GRUVBOX_AQUA = "\x1B[38;2;142;192;124m";
|
|
14315
14721
|
var RESET2 = "\x1B[0m";
|
|
14316
14722
|
var FILE_TOOLS = /* @__PURE__ */ new Set(["read", "write", "edit", "stat"]);
|
|
14723
|
+
var SEARCH_TOOLS = /* @__PURE__ */ new Set(["web_search", "docs"]);
|
|
14317
14724
|
var SILENT_TOOLS = /* @__PURE__ */ new Set(["diff"]);
|
|
14318
14725
|
function toolDetail(name, args) {
|
|
14319
14726
|
switch (name) {
|
|
14727
|
+
case "web_search":
|
|
14728
|
+
return String(args.query || "?");
|
|
14729
|
+
case "docs": {
|
|
14730
|
+
let d2 = String(args.query || "?");
|
|
14731
|
+
if (args.library) d2 = `${args.library}: ${d2}`;
|
|
14732
|
+
return d2;
|
|
14733
|
+
}
|
|
14320
14734
|
case "read": {
|
|
14321
14735
|
let d2 = String(args.path || "?");
|
|
14322
14736
|
if (args.offset) d2 += ` [L${args.offset}${args.limit ? `-${Number(args.offset) + Number(args.limit) - 1}` : "+"}]`;
|
|
@@ -14345,7 +14759,7 @@ function displayToolLine(name, args, ok, spin) {
|
|
|
14345
14759
|
}
|
|
14346
14760
|
const statusIcon = ok ? "\u2713" : "\u2717";
|
|
14347
14761
|
const statusColor = ok ? GRUVBOX_GREEN : GRUVBOX_RED;
|
|
14348
|
-
const toolColor = FILE_TOOLS.has(name) ? GRUVBOX_GREEN : GRUVBOX_GRAY;
|
|
14762
|
+
const toolColor = SEARCH_TOOLS.has(name) ? GRUVBOX_AQUA : FILE_TOOLS.has(name) ? GRUVBOX_GREEN : GRUVBOX_GRAY;
|
|
14349
14763
|
const prefix = `${toolColor}${name}${RESET2}: `;
|
|
14350
14764
|
const suffix = ` ${statusColor}${statusIcon}${RESET2}`;
|
|
14351
14765
|
let detail = toolDetail(name, args);
|
|
@@ -14355,13 +14769,6 @@ function displayToolLine(name, args, ok, spin) {
|
|
|
14355
14769
|
spin.start("Working\u2026");
|
|
14356
14770
|
}
|
|
14357
14771
|
}
|
|
14358
|
-
async function safeCall(fn, label) {
|
|
14359
|
-
try {
|
|
14360
|
-
return await fn();
|
|
14361
|
-
} catch {
|
|
14362
|
-
return void 0;
|
|
14363
|
-
}
|
|
14364
|
-
}
|
|
14365
14772
|
async function withRetry(fn, maxRetries = 3, baseDelay = 1e3) {
|
|
14366
14773
|
let lastErr;
|
|
14367
14774
|
for (let i2 = 0; i2 <= maxRetries; i2++) {
|
|
@@ -14465,26 +14872,7 @@ async function runAgent(userMessage, options = {}) {
|
|
|
14465
14872
|
baseURL: config.baseURL,
|
|
14466
14873
|
apiKey: config.apiKey
|
|
14467
14874
|
});
|
|
14468
|
-
const
|
|
14469
|
-
let sessionId = options.sessionId || "local";
|
|
14470
|
-
const hasRealSession = sessionId !== "local";
|
|
14471
|
-
const isNewSession = !options.sessionId;
|
|
14472
|
-
if (isNewSession) {
|
|
14473
|
-
const created = await safeCall(
|
|
14474
|
-
() => createSession(
|
|
14475
|
-
config.convexUrl,
|
|
14476
|
-
options.title || userMessage.slice(0, 100),
|
|
14477
|
-
config.model,
|
|
14478
|
-
SYSTEM_PROMPT,
|
|
14479
|
-
toolNames
|
|
14480
|
-
),
|
|
14481
|
-
"createSession"
|
|
14482
|
-
);
|
|
14483
|
-
if (created) sessionId = created;
|
|
14484
|
-
}
|
|
14485
|
-
if (hasRealSession || sessionId !== "local") {
|
|
14486
|
-
safeCall(() => appendUserMessage(config.convexUrl, sessionId, userMessage), "appendUserMessage");
|
|
14487
|
-
}
|
|
14875
|
+
const sessionId = options.sessionId || "local";
|
|
14488
14876
|
const messages = [
|
|
14489
14877
|
{ role: "system", content: SYSTEM_PROMPT }
|
|
14490
14878
|
];
|
|
@@ -14574,21 +14962,6 @@ async function runAgent(userMessage, options = {}) {
|
|
|
14574
14962
|
const hasToolCalls = streamedToolCalls.size > 0;
|
|
14575
14963
|
if (hasToolCalls) {
|
|
14576
14964
|
const toolCallArray = Array.from(streamedToolCalls.entries()).sort(([a2], [b2]) => a2 - b2).map(([_2, tc]) => tc);
|
|
14577
|
-
const toolCallsMeta = toolCallArray.map((tc) => ({
|
|
14578
|
-
id: tc.id,
|
|
14579
|
-
name: tc.name,
|
|
14580
|
-
arguments: tc.arguments
|
|
14581
|
-
}));
|
|
14582
|
-
safeCall(
|
|
14583
|
-
() => appendAssistantMessage(
|
|
14584
|
-
config.convexUrl,
|
|
14585
|
-
sessionId,
|
|
14586
|
-
streamedContent || null,
|
|
14587
|
-
toolCallsMeta.length > 0 ? toolCallsMeta : null,
|
|
14588
|
-
streamUsage
|
|
14589
|
-
),
|
|
14590
|
-
"appendAssistantMessage"
|
|
14591
|
-
);
|
|
14592
14965
|
const openaiToolCalls = toolCallArray.map((tc) => ({
|
|
14593
14966
|
id: tc.id,
|
|
14594
14967
|
type: "function",
|
|
@@ -14626,34 +14999,17 @@ async function runAgent(userMessage, options = {}) {
|
|
|
14626
14999
|
tool_call_id: result.tool_call_id,
|
|
14627
15000
|
content: result.content
|
|
14628
15001
|
});
|
|
14629
|
-
const isError = !ok;
|
|
14630
|
-
safeCall(
|
|
14631
|
-
() => appendToolResult(
|
|
14632
|
-
config.convexUrl,
|
|
14633
|
-
sessionId,
|
|
14634
|
-
result.tool_call_id,
|
|
14635
|
-
tc.name,
|
|
14636
|
-
result.content,
|
|
14637
|
-
isError
|
|
14638
|
-
),
|
|
14639
|
-
"appendToolResult"
|
|
14640
|
-
);
|
|
14641
15002
|
}
|
|
14642
15003
|
continue;
|
|
14643
15004
|
}
|
|
14644
15005
|
spin.stop();
|
|
14645
15006
|
const text = stripThinking(streamedContent) || "(empty response)";
|
|
14646
|
-
safeCall(
|
|
14647
|
-
() => appendAssistantMessage(config.convexUrl, sessionId, text, null, streamUsage),
|
|
14648
|
-
"appendAssistantMessage"
|
|
14649
|
-
);
|
|
14650
15007
|
console.log("");
|
|
14651
15008
|
printAssistantResponse(text);
|
|
14652
15009
|
return { text, sessionId, history: messages };
|
|
14653
15010
|
}
|
|
14654
15011
|
spin.stop();
|
|
14655
15012
|
const msg = "Agent reached maximum turns without completing the task.";
|
|
14656
|
-
safeCall(() => appendAssistantMessage(config.convexUrl, sessionId, msg, null), "appendAssistantMessage");
|
|
14657
15013
|
return { text: msg, sessionId, history: messages };
|
|
14658
15014
|
}
|
|
14659
15015
|
|
|
@@ -15370,8 +15726,7 @@ function printHelp() {
|
|
|
15370
15726
|
console.log(`
|
|
15371
15727
|
${G_GREEN}Commands:${R3}
|
|
15372
15728
|
${G_GRAY2}/help${R3} Show this help
|
|
15373
|
-
|
|
15374
|
-
${G_GRAY2}/load${R3} <num> Resume a session (run /sessions first)
|
|
15729
|
+
|
|
15375
15730
|
${G_GRAY2}/key${R3} Show API key status
|
|
15376
15731
|
${G_GRAY2}/setup${R3} Re-enter API key
|
|
15377
15732
|
${G_GRAY2}/clear${R3} Clear screen & start fresh session
|
|
@@ -15397,31 +15752,6 @@ async function setupKey() {
|
|
|
15397
15752
|
await saveKey(key);
|
|
15398
15753
|
console.log(`API key encrypted and saved to ~/.jawere/key.enc`);
|
|
15399
15754
|
}
|
|
15400
|
-
async function showSessions(convexUrl) {
|
|
15401
|
-
const map = /* @__PURE__ */ new Map();
|
|
15402
|
-
try {
|
|
15403
|
-
const sessions = await listSessions(convexUrl);
|
|
15404
|
-
if (sessions.length === 0) {
|
|
15405
|
-
console.log("No sessions found.");
|
|
15406
|
-
return map;
|
|
15407
|
-
}
|
|
15408
|
-
console.log(`
|
|
15409
|
-
Recent sessions:`);
|
|
15410
|
-
let i2 = 1;
|
|
15411
|
-
for (const s2 of sessions) {
|
|
15412
|
-
const date = new Date(s2.updatedAt).toLocaleString();
|
|
15413
|
-
const shortId = s2._id.slice(0, 10);
|
|
15414
|
-
console.log(` ${i2}. ${shortId}\u2026 ${date} ${s2.title.slice(0, 50)}`);
|
|
15415
|
-
map.set(i2, s2._id);
|
|
15416
|
-
i2++;
|
|
15417
|
-
}
|
|
15418
|
-
console.log(`
|
|
15419
|
-
Use /load <number> to resume a session.`);
|
|
15420
|
-
} catch (err) {
|
|
15421
|
-
console.log(`Error fetching sessions: ${err.message}`);
|
|
15422
|
-
}
|
|
15423
|
-
return map;
|
|
15424
|
-
}
|
|
15425
15755
|
async function main() {
|
|
15426
15756
|
const setupMode = process.argv.includes("--setup");
|
|
15427
15757
|
const config = await loadConfig();
|
|
@@ -15463,7 +15793,6 @@ async function main() {
|
|
|
15463
15793
|
}
|
|
15464
15794
|
let currentSessionId;
|
|
15465
15795
|
let conversationHistory = [];
|
|
15466
|
-
let sessionMap = /* @__PURE__ */ new Map();
|
|
15467
15796
|
let sessionShown = false;
|
|
15468
15797
|
try {
|
|
15469
15798
|
const codebaseDir = resolve3(config.workDir, ".codebase");
|
|
@@ -15512,35 +15841,12 @@ async function main() {
|
|
|
15512
15841
|
case "help":
|
|
15513
15842
|
printHelp();
|
|
15514
15843
|
break;
|
|
15515
|
-
case "sessions":
|
|
15516
|
-
sessionMap = await showSessions(config.convexUrl);
|
|
15517
|
-
break;
|
|
15518
|
-
case "load":
|
|
15519
|
-
if (arg) {
|
|
15520
|
-
const num = parseInt(arg, 10);
|
|
15521
|
-
if (!isNaN(num) && sessionMap.has(num)) {
|
|
15522
|
-
currentSessionId = sessionMap.get(num);
|
|
15523
|
-
conversationHistory = [];
|
|
15524
|
-
sessionShown = true;
|
|
15525
|
-
console.log(`Resumed session #${num}: ${currentSessionId?.slice(0, 12)}\u2026`);
|
|
15526
|
-
} else if (arg.length > 10) {
|
|
15527
|
-
currentSessionId = arg;
|
|
15528
|
-
conversationHistory = [];
|
|
15529
|
-
sessionShown = true;
|
|
15530
|
-
console.log(`Resumed session: ${arg.slice(0, 12)}\u2026`);
|
|
15531
|
-
} else {
|
|
15532
|
-
console.log(`Session #${arg} not found. Run /sessions first.`);
|
|
15533
|
-
}
|
|
15534
|
-
} else {
|
|
15535
|
-
console.log("Usage: /load <number> (run /sessions first)");
|
|
15536
|
-
}
|
|
15537
|
-
break;
|
|
15538
15844
|
case "key": {
|
|
15539
15845
|
const savedKey = await loadKey();
|
|
15540
15846
|
if (savedKey) {
|
|
15541
|
-
console.log(`Key status: saved (encrypted) \u2014 ${savedKey.slice(0,
|
|
15847
|
+
console.log(`Key status: saved (encrypted) \u2014 ${savedKey.slice(0, 3)}...`);
|
|
15542
15848
|
} else if (process.env.DEEPSEEK_API_KEY) {
|
|
15543
|
-
console.log(`Key status: DEEPSEEK_API_KEY env var`);
|
|
15849
|
+
console.log(`Key status: DEEPSEEK_API_KEY env var (starts ${process.env.DEEPSEEK_API_KEY.slice(0, 3)}...)`);
|
|
15544
15850
|
} else {
|
|
15545
15851
|
console.log(`Key status: NOT SET`);
|
|
15546
15852
|
}
|
package/package.json
CHANGED
|
@@ -1,28 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jawere",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"jawere": "bin/jawere.js"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
"bin",
|
|
10
|
-
"dist"
|
|
11
|
-
"convex/_generated"
|
|
10
|
+
"dist"
|
|
12
11
|
],
|
|
13
12
|
"scripts": {
|
|
14
13
|
"start": "node --env-file .env.local --import tsx src/index.ts",
|
|
15
14
|
"start:prod": "tsx src/index.ts",
|
|
16
15
|
"build": "node scripts/build.js",
|
|
17
|
-
"dev": "npx convex dev",
|
|
18
|
-
"deploy:dev": "npx convex dev --env-file .env.local",
|
|
19
|
-
"deploy:prod": "npx convex deploy --env-file .env.prod",
|
|
20
|
-
"seed:dev": "CONVEX_URL=https://dazzling-jackal-33.convex.cloud tsx scripts/seed.ts",
|
|
21
|
-
"seed:prod": "CONVEX_URL=https://friendly-pigeon-624.convex.cloud tsx scripts/seed.ts",
|
|
22
16
|
"prepublishOnly": "npm run build"
|
|
23
17
|
},
|
|
24
18
|
"dependencies": {
|
|
25
|
-
"convex": "^1.42.1",
|
|
26
19
|
"marked": "^18.0.5",
|
|
27
20
|
"openai": "^4.73.0"
|
|
28
21
|
},
|