lupacode 1.0.0 → 1.0.2

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 CHANGED
@@ -2,30 +2,58 @@
2
2
 
3
3
  AI-powered terminal coding assistant.
4
4
 
5
- ## Install
5
+ ## Install globally
6
+
7
+ Requires [Bun](https://bun.sh) >= 1.0.
6
8
 
7
9
  ```bash
8
- npm install -g lupacode
9
- # or
10
+ # Remove any old global install first (important on Windows)
11
+ npm uninstall -g lupacode
12
+
13
+ # Install latest
10
14
  bun install -g lupacode
15
+ # or
16
+ npm install -g lupacode
11
17
  ```
12
18
 
13
- ## Quick start
19
+ Then run from **any directory**:
14
20
 
15
21
  ```bash
16
22
  lupacode
17
23
  ```
18
24
 
25
+ Verify the version in the footer (e.g. `v1.0.2`).
26
+
19
27
  ## Configuration
20
28
 
21
- | Env var | Default | Required | Description |
22
- |---|---|---|---|
23
- | `API_URL` | `https://lupacodeserver-production.up.railway.app` | No | LupaCode API server |
29
+ Global config lives in `~/.lupacode/` (same on every machine/user):
30
+
31
+ | File | Purpose |
32
+ |---|---|
33
+ | `~/.lupacode/.env` | Optional env overrides (see below) |
34
+ | `~/.lupacode/auth.json` | Saved login token (created by `/login`) |
35
+ | `~/.lupacode/preferences.json` | Theme, model, mode preferences |
24
36
 
25
- ### Setting up authentication
37
+ | Env var | Default | Description |
38
+ |---|---|---|
39
+ | `API_URL` | `https://lupacodeserver-production.up.railway.app` | LupaCode API server |
26
40
 
27
- Type `/login` in the CLI it opens your browser for Clerk OAuth. No manual configuration needed.
41
+ Example `~/.lupacode/.env` (only if you need a custom server):
28
42
 
29
- ## Requirements
43
+ ```env
44
+ API_URL=https://lupacodeserver-production.up.railway.app
45
+ ```
46
+
47
+ The CLI does **not** read `.env` from your current project folder, so running `lupacode` in a repo with `API_URL=http://localhost:3000` will not break the global install.
48
+
49
+ ### Authentication
50
+
51
+ Type `/login` in the CLI — it opens your browser for Clerk OAuth.
52
+
53
+ ## Local development (monorepo)
54
+
55
+ ```bash
56
+ bun run dev:cli
57
+ ```
30
58
 
31
- - [Bun](https://bun.sh) >= 1.0
59
+ Dev mode uses Bun's normal `.env` loading from the repo root for local server testing.
package/bin/lupacode CHANGED
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env bun
2
2
  import dotenv from "dotenv";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
3
5
 
4
- dotenv.config({ quiet: true });
6
+ // Load user config only never cwd .env, so `lupacode` works the same in every directory.
7
+ dotenv.config({ path: join(homedir(), ".lupacode", ".env"), quiet: true });
5
8
 
6
9
  // @ts-expect-error - dist/index.js is a Bun bundle with no exports
7
10
  await import("../dist/index.js");
package/dist/index.js CHANGED
@@ -85693,6 +85693,12 @@ var toolInputSchemas = {
85693
85693
  command: exports_external.string().describe("Shell command to run"),
85694
85694
  description: exports_external.string().optional().describe("Short description of the command"),
85695
85695
  timeout: exports_external.number().optional().describe("Timeout in milliseconds")
85696
+ }),
85697
+ deleteFile: exports_external.object({
85698
+ path: exports_external.string().describe("Relative path to the file to delete")
85699
+ }),
85700
+ webSearch: exports_external.object({
85701
+ query: exports_external.string().describe("Search query for web search")
85696
85702
  })
85697
85703
  };
85698
85704
  // node_modules/ai/node_modules/@ai-sdk/provider/dist/index.js
@@ -93401,10 +93407,18 @@ var readOnlyToolContracts = {
93401
93407
  grep: tool({
93402
93408
  description: "Search file contents with a regular expression under the current project directory.",
93403
93409
  inputSchema: toolInputSchemas.grep
93410
+ }),
93411
+ webSearch: tool({
93412
+ description: "Search the web for up-to-date information on any topic.",
93413
+ inputSchema: toolInputSchemas.webSearch
93404
93414
  })
93405
93415
  };
93406
93416
  var buildToolContracts = {
93407
93417
  ...readOnlyToolContracts,
93418
+ deleteFile: tool({
93419
+ description: "Delete a file under the current project directory.",
93420
+ inputSchema: toolInputSchemas.deleteFile
93421
+ }),
93408
93422
  writeFile: tool({
93409
93423
  description: "Create or overwrite a file under the current project directory.",
93410
93424
  inputSchema: toolInputSchemas.writeFile
@@ -93421,7 +93435,7 @@ var buildToolContracts = {
93421
93435
  // src/lib/shared/models.ts
93422
93436
  var SUPPORTED_CHAT_MODELS = [
93423
93437
  {
93424
- id: "claude-sonnet-4-20250514",
93438
+ id: "claude-sonnet-5",
93425
93439
  provider: "anthropic",
93426
93440
  pricing: {
93427
93441
  inputUsdPerMillionTokens: 3,
@@ -93429,7 +93443,7 @@ var SUPPORTED_CHAT_MODELS = [
93429
93443
  }
93430
93444
  },
93431
93445
  {
93432
- id: "claude-3-5-haiku-latest",
93446
+ id: "claude-haiku-4-5",
93433
93447
  provider: "anthropic",
93434
93448
  pricing: {
93435
93449
  inputUsdPerMillionTokens: 1,
@@ -93437,7 +93451,7 @@ var SUPPORTED_CHAT_MODELS = [
93437
93451
  }
93438
93452
  },
93439
93453
  {
93440
- id: "claude-opus-4-20250514",
93454
+ id: "claude-opus-4-8",
93441
93455
  provider: "anthropic",
93442
93456
  pricing: {
93443
93457
  inputUsdPerMillionTokens: 5,
@@ -93445,7 +93459,7 @@ var SUPPORTED_CHAT_MODELS = [
93445
93459
  }
93446
93460
  },
93447
93461
  {
93448
- id: "gpt-4.1",
93462
+ id: "gpt-5.5",
93449
93463
  provider: "openai",
93450
93464
  pricing: {
93451
93465
  inputUsdPerMillionTokens: 2.5,
@@ -93453,7 +93467,7 @@ var SUPPORTED_CHAT_MODELS = [
93453
93467
  }
93454
93468
  },
93455
93469
  {
93456
- id: "gpt-4.1-mini",
93470
+ id: "gpt-5.4-mini",
93457
93471
  provider: "openai",
93458
93472
  pricing: {
93459
93473
  inputUsdPerMillionTokens: 0.75,
@@ -93461,7 +93475,7 @@ var SUPPORTED_CHAT_MODELS = [
93461
93475
  }
93462
93476
  },
93463
93477
  {
93464
- id: "gpt-4.1-nano",
93478
+ id: "gpt-5.4-nano",
93465
93479
  provider: "openai",
93466
93480
  pricing: {
93467
93481
  inputUsdPerMillionTokens: 0.2,
@@ -93469,19 +93483,19 @@ var SUPPORTED_CHAT_MODELS = [
93469
93483
  }
93470
93484
  },
93471
93485
  {
93472
- id: "qwen/qwen3-coder",
93486
+ id: "cohere/north-mini-code:free",
93473
93487
  provider: "openrouter",
93474
93488
  pricing: {
93475
- inputUsdPerMillionTokens: 0.3,
93476
- outputUsdPerMillionTokens: 1.2
93489
+ inputUsdPerMillionTokens: 0,
93490
+ outputUsdPerMillionTokens: 0
93477
93491
  }
93478
93492
  },
93479
93493
  {
93480
- id: "qwen/qwen3-32b",
93481
- provider: "groq",
93494
+ id: "deepseek/deepseek-chat-v3",
93495
+ provider: "openrouter",
93482
93496
  pricing: {
93483
- inputUsdPerMillionTokens: 0.29,
93484
- outputUsdPerMillionTokens: 0.59
93497
+ inputUsdPerMillionTokens: 0.27,
93498
+ outputUsdPerMillionTokens: 1.1
93485
93499
  }
93486
93500
  },
93487
93501
  {
@@ -93493,11 +93507,67 @@ var SUPPORTED_CHAT_MODELS = [
93493
93507
  }
93494
93508
  },
93495
93509
  {
93496
- id: "deepseek/deepseek-chat-v3",
93510
+ id: "deepseek/deepseek-v3.2",
93497
93511
  provider: "openrouter",
93498
93512
  pricing: {
93499
- inputUsdPerMillionTokens: 0.27,
93500
- outputUsdPerMillionTokens: 1.1
93513
+ inputUsdPerMillionTokens: 0.2288,
93514
+ outputUsdPerMillionTokens: 0.3432
93515
+ }
93516
+ },
93517
+ {
93518
+ id: "deepseek/deepseek-v4-flash",
93519
+ provider: "openrouter",
93520
+ pricing: {
93521
+ inputUsdPerMillionTokens: 0.1,
93522
+ outputUsdPerMillionTokens: 0.2
93523
+ }
93524
+ },
93525
+ {
93526
+ id: "deepseek/deepseek-v4-pro",
93527
+ provider: "openrouter",
93528
+ pricing: {
93529
+ inputUsdPerMillionTokens: 0.435,
93530
+ outputUsdPerMillionTokens: 0.87
93531
+ }
93532
+ },
93533
+ {
93534
+ id: "nvidia/nemotron-3-super-120b-a12b:free",
93535
+ provider: "openrouter",
93536
+ pricing: {
93537
+ inputUsdPerMillionTokens: 0,
93538
+ outputUsdPerMillionTokens: 0
93539
+ }
93540
+ },
93541
+ {
93542
+ id: "nvidia/nemotron-3-ultra-550b-a55b:free",
93543
+ provider: "openrouter",
93544
+ pricing: {
93545
+ inputUsdPerMillionTokens: 0,
93546
+ outputUsdPerMillionTokens: 0
93547
+ }
93548
+ },
93549
+ {
93550
+ id: "poolside/laguna-m.1:free",
93551
+ provider: "openrouter",
93552
+ pricing: {
93553
+ inputUsdPerMillionTokens: 0,
93554
+ outputUsdPerMillionTokens: 0
93555
+ }
93556
+ },
93557
+ {
93558
+ id: "qwen/qwen3-coder",
93559
+ provider: "openrouter",
93560
+ pricing: {
93561
+ inputUsdPerMillionTokens: 0.3,
93562
+ outputUsdPerMillionTokens: 1.2
93563
+ }
93564
+ },
93565
+ {
93566
+ id: "qwen/qwen3-coder-next",
93567
+ provider: "openrouter",
93568
+ pricing: {
93569
+ inputUsdPerMillionTokens: 0.11,
93570
+ outputUsdPerMillionTokens: 0.8
93501
93571
  }
93502
93572
  },
93503
93573
  {
@@ -93508,6 +93578,22 @@ var SUPPORTED_CHAT_MODELS = [
93508
93578
  outputUsdPerMillionTokens: 0
93509
93579
  }
93510
93580
  },
93581
+ {
93582
+ id: "gemini-3-flash-lite",
93583
+ provider: "google",
93584
+ pricing: {
93585
+ inputUsdPerMillionTokens: 0.25,
93586
+ outputUsdPerMillionTokens: 1.5
93587
+ }
93588
+ },
93589
+ {
93590
+ id: "gemini-3-flash-preview",
93591
+ provider: "google",
93592
+ pricing: {
93593
+ inputUsdPerMillionTokens: 0.5,
93594
+ outputUsdPerMillionTokens: 3
93595
+ }
93596
+ },
93511
93597
  {
93512
93598
  id: "llama-3.3-70b-versatile",
93513
93599
  provider: "groq",
@@ -93555,7 +93641,7 @@ function findSupportedChatModel(modelId) {
93555
93641
  function isFreeModel(model) {
93556
93642
  return model.pricing.inputUsdPerMillionTokens === 0 && model.pricing.outputUsdPerMillionTokens === 0;
93557
93643
  }
93558
- var DEFAULT_CHAT_MODEL_ID = "qwen/qwen3-32b";
93644
+ var DEFAULT_CHAT_MODEL_ID = "deepseek/deepseek-v4-flash";
93559
93645
  // src/lib/shared/domains.ts
93560
93646
  var DOMAINS = [
93561
93647
  {
@@ -93823,11 +93909,8 @@ function DialogProvider({ children }) {
93823
93909
  const [dialogStack, setDialogStack] = import_react24.useState([]);
93824
93910
  const { push, pop } = useKeyboardLayer();
93825
93911
  const close = import_react24.useCallback(() => {
93826
- setDialogStack((prev) => {
93827
- const next = prev.slice(0, -1);
93828
- pop("dialog");
93829
- return next;
93830
- });
93912
+ setDialogStack((prev) => prev.slice(0, -1));
93913
+ pop("dialog");
93831
93914
  }, [pop]);
93832
93915
  const open = import_react24.useCallback((config2) => {
93833
93916
  setDialogStack((prev) => [...prev, config2]);
@@ -93994,7 +94077,7 @@ function RootLayout() {
93994
94077
  }
93995
94078
 
93996
94079
  // src/screens/home.tsx
93997
- var import_react41 = __toESM(require_react(), 1);
94080
+ var import_react42 = __toESM(require_react(), 1);
93998
94081
 
93999
94082
  // src/components/header.tsx
94000
94083
  function Header() {
@@ -94022,7 +94105,7 @@ function Header() {
94022
94105
  }
94023
94106
 
94024
94107
  // src/components/input-bar.tsx
94025
- var import_react39 = __toESM(require_react(), 1);
94108
+ var import_react40 = __toESM(require_react(), 1);
94026
94109
  import { readdir } from "fs/promises";
94027
94110
  import { isAbsolute as isAbsolute3, relative, resolve as resolve5 } from "path";
94028
94111
 
@@ -94524,6 +94607,12 @@ function clearAuth() {
94524
94607
  } catch {}
94525
94608
  }
94526
94609
 
94610
+ // src/lib/env.ts
94611
+ var DEFAULT_API_URL = "https://lupacodeserver-production.up.railway.app";
94612
+ function getApiUrl() {
94613
+ return process.env.API_URL ?? DEFAULT_API_URL;
94614
+ }
94615
+
94527
94616
  // src/lib/api-client.ts
94528
94617
  var RETRY_ATTEMPTS = 3;
94529
94618
  var RETRY_BASE_DELAY_MS = 1000;
@@ -94546,7 +94635,7 @@ async function withRetry(fn, options) {
94546
94635
  }
94547
94636
  throw lastError;
94548
94637
  }
94549
- var apiClient = hc(process.env.API_URL ?? "https://lupacodeserver-production.up.railway.app", {
94638
+ var apiClient = hc(getApiUrl(), {
94550
94639
  fetch: async (input, init) => {
94551
94640
  const headers = new Headers(init?.headers);
94552
94641
  const auth = getAuth();
@@ -96333,7 +96422,7 @@ var import_react35 = __toESM(require_react(), 1);
96333
96422
  // package.json
96334
96423
  var package_default2 = {
96335
96424
  name: "lupacode",
96336
- version: "1.0.0",
96425
+ version: "1.0.2",
96337
96426
  description: "AI-powered terminal coding assistant",
96338
96427
  type: "module",
96339
96428
  bin: {
@@ -96415,7 +96504,7 @@ async function checkForUpdate(currentVersion) {
96415
96504
  const cache = readCache();
96416
96505
  if (cache && Date.now() - cache.checkedAt < CHECK_INTERVAL_MS) {
96417
96506
  cachedLatest = cache.latest;
96418
- return cache.latest && cache.latest !== currentVersion ? cache.latest : null;
96507
+ return isNewer(cache.latest, currentVersion) ? cache.latest : null;
96419
96508
  }
96420
96509
  try {
96421
96510
  const res = await fetch("https://registry.npmjs.org/lupacode/latest");
@@ -96426,13 +96515,32 @@ async function checkForUpdate(currentVersion) {
96426
96515
  const result = { latest, checkedAt: Date.now() };
96427
96516
  writeCache(result);
96428
96517
  cachedLatest = latest;
96429
- return latest !== currentVersion ? latest : null;
96518
+ return isNewer(latest, currentVersion) ? latest : null;
96430
96519
  } catch {
96431
96520
  return null;
96432
96521
  }
96433
96522
  }
96523
+ function isNewer(latest, current) {
96524
+ if (!latest)
96525
+ return false;
96526
+ const a = parseSemver(latest);
96527
+ const b2 = parseSemver(current);
96528
+ if (!a || !b2)
96529
+ return false;
96530
+ if (a.major !== b2.major)
96531
+ return a.major > b2.major;
96532
+ if (a.minor !== b2.minor)
96533
+ return a.minor > b2.minor;
96534
+ return a.patch > b2.patch;
96535
+ }
96536
+ function parseSemver(v2) {
96537
+ const m2 = /^v?(\d+)\.(\d+)\.(\d+)/.exec(v2.trim());
96538
+ if (!m2 || m2[1] == null || m2[2] == null || m2[3] == null)
96539
+ return null;
96540
+ return { major: Number(m2[1]), minor: Number(m2[2]), patch: Number(m2[3]) };
96541
+ }
96434
96542
  function hasUpdate() {
96435
- return cachedLatest !== null && cachedLatest !== cachedCurrent;
96543
+ return isNewer(cachedLatest, cachedCurrent);
96436
96544
  }
96437
96545
 
96438
96546
  // src/components/dialogs/settings-dialog.tsx
@@ -96460,7 +96568,7 @@ var SettingsDialogContent = () => {
96460
96568
  break;
96461
96569
  case "model":
96462
96570
  dialog.open({ title: "Select Model", children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ModelsDialogContent, {
96463
- models: SUPPORTED_CHAT_MODELS,
96571
+ models: [...SUPPORTED_CHAT_MODELS],
96464
96572
  onSelectModel: setModel
96465
96573
  }, undefined, false, undefined, this) });
96466
96574
  break;
@@ -96549,7 +96657,7 @@ var shortcuts = [
96549
96657
  { key: "ctrl+d", description: "Switch domain" },
96550
96658
  { key: "ctrl+s", description: "Open settings" },
96551
96659
  { key: "ctrl+r", description: "Regenerate last assistant response" },
96552
- { key: "e", description: "Edit last user message" },
96660
+ { key: "ctrl+e", description: "Edit last user message" },
96553
96661
  { key: "escape", description: "Interrupt response / Cancel / Close dialog" }
96554
96662
  ];
96555
96663
  var KeyboardHelpDialogContent = () => {
@@ -96616,6 +96724,9 @@ function StatusBar() {
96616
96724
  }, undefined, true, undefined, this);
96617
96725
  }
96618
96726
 
96727
+ // src/components/command-menu/commands.tsx
96728
+ var import_react37 = __toESM(require_react(), 1);
96729
+
96619
96730
  // ../../node_modules/open/index.js
96620
96731
  import process9 from "process";
96621
96732
  import path4 from "path";
@@ -97229,9 +97340,6 @@ var open_default = open;
97229
97340
  // src/lib/oauth.ts
97230
97341
  var LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
97231
97342
  var POLL_INTERVAL_MS = 1000;
97232
- function getApiUrl() {
97233
- return process.env.API_URL ?? "https://lupacodeserver-production.up.railway.app";
97234
- }
97235
97343
  async function performLogin() {
97236
97344
  const apiUrl = getApiUrl();
97237
97345
  const initRes = await fetch(`${apiUrl}/auth/init`, { method: "POST" });
@@ -97440,6 +97548,85 @@ var COMMANDS = [
97440
97548
  }
97441
97549
  }
97442
97550
  },
97551
+ {
97552
+ name: "rename",
97553
+ description: "Rename the current session",
97554
+ value: "/rename",
97555
+ action: (ctx) => {
97556
+ if (!ctx.sessionId) {
97557
+ ctx.toast.show({ variant: "error", message: "No active session" });
97558
+ return;
97559
+ }
97560
+ const RenameDialog = () => {
97561
+ const [text2, setText] = import_react37.useState("");
97562
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
97563
+ flexDirection: "column",
97564
+ gap: 1,
97565
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("input", {
97566
+ focused: true,
97567
+ placeholder: "New session name",
97568
+ onContentChange: (event) => setText(String(event.value ?? event)),
97569
+ onSubmit: () => {
97570
+ const name23 = text2.trim();
97571
+ if (!name23)
97572
+ return;
97573
+ const baseUrl = getApiUrl();
97574
+ const token = getAuth()?.token ?? "";
97575
+ fetch(`${baseUrl}/sessions/${ctx.sessionId}`, {
97576
+ method: "PATCH",
97577
+ headers: {
97578
+ "Content-Type": "application/json",
97579
+ ...token ? { Authorization: `Bearer ${token}` } : {}
97580
+ },
97581
+ body: JSON.stringify({ title: name23 })
97582
+ }).then((res) => {
97583
+ if (!res.ok)
97584
+ throw new Error("Failed to rename");
97585
+ ctx.toast.show({ variant: "success", message: `Renamed to "${name23}"` });
97586
+ }).catch(() => {
97587
+ ctx.toast.show({ variant: "error", message: "Failed to rename session" });
97588
+ });
97589
+ ctx.dialog.close();
97590
+ }
97591
+ }, undefined, false, undefined, this)
97592
+ }, undefined, false, undefined, this);
97593
+ };
97594
+ ctx.dialog.open({ title: "Rename Session", children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(RenameDialog, {}, undefined, false, undefined, this) });
97595
+ }
97596
+ },
97597
+ {
97598
+ name: "export",
97599
+ description: "Export conversation as markdown",
97600
+ value: "/export",
97601
+ action: (ctx) => {
97602
+ if (!ctx.messages || ctx.messages.length === 0) {
97603
+ ctx.toast.show({ variant: "error", message: "No messages to export" });
97604
+ return;
97605
+ }
97606
+ const lines = [];
97607
+ lines.push("# Chat Export");
97608
+ lines.push("");
97609
+ for (const msg of ctx.messages) {
97610
+ const role = msg.role === "user" ? "User" : "Assistant";
97611
+ const text2 = msg.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
97612
+ if (!text2)
97613
+ continue;
97614
+ lines.push(`**${role}:**`);
97615
+ lines.push("");
97616
+ lines.push(text2);
97617
+ lines.push("");
97618
+ }
97619
+ const content = lines.join(`
97620
+ `);
97621
+ const filename = `chat-export-${Date.now()}.md`;
97622
+ try {
97623
+ Bun.write(filename, content);
97624
+ ctx.toast.show({ variant: "success", message: `Exported to ${filename}` });
97625
+ } catch {
97626
+ ctx.toast.show({ variant: "error", message: "Failed to write export file" });
97627
+ }
97628
+ }
97629
+ },
97443
97630
  {
97444
97631
  name: "exit",
97445
97632
  description: "Quit the application",
@@ -97520,15 +97707,15 @@ function CommandMenu({
97520
97707
  }
97521
97708
 
97522
97709
  // src/components/command-menu/use-command-menu.ts
97523
- var import_react37 = __toESM(require_react(), 1);
97710
+ var import_react38 = __toESM(require_react(), 1);
97524
97711
  function useCommandMenu() {
97525
- const [textValue, setTextValue] = import_react37.useState("");
97526
- const [selectedIndex, setSelectedIndex] = import_react37.useState(0);
97527
- const [showCommandMenu, setShowCommandMenu] = import_react37.useState(false);
97528
- const scrollRef = import_react37.useRef(null);
97712
+ const [textValue, setTextValue] = import_react38.useState("");
97713
+ const [selectedIndex, setSelectedIndex] = import_react38.useState(0);
97714
+ const [showCommandMenu, setShowCommandMenu] = import_react38.useState(false);
97715
+ const scrollRef = import_react38.useRef(null);
97529
97716
  const { push, pop, isTopLayer } = useKeyboardLayer();
97530
97717
  const commandQuery = showCommandMenu && textValue.startsWith("/") ? textValue.slice(1) : "";
97531
- const filteredCommands = import_react37.useMemo(() => getFilteredCommands(commandQuery), [commandQuery]);
97718
+ const filteredCommands = import_react38.useMemo(() => getFilteredCommands(commandQuery), [commandQuery]);
97532
97719
  const close = () => {
97533
97720
  setShowCommandMenu(false);
97534
97721
  pop("command");
@@ -97785,21 +97972,21 @@ var TEXTAREA_KEY_BINDINGS = [
97785
97972
  { name: "return", shift: true, action: "newline" },
97786
97973
  { name: "enter", shift: true, action: "newline" }
97787
97974
  ];
97788
- function InputBar({ onSubmit, disabled = false }) {
97975
+ function InputBar({ onSubmit, disabled = false, sessionId, messages }) {
97789
97976
  const { mode, toggleMode, setMode, setModel, domain: domain2, setDomain } = usePromptConfig();
97790
- const textareaRef = import_react39.useRef(null);
97791
- const onSubmitRef = import_react39.useRef(() => {});
97792
- const activeMentionRef = import_react39.useRef(null);
97793
- const mentionScrollRef = import_react39.useRef(null);
97977
+ const textareaRef = import_react40.useRef(null);
97978
+ const onSubmitRef = import_react40.useRef(() => {});
97979
+ const activeMentionRef = import_react40.useRef(null);
97980
+ const mentionScrollRef = import_react40.useRef(null);
97794
97981
  const renderer = useRenderer();
97795
97982
  const toast = useToast();
97796
97983
  const navigate = useNavigate();
97797
97984
  const dialog = useDialog();
97798
97985
  const { colors } = useTheme();
97799
97986
  const { isTopLayer, setResponder, push, pop } = useKeyboardLayer();
97800
- const [activeMention, setActiveMention] = import_react39.useState(null);
97801
- const [mentionCandidates, setMentionCandidates] = import_react39.useState([]);
97802
- const [mentionSelectedIndex, setMentionSelectedIndex] = import_react39.useState(0);
97987
+ const [activeMention, setActiveMention] = import_react40.useState(null);
97988
+ const [mentionCandidates, setMentionCandidates] = import_react40.useState([]);
97989
+ const [mentionSelectedIndex, setMentionSelectedIndex] = import_react40.useState(0);
97803
97990
  const {
97804
97991
  showCommandMenu,
97805
97992
  commandQuery,
@@ -97810,13 +97997,13 @@ function InputBar({ onSubmit, disabled = false }) {
97810
97997
  setSelectedIndex
97811
97998
  } = useCommandMenu();
97812
97999
  const showMentionMenu = activeMention !== null;
97813
- const closeMentionMenu = import_react39.useCallback(() => {
98000
+ const closeMentionMenu = import_react40.useCallback(() => {
97814
98001
  activeMentionRef.current = null;
97815
98002
  setActiveMention(null);
97816
98003
  setMentionCandidates([]);
97817
98004
  pop("mention");
97818
98005
  }, [pop]);
97819
- const syncMentionMenu = import_react39.useCallback((text2, cursorOffset) => {
98006
+ const syncMentionMenu = import_react40.useCallback((text2, cursorOffset) => {
97820
98007
  const nextMention = findActiveMention(text2, cursorOffset);
97821
98008
  const previousMention = activeMentionRef.current;
97822
98009
  const mentionChanged = previousMention?.start !== nextMention?.start || previousMention?.end !== nextMention?.end || previousMention?.query !== nextMention?.query;
@@ -97839,7 +98026,7 @@ function InputBar({ onSubmit, disabled = false }) {
97839
98026
  mentionScrollRef.current?.scrollTo(0);
97840
98027
  }
97841
98028
  }, [closeMentionMenu, push]);
97842
- const handleTextareaContentChange = import_react39.useCallback(() => {
98029
+ const handleTextareaContentChange = import_react40.useCallback(() => {
97843
98030
  const textarea = textareaRef.current;
97844
98031
  if (!textarea)
97845
98032
  return;
@@ -97847,7 +98034,7 @@ function InputBar({ onSubmit, disabled = false }) {
97847
98034
  handleContentChange(textarea.plainText);
97848
98035
  syncMentionMenu(text2, textarea.cursorOffset);
97849
98036
  }, [handleContentChange, syncMentionMenu]);
97850
- const handleSubmit = import_react39.useCallback(() => {
98037
+ const handleSubmit = import_react40.useCallback(() => {
97851
98038
  if (disabled)
97852
98039
  return;
97853
98040
  const textarea = textareaRef.current;
@@ -97859,7 +98046,7 @@ function InputBar({ onSubmit, disabled = false }) {
97859
98046
  onSubmit(text2);
97860
98047
  textarea.setText("");
97861
98048
  }, [disabled, onSubmit]);
97862
- const handleMentionExecute = import_react39.useCallback((index) => {
98049
+ const handleMentionExecute = import_react40.useCallback((index) => {
97863
98050
  const textarea = textareaRef.current;
97864
98051
  const mention = activeMentionRef.current;
97865
98052
  const candidate = mentionCandidates[index];
@@ -97871,13 +98058,13 @@ function InputBar({ onSubmit, disabled = false }) {
97871
98058
  textarea.cursorOffset = mention.start + insertion.length + 1;
97872
98059
  syncMentionMenu(nextText, textarea.cursorOffset);
97873
98060
  }, [mentionCandidates, syncMentionMenu]);
97874
- const handleTextareaCursorChange = import_react39.useCallback(() => {
98061
+ const handleTextareaCursorChange = import_react40.useCallback(() => {
97875
98062
  const textarea = textareaRef.current;
97876
98063
  if (!textarea)
97877
98064
  return;
97878
98065
  syncMentionMenu(textarea.plainText, textarea.cursorOffset);
97879
98066
  }, [syncMentionMenu]);
97880
- const handleCommand = import_react39.useCallback((command) => {
98067
+ const handleCommand = import_react40.useCallback((command) => {
97881
98068
  const textarea = textareaRef.current;
97882
98069
  if (!textarea || !command)
97883
98070
  return;
@@ -97891,17 +98078,19 @@ function InputBar({ onSubmit, disabled = false }) {
97891
98078
  mode,
97892
98079
  setMode,
97893
98080
  setModel,
97894
- setDomain
98081
+ setDomain,
98082
+ sessionId,
98083
+ messages
97895
98084
  });
97896
98085
  } else {
97897
98086
  textarea.insertText(command.value + " ");
97898
98087
  }
97899
98088
  }, [renderer, toast, dialog, navigate, mode, setMode, setModel, setDomain]);
97900
- const handleCommandExecute = import_react39.useCallback((index) => {
98089
+ const handleCommandExecute = import_react40.useCallback((index) => {
97901
98090
  const command = resolveCommand(index);
97902
98091
  handleCommand(command);
97903
98092
  }, [resolveCommand, handleCommand]);
97904
- import_react39.useEffect(() => {
98093
+ import_react40.useEffect(() => {
97905
98094
  if (!activeMention) {
97906
98095
  setMentionCandidates([]);
97907
98096
  return;
@@ -97924,7 +98113,7 @@ function InputBar({ onSubmit, disabled = false }) {
97924
98113
  ignore = true;
97925
98114
  };
97926
98115
  }, [activeMention]);
97927
- import_react39.useEffect(() => {
98116
+ import_react40.useEffect(() => {
97928
98117
  const textare = textareaRef.current;
97929
98118
  if (!textare)
97930
98119
  return;
@@ -97982,7 +98171,7 @@ function InputBar({ onSubmit, disabled = false }) {
97982
98171
  });
97983
98172
  }
97984
98173
  });
97985
- import_react39.useEffect(() => {
98174
+ import_react40.useEffect(() => {
97986
98175
  setResponder("base", () => {
97987
98176
  if (disabled)
97988
98177
  return false;
@@ -98100,7 +98289,7 @@ function InputBar({ onSubmit, disabled = false }) {
98100
98289
  function Home() {
98101
98290
  const navigate = useNavigate();
98102
98291
  const { mode, model, domain: domain2 } = usePromptConfig();
98103
- const handleSubmit = import_react41.useCallback((text2) => {
98292
+ const handleSubmit = import_react42.useCallback((text2) => {
98104
98293
  navigate("/sessions/new", { state: { message: text2, mode, model, domain: domain2 } });
98105
98294
  }, [navigate, mode, model, domain2]);
98106
98295
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
@@ -98178,7 +98367,7 @@ function Home() {
98178
98367
  }
98179
98368
 
98180
98369
  // src/screens/new-session.tsx
98181
- var import_react44 = __toESM(require_react(), 1);
98370
+ var import_react45 = __toESM(require_react(), 1);
98182
98371
 
98183
98372
  // src/components/messages/error-message.tsx
98184
98373
  function ErrorMessage({ message: message2 }) {
@@ -98369,7 +98558,45 @@ function prettyMilliseconds(milliseconds, options) {
98369
98558
  }
98370
98559
 
98371
98560
  // src/components/messages/bot-message.tsx
98372
- var globalSyntaxStyle = SyntaxStyle.create();
98561
+ var globalSyntaxStyle = SyntaxStyle.fromStyles({
98562
+ default: { fg: "#D4D4D4" },
98563
+ keyword: { fg: "#569CD6" },
98564
+ "keyword.control": { fg: "#C586C0" },
98565
+ "keyword.import": { fg: "#C586C0" },
98566
+ "keyword.function": { fg: "#DCDCAA" },
98567
+ string: { fg: "#CE9178" },
98568
+ "string.special": { fg: "#CE9178" },
98569
+ number: { fg: "#B5CEA8" },
98570
+ "number.float": { fg: "#B5CEA8" },
98571
+ comment: { fg: "#6A9955", italic: true },
98572
+ function: { fg: "#DCDCAA" },
98573
+ "function.call": { fg: "#DCDCAA" },
98574
+ "function.method": { fg: "#DCDCAA" },
98575
+ type: { fg: "#4EC9B0" },
98576
+ "type.builtin": { fg: "#4EC9B0" },
98577
+ variable: { fg: "#9CDCFE" },
98578
+ "variable.parameter": { fg: "#9CDCFE" },
98579
+ "variable.builtin": { fg: "#9CDCFE" },
98580
+ constant: { fg: "#4FC1FF" },
98581
+ "constant.builtin": { fg: "#569CD6" },
98582
+ property: { fg: "#9CDCFE" },
98583
+ operator: { fg: "#D4D4D4" },
98584
+ "punctuation.delimiter": { fg: "#D4D4D4" },
98585
+ "punctuation.bracket": { fg: "#D4D4D4" },
98586
+ boolean: { fg: "#569CD6" },
98587
+ label: { fg: "#DCDCAA" },
98588
+ "markup.heading": { fg: "#569CD6", bold: true },
98589
+ "markup.italic": { italic: true },
98590
+ "markup.strong": { fg: "#DCDCAA", bold: true },
98591
+ "markup.strikethrough": { fg: "#888888", italic: true, dim: true },
98592
+ "markup.raw": { fg: "#CE9178" },
98593
+ "markup.link": { fg: "#569CD6", underline: true },
98594
+ "markup.link.label": { fg: "#569CD6", underline: true },
98595
+ "markup.link.url": { fg: "#4FC1FF" },
98596
+ "markup.list": { fg: "#569CD6" },
98597
+ "markup.quote": { fg: "#6A9955", italic: true },
98598
+ conceal: { fg: "#555555" }
98599
+ });
98373
98600
  function formatToolName(name23) {
98374
98601
  return name23.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (c) => c.toUpperCase());
98375
98602
  }
@@ -98383,6 +98610,79 @@ function formatToolArgs(tc) {
98383
98610
  return String(tc.input);
98384
98611
  return Object.values(tc.input).map(String).join(" ");
98385
98612
  }
98613
+ function isBashTool(tc) {
98614
+ const name23 = tc.type === "dynamic-tool" ? tc.toolName : tc.type.slice("tool-".length);
98615
+ return name23 === "bash";
98616
+ }
98617
+ function isToolRunning(tc) {
98618
+ return tc.state !== "output-available" && tc.state !== "output-error";
98619
+ }
98620
+ function renderToolCall(tc, colors, j2, renderKey) {
98621
+ const toolName = tc.type === "dynamic-tool" ? tc.toolName : tc.type.slice("tool-".length);
98622
+ const running = isToolRunning(tc);
98623
+ const done = tc.state === "output-available";
98624
+ if (isBashTool(tc)) {
98625
+ const command = typeof tc.input === "object" && tc.input != null ? String(tc.input.command ?? "") : "";
98626
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
98627
+ width: "100%",
98628
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
98629
+ attributes: TextAttributes.DIM,
98630
+ children: [
98631
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("em", {
98632
+ fg: colors.success,
98633
+ attributes: TextAttributes.BOLD,
98634
+ children: "$"
98635
+ }, undefined, false, undefined, this),
98636
+ " ",
98637
+ command,
98638
+ running ? "" : "",
98639
+ tc.state === "output-error" ? /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("em", {
98640
+ fg: colors.error,
98641
+ children: [
98642
+ " ",
98643
+ "\u2716",
98644
+ " ",
98645
+ tc.errorText
98646
+ ]
98647
+ }, undefined, true, undefined, this) : null
98648
+ ]
98649
+ }, undefined, true, undefined, this)
98650
+ }, renderKey, false, undefined, this);
98651
+ }
98652
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
98653
+ width: "100%",
98654
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
98655
+ attributes: TextAttributes.DIM,
98656
+ children: [
98657
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("em", {
98658
+ fg: colors.info,
98659
+ children: [
98660
+ formatToolName(toolName),
98661
+ ":"
98662
+ ]
98663
+ }, undefined, true, undefined, this),
98664
+ " ",
98665
+ formatToolArgs(tc),
98666
+ running ? "..." : done ? " \u2713" : "",
98667
+ tc.state === "output-error" ? /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("em", {
98668
+ fg: colors.error,
98669
+ children: [
98670
+ " ",
98671
+ "\u2716",
98672
+ " ",
98673
+ tc.errorText
98674
+ ]
98675
+ }, undefined, true, undefined, this) : null
98676
+ ]
98677
+ }, undefined, true, undefined, this)
98678
+ }, renderKey, false, undefined, this);
98679
+ }
98680
+ function formatTokens(usage) {
98681
+ const total = usage.totalTokens ?? usage.inputTokens ?? usage.outputTokens;
98682
+ if (total == null)
98683
+ return "";
98684
+ return `${total.toLocaleString()} tok`;
98685
+ }
98386
98686
  function mergeReasoningParts(parts) {
98387
98687
  const reasoningParts = parts.filter((p) => p.type === "reasoning");
98388
98688
  if (reasoningParts.length <= 1)
@@ -98420,6 +98720,7 @@ function BotMessage({
98420
98720
  model,
98421
98721
  mode,
98422
98722
  durationMs,
98723
+ usage,
98423
98724
  streaming = false
98424
98725
  }) {
98425
98726
  const { colors } = useTheme();
@@ -98445,22 +98746,7 @@ function BotMessage({
98445
98746
  flexDirection: "column",
98446
98747
  children: group.parts.map((part, j2) => {
98447
98748
  const tc = part;
98448
- const toolName = tc.type === "dynamic-tool" ? tc.toolName : tc.type.slice("tool-".length);
98449
- return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
98450
- attributes: TextAttributes.DIM,
98451
- children: [
98452
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("em", {
98453
- fg: colors.info,
98454
- children: [
98455
- formatToolName(toolName),
98456
- ":"
98457
- ]
98458
- }, undefined, true, undefined, this),
98459
- formatToolArgs(tc),
98460
- tc.state !== "output-available" && tc.state !== "output-error" ? "..." : "",
98461
- tc.state === "output-error" ? ` ${tc.errorText}` : ""
98462
- ]
98463
- }, tc.toolCallId, true, undefined, this);
98749
+ return renderToolCall(tc, colors, j2, tc.toolCallId);
98464
98750
  })
98465
98751
  }, undefined, false, undefined, this)
98466
98752
  }, group.key, false, undefined, this);
@@ -98493,7 +98779,6 @@ function BotMessage({
98493
98779
  }, `reasoning-${j2}`, false, undefined, this);
98494
98780
  }
98495
98781
  if (isToolPart(part)) {
98496
- const toolName = part.type === "dynamic-tool" ? part.toolName : part.type.slice("tool-".length);
98497
98782
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
98498
98783
  border: ["left"],
98499
98784
  borderColor: colors.thinkingBorder,
@@ -98503,21 +98788,7 @@ function BotMessage({
98503
98788
  },
98504
98789
  width: "100%",
98505
98790
  paddingX: 2,
98506
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
98507
- attributes: TextAttributes.DIM,
98508
- children: [
98509
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("em", {
98510
- fg: colors.info,
98511
- children: [
98512
- formatToolName(toolName),
98513
- ":"
98514
- ]
98515
- }, undefined, true, undefined, this),
98516
- formatToolArgs(part),
98517
- part.state !== "output-available" && part.state !== "output-error" ? "..." : "",
98518
- part.state === "output-error" ? ` ${part.errorText}` : ""
98519
- ]
98520
- }, undefined, true, undefined, this)
98791
+ children: renderToolCall(part, colors, j2, part.toolCallId)
98521
98792
  }, part.toolCallId, false, undefined, this);
98522
98793
  }
98523
98794
  if (part.type === "text") {
@@ -98527,7 +98798,17 @@ function BotMessage({
98527
98798
  children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("markdown", {
98528
98799
  content: part.text,
98529
98800
  syntaxStyle: globalSyntaxStyle,
98530
- conceal: true
98801
+ conceal: true,
98802
+ concealCode: true,
98803
+ streaming,
98804
+ tableOptions: {
98805
+ style: "grid",
98806
+ borders: true,
98807
+ outerBorder: true,
98808
+ borderStyle: "rounded",
98809
+ cellPadding: 1,
98810
+ borderColor: "#555555"
98811
+ }
98531
98812
  }, undefined, false, undefined, this)
98532
98813
  }, `text-${j2}`, false, undefined, this);
98533
98814
  }
@@ -98576,6 +98857,19 @@ function BotMessage({
98576
98857
  children: prettyMilliseconds(durationMs)
98577
98858
  }, undefined, false, undefined, this)
98578
98859
  ]
98860
+ }, undefined, true, undefined, this),
98861
+ usage && formatTokens(usage) && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(import_jsx_dev_runtime2.Fragment, {
98862
+ children: [
98863
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
98864
+ attributes: TextAttributes.DIM,
98865
+ fg: colors.dimSeparator,
98866
+ children: ">"
98867
+ }, undefined, false, undefined, this),
98868
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
98869
+ attributes: TextAttributes.DIM,
98870
+ children: formatTokens(usage)
98871
+ }, undefined, false, undefined, this)
98872
+ ]
98579
98873
  }, undefined, true, undefined, this)
98580
98874
  ]
98581
98875
  }, undefined, true, undefined, this)
@@ -100570,7 +100864,9 @@ function SessionShell({
100570
100864
  onSubmit,
100571
100865
  inputDisabled = false,
100572
100866
  loading = false,
100573
- interruptible = false
100867
+ interruptible = false,
100868
+ sessionId,
100869
+ messages
100574
100870
  }) {
100575
100871
  const { mode } = usePromptConfig();
100576
100872
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
@@ -100595,7 +100891,9 @@ function SessionShell({
100595
100891
  flexShrink: 0,
100596
100892
  children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(InputBar, {
100597
100893
  onSubmit,
100598
- disabled: inputDisabled
100894
+ disabled: inputDisabled,
100895
+ sessionId,
100896
+ messages
100599
100897
  }, undefined, false, undefined, this)
100600
100898
  }, undefined, false, undefined, this),
100601
100899
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
@@ -100684,17 +100982,17 @@ function NewSession() {
100684
100982
  const navigate = useNavigate();
100685
100983
  const location = useLocation();
100686
100984
  const toast = useToast();
100687
- const hasStartedRef = import_react44.useRef(false);
100688
- const state = import_react44.useMemo(() => {
100985
+ const hasStartedRef = import_react45.useRef(false);
100986
+ const state = import_react45.useMemo(() => {
100689
100987
  const parsed = newSessionStateSchema.safeParse(location.state);
100690
100988
  return parsed.success ? parsed.data : null;
100691
100989
  }, [location.state]);
100692
- import_react44.useEffect(() => {
100990
+ import_react45.useEffect(() => {
100693
100991
  if (!state?.message) {
100694
100992
  navigate("/", { replace: true });
100695
100993
  }
100696
100994
  }, [state, navigate]);
100697
- import_react44.useEffect(() => {
100995
+ import_react45.useEffect(() => {
100698
100996
  if (!state || hasStartedRef.current)
100699
100997
  return;
100700
100998
  hasStartedRef.current = true;
@@ -100742,13 +101040,13 @@ function NewSession() {
100742
101040
  }
100743
101041
 
100744
101042
  // src/screens/sessions.tsx
100745
- var import_react53 = __toESM(require_react(), 1);
101043
+ var import_react54 = __toESM(require_react(), 1);
100746
101044
 
100747
101045
  // src/hooks/use-chat.ts
100748
- var import_react51 = __toESM(require_react(), 1);
101046
+ var import_react52 = __toESM(require_react(), 1);
100749
101047
 
100750
101048
  // ../../node_modules/@ai-sdk/react/dist/index.js
100751
- var import_react45 = __toESM(require_react(), 1);
101049
+ var import_react46 = __toESM(require_react(), 1);
100752
101050
 
100753
101051
  // ../../node_modules/@ai-sdk/react/node_modules/@ai-sdk/provider/dist/index.js
100754
101052
  var marker23 = "vercel.ai.error";
@@ -105501,11 +105799,11 @@ var AbstractChat = class {
105501
105799
 
105502
105800
  // ../../node_modules/@ai-sdk/react/dist/index.js
105503
105801
  var import_throttleit = __toESM(require_throttleit(), 1);
105504
- var import_react46 = __toESM(require_react(), 1);
105505
105802
  var import_react47 = __toESM(require_react(), 1);
105506
105803
  var import_react48 = __toESM(require_react(), 1);
105507
105804
  var import_react49 = __toESM(require_react(), 1);
105508
105805
  var import_react50 = __toESM(require_react(), 1);
105806
+ var import_react51 = __toESM(require_react(), 1);
105509
105807
  var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
105510
105808
  var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
105511
105809
  var __accessCheck = (obj, member, msg) => {
@@ -105642,7 +105940,7 @@ function useChat({
105642
105940
  resume = false,
105643
105941
  ...options
105644
105942
  } = {}) {
105645
- const callbacksRef = import_react45.useRef(!("chat" in options) ? {
105943
+ const callbacksRef = import_react46.useRef(!("chat" in options) ? {
105646
105944
  onToolCall: options.onToolCall,
105647
105945
  onData: options.onData,
105648
105946
  onFinish: options.onFinish,
@@ -105681,22 +105979,22 @@ function useChat({
105681
105979
  return (_c = (_b17 = (_a29 = callbacksRef.current).sendAutomaticallyWhen) == null ? undefined : _b17.call(_a29, arg)) != null ? _c : false;
105682
105980
  }
105683
105981
  };
105684
- const chatRef = import_react45.useRef("chat" in options ? options.chat : new Chat(optionsWithCallbacks));
105982
+ const chatRef = import_react46.useRef("chat" in options ? options.chat : new Chat(optionsWithCallbacks));
105685
105983
  const shouldRecreateChat = "chat" in options && options.chat !== chatRef.current || "id" in options && chatRef.current.id !== options.id;
105686
105984
  if (shouldRecreateChat) {
105687
105985
  chatRef.current = "chat" in options ? options.chat : new Chat(optionsWithCallbacks);
105688
105986
  }
105689
- const subscribeToMessages = import_react45.useCallback((update) => chatRef.current["~registerMessagesCallback"](update, throttleWaitMs), [throttleWaitMs, chatRef.current.id]);
105690
- const messages = import_react45.useSyncExternalStore(subscribeToMessages, () => chatRef.current.messages, () => chatRef.current.messages);
105691
- const status = import_react45.useSyncExternalStore(chatRef.current["~registerStatusCallback"], () => chatRef.current.status, () => chatRef.current.status);
105692
- const error51 = import_react45.useSyncExternalStore(chatRef.current["~registerErrorCallback"], () => chatRef.current.error, () => chatRef.current.error);
105693
- const setMessages = import_react45.useCallback((messagesParam) => {
105987
+ const subscribeToMessages = import_react46.useCallback((update) => chatRef.current["~registerMessagesCallback"](update, throttleWaitMs), [throttleWaitMs, chatRef.current.id]);
105988
+ const messages = import_react46.useSyncExternalStore(subscribeToMessages, () => chatRef.current.messages, () => chatRef.current.messages);
105989
+ const status = import_react46.useSyncExternalStore(chatRef.current["~registerStatusCallback"], () => chatRef.current.status, () => chatRef.current.status);
105990
+ const error51 = import_react46.useSyncExternalStore(chatRef.current["~registerErrorCallback"], () => chatRef.current.error, () => chatRef.current.error);
105991
+ const setMessages = import_react46.useCallback((messagesParam) => {
105694
105992
  if (typeof messagesParam === "function") {
105695
105993
  messagesParam = messagesParam(chatRef.current.messages);
105696
105994
  }
105697
105995
  chatRef.current.messages = messagesParam;
105698
105996
  }, [chatRef]);
105699
- import_react45.useEffect(() => {
105997
+ import_react46.useEffect(() => {
105700
105998
  if (resume) {
105701
105999
  chatRef.current.resumeStream();
105702
106000
  }
@@ -105719,7 +106017,7 @@ function useChat({
105719
106017
  }
105720
106018
 
105721
106019
  // src/lib/local-tools.ts
105722
- import { mkdir as mkdir2, readFile as readFile2, readdir as readdir2, stat, writeFile as writeFile2 } from "fs/promises";
106020
+ import { mkdir as mkdir2, readFile as readFile2, readdir as readdir2, stat, writeFile as writeFile2, unlink as unlink2 } from "fs/promises";
105723
106021
  import { dirname as dirname2, isAbsolute as isAbsolute5, join as join5, relative as relative2, resolve as resolve7 } from "path";
105724
106022
  var MAX_FILE_SIZE = 1e4;
105725
106023
  var MAX_RESULTS = 200;
@@ -105739,7 +106037,7 @@ function truncate(value, limit) {
105739
106037
  return value.length > limit ? `${value.slice(0, limit)}n... (truncated, ${value.length} total chars)` : value;
105740
106038
  }
105741
106039
  async function executeLocalTool(toolName, input, mode) {
105742
- if (mode === Mode.PLAN && !["readFile", "listDirectory", "glob", "grep"].includes(toolName)) {
106040
+ if (mode === Mode.PLAN && !["readFile", "listDirectory", "glob", "grep", "webSearch"].includes(toolName)) {
105743
106041
  throw new Error(`Tool ${toolName} is not available in PLAN mode`);
105744
106042
  }
105745
106043
  switch (toolName) {
@@ -105849,11 +106147,15 @@ async function executeLocalTool(toolName, input, mode) {
105849
106147
  }
105850
106148
  case "bash": {
105851
106149
  const { command, timeout = DEFAULT_TIMEOUT } = toolInputSchemas.bash.parse(input);
105852
- const proc = Bun.spawn(["bash", "-c", command], {
106150
+ const isWindows = process.platform === "win32";
106151
+ const shell = isWindows ? "cmd.exe" : "bash";
106152
+ const shellFlag = isWindows ? "/c" : "-c";
106153
+ const env2 = isWindows ? { ...process.env } : { ...process.env, TERM: "dumb" };
106154
+ const proc = Bun.spawn([shell, shellFlag, command], {
105853
106155
  cwd: resolveInsideCwd(".").resolved,
105854
106156
  stdout: "pipe",
105855
106157
  stderr: "pipe",
105856
- env: { ...process.env, TERM: "dumb" }
106158
+ env: env2
105857
106159
  });
105858
106160
  const timer = setTimeout(() => proc.kill(), timeout);
105859
106161
  const [stdout, stderr] = await Promise.all([
@@ -105868,6 +106170,40 @@ async function executeLocalTool(toolName, input, mode) {
105868
106170
  exitCode
105869
106171
  };
105870
106172
  }
106173
+ case "deleteFile": {
106174
+ const { path: path5 } = toolInputSchemas.deleteFile.parse(input);
106175
+ const { cwd, resolved } = resolveInsideCwd(path5);
106176
+ await unlink2(resolved);
106177
+ return { success: true, path: relative2(cwd, resolved) };
106178
+ }
106179
+ case "webSearch": {
106180
+ const { query } = toolInputSchemas.webSearch.parse(input);
106181
+ const apiKey = process.env.TAVILY_API_KEY;
106182
+ if (!apiKey)
106183
+ throw new Error("TAVILY_API_KEY is not set");
106184
+ const res = await fetch("https://api.tavily.com/search", {
106185
+ method: "POST",
106186
+ headers: { "Content-Type": "application/json" },
106187
+ body: JSON.stringify({
106188
+ api_key: apiKey,
106189
+ query,
106190
+ search_depth: "advanced",
106191
+ include_answer: true,
106192
+ max_results: 5
106193
+ })
106194
+ });
106195
+ if (!res.ok)
106196
+ throw new Error(`Web search failed: ${res.status} ${res.statusText}`);
106197
+ const data2 = await res.json();
106198
+ return {
106199
+ results: data2.results.map((r) => ({
106200
+ title: r.title,
106201
+ url: r.url,
106202
+ content: r.content
106203
+ })),
106204
+ answer: data2.answer
106205
+ };
106206
+ }
105871
106207
  default:
105872
106208
  throw new Error(`Unknown tool ${toolName}`);
105873
106209
  }
@@ -105875,7 +106211,7 @@ async function executeLocalTool(toolName, input, mode) {
105875
106211
 
105876
106212
  // src/hooks/use-chat.ts
105877
106213
  function useChat2(sessionId, initialMessages) {
105878
- const transport = import_react51.useMemo(() => {
106214
+ const transport = import_react52.useMemo(() => {
105879
106215
  return new DefaultChatTransport({
105880
106216
  api: apiClient.chat.$url().toString(),
105881
106217
  headers() {
@@ -105963,6 +106299,7 @@ function ChatMessage({ msg }) {
105963
106299
  model: msg.metadata?.model ?? "unknown",
105964
106300
  mode: msg.metadata?.mode ?? "BUILD",
105965
106301
  durationMs: msg.metadata?.durationMs,
106302
+ usage: msg.metadata?.usage,
105966
106303
  streaming: false
105967
106304
  }, undefined, false, undefined, this);
105968
106305
  }
@@ -105986,14 +106323,14 @@ function SessionChat({
105986
106323
  session,
105987
106324
  initialPrompt
105988
106325
  }) {
105989
- const [initialMessages] = import_react53.useState(() => session.messages);
106326
+ const [initialMessages] = import_react54.useState(() => session.messages);
105990
106327
  const { mode, model, domain: domain2 } = usePromptConfig();
105991
106328
  const { isTopLayer } = useKeyboardLayer();
105992
106329
  const { messages, status, submit, setMessages, reload, abort, interrupt, error: error51 } = useChat2(session.id, initialMessages);
105993
- const hasSubmittedInitialPromptRef = import_react53.useRef(false);
106330
+ const hasSubmittedInitialPromptRef = import_react54.useRef(false);
105994
106331
  const toast = useToast();
105995
106332
  const dialog = useDialog();
105996
- import_react53.useEffect(() => {
106333
+ import_react54.useEffect(() => {
105997
106334
  return () => {
105998
106335
  abort();
105999
106336
  };
@@ -106014,7 +106351,7 @@ function SessionChat({
106014
106351
  }
106015
106352
  });
106016
106353
  useKeyboard((key) => {
106017
- if (key.name === "e" && !key.ctrl && isTopLayer("base") && status !== "streaming" && status !== "submitted") {
106354
+ if (key.name === "e" && key.ctrl && isTopLayer("base") && status !== "streaming" && status !== "submitted") {
106018
106355
  const lastUserMsg = messages.findLast((m2) => m2.role === "user");
106019
106356
  if (!lastUserMsg)
106020
106357
  return;
@@ -106052,7 +106389,7 @@ function SessionChat({
106052
106389
  });
106053
106390
  }
106054
106391
  });
106055
- import_react53.useEffect(() => {
106392
+ import_react54.useEffect(() => {
106056
106393
  if (!initialPrompt || hasSubmittedInitialPromptRef.current)
106057
106394
  return;
106058
106395
  hasSubmittedInitialPromptRef.current = true;
@@ -106066,6 +106403,8 @@ function SessionChat({
106066
106403
  onSubmit: (text3) => submit({ userText: text3, mode, model, domain: domain2 }),
106067
106404
  loading: status === "submitted" || status === "streaming",
106068
106405
  interruptible: status === "submitted" || status === "streaming",
106406
+ sessionId: session.id,
106407
+ messages,
106069
106408
  children: [
106070
106409
  messages.map((msg) => /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ChatMessage, {
106071
106410
  msg
@@ -106081,12 +106420,12 @@ function Session() {
106081
106420
  const location = useLocation();
106082
106421
  const navigate = useNavigate();
106083
106422
  const toast = useToast();
106084
- const prefetched = import_react53.useMemo(() => {
106423
+ const prefetched = import_react54.useMemo(() => {
106085
106424
  const parsed = sessionLocationSchema.safeParse(location.state);
106086
106425
  return parsed.success ? parsed.data : null;
106087
106426
  }, [location.state]);
106088
- const [session, setSession] = import_react53.useState(prefetched?.session ?? null);
106089
- import_react53.useEffect(() => {
106427
+ const [session, setSession] = import_react54.useState(prefetched?.session ?? null);
106428
+ import_react54.useEffect(() => {
106090
106429
  if (prefetched?.session)
106091
106430
  return;
106092
106431
  setSession(null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lupacode",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "AI-powered terminal coding assistant",
5
5
  "type": "module",
6
6
  "bin": {