@hasna/coders 0.1.2 → 0.1.3

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/cli.mjs CHANGED
@@ -70172,21 +70172,18 @@ function useSpinner(active) {
70172
70172
  }, [active]);
70173
70173
  return active ? FRAMES[i] : " ";
70174
70174
  }
70175
- function createToolHandlers(onToolStart, onToolEnd, requestPermission) {
70175
+ function createToolHandlers() {
70176
70176
  const tools = [bashTool, readTool, editTool, writeTool, globTool, grepTool];
70177
70177
  const permCtx = createDefaultPermissionContext();
70178
70178
  const appState = { toolPermissionContext: permCtx, verbose: false };
70179
- const alwaysAllowed = /* @__PURE__ */ new Set();
70180
70179
  return tools.map((tool) => ({
70181
70180
  name: tool.name,
70182
- description: typeof tool.description === "function" ? `Tool: ${tool.name}` : String(tool.description),
70183
- inputSchema: { type: "object", properties: {} },
70181
+ description: TOOL_DESCRIPTIONS[tool.name] ?? `Tool: ${tool.name}`,
70182
+ inputSchema: TOOL_JSON_SCHEMAS[tool.name] ?? { type: "object", properties: {} },
70184
70183
  isReadOnly: tool.isReadOnly(),
70185
70184
  isConcurrencySafe: tool.isConcurrencySafe(),
70186
70185
  call: async (input, ctx) => {
70187
70186
  const summary = toolSummary(tool.name, input);
70188
- const toolId = `tool-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
70189
- onToolStart(toolId, tool.name, summary);
70190
70187
  const t0 = performance.now();
70191
70188
  try {
70192
70189
  const toolCtx = {
@@ -70196,18 +70193,16 @@ function createToolHandlers(onToolStart, onToolEnd, requestPermission) {
70196
70193
  options: { mainLoopModel: "sonnet", thinkingConfig: { type: "disabled" }, isNonInteractiveSession: false, tools: [], agentDefinitions: { activeAgents: [] } }
70197
70194
  };
70198
70195
  const result = await tool.call(input, toolCtx);
70199
- const block2 = tool.mapToolResultToToolResultBlockParam(result.data, toolId);
70196
+ const block2 = tool.mapToolResultToToolResultBlockParam(result.data, ctx.toolUseId ?? "");
70200
70197
  const dur = performance.now() - t0;
70201
- onToolEnd(toolId, block2.content.slice(0, 500), block2.is_error ? block2.content : void 0, dur);
70202
70198
  try {
70203
70199
  dbRun("INSERT INTO audit_log (tool_name, input_summary, result_summary, duration_ms, was_allowed) VALUES (?, ?, ?, ?, 1)", [tool.name, summary, block2.content.slice(0, 200), dur]);
70204
70200
  } catch {
70205
70201
  }
70206
- return { data: block2.content };
70202
+ return block2.is_error ? { data: block2.content, error: block2.content, isError: true } : { data: block2.content };
70207
70203
  } catch (err) {
70208
70204
  const dur = performance.now() - t0;
70209
70205
  const errMsg = err instanceof Error ? err.message : String(err);
70210
- onToolEnd(toolId, "", errMsg, dur);
70211
70206
  try {
70212
70207
  dbRun("INSERT INTO audit_log (tool_name, input_summary, result_summary, duration_ms, was_allowed) VALUES (?, ?, ?, ?, 1)", [tool.name, summary, errMsg.slice(0, 200), dur]);
70213
70208
  } catch {
@@ -70390,11 +70385,23 @@ function App2({ model, mode, initialPrompt }) {
70390
70385
  setActiveTools((prev) => [...prev, { id, name, summary, status: "running" }]);
70391
70386
  }, []);
70392
70387
  const onToolEnd = (0, import_react22.useCallback)((id, result, error, durationMs) => {
70393
- setActiveTools(
70394
- (prev) => prev.map(
70395
- (t) => t.id === id ? { ...t, status: error ? "error" : "done", result, error, durationMs } : t
70396
- )
70397
- );
70388
+ const tool = activeToolsRef.current.find((t) => t.id === id);
70389
+ if (!tool) return;
70390
+ const completed = {
70391
+ ...tool,
70392
+ status: error ? "error" : "done",
70393
+ result,
70394
+ error,
70395
+ durationMs
70396
+ };
70397
+ setMsgs((prev) => [...prev, {
70398
+ id: `td-${id}`,
70399
+ role: "assistant",
70400
+ content: "",
70401
+ timestamp: Date.now(),
70402
+ tools: [completed]
70403
+ }]);
70404
+ setActiveTools((prev) => prev.filter((t) => t.id !== id));
70398
70405
  }, []);
70399
70406
  const submit = (0, import_react22.useCallback)(async (text) => {
70400
70407
  if (!text.trim() || busy) return;
@@ -70416,8 +70423,9 @@ function App2({ model, mode, initialPrompt }) {
70416
70423
  setActiveTools([]);
70417
70424
  const t0 = performance.now();
70418
70425
  try {
70419
- const toolHandlers = createToolHandlers(onToolStart, onToolEnd, requestPermission);
70426
+ const toolHandlers = createToolHandlers();
70420
70427
  const permCtx = createDefaultPermissionContext();
70428
+ const toolStartTimes = /* @__PURE__ */ new Map();
70421
70429
  const result = await runAgentLoop(
70422
70430
  newHistory,
70423
70431
  {
@@ -70434,7 +70442,7 @@ function App2({ model, mode, initialPrompt }) {
70434
70442
  permissionContext: permCtx,
70435
70443
  maxTurns: 10,
70436
70444
  onTextDelta: (text2) => {
70437
- if (activeToolsRef.current.length === 0 || activeToolsRef.current.every((t) => t.status === "done" || t.status === "error")) {
70445
+ if (activeToolsRef.current.length === 0) {
70438
70446
  setStreaming((prev) => prev + text2);
70439
70447
  }
70440
70448
  },
@@ -70442,11 +70450,14 @@ function App2({ model, mode, initialPrompt }) {
70442
70450
  },
70443
70451
  onToolUseStart: (name, id, toolInput) => {
70444
70452
  setStreaming("");
70453
+ toolStartTimes.set(id, performance.now());
70445
70454
  onToolStart(id, name, toolSummary(name, toolInput));
70446
70455
  },
70447
70456
  onToolUseEnd: (name, id, toolResult) => {
70448
70457
  const isErr = toolResult.isError || !!toolResult.error;
70449
- onToolEnd(id, String(toolResult.data ?? "").slice(0, 500), isErr ? toolResult.error : void 0);
70458
+ const startTime = toolStartTimes.get(id);
70459
+ const dur2 = startTime ? performance.now() - startTime : void 0;
70460
+ onToolEnd(id, String(toolResult.data ?? "").slice(0, 500), isErr ? toolResult.error : void 0, dur2);
70450
70461
  }
70451
70462
  }
70452
70463
  );
@@ -70466,19 +70477,27 @@ function App2({ model, mode, initialPrompt }) {
70466
70477
  );
70467
70478
  setCost((p) => p + c.totalCostUsd);
70468
70479
  setTokens((p) => p + result.usage.totalInputTokens + result.usage.totalOutputTokens);
70469
- const frozenTools = [...activeToolsRef.current].map(
70470
- (t) => t.status === "running" ? { ...t, status: "done" } : t
70471
- );
70472
70480
  const verb = VERBS[Math.floor(Math.random() * VERBS.length)];
70473
- setMsgs((p) => [...p, {
70474
- id: `a${Date.now()}`,
70475
- role: "assistant",
70476
- content: finalText || streaming || "(no response)",
70477
- timestamp: Date.now(),
70478
- tools: frozenTools.length > 0 ? frozenTools : void 0,
70479
- durationMs: dur,
70480
- durationVerb: verb
70481
- }]);
70481
+ const textContent = finalText || streaming || "";
70482
+ if (textContent && textContent !== "(no response)") {
70483
+ setMsgs((p) => [...p, {
70484
+ id: `a${Date.now()}`,
70485
+ role: "assistant",
70486
+ content: textContent,
70487
+ timestamp: Date.now(),
70488
+ durationMs: dur,
70489
+ durationVerb: verb
70490
+ }]);
70491
+ } else if (dur > 1e3) {
70492
+ setMsgs((p) => [...p, {
70493
+ id: `a${Date.now()}`,
70494
+ role: "assistant",
70495
+ content: "",
70496
+ timestamp: Date.now(),
70497
+ durationMs: dur,
70498
+ durationVerb: verb
70499
+ }]);
70500
+ }
70482
70501
  setHistory(result.messages.filter((m) => m.role === "user" || m.role === "assistant"));
70483
70502
  setStreaming("");
70484
70503
  setActiveTools([]);
@@ -70488,7 +70507,7 @@ function App2({ model, mode, initialPrompt }) {
70488
70507
  } finally {
70489
70508
  setBusy(false);
70490
70509
  }
70491
- }, [busy, history, model, exit, onToolStart, onToolEnd, requestPermission, streaming, activeTools]);
70510
+ }, [busy, history, model, exit, onToolStart, onToolEnd, streaming, activeTools]);
70492
70511
  (0, import_react22.useEffect)(() => {
70493
70512
  if (initialPrompt) submit(initialPrompt);
70494
70513
  }, []);
@@ -70541,47 +70560,49 @@ function App2({ model, mode, initialPrompt }) {
70541
70560
  } else if (key.escape) setInput("");
70542
70561
  else if (!key.ctrl && !key.meta && ch) setInput((p) => p + ch);
70543
70562
  });
70544
- const recentTools = activeTools.slice(-2);
70545
70563
  const cols = stdout?.columns ?? 80;
70546
70564
  const sep = "\u2500".repeat(Math.min(cols, 120));
70565
+ const hasRunningTools = activeTools.some((t) => t.status === "running");
70547
70566
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
70548
70567
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Static, { items: msgs, children: (msg) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexDirection: "column", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageView, { msg }) }, msg.id) }),
70549
- busy && recentTools.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexDirection: "column", children: recentTools.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolItem, { tool: t }, t.id)) }),
70550
- busy && streaming && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
70551
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "green", children: "\u25CF " }),
70552
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: streaming.split("\n").filter((l) => l.trim()).slice(-3).join("\n").slice(-200) })
70553
- ] }),
70554
- busy && !streaming && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
70555
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SpinnerDot, {}),
70556
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { dimColor: true, children: [
70557
- " ",
70558
- recentTools.some((t) => t.status === "running") ? "Working" : "Thinking",
70559
- "..."
70560
- ] })
70561
- ] }),
70562
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { flexDirection: "column", children: [
70563
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { dimColor: true, children: sep }),
70564
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
70565
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { color: "cyan", bold: true, children: [
70566
- PROMPT,
70567
- " "
70568
- ] }),
70569
- input.startsWith("/") ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "magenta", children: input }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: input }),
70570
- !busy && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "gray", children: "\u258E" })
70568
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { flexDirection: "column", minHeight: rows, justifyContent: "flex-end", children: [
70569
+ busy && activeTools.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexDirection: "column", children: activeTools.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolItem, { tool: t }, t.id)) }),
70570
+ busy && streaming && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
70571
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "green", children: "\u25CF " }),
70572
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: streaming.split("\n").filter((l) => l.trim()).slice(-3).join("\n").slice(-200) })
70573
+ ] }),
70574
+ busy && !streaming && activeTools.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
70575
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SpinnerDot, {}),
70576
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { dimColor: true, children: " Thinking..." })
70571
70577
  ] }),
70572
- showSlashMenu && filteredCommands.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexDirection: "column", paddingLeft: 2, children: filteredCommands.map((cmd, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
70573
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: i === slashSelected ? "cyan" : void 0, bold: i === slashSelected, children: i === slashSelected ? "\u25B8 " : " " }),
70574
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { color: i === slashSelected ? "cyan" : "blue", children: [
70575
- "/",
70576
- cmd.name.padEnd(16)
70578
+ busy && hasRunningTools && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
70579
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SpinnerDot, {}),
70580
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { dimColor: true, children: " Working..." })
70581
+ ] }),
70582
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { flexDirection: "column", children: [
70583
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { dimColor: true, children: sep }),
70584
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
70585
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { color: "cyan", bold: true, children: [
70586
+ PROMPT,
70587
+ " "
70588
+ ] }),
70589
+ input.startsWith("/") ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "magenta", children: input }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: input }),
70590
+ !busy && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "gray", children: "\u258E" })
70577
70591
  ] }),
70578
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { dimColor: true, children: [
70579
- " ",
70580
- cmd.description
70581
- ] })
70582
- ] }, cmd.name)) }),
70583
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { dimColor: true, children: sep }),
70584
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusBar, { model, mode, cost, tokens })
70592
+ showSlashMenu && filteredCommands.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexDirection: "column", paddingLeft: 2, children: filteredCommands.map((cmd, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
70593
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: i === slashSelected ? "cyan" : void 0, bold: i === slashSelected, children: i === slashSelected ? "\u25B8 " : " " }),
70594
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { color: i === slashSelected ? "cyan" : "blue", children: [
70595
+ "/",
70596
+ cmd.name.padEnd(16)
70597
+ ] }),
70598
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { dimColor: true, children: [
70599
+ " ",
70600
+ cmd.description
70601
+ ] })
70602
+ ] }, cmd.name)) }),
70603
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { dimColor: true, children: sep }),
70604
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusBar, { model, mode, cost, tokens })
70605
+ ] })
70585
70606
  ] })
70586
70607
  ] });
70587
70608
  }
@@ -70602,6 +70623,8 @@ function launchInkApp(opts = {}) {
70602
70623
  };
70603
70624
  console.error = () => {
70604
70625
  };
70626
+ const termRows = process.stdout.rows ?? 24;
70627
+ process.stdout.write("\n".repeat(Math.max(termRows - 5, 0)));
70605
70628
  const { waitUntilExit } = render_default(
70606
70629
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(App2, { model, mode, initialPrompt: opts.initialPrompt }),
70607
70630
  { exitOnCtrlC: false }
@@ -70634,7 +70657,7 @@ function fmtTok(n) {
70634
70657
  if (n < 1e6) return `${(n / 1e3).toFixed(1)}K`;
70635
70658
  return `${(n / 1e6).toFixed(2)}M`;
70636
70659
  }
70637
- var import_react22, import_jsx_runtime, CONN, PROMPT, FRAMES, VERBS;
70660
+ var import_react22, import_jsx_runtime, CONN, PROMPT, FRAMES, VERBS, TOOL_JSON_SCHEMAS, TOOL_DESCRIPTIONS;
70638
70661
  var init_app = __esm({
70639
70662
  async "src/ui/app.tsx"() {
70640
70663
  "use strict";
@@ -70662,6 +70685,82 @@ var init_app = __esm({
70662
70685
  PROMPT = "\u276F";
70663
70686
  FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
70664
70687
  VERBS = ["Baked", "Brewed", "Churned", "Cogitated", "Cooked", "Crunched", "Saut\xE9ed", "Worked"];
70688
+ TOOL_JSON_SCHEMAS = {
70689
+ Bash: {
70690
+ type: "object",
70691
+ properties: {
70692
+ command: { type: "string", description: "The command to execute" },
70693
+ description: { type: "string", description: "Clear, concise description of what this command does" },
70694
+ timeout: { type: "number", description: "Optional timeout in milliseconds (max 600000)" },
70695
+ run_in_background: { type: "boolean", description: "Set to true to run in the background" }
70696
+ },
70697
+ required: ["command"]
70698
+ },
70699
+ Read: {
70700
+ type: "object",
70701
+ properties: {
70702
+ file_path: { type: "string", description: "The absolute path to the file to read" },
70703
+ offset: { type: "number", description: "The line number to start reading from" },
70704
+ limit: { type: "number", description: "The number of lines to read" },
70705
+ pages: { type: "string", description: "Page range for PDF files (e.g. '1-5')" }
70706
+ },
70707
+ required: ["file_path"]
70708
+ },
70709
+ Edit: {
70710
+ type: "object",
70711
+ properties: {
70712
+ file_path: { type: "string", description: "The absolute path to the file to modify" },
70713
+ old_string: { type: "string", description: "The text to replace" },
70714
+ new_string: { type: "string", description: "The text to replace it with (must be different from old_string)" },
70715
+ replace_all: { type: "boolean", description: "Replace all occurrences of old_string (default false)", default: false }
70716
+ },
70717
+ required: ["file_path", "old_string", "new_string"]
70718
+ },
70719
+ Write: {
70720
+ type: "object",
70721
+ properties: {
70722
+ file_path: { type: "string", description: "The absolute path to the file to write" },
70723
+ content: { type: "string", description: "The content to write to the file" }
70724
+ },
70725
+ required: ["file_path", "content"]
70726
+ },
70727
+ Glob: {
70728
+ type: "object",
70729
+ properties: {
70730
+ pattern: { type: "string", description: "The glob pattern to match files against" },
70731
+ path: { type: "string", description: "The directory to search in. Defaults to current working directory." }
70732
+ },
70733
+ required: ["pattern"]
70734
+ },
70735
+ Grep: {
70736
+ type: "object",
70737
+ properties: {
70738
+ pattern: { type: "string", description: "The regular expression pattern to search for" },
70739
+ path: { type: "string", description: "File or directory to search in. Defaults to cwd." },
70740
+ glob: { type: "string", description: "Glob pattern to filter files (e.g. '*.js', '*.{ts,tsx}')" },
70741
+ type: { type: "string", description: "File type to search (js, py, rust, go, etc.)" },
70742
+ output_mode: { type: "string", enum: ["content", "files_with_matches", "count"], description: "Output mode. Defaults to 'files_with_matches'." },
70743
+ "-A": { type: "number", description: "Lines to show after each match" },
70744
+ "-B": { type: "number", description: "Lines to show before each match" },
70745
+ "-C": { type: "number", description: "Context lines before and after each match" },
70746
+ context: { type: "number", description: "Alias for -C" },
70747
+ "-i": { type: "boolean", description: "Case insensitive search" },
70748
+ "-n": { type: "boolean", description: "Show line numbers (default true)" },
70749
+ multiline: { type: "boolean", description: "Enable multiline mode" },
70750
+ head_limit: { type: "number", description: "Limit output to first N entries" },
70751
+ offset: { type: "number", description: "Skip first N entries" }
70752
+ },
70753
+ required: ["pattern"]
70754
+ }
70755
+ };
70756
+ TOOL_DESCRIPTIONS = {
70757
+ Bash: "Executes a bash command and returns its output. Use for system commands, builds, and terminal operations.",
70758
+ Read: "Reads a file from the filesystem. Returns contents with line numbers. Supports text, PDF, images, and notebooks.",
70759
+ Edit: "Performs exact string replacements in files. Use old_string/new_string for targeted edits.",
70760
+ Write: "Writes content to a file, creating it if needed or overwriting if it exists.",
70761
+ Glob: "Fast file pattern matching. Returns matching file paths sorted by modification time.",
70762
+ Grep: "Content search powered by ripgrep. Supports regex, file type filters, and multiple output modes."
70763
+ };
70665
70764
  }
70666
70765
  });
70667
70766
 
@@ -70879,7 +70978,7 @@ var VERSION, BUILD_TIME, PACKAGE_NAME2, ISSUES_URL2, startupTimestamps, original
70879
70978
  var init_index = __esm({
70880
70979
  "src/cli/index.ts"() {
70881
70980
  VERSION = "0.1.2";
70882
- BUILD_TIME = "2026-03-20T13:23:30.894Z";
70981
+ BUILD_TIME = "2026-03-20T14:01:22.604Z";
70883
70982
  PACKAGE_NAME2 = "@hasna/coders";
70884
70983
  ISSUES_URL2 = "https://github.com/hasnaxyz/open-coders/issues";
70885
70984
  startupTimestamps = {};