jawere 1.6.0 → 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.
Files changed (3) hide show
  1. package/README.md +8 -16
  2. package/dist/index.js +177 -230
  3. 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 seven filesystem tools
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 backed by Convex
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 seven filesystem tools:
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 fetch3(url, opts) {
2805
- if (!fetch3.Promise) {
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 = fetch3.Promise;
2809
- return new fetch3.Promise(function(resolve4, reject) {
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 (fetch3.isRedirect(res.statusCode)) {
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(fetch3(new Request3(locationURL, requestOpts)));
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
- fetch3.isRedirect = function(code) {
3034
+ fetch2.isRedirect = function(code) {
3035
3035
  return code === 301 || code === 302 || code === 303 || code === 307 || code === 308;
3036
3036
  };
3037
- fetch3.Promise = global.Promise;
3038
- module.exports = exports = fetch3;
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 fetch2 = void 0;
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
- fetch2 = shims.fetch;
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 ?? fetch2;
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
- const fullPath = resolve(workDir, path);
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
- const fullPath = resolve(workDir, path);
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
- const fullPath = resolve(workDir, path);
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
- const dir = resolve(workDir, path || ".");
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
- const base = resolve(workDir, searchPath || ".");
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
- const base = resolve(workDir, searchPath || ".");
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
- const fullPath = resolve(workDir, path);
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
- function deriveKey() {
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);
@@ -14253,14 +14390,22 @@ var KEY_FILE = join(homedir(), ".jawere", "key.enc");
14253
14390
  async function saveKey(apiKey) {
14254
14391
  const dir = join(homedir(), ".jawere");
14255
14392
  await mkdir2(dir, { recursive: true });
14256
- const encrypted = encrypt(apiKey.trim());
14393
+ const encrypted = await encrypt(apiKey.trim());
14257
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
+ }
14258
14403
  }
14259
14404
  async function loadKey() {
14260
14405
  try {
14261
14406
  await access2(KEY_FILE, constants2.R_OK);
14262
14407
  const encrypted = await readFile2(KEY_FILE, "utf-8");
14263
- return decrypt(encrypted.trim());
14408
+ return await decrypt(encrypted.trim());
14264
14409
  } catch {
14265
14410
  return null;
14266
14411
  }
@@ -14275,8 +14420,6 @@ async function hasKey() {
14275
14420
  }
14276
14421
 
14277
14422
  // src/config.ts
14278
- var CONVEX_DEV = "https://dazzling-jackal-33.convex.cloud";
14279
- var CONVEX_PROD = "https://friendly-pigeon-624.convex.cloud";
14280
14423
  function isDevMode() {
14281
14424
  if (process.env.NODE_ENV === "production") return false;
14282
14425
  if (process.env.NODE_ENV === "development") return true;
@@ -14305,7 +14448,6 @@ async function loadConfig() {
14305
14448
  apiKey,
14306
14449
  model: process.env.DEEPSEEK_MODEL || "deepseek-v4-pro",
14307
14450
  workDir: process.env.WORK_DIR || process.cwd(),
14308
- convexUrl: process.env.CONVEX_URL || (isDev ? CONVEX_DEV : CONVEX_PROD),
14309
14451
  keyFromFile,
14310
14452
  isDev
14311
14453
  };
@@ -14568,93 +14710,6 @@ function createSpinner() {
14568
14710
  return self;
14569
14711
  }
14570
14712
 
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
14713
  // src/agent.ts
14659
14714
  var MAX_TURNS = 500;
14660
14715
  var MAX_OUTPUT_TOKENS = 393216;
@@ -14714,13 +14769,6 @@ function displayToolLine(name, args, ok, spin) {
14714
14769
  spin.start("Working\u2026");
14715
14770
  }
14716
14771
  }
14717
- async function safeCall(fn, label) {
14718
- try {
14719
- return await fn();
14720
- } catch {
14721
- return void 0;
14722
- }
14723
- }
14724
14772
  async function withRetry(fn, maxRetries = 3, baseDelay = 1e3) {
14725
14773
  let lastErr;
14726
14774
  for (let i2 = 0; i2 <= maxRetries; i2++) {
@@ -14824,26 +14872,7 @@ async function runAgent(userMessage, options = {}) {
14824
14872
  baseURL: config.baseURL,
14825
14873
  apiKey: config.apiKey
14826
14874
  });
14827
- const toolNames = TOOL_DEFS.map((t2) => t2.function.name);
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
- }
14875
+ const sessionId = options.sessionId || "local";
14847
14876
  const messages = [
14848
14877
  { role: "system", content: SYSTEM_PROMPT }
14849
14878
  ];
@@ -14933,21 +14962,6 @@ async function runAgent(userMessage, options = {}) {
14933
14962
  const hasToolCalls = streamedToolCalls.size > 0;
14934
14963
  if (hasToolCalls) {
14935
14964
  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
14965
  const openaiToolCalls = toolCallArray.map((tc) => ({
14952
14966
  id: tc.id,
14953
14967
  type: "function",
@@ -14985,34 +14999,17 @@ async function runAgent(userMessage, options = {}) {
14985
14999
  tool_call_id: result.tool_call_id,
14986
15000
  content: result.content
14987
15001
  });
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
15002
  }
15001
15003
  continue;
15002
15004
  }
15003
15005
  spin.stop();
15004
15006
  const text = stripThinking(streamedContent) || "(empty response)";
15005
- safeCall(
15006
- () => appendAssistantMessage(config.convexUrl, sessionId, text, null, streamUsage),
15007
- "appendAssistantMessage"
15008
- );
15009
15007
  console.log("");
15010
15008
  printAssistantResponse(text);
15011
15009
  return { text, sessionId, history: messages };
15012
15010
  }
15013
15011
  spin.stop();
15014
15012
  const msg = "Agent reached maximum turns without completing the task.";
15015
- safeCall(() => appendAssistantMessage(config.convexUrl, sessionId, msg, null), "appendAssistantMessage");
15016
15013
  return { text: msg, sessionId, history: messages };
15017
15014
  }
15018
15015
 
@@ -15729,8 +15726,7 @@ function printHelp() {
15729
15726
  console.log(`
15730
15727
  ${G_GREEN}Commands:${R3}
15731
15728
  ${G_GRAY2}/help${R3} Show this help
15732
- ${G_GRAY2}/sessions${R3} List recent Convex sessions
15733
- ${G_GRAY2}/load${R3} <num> Resume a session (run /sessions first)
15729
+
15734
15730
  ${G_GRAY2}/key${R3} Show API key status
15735
15731
  ${G_GRAY2}/setup${R3} Re-enter API key
15736
15732
  ${G_GRAY2}/clear${R3} Clear screen & start fresh session
@@ -15756,31 +15752,6 @@ async function setupKey() {
15756
15752
  await saveKey(key);
15757
15753
  console.log(`API key encrypted and saved to ~/.jawere/key.enc`);
15758
15754
  }
15759
- async function showSessions(convexUrl) {
15760
- const map = /* @__PURE__ */ new Map();
15761
- try {
15762
- const sessions = await listSessions(convexUrl);
15763
- if (sessions.length === 0) {
15764
- console.log("No sessions found.");
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;
15783
- }
15784
15755
  async function main() {
15785
15756
  const setupMode = process.argv.includes("--setup");
15786
15757
  const config = await loadConfig();
@@ -15822,7 +15793,6 @@ async function main() {
15822
15793
  }
15823
15794
  let currentSessionId;
15824
15795
  let conversationHistory = [];
15825
- let sessionMap = /* @__PURE__ */ new Map();
15826
15796
  let sessionShown = false;
15827
15797
  try {
15828
15798
  const codebaseDir = resolve3(config.workDir, ".codebase");
@@ -15871,35 +15841,12 @@ async function main() {
15871
15841
  case "help":
15872
15842
  printHelp();
15873
15843
  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
15844
  case "key": {
15898
15845
  const savedKey = await loadKey();
15899
15846
  if (savedKey) {
15900
- console.log(`Key status: saved (encrypted) \u2014 ${savedKey.slice(0, 8)}...`);
15847
+ console.log(`Key status: saved (encrypted) \u2014 ${savedKey.slice(0, 3)}...`);
15901
15848
  } else if (process.env.DEEPSEEK_API_KEY) {
15902
- 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)}...)`);
15903
15850
  } else {
15904
15851
  console.log(`Key status: NOT SET`);
15905
15852
  }
package/package.json CHANGED
@@ -1,28 +1,21 @@
1
1
  {
2
2
  "name": "jawere",
3
- "version": "1.6.0",
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
  },