jawere 1.4.0 → 1.5.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/dist/index.js CHANGED
@@ -13339,6 +13339,30 @@ var TOOL_DEFS = [
13339
13339
  }
13340
13340
  }
13341
13341
  },
13342
+ {
13343
+ type: "function",
13344
+ function: {
13345
+ name: "diff",
13346
+ description: "Show a git diff of changes. Useful for reviewing what changed before committing. Supports --staged for staged changes, and optional path/file and base ref.",
13347
+ parameters: {
13348
+ type: "object",
13349
+ properties: {
13350
+ path: {
13351
+ type: "string",
13352
+ description: "Specific file or directory to diff (optional \u2014 diffs everything if omitted)"
13353
+ },
13354
+ staged: {
13355
+ type: "boolean",
13356
+ description: "Show staged changes (git diff --staged). Default: false (working tree)"
13357
+ },
13358
+ base: {
13359
+ type: "string",
13360
+ description: "Base ref to diff against (e.g. HEAD~1, main). Default: HEAD for staged, working tree otherwise"
13361
+ }
13362
+ }
13363
+ }
13364
+ }
13365
+ },
13342
13366
  {
13343
13367
  type: "function",
13344
13368
  function: {
@@ -13757,6 +13781,14 @@ async function statTool(path, workDir) {
13757
13781
  return `Error: [stat-failed] ${fullPath}: ${err.message}`;
13758
13782
  }
13759
13783
  }
13784
+ async function diffTool(path, staged, base, workDir) {
13785
+ const args = ["diff"];
13786
+ if (staged) args.push("--staged");
13787
+ if (base) args.push(base);
13788
+ if (path) args.push("--", path);
13789
+ else args.push("--", ".");
13790
+ return execBash(`git ${args.join(" ")}`, workDir, 30);
13791
+ }
13760
13792
  async function executeTool(call, workDir) {
13761
13793
  const { id, function: fn } = call;
13762
13794
  let args;
@@ -13799,6 +13831,14 @@ async function executeTool(call, workDir) {
13799
13831
  case "find":
13800
13832
  result = await findTool(args.pattern, args.path, workDir);
13801
13833
  break;
13834
+ case "diff":
13835
+ result = await diffTool(
13836
+ args.path,
13837
+ args.staged,
13838
+ args.base,
13839
+ workDir
13840
+ );
13841
+ break;
13802
13842
  case "grep":
13803
13843
  result = await grepTool(
13804
13844
  args.pattern,
@@ -13891,8 +13931,10 @@ function isDevMode() {
13891
13931
  if (process.env.NODE_ENV === "production") return false;
13892
13932
  if (process.env.NODE_ENV === "development") return true;
13893
13933
  const main2 = process.argv[1] || "";
13934
+ if (main2.includes("node_modules")) return false;
13935
+ if (main2.endsWith("bin/jawere.js")) return false;
13936
+ if (main2.includes("/dist/")) return false;
13894
13937
  if (main2.endsWith(".ts") || main2.includes("/src/")) return true;
13895
- if (main2.includes("/node_modules/") || main2.includes("/.local/lib/node_modules/")) return false;
13896
13938
  return true;
13897
13939
  }
13898
13940
  var cachedConfig = null;
@@ -14265,13 +14307,14 @@ async function listSessions(convexUrl) {
14265
14307
 
14266
14308
  // src/agent.ts
14267
14309
  var MAX_TURNS = 500;
14268
- var MAX_OUTPUT_TOKENS = 3e5;
14310
+ var MAX_OUTPUT_TOKENS = 393216;
14269
14311
  var COL = () => process.stdout.columns || 80;
14270
14312
  var GRUVBOX_GREEN = "\x1B[38;2;184;187;3m";
14271
14313
  var GRUVBOX_GRAY = "\x1B[38;2;146;131;116m";
14272
14314
  var GRUVBOX_RED = "\x1B[38;2;251;73;52m";
14273
14315
  var RESET2 = "\x1B[0m";
14274
14316
  var FILE_TOOLS = /* @__PURE__ */ new Set(["read", "write", "edit", "stat"]);
14317
+ var SILENT_TOOLS = /* @__PURE__ */ new Set(["diff"]);
14275
14318
  function toolDetail(name, args) {
14276
14319
  switch (name) {
14277
14320
  case "read": {
@@ -14296,6 +14339,7 @@ function toolDetail(name, args) {
14296
14339
  }
14297
14340
  }
14298
14341
  function displayToolLine(name, args, ok, spin) {
14342
+ if (SILENT_TOOLS.has(name)) return;
14299
14343
  if (spin?.running) {
14300
14344
  spin.stop();
14301
14345
  }
@@ -14463,86 +14507,120 @@ async function runAgent(userMessage, options = {}) {
14463
14507
  return { text: msg2, sessionId, history: messages };
14464
14508
  }
14465
14509
  spin.update("Thinking\u2026");
14466
- const response = await withRetry(() => {
14467
- return client.chat.completions.create({
14468
- model: config.model,
14469
- messages,
14470
- tools: TOOL_DEFS,
14471
- tool_choice: "auto",
14472
- temperature: 0.2,
14473
- max_tokens: MAX_OUTPUT_TOKENS,
14474
- // Enable thinking/reasoning — DeepSeek specific params
14475
- ...{
14476
- thinking: { type: "enabled" },
14477
- reasoning_effort: "max"
14478
- }
14510
+ let streamedContent = "";
14511
+ const streamedToolCalls = /* @__PURE__ */ new Map();
14512
+ let streamUsage;
14513
+ try {
14514
+ const stream = await withRetry(async () => {
14515
+ return client.chat.completions.create({
14516
+ model: config.model,
14517
+ messages,
14518
+ tools: TOOL_DEFS,
14519
+ tool_choice: "auto",
14520
+ temperature: 0.2,
14521
+ max_tokens: MAX_OUTPUT_TOKENS,
14522
+ stream: true,
14523
+ stream_options: { include_usage: true },
14524
+ // Enable thinking/reasoning — DeepSeek specific params
14525
+ ...{
14526
+ thinking: { type: "enabled" },
14527
+ reasoning_effort: "max"
14528
+ }
14529
+ });
14479
14530
  });
14480
- }).catch((err) => {
14531
+ for await (const chunk of stream) {
14532
+ if (options.signal?.aborted) break;
14533
+ const delta = chunk.choices?.[0]?.delta;
14534
+ if (!delta) {
14535
+ if (chunk.usage) {
14536
+ streamUsage = {
14537
+ input: chunk.usage.prompt_tokens || 0,
14538
+ output: chunk.usage.completion_tokens || 0,
14539
+ total: chunk.usage.total_tokens || 0
14540
+ };
14541
+ }
14542
+ continue;
14543
+ }
14544
+ if (delta.content) {
14545
+ streamedContent += delta.content;
14546
+ }
14547
+ if (delta.tool_calls) {
14548
+ for (const tc of delta.tool_calls) {
14549
+ const idx = tc.index ?? 0;
14550
+ if (!streamedToolCalls.has(idx)) {
14551
+ streamedToolCalls.set(idx, { id: "", name: "", arguments: "" });
14552
+ }
14553
+ const entry = streamedToolCalls.get(idx);
14554
+ if (tc.id) entry.id = tc.id;
14555
+ if (tc.function?.name) entry.name += tc.function.name;
14556
+ if (tc.function?.arguments) entry.arguments += tc.function.arguments;
14557
+ }
14558
+ }
14559
+ spin.update("Thinking\u2026");
14560
+ }
14561
+ } catch (err) {
14481
14562
  if (err.name === "AbortError" || err.name === "Canceled") {
14482
- return null;
14563
+ spin.stop();
14564
+ const msg2 = "\n[Cancelled by user]";
14565
+ return { text: msg2, sessionId, history: messages };
14483
14566
  }
14484
14567
  throw err;
14485
- });
14486
- if (!response) {
14568
+ }
14569
+ if (options.signal?.aborted) {
14487
14570
  spin.stop();
14488
14571
  const msg2 = "\n[Cancelled by user]";
14489
14572
  return { text: msg2, sessionId, history: messages };
14490
14573
  }
14491
- const choice = response.choices[0];
14492
- if (!choice) {
14493
- spin.stop();
14494
- safeCall(
14495
- () => appendAssistantMessage(config.convexUrl, sessionId, "(error: no response)", null),
14496
- "appendAssistantMessage"
14497
- );
14498
- return { text: "Error: No response from model.", sessionId, history: messages };
14499
- }
14500
- const { message } = choice;
14501
- const usage = response.usage ? {
14502
- input: response.usage.prompt_tokens || 0,
14503
- output: response.usage.completion_tokens || 0,
14504
- total: response.usage.total_tokens || 0
14505
- } : void 0;
14506
- if (message.tool_calls && message.tool_calls.length > 0) {
14507
- const toolCallsMeta = message.tool_calls.map((tc) => ({
14574
+ const hasToolCalls = streamedToolCalls.size > 0;
14575
+ if (hasToolCalls) {
14576
+ const toolCallArray = Array.from(streamedToolCalls.entries()).sort(([a2], [b2]) => a2 - b2).map(([_2, tc]) => tc);
14577
+ const toolCallsMeta = toolCallArray.map((tc) => ({
14508
14578
  id: tc.id,
14509
- name: tc.function.name,
14510
- arguments: tc.function.arguments
14579
+ name: tc.name,
14580
+ arguments: tc.arguments
14511
14581
  }));
14512
14582
  safeCall(
14513
14583
  () => appendAssistantMessage(
14514
14584
  config.convexUrl,
14515
14585
  sessionId,
14516
- message.content || null,
14586
+ streamedContent || null,
14517
14587
  toolCallsMeta.length > 0 ? toolCallsMeta : null,
14518
- usage
14588
+ streamUsage
14519
14589
  ),
14520
14590
  "appendAssistantMessage"
14521
14591
  );
14592
+ const openaiToolCalls = toolCallArray.map((tc) => ({
14593
+ id: tc.id,
14594
+ type: "function",
14595
+ function: {
14596
+ name: tc.name,
14597
+ arguments: tc.arguments
14598
+ }
14599
+ }));
14522
14600
  messages.push({
14523
14601
  role: "assistant",
14524
- content: message.content || null,
14525
- tool_calls: message.tool_calls
14602
+ content: streamedContent || null,
14603
+ tool_calls: openaiToolCalls
14526
14604
  });
14527
- for (const tc of message.tool_calls) {
14605
+ for (const tc of toolCallArray) {
14528
14606
  let args = {};
14529
14607
  try {
14530
- args = JSON.parse(tc.function.arguments);
14608
+ args = JSON.parse(tc.arguments);
14531
14609
  } catch {
14532
14610
  }
14533
- spin.update(`Running ${tc.function.name}\u2026`);
14611
+ spin.update(`Running ${tc.name}\u2026`);
14534
14612
  const result = await executeTool(
14535
14613
  {
14536
14614
  id: tc.id,
14537
14615
  function: {
14538
- name: tc.function.name,
14539
- arguments: tc.function.arguments
14616
+ name: tc.name,
14617
+ arguments: tc.arguments
14540
14618
  }
14541
14619
  },
14542
14620
  config.workDir
14543
14621
  );
14544
14622
  const ok = !result.content.startsWith("Error");
14545
- displayToolLine(tc.function.name, args, ok, spin);
14623
+ displayToolLine(tc.name, args, ok, spin);
14546
14624
  messages.push({
14547
14625
  role: "tool",
14548
14626
  tool_call_id: result.tool_call_id,
@@ -14554,7 +14632,7 @@ async function runAgent(userMessage, options = {}) {
14554
14632
  config.convexUrl,
14555
14633
  sessionId,
14556
14634
  result.tool_call_id,
14557
- tc.function.name,
14635
+ tc.name,
14558
14636
  result.content,
14559
14637
  isError
14560
14638
  ),
@@ -14564,10 +14642,9 @@ async function runAgent(userMessage, options = {}) {
14564
14642
  continue;
14565
14643
  }
14566
14644
  spin.stop();
14567
- const rawText = message.content || "";
14568
- const text = stripThinking(rawText) || "(empty response)";
14645
+ const text = stripThinking(streamedContent) || "(empty response)";
14569
14646
  safeCall(
14570
- () => appendAssistantMessage(config.convexUrl, sessionId, text, null, usage),
14647
+ () => appendAssistantMessage(config.convexUrl, sessionId, text, null, streamUsage),
14571
14648
  "appendAssistantMessage"
14572
14649
  );
14573
14650
  console.log("");
@@ -14871,20 +14948,32 @@ function getProjectInfo(workDir) {
14871
14948
  }
14872
14949
  return { name, version };
14873
14950
  }
14874
- async function scanCodebase(workDir) {
14951
+ async function scanCodebase(workDir, onProgress) {
14875
14952
  const codebaseDir = resolve2(workDir, CODEBASE_DIR);
14876
14953
  await mkdir3(codebaseDir, { recursive: true });
14954
+ onProgress?.("listing files");
14877
14955
  const allFiles = await getAllFiles(workDir, workDir);
14878
14956
  const files = allFiles.sort();
14957
+ onProgress?.(`found ${files.length} files`);
14958
+ onProgress?.("building tree");
14879
14959
  const tree = buildTree(files);
14960
+ onProgress?.(`summarizing ${files.length} files`);
14880
14961
  const summaries = {};
14881
- for (const file of files) {
14882
- const summary = await summarizeFile(resolve2(workDir, file));
14962
+ let lastReport = 0;
14963
+ for (let i2 = 0; i2 < files.length; i2++) {
14964
+ const summary = await summarizeFile(resolve2(workDir, files[i2]));
14883
14965
  if (summary) {
14884
- summaries[file] = summary;
14966
+ summaries[files[i2]] = summary;
14967
+ }
14968
+ const now = Date.now();
14969
+ if (i2 - lastReport >= 10 || now - lastReport > 200) {
14970
+ onProgress?.(`summarizing ${i2 + 1}/${files.length}`);
14971
+ lastReport = i2;
14885
14972
  }
14886
14973
  }
14974
+ onProgress?.("reading project info");
14887
14975
  const { name, version } = getProjectInfo(workDir);
14976
+ onProgress?.("hashing files");
14888
14977
  const gitHash = await getGitHash(workDir);
14889
14978
  const checksums = {
14890
14979
  scannedAt: Date.now(),
@@ -14902,9 +14991,12 @@ async function scanCodebase(workDir) {
14902
14991
  }
14903
14992
  }
14904
14993
  }
14994
+ onProgress?.("writing checksums");
14905
14995
  await writeFile3(resolve2(workDir, CHECKSUMS_FILE), JSON.stringify(checksums, null, 2) + "\n", "utf-8");
14996
+ onProgress?.("writing tree.yaml");
14906
14997
  const treeYaml = generateTreeYaml(name, version, tree, summaries);
14907
14998
  await writeFile3(resolve2(workDir, TREE_FILE), treeYaml, "utf-8");
14999
+ onProgress?.("writing meta.json");
14908
15000
  const meta = {
14909
15001
  scannedAt: Date.now(),
14910
15002
  fileCount: files.length,
@@ -14992,11 +15084,18 @@ async function runScanner(workDir, force = false) {
14992
15084
  const meta = JSON.parse(
14993
15085
  await readFile3(resolve2(workDir, META_FILE), "utf-8")
14994
15086
  );
15087
+ process.stderr.write(` (cached)`);
14995
15088
  return { fileCount: meta.fileCount, cached: true };
14996
15089
  } catch {
14997
15090
  }
14998
15091
  }
14999
- const { fileCount } = await scanCodebase(workDir);
15092
+ const GRAY2 = "\x1B[38;2;146;131;116m";
15093
+ const RESET3 = "\x1B[0m";
15094
+ const { fileCount } = await scanCodebase(workDir, (phase, detail) => {
15095
+ const msg = detail || phase;
15096
+ process.stderr.write(`\r\x1B[K${GRAY2} scanning: ${msg}${RESET3}`);
15097
+ });
15098
+ process.stderr.write(`\r\x1B[K`);
15000
15099
  return { fileCount, cached: false };
15001
15100
  }
15002
15101
 
@@ -15351,10 +15450,10 @@ async function main() {
15351
15450
  try {
15352
15451
  const scanResult = await runScanner(config.workDir);
15353
15452
  if (scanResult.cached) {
15354
- process.stderr.write(` ${G_GRAY2}(cached, ${scanResult.fileCount} files)${R3}
15453
+ process.stderr.write(` ${G_GRAY2}(${scanResult.fileCount} files)${R3}
15355
15454
  `);
15356
15455
  } else {
15357
- process.stderr.write(` ${G_GRAY2}(${scanResult.fileCount} files)${R3}
15456
+ process.stderr.write(`${G_GRAY2}Done \u2014 ${scanResult.fileCount} files indexed${R3}
15358
15457
  `);
15359
15458
  }
15360
15459
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jawere",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "jawere": "bin/jawere.js"
package/dist/agent.d.ts DELETED
@@ -1,15 +0,0 @@
1
- import type { ChatCompletionMessageParam } from 'openai/resources/chat/completions';
2
- export interface AgentOptions {
3
- sessionId?: string;
4
- title?: string;
5
- history?: ChatCompletionMessageParam[];
6
- signal?: AbortSignal;
7
- }
8
- export interface AgentResult {
9
- text: string;
10
- sessionId: string;
11
- history: ChatCompletionMessageParam[];
12
- }
13
- /** Print the assistant's FINAL response — the summary shown when all work is done. */
14
- export declare function printAssistantResponse(text: string): void;
15
- export declare function runAgent(userMessage: string, options?: AgentOptions): Promise<AgentResult>;