jawere 1.6.0 → 1.7.0
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 +274 -258
- 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 {};
|
|
@@ -13194,6 +13194,34 @@ import { readFile, writeFile, mkdir, access, stat as fsStat } from "fs/promises"
|
|
|
13194
13194
|
import { constants } from "fs";
|
|
13195
13195
|
import { resolve, dirname } from "path";
|
|
13196
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
|
+
}
|
|
13197
13225
|
var TOOL_DEFS = [
|
|
13198
13226
|
{
|
|
13199
13227
|
type: "function",
|
|
@@ -13478,6 +13506,8 @@ function truncateOutput(text) {
|
|
|
13478
13506
|
return { text: truncated, totalLines, totalBytes, linesShown, truncated: true };
|
|
13479
13507
|
}
|
|
13480
13508
|
async function execBash(command, workDir, timeoutSec) {
|
|
13509
|
+
const blocked = checkDangerousCommand(command);
|
|
13510
|
+
if (blocked) return blocked;
|
|
13481
13511
|
const timeout = Math.min(timeoutSec ?? 120, 300) * 1e3;
|
|
13482
13512
|
return new Promise((resolve4) => {
|
|
13483
13513
|
const child = exec(
|
|
@@ -13511,7 +13541,12 @@ async function execBash(command, workDir, timeoutSec) {
|
|
|
13511
13541
|
});
|
|
13512
13542
|
}
|
|
13513
13543
|
async function readFileTool(path, workDir, offset, limit2) {
|
|
13514
|
-
|
|
13544
|
+
let fullPath;
|
|
13545
|
+
try {
|
|
13546
|
+
fullPath = safeResolve(workDir, path);
|
|
13547
|
+
} catch (err) {
|
|
13548
|
+
return `Error: ${err.message}`;
|
|
13549
|
+
}
|
|
13515
13550
|
try {
|
|
13516
13551
|
await access(fullPath, constants.R_OK);
|
|
13517
13552
|
} catch {
|
|
@@ -13546,7 +13581,12 @@ function diffSummary(before, after) {
|
|
|
13546
13581
|
return `(${linePart} lines, ${bytePart})`;
|
|
13547
13582
|
}
|
|
13548
13583
|
async function editFileTool(path, edits, workDir) {
|
|
13549
|
-
|
|
13584
|
+
let fullPath;
|
|
13585
|
+
try {
|
|
13586
|
+
fullPath = safeResolve(workDir, path);
|
|
13587
|
+
} catch (err) {
|
|
13588
|
+
return `Error: ${err.message}`;
|
|
13589
|
+
}
|
|
13550
13590
|
let content;
|
|
13551
13591
|
try {
|
|
13552
13592
|
content = await readFile(fullPath, "utf-8");
|
|
@@ -13595,13 +13635,23 @@ ${errors.map((e2) => ` - ${e2}`).join("\n")}`;
|
|
|
13595
13635
|
return parts.join("\n");
|
|
13596
13636
|
}
|
|
13597
13637
|
async function writeFileTool(path, content, workDir) {
|
|
13598
|
-
|
|
13638
|
+
let fullPath;
|
|
13639
|
+
try {
|
|
13640
|
+
fullPath = safeResolve(workDir, path);
|
|
13641
|
+
} catch (err) {
|
|
13642
|
+
return `Error: ${err.message}`;
|
|
13643
|
+
}
|
|
13599
13644
|
await mkdir(dirname(fullPath), { recursive: true });
|
|
13600
13645
|
await writeFile(fullPath, content, "utf-8");
|
|
13601
13646
|
return `Successfully wrote ${Buffer.byteLength(content, "utf-8")} bytes to ${fullPath}`;
|
|
13602
13647
|
}
|
|
13603
13648
|
async function lsTool(path, workDir) {
|
|
13604
|
-
|
|
13649
|
+
let dir;
|
|
13650
|
+
try {
|
|
13651
|
+
dir = safeResolve(workDir, path || ".");
|
|
13652
|
+
} catch (err) {
|
|
13653
|
+
return `Error: ${err.message}`;
|
|
13654
|
+
}
|
|
13605
13655
|
const { readdir: readdir2, stat } = await import("fs/promises");
|
|
13606
13656
|
try {
|
|
13607
13657
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
@@ -13641,7 +13691,12 @@ ${lines.join("\n")}`).text;
|
|
|
13641
13691
|
}
|
|
13642
13692
|
}
|
|
13643
13693
|
async function findTool(pattern, searchPath, workDir) {
|
|
13644
|
-
|
|
13694
|
+
let base;
|
|
13695
|
+
try {
|
|
13696
|
+
base = safeResolve(workDir, searchPath || ".");
|
|
13697
|
+
} catch (err) {
|
|
13698
|
+
return `Error: ${err.message}`;
|
|
13699
|
+
}
|
|
13645
13700
|
const SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
13646
13701
|
"node_modules",
|
|
13647
13702
|
".git",
|
|
@@ -13707,7 +13762,12 @@ ${results.join("\n")}`
|
|
|
13707
13762
|
).text;
|
|
13708
13763
|
}
|
|
13709
13764
|
async function grepTool(pattern, searchPath, include, workDir) {
|
|
13710
|
-
|
|
13765
|
+
let base;
|
|
13766
|
+
try {
|
|
13767
|
+
base = safeResolve(workDir, searchPath || ".");
|
|
13768
|
+
} catch (err) {
|
|
13769
|
+
return `Error: ${err.message}`;
|
|
13770
|
+
}
|
|
13711
13771
|
const SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
13712
13772
|
"node_modules",
|
|
13713
13773
|
".git",
|
|
@@ -13798,7 +13858,12 @@ ${results.join("\n")}`
|
|
|
13798
13858
|
).text;
|
|
13799
13859
|
}
|
|
13800
13860
|
async function statTool(path, workDir) {
|
|
13801
|
-
|
|
13861
|
+
let fullPath;
|
|
13862
|
+
try {
|
|
13863
|
+
fullPath = safeResolve(workDir, path);
|
|
13864
|
+
} catch (err) {
|
|
13865
|
+
return `Error: ${err.message}`;
|
|
13866
|
+
}
|
|
13802
13867
|
try {
|
|
13803
13868
|
const s2 = await fsStat(fullPath);
|
|
13804
13869
|
const parts = [`${fullPath}:`];
|
|
@@ -14139,6 +14204,60 @@ async function executeTool(call, workDir) {
|
|
|
14139
14204
|
};
|
|
14140
14205
|
}
|
|
14141
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
|
+
}
|
|
14142
14261
|
try {
|
|
14143
14262
|
switch (fn.name) {
|
|
14144
14263
|
case "bash":
|
|
@@ -14215,7 +14334,7 @@ async function executeTool(call, workDir) {
|
|
|
14215
14334
|
|
|
14216
14335
|
// src/crypto.ts
|
|
14217
14336
|
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
|
|
14218
|
-
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";
|
|
14219
14338
|
import { constants as constants2 } from "fs";
|
|
14220
14339
|
import { homedir, hostname, userInfo } from "os";
|
|
14221
14340
|
import { join } from "path";
|
|
@@ -14223,21 +14342,39 @@ var ALGORITHM = "aes-256-gcm";
|
|
|
14223
14342
|
var IV_LENGTH = 16;
|
|
14224
14343
|
var TAG_LENGTH = 16;
|
|
14225
14344
|
var SALT = "jawere-agent-key-vault-2026";
|
|
14226
|
-
|
|
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();
|
|
14227
14364
|
const machineId = `${hostname()}-${userInfo().username}-jawere`;
|
|
14228
|
-
return scryptSync(machineId, SALT, 32);
|
|
14365
|
+
return scryptSync(machineId, Buffer.concat([Buffer.from(SALT), _pepper]), 32);
|
|
14229
14366
|
}
|
|
14230
|
-
function encrypt(plaintext) {
|
|
14231
|
-
const key = deriveKey();
|
|
14367
|
+
async function encrypt(plaintext) {
|
|
14368
|
+
const key = await deriveKey();
|
|
14232
14369
|
const iv = randomBytes(IV_LENGTH);
|
|
14233
14370
|
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
14234
14371
|
const encrypted = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
|
|
14235
14372
|
const tag = cipher.getAuthTag();
|
|
14236
14373
|
return Buffer.concat([iv, tag, encrypted]).toString("base64");
|
|
14237
14374
|
}
|
|
14238
|
-
function decrypt(encoded) {
|
|
14375
|
+
async function decrypt(encoded) {
|
|
14239
14376
|
try {
|
|
14240
|
-
const key = deriveKey();
|
|
14377
|
+
const key = await deriveKey();
|
|
14241
14378
|
const buf = Buffer.from(encoded, "base64");
|
|
14242
14379
|
const iv = buf.subarray(0, IV_LENGTH);
|
|
14243
14380
|
const tag = buf.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
|
|
@@ -14250,33 +14387,55 @@ function decrypt(encoded) {
|
|
|
14250
14387
|
}
|
|
14251
14388
|
}
|
|
14252
14389
|
var KEY_FILE = join(homedir(), ".jawere", "key.enc");
|
|
14253
|
-
|
|
14390
|
+
var CONFIG_FILE = join(homedir(), ".jawere", "config.json");
|
|
14391
|
+
async function saveConfig(config) {
|
|
14254
14392
|
const dir = join(homedir(), ".jawere");
|
|
14255
14393
|
await mkdir2(dir, { recursive: true });
|
|
14256
|
-
|
|
14257
|
-
|
|
14394
|
+
await writeFile2(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
|
|
14395
|
+
try {
|
|
14396
|
+
await chmod(CONFIG_FILE, 384);
|
|
14397
|
+
} catch {
|
|
14398
|
+
}
|
|
14258
14399
|
}
|
|
14259
|
-
async function
|
|
14400
|
+
async function loadSavedConfig() {
|
|
14260
14401
|
try {
|
|
14261
|
-
await access2(
|
|
14262
|
-
const
|
|
14263
|
-
return
|
|
14402
|
+
await access2(CONFIG_FILE, constants2.R_OK);
|
|
14403
|
+
const raw = await readFile2(CONFIG_FILE, "utf-8");
|
|
14404
|
+
return JSON.parse(raw);
|
|
14264
14405
|
} catch {
|
|
14265
14406
|
return null;
|
|
14266
14407
|
}
|
|
14267
14408
|
}
|
|
14268
|
-
async function
|
|
14409
|
+
async function saveKey(apiKey) {
|
|
14410
|
+
const dir = join(homedir(), ".jawere");
|
|
14411
|
+
await mkdir2(dir, { recursive: true });
|
|
14412
|
+
const encrypted = await encrypt(apiKey.trim());
|
|
14413
|
+
await writeFile2(KEY_FILE, encrypted, "utf-8");
|
|
14414
|
+
try {
|
|
14415
|
+
await chmod(KEY_FILE, 384);
|
|
14416
|
+
} catch {
|
|
14417
|
+
}
|
|
14418
|
+
try {
|
|
14419
|
+
await chmod(dir, 448);
|
|
14420
|
+
} catch {
|
|
14421
|
+
}
|
|
14422
|
+
}
|
|
14423
|
+
async function loadKey() {
|
|
14269
14424
|
try {
|
|
14270
14425
|
await access2(KEY_FILE, constants2.R_OK);
|
|
14271
|
-
|
|
14426
|
+
const encrypted = await readFile2(KEY_FILE, "utf-8");
|
|
14427
|
+
return await decrypt(encrypted.trim());
|
|
14272
14428
|
} catch {
|
|
14273
|
-
return
|
|
14429
|
+
return null;
|
|
14274
14430
|
}
|
|
14275
14431
|
}
|
|
14276
14432
|
|
|
14277
14433
|
// src/config.ts
|
|
14278
|
-
var
|
|
14279
|
-
|
|
14434
|
+
var PROVIDER_DEFAULTS = {
|
|
14435
|
+
deepseek: { baseURL: "https://api.deepseek.com/v1", model: "deepseek-v4-pro" },
|
|
14436
|
+
openai: { baseURL: "https://api.openai.com/v1", model: "gpt-4o" },
|
|
14437
|
+
custom: { baseURL: "https://api.openai.com/v1", model: "gpt-4o" }
|
|
14438
|
+
};
|
|
14280
14439
|
function isDevMode() {
|
|
14281
14440
|
if (process.env.NODE_ENV === "production") return false;
|
|
14282
14441
|
if (process.env.NODE_ENV === "development") return true;
|
|
@@ -14298,14 +14457,27 @@ async function loadConfig() {
|
|
|
14298
14457
|
keyFromFile = true;
|
|
14299
14458
|
} else if (process.env.DEEPSEEK_API_KEY) {
|
|
14300
14459
|
apiKey = process.env.DEEPSEEK_API_KEY;
|
|
14460
|
+
} else if (process.env.OPENAI_API_KEY) {
|
|
14461
|
+
apiKey = process.env.OPENAI_API_KEY;
|
|
14462
|
+
} else if (process.env.AI_API_KEY) {
|
|
14463
|
+
apiKey = process.env.AI_API_KEY;
|
|
14301
14464
|
}
|
|
14465
|
+
let savedConfig = null;
|
|
14466
|
+
try {
|
|
14467
|
+
savedConfig = await loadSavedConfig();
|
|
14468
|
+
} catch {
|
|
14469
|
+
}
|
|
14470
|
+
const provider = process.env.AI_PROVIDER || savedConfig?.provider || "deepseek";
|
|
14471
|
+
const defaults2 = PROVIDER_DEFAULTS[provider] || PROVIDER_DEFAULTS.deepseek;
|
|
14472
|
+
const baseURL = process.env.AI_BASE_URL || savedConfig?.baseURL || defaults2.baseURL;
|
|
14473
|
+
const model = process.env.AI_MODEL || savedConfig?.model || defaults2.model;
|
|
14302
14474
|
const isDev = isDevMode();
|
|
14303
14475
|
cachedConfig = {
|
|
14304
|
-
baseURL
|
|
14476
|
+
baseURL,
|
|
14305
14477
|
apiKey,
|
|
14306
|
-
model
|
|
14478
|
+
model,
|
|
14479
|
+
provider,
|
|
14307
14480
|
workDir: process.env.WORK_DIR || process.cwd(),
|
|
14308
|
-
convexUrl: process.env.CONVEX_URL || (isDev ? CONVEX_DEV : CONVEX_PROD),
|
|
14309
14481
|
keyFromFile,
|
|
14310
14482
|
isDev
|
|
14311
14483
|
};
|
|
@@ -14568,93 +14740,6 @@ function createSpinner() {
|
|
|
14568
14740
|
return self;
|
|
14569
14741
|
}
|
|
14570
14742
|
|
|
14571
|
-
// src/convex-client.ts
|
|
14572
|
-
async function convexMutation(convexUrl, name, args) {
|
|
14573
|
-
const res = await fetch(`${convexUrl}/api/mutation`, {
|
|
14574
|
-
method: "POST",
|
|
14575
|
-
headers: { "content-type": "application/json" },
|
|
14576
|
-
body: JSON.stringify({ path: name, format: "json", args })
|
|
14577
|
-
});
|
|
14578
|
-
if (!res.ok) {
|
|
14579
|
-
const err = await res.text();
|
|
14580
|
-
throw new Error(`Convex mutation ${name} failed (${res.status}): ${err}`);
|
|
14581
|
-
}
|
|
14582
|
-
const json = await res.json();
|
|
14583
|
-
if (json.status === "error") {
|
|
14584
|
-
throw new Error(`Convex mutation ${name} error: ${json.errorMessage || JSON.stringify(json)}`);
|
|
14585
|
-
}
|
|
14586
|
-
return json.value;
|
|
14587
|
-
}
|
|
14588
|
-
async function convexQuery(convexUrl, name, args) {
|
|
14589
|
-
const res = await fetch(`${convexUrl}/api/query`, {
|
|
14590
|
-
method: "POST",
|
|
14591
|
-
headers: { "content-type": "application/json" },
|
|
14592
|
-
body: JSON.stringify({ path: name, format: "json", args })
|
|
14593
|
-
});
|
|
14594
|
-
if (!res.ok) {
|
|
14595
|
-
const err = await res.text();
|
|
14596
|
-
throw new Error(`Convex query ${name} failed (${res.status}): ${err}`);
|
|
14597
|
-
}
|
|
14598
|
-
const json = await res.json();
|
|
14599
|
-
if (json.status === "error") {
|
|
14600
|
-
throw new Error(`Convex query ${name} error: ${json.errorMessage || JSON.stringify(json)}`);
|
|
14601
|
-
}
|
|
14602
|
-
return json.value;
|
|
14603
|
-
}
|
|
14604
|
-
async function createSession(convexUrl, title, model, systemPrompt, toolNames) {
|
|
14605
|
-
return convexMutation(convexUrl, "sessions:create", {
|
|
14606
|
-
title,
|
|
14607
|
-
model,
|
|
14608
|
-
systemPrompt,
|
|
14609
|
-
toolNames
|
|
14610
|
-
});
|
|
14611
|
-
}
|
|
14612
|
-
async function appendUserMessage(convexUrl, sessionId, text) {
|
|
14613
|
-
return convexMutation(convexUrl, "sessions:appendMessage", {
|
|
14614
|
-
sessionId,
|
|
14615
|
-
role: "user",
|
|
14616
|
-
content: [{ type: "text", text }],
|
|
14617
|
-
timestamp: Date.now()
|
|
14618
|
-
});
|
|
14619
|
-
}
|
|
14620
|
-
async function appendAssistantMessage(convexUrl, sessionId, text, toolCalls, usage) {
|
|
14621
|
-
const content = [];
|
|
14622
|
-
if (text) {
|
|
14623
|
-
content.push({ type: "text", text });
|
|
14624
|
-
}
|
|
14625
|
-
if (toolCalls) {
|
|
14626
|
-
for (const tc of toolCalls) {
|
|
14627
|
-
content.push({
|
|
14628
|
-
type: "tool_call",
|
|
14629
|
-
id: tc.id,
|
|
14630
|
-
name: tc.name,
|
|
14631
|
-
arguments: tc.arguments
|
|
14632
|
-
});
|
|
14633
|
-
}
|
|
14634
|
-
}
|
|
14635
|
-
return convexMutation(convexUrl, "sessions:appendMessage", {
|
|
14636
|
-
sessionId,
|
|
14637
|
-
role: "assistant",
|
|
14638
|
-
content,
|
|
14639
|
-
timestamp: Date.now(),
|
|
14640
|
-
usage
|
|
14641
|
-
});
|
|
14642
|
-
}
|
|
14643
|
-
async function appendToolResult(convexUrl, sessionId, toolCallId, toolName, result, isError) {
|
|
14644
|
-
return convexMutation(convexUrl, "sessions:appendMessage", {
|
|
14645
|
-
sessionId,
|
|
14646
|
-
role: "toolResult",
|
|
14647
|
-
content: [{ type: "tool_result", toolCallId, content: result }],
|
|
14648
|
-
toolCallId,
|
|
14649
|
-
toolName,
|
|
14650
|
-
isError,
|
|
14651
|
-
timestamp: Date.now()
|
|
14652
|
-
});
|
|
14653
|
-
}
|
|
14654
|
-
async function listSessions(convexUrl) {
|
|
14655
|
-
return convexQuery(convexUrl, "sessions:list", {});
|
|
14656
|
-
}
|
|
14657
|
-
|
|
14658
14743
|
// src/agent.ts
|
|
14659
14744
|
var MAX_TURNS = 500;
|
|
14660
14745
|
var MAX_OUTPUT_TOKENS = 393216;
|
|
@@ -14714,13 +14799,6 @@ function displayToolLine(name, args, ok, spin) {
|
|
|
14714
14799
|
spin.start("Working\u2026");
|
|
14715
14800
|
}
|
|
14716
14801
|
}
|
|
14717
|
-
async function safeCall(fn, label) {
|
|
14718
|
-
try {
|
|
14719
|
-
return await fn();
|
|
14720
|
-
} catch {
|
|
14721
|
-
return void 0;
|
|
14722
|
-
}
|
|
14723
|
-
}
|
|
14724
14802
|
async function withRetry(fn, maxRetries = 3, baseDelay = 1e3) {
|
|
14725
14803
|
let lastErr;
|
|
14726
14804
|
for (let i2 = 0; i2 <= maxRetries; i2++) {
|
|
@@ -14817,33 +14895,14 @@ async function runAgent(userMessage, options = {}) {
|
|
|
14817
14895
|
const config = await loadConfig();
|
|
14818
14896
|
if (!config.apiKey) {
|
|
14819
14897
|
throw new Error(
|
|
14820
|
-
|
|
14898
|
+
'No API key configured. Run "jawere --setup" to configure your AI provider and key, or set AI_API_KEY env var.'
|
|
14821
14899
|
);
|
|
14822
14900
|
}
|
|
14823
14901
|
const client = new openai_default({
|
|
14824
14902
|
baseURL: config.baseURL,
|
|
14825
14903
|
apiKey: config.apiKey
|
|
14826
14904
|
});
|
|
14827
|
-
const
|
|
14828
|
-
let sessionId = options.sessionId || "local";
|
|
14829
|
-
const hasRealSession = sessionId !== "local";
|
|
14830
|
-
const isNewSession = !options.sessionId;
|
|
14831
|
-
if (isNewSession) {
|
|
14832
|
-
const created = await safeCall(
|
|
14833
|
-
() => createSession(
|
|
14834
|
-
config.convexUrl,
|
|
14835
|
-
options.title || userMessage.slice(0, 100),
|
|
14836
|
-
config.model,
|
|
14837
|
-
SYSTEM_PROMPT,
|
|
14838
|
-
toolNames
|
|
14839
|
-
),
|
|
14840
|
-
"createSession"
|
|
14841
|
-
);
|
|
14842
|
-
if (created) sessionId = created;
|
|
14843
|
-
}
|
|
14844
|
-
if (hasRealSession || sessionId !== "local") {
|
|
14845
|
-
safeCall(() => appendUserMessage(config.convexUrl, sessionId, userMessage), "appendUserMessage");
|
|
14846
|
-
}
|
|
14905
|
+
const sessionId = options.sessionId || "local";
|
|
14847
14906
|
const messages = [
|
|
14848
14907
|
{ role: "system", content: SYSTEM_PROMPT }
|
|
14849
14908
|
];
|
|
@@ -14933,21 +14992,6 @@ async function runAgent(userMessage, options = {}) {
|
|
|
14933
14992
|
const hasToolCalls = streamedToolCalls.size > 0;
|
|
14934
14993
|
if (hasToolCalls) {
|
|
14935
14994
|
const toolCallArray = Array.from(streamedToolCalls.entries()).sort(([a2], [b2]) => a2 - b2).map(([_2, tc]) => tc);
|
|
14936
|
-
const toolCallsMeta = toolCallArray.map((tc) => ({
|
|
14937
|
-
id: tc.id,
|
|
14938
|
-
name: tc.name,
|
|
14939
|
-
arguments: tc.arguments
|
|
14940
|
-
}));
|
|
14941
|
-
safeCall(
|
|
14942
|
-
() => appendAssistantMessage(
|
|
14943
|
-
config.convexUrl,
|
|
14944
|
-
sessionId,
|
|
14945
|
-
streamedContent || null,
|
|
14946
|
-
toolCallsMeta.length > 0 ? toolCallsMeta : null,
|
|
14947
|
-
streamUsage
|
|
14948
|
-
),
|
|
14949
|
-
"appendAssistantMessage"
|
|
14950
|
-
);
|
|
14951
14995
|
const openaiToolCalls = toolCallArray.map((tc) => ({
|
|
14952
14996
|
id: tc.id,
|
|
14953
14997
|
type: "function",
|
|
@@ -14985,34 +15029,17 @@ async function runAgent(userMessage, options = {}) {
|
|
|
14985
15029
|
tool_call_id: result.tool_call_id,
|
|
14986
15030
|
content: result.content
|
|
14987
15031
|
});
|
|
14988
|
-
const isError = !ok;
|
|
14989
|
-
safeCall(
|
|
14990
|
-
() => appendToolResult(
|
|
14991
|
-
config.convexUrl,
|
|
14992
|
-
sessionId,
|
|
14993
|
-
result.tool_call_id,
|
|
14994
|
-
tc.name,
|
|
14995
|
-
result.content,
|
|
14996
|
-
isError
|
|
14997
|
-
),
|
|
14998
|
-
"appendToolResult"
|
|
14999
|
-
);
|
|
15000
15032
|
}
|
|
15001
15033
|
continue;
|
|
15002
15034
|
}
|
|
15003
15035
|
spin.stop();
|
|
15004
15036
|
const text = stripThinking(streamedContent) || "(empty response)";
|
|
15005
|
-
safeCall(
|
|
15006
|
-
() => appendAssistantMessage(config.convexUrl, sessionId, text, null, streamUsage),
|
|
15007
|
-
"appendAssistantMessage"
|
|
15008
|
-
);
|
|
15009
15037
|
console.log("");
|
|
15010
15038
|
printAssistantResponse(text);
|
|
15011
15039
|
return { text, sessionId, history: messages };
|
|
15012
15040
|
}
|
|
15013
15041
|
spin.stop();
|
|
15014
15042
|
const msg = "Agent reached maximum turns without completing the task.";
|
|
15015
|
-
safeCall(() => appendAssistantMessage(config.convexUrl, sessionId, msg, null), "appendAssistantMessage");
|
|
15016
15043
|
return { text: msg, sessionId, history: messages };
|
|
15017
15044
|
}
|
|
15018
15045
|
|
|
@@ -15711,6 +15738,7 @@ function createPrompt() {
|
|
|
15711
15738
|
// src/index.ts
|
|
15712
15739
|
var G_GREEN = "\x1B[38;2;184;187;3m";
|
|
15713
15740
|
var G_GRAY2 = "\x1B[38;2;146;131;116m";
|
|
15741
|
+
var G_AQUA = "\x1B[38;2;142;192;124m";
|
|
15714
15742
|
var R3 = "\x1B[0m";
|
|
15715
15743
|
function center(text, width) {
|
|
15716
15744
|
const visible = text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
@@ -15721,7 +15749,7 @@ function printBanner(config) {
|
|
|
15721
15749
|
const cols = process.stdout.columns || 60;
|
|
15722
15750
|
const sep = G_GRAY2 + "\u2500".repeat(Math.min(cols - 2, 50)) + R3;
|
|
15723
15751
|
console.log("");
|
|
15724
|
-
console.log(center(`${G_GRAY2}${config.model}${R3} ${G_GRAY2}${config.isDev ? "dev" : "prod"}${R3}`, cols));
|
|
15752
|
+
console.log(center(`${G_GRAY2}${config.provider} / ${config.model}${R3} ${G_GRAY2}${config.isDev ? "dev" : "prod"}${R3}`, cols));
|
|
15725
15753
|
console.log(center(sep, cols));
|
|
15726
15754
|
console.log("");
|
|
15727
15755
|
}
|
|
@@ -15729,57 +15757,71 @@ function printHelp() {
|
|
|
15729
15757
|
console.log(`
|
|
15730
15758
|
${G_GREEN}Commands:${R3}
|
|
15731
15759
|
${G_GRAY2}/help${R3} Show this help
|
|
15732
|
-
|
|
15733
|
-
${G_GRAY2}/load${R3} <num> Resume a session (run /sessions first)
|
|
15760
|
+
|
|
15734
15761
|
${G_GRAY2}/key${R3} Show API key status
|
|
15735
|
-
${G_GRAY2}/setup${R3} Re-
|
|
15762
|
+
${G_GRAY2}/setup${R3} Re-configure AI provider & key
|
|
15736
15763
|
${G_GRAY2}/clear${R3} Clear screen & start fresh session
|
|
15737
15764
|
${G_GRAY2}/exit${R3}, ${G_GRAY2}/quit${R3} Quit
|
|
15765
|
+
|
|
15766
|
+
${G_GREEN}CLI flags:${R3}
|
|
15767
|
+
${G_GRAY2}--setup${R3} Run setup wizard (provider, key, model)
|
|
15738
15768
|
`);
|
|
15739
15769
|
}
|
|
15740
15770
|
async function setupKey() {
|
|
15741
15771
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
15742
|
-
|
|
15743
|
-
|
|
15744
|
-
|
|
15772
|
+
const ask = (q2) => new Promise((resolve4) => rl.question(q2, (a2) => resolve4(a2.trim())));
|
|
15773
|
+
console.log("");
|
|
15774
|
+
console.log(`${G_GREEN}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557${R3}`);
|
|
15775
|
+
console.log(`${G_GREEN}\u2551 jawere \u2014 Setup \u2551${R3}`);
|
|
15776
|
+
console.log(`${G_GREEN}\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${R3}`);
|
|
15777
|
+
console.log("");
|
|
15778
|
+
console.log("Select AI provider:");
|
|
15779
|
+
console.log(` ${G_AQUA}1.${R3} DeepSeek`);
|
|
15780
|
+
console.log(` ${G_AQUA}2.${R3} OpenAI`);
|
|
15781
|
+
console.log(` ${G_AQUA}3.${R3} Custom (enter base URL)`);
|
|
15782
|
+
console.log("");
|
|
15783
|
+
const choice = await ask(`Choice [1]: `) || "1";
|
|
15784
|
+
let provider = "deepseek";
|
|
15785
|
+
let baseURL;
|
|
15786
|
+
let defaultModel;
|
|
15787
|
+
if (choice === "2") {
|
|
15788
|
+
provider = "openai";
|
|
15789
|
+
defaultModel = "gpt-4o";
|
|
15790
|
+
} else if (choice === "3") {
|
|
15791
|
+
provider = "custom";
|
|
15792
|
+
console.log("");
|
|
15793
|
+
baseURL = await ask("Base URL (e.g. https://api.openai.com/v1): ");
|
|
15794
|
+
if (!baseURL) {
|
|
15795
|
+
console.log("No URL entered. Aborting setup.");
|
|
15745
15796
|
rl.close();
|
|
15746
|
-
|
|
15747
|
-
}
|
|
15748
|
-
|
|
15797
|
+
return;
|
|
15798
|
+
}
|
|
15799
|
+
defaultModel = "gpt-4o";
|
|
15800
|
+
} else {
|
|
15801
|
+
provider = "deepseek";
|
|
15802
|
+
defaultModel = "deepseek-v4-pro";
|
|
15803
|
+
}
|
|
15804
|
+
console.log("");
|
|
15805
|
+
const keyHint = provider === "openai" ? "sk-" : provider === "deepseek" ? "sk-" : "";
|
|
15806
|
+
const hintText = keyHint ? ` (starts with ${keyHint})` : "";
|
|
15807
|
+
console.log(`Enter your API key${hintText}:`);
|
|
15808
|
+
const key = await ask("API Key: ");
|
|
15749
15809
|
if (!key) {
|
|
15750
15810
|
console.log("No key entered. Aborting setup.");
|
|
15811
|
+
rl.close();
|
|
15751
15812
|
return;
|
|
15752
15813
|
}
|
|
15753
|
-
|
|
15754
|
-
|
|
15755
|
-
}
|
|
15814
|
+
console.log("");
|
|
15815
|
+
const model = await ask(`Model [${defaultModel}]: `) || defaultModel;
|
|
15756
15816
|
await saveKey(key);
|
|
15757
|
-
|
|
15758
|
-
|
|
15759
|
-
|
|
15760
|
-
|
|
15761
|
-
|
|
15762
|
-
|
|
15763
|
-
|
|
15764
|
-
|
|
15765
|
-
return map;
|
|
15766
|
-
}
|
|
15767
|
-
console.log(`
|
|
15768
|
-
Recent sessions:`);
|
|
15769
|
-
let i2 = 1;
|
|
15770
|
-
for (const s2 of sessions) {
|
|
15771
|
-
const date = new Date(s2.updatedAt).toLocaleString();
|
|
15772
|
-
const shortId = s2._id.slice(0, 10);
|
|
15773
|
-
console.log(` ${i2}. ${shortId}\u2026 ${date} ${s2.title.slice(0, 50)}`);
|
|
15774
|
-
map.set(i2, s2._id);
|
|
15775
|
-
i2++;
|
|
15776
|
-
}
|
|
15777
|
-
console.log(`
|
|
15778
|
-
Use /load <number> to resume a session.`);
|
|
15779
|
-
} catch (err) {
|
|
15780
|
-
console.log(`Error fetching sessions: ${err.message}`);
|
|
15781
|
-
}
|
|
15782
|
-
return map;
|
|
15817
|
+
await saveConfig({ provider, baseURL: baseURL || void 0, model });
|
|
15818
|
+
console.log("");
|
|
15819
|
+
console.log(`${G_GREEN}Setup complete!${R3}`);
|
|
15820
|
+
console.log(` Provider : ${G_AQUA}${provider}${R3}`);
|
|
15821
|
+
console.log(` Model : ${G_AQUA}${model}${R3}`);
|
|
15822
|
+
console.log(` Key saved: ~/.jawere/key.enc`);
|
|
15823
|
+
console.log("");
|
|
15824
|
+
rl.close();
|
|
15783
15825
|
}
|
|
15784
15826
|
async function main() {
|
|
15785
15827
|
const setupMode = process.argv.includes("--setup");
|
|
@@ -15790,16 +15832,14 @@ async function main() {
|
|
|
15790
15832
|
process.exit(0);
|
|
15791
15833
|
}
|
|
15792
15834
|
if (!config.apiKey) {
|
|
15793
|
-
const hasExistingKey = await hasKey();
|
|
15794
15835
|
console.log("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
|
|
15795
|
-
console.log("\u2551
|
|
15836
|
+
console.log("\u2551 jawere \u2014 AI Coding Agent \u2551");
|
|
15796
15837
|
console.log("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563");
|
|
15797
15838
|
console.log("\u2551 No API key found. \u2551");
|
|
15798
15839
|
console.log("\u2551 \u2551");
|
|
15799
|
-
console.log("\u2551 Option 1: Set
|
|
15800
|
-
console.log("\u2551 Option 2: Run --setup
|
|
15840
|
+
console.log("\u2551 Option 1: Set AI_API_KEY env var \u2551");
|
|
15841
|
+
console.log("\u2551 Option 2: Run jawere --setup \u2551");
|
|
15801
15842
|
console.log("\u2551 \u2551");
|
|
15802
|
-
console.log("\u2551 npx tsx src/index.ts --setup \u2551");
|
|
15803
15843
|
console.log("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
|
|
15804
15844
|
process.exit(1);
|
|
15805
15845
|
}
|
|
@@ -15822,7 +15862,6 @@ async function main() {
|
|
|
15822
15862
|
}
|
|
15823
15863
|
let currentSessionId;
|
|
15824
15864
|
let conversationHistory = [];
|
|
15825
|
-
let sessionMap = /* @__PURE__ */ new Map();
|
|
15826
15865
|
let sessionShown = false;
|
|
15827
15866
|
try {
|
|
15828
15867
|
const codebaseDir = resolve3(config.workDir, ".codebase");
|
|
@@ -15871,35 +15910,12 @@ async function main() {
|
|
|
15871
15910
|
case "help":
|
|
15872
15911
|
printHelp();
|
|
15873
15912
|
break;
|
|
15874
|
-
case "sessions":
|
|
15875
|
-
sessionMap = await showSessions(config.convexUrl);
|
|
15876
|
-
break;
|
|
15877
|
-
case "load":
|
|
15878
|
-
if (arg) {
|
|
15879
|
-
const num = parseInt(arg, 10);
|
|
15880
|
-
if (!isNaN(num) && sessionMap.has(num)) {
|
|
15881
|
-
currentSessionId = sessionMap.get(num);
|
|
15882
|
-
conversationHistory = [];
|
|
15883
|
-
sessionShown = true;
|
|
15884
|
-
console.log(`Resumed session #${num}: ${currentSessionId?.slice(0, 12)}\u2026`);
|
|
15885
|
-
} else if (arg.length > 10) {
|
|
15886
|
-
currentSessionId = arg;
|
|
15887
|
-
conversationHistory = [];
|
|
15888
|
-
sessionShown = true;
|
|
15889
|
-
console.log(`Resumed session: ${arg.slice(0, 12)}\u2026`);
|
|
15890
|
-
} else {
|
|
15891
|
-
console.log(`Session #${arg} not found. Run /sessions first.`);
|
|
15892
|
-
}
|
|
15893
|
-
} else {
|
|
15894
|
-
console.log("Usage: /load <number> (run /sessions first)");
|
|
15895
|
-
}
|
|
15896
|
-
break;
|
|
15897
15913
|
case "key": {
|
|
15898
15914
|
const savedKey = await loadKey();
|
|
15899
15915
|
if (savedKey) {
|
|
15900
|
-
console.log(`Key status: saved (encrypted) \u2014 ${savedKey.slice(0,
|
|
15916
|
+
console.log(`Key status: saved (encrypted) \u2014 ${savedKey.slice(0, 3)}...`);
|
|
15901
15917
|
} else if (process.env.DEEPSEEK_API_KEY) {
|
|
15902
|
-
console.log(`Key status: DEEPSEEK_API_KEY env var`);
|
|
15918
|
+
console.log(`Key status: DEEPSEEK_API_KEY env var (starts ${process.env.DEEPSEEK_API_KEY.slice(0, 3)}...)`);
|
|
15903
15919
|
} else {
|
|
15904
15920
|
console.log(`Key status: NOT SET`);
|
|
15905
15921
|
}
|
package/package.json
CHANGED
|
@@ -1,28 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jawere",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
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
|
},
|