lupacode 0.2.5 → 1.0.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 (2) hide show
  1. package/dist/index.js +1795 -579
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -70792,9 +70792,6 @@ var SplitBorderChars = {
70792
70792
 
70793
70793
  // src/providers/theme/index.tsx
70794
70794
  var import_react18 = __toESM(require_react(), 1);
70795
- import { mkdirSync, readFileSync, writeFileSync as writeFileSync2 } from "fs";
70796
- import { homedir } from "os";
70797
- import { join as join2 } from "path";
70798
70795
 
70799
70796
  // src/theme.ts
70800
70797
  var THEMES = [
@@ -70804,7 +70801,7 @@ var THEMES = [
70804
70801
  primary: "#719cd6",
70805
70802
  planMode: "#db9b59",
70806
70803
  selectMode: "#b55c7a",
70807
- thinking: "#dfdfe0",
70804
+ thinking: "#9DDAF6",
70808
70805
  success: "#81b29a",
70809
70806
  error: "#c94f6d",
70810
70807
  info: "#7dcfff",
@@ -70923,7 +70920,7 @@ var THEMES = [
70923
70920
  primary: "#C4A7E7",
70924
70921
  planMode: "#F6C177",
70925
70922
  selectMode: "#9CCFD8",
70926
- thinking: "#31748F",
70923
+ thinking: "#9CCFD8",
70927
70924
  success: "#9CCFD8",
70928
70925
  error: "#EB6F92",
70929
70926
  info: "#C4A7E7",
@@ -71035,343 +71032,354 @@ var THEMES = [
71035
71032
  thinkingBorder: "#22DA6E",
71036
71033
  dimSeparator: "#5F7E97"
71037
71034
  }
71038
- }
71039
- ];
71040
- var DEFAULT_THEME = THEMES.find((t2) => t2.name === "Nightfox");
71041
-
71042
- // ../../node_modules/@opentui/react/jsx-dev-runtime.js
71043
- var import_jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
71044
-
71045
- // src/providers/theme/index.tsx
71046
- var CONFIG_DIR = join2(homedir(), ".lupacode");
71047
- var THEME_PREFERENCES_PATH = join2(CONFIG_DIR, "preferences.json");
71048
- function getInitialTheme() {
71049
- try {
71050
- const preferences = JSON.parse(readFileSync(THEME_PREFERENCES_PATH, "utf8"));
71051
- const savedTheme = THEMES.find((theme) => theme.name === preferences.themeName);
71052
- return savedTheme ?? DEFAULT_THEME;
71053
- } catch {
71054
- return DEFAULT_THEME;
71055
- }
71056
- }
71057
- function persistTheme(theme) {
71058
- try {
71059
- mkdirSync(CONFIG_DIR, { recursive: true });
71060
- writeFileSync2(THEME_PREFERENCES_PATH, JSON.stringify({ themeName: theme.name }, null, 2), "utf8");
71061
- } catch {}
71062
- }
71063
- var ThemeContext = import_react18.createContext(null);
71064
- function useTheme() {
71065
- const value = import_react18.useContext(ThemeContext);
71066
- if (!value) {
71067
- throw new Error("useTheme must be used within a ThemeProvider");
71068
- }
71069
- return value;
71070
- }
71071
- function ThemeProvider({ children }) {
71072
- const [currentTheme, setCurrentTheme] = import_react18.useState(getInitialTheme);
71073
- const setTheme = import_react18.useCallback((theme) => {
71074
- setCurrentTheme(theme);
71075
- persistTheme(theme);
71076
- }, []);
71077
- return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ThemeContext.Provider, {
71078
- value: { colors: currentTheme.colors, currentTheme, setTheme },
71079
- children
71080
- }, undefined, false, undefined, this);
71081
- }
71082
-
71083
- // src/providers/toast/index.tsx
71084
- var ToastContext = import_react19.createContext(null);
71085
- function useToast() {
71086
- const value = import_react19.useContext(ToastContext);
71087
- if (!value) {
71088
- throw new Error("useToast must be used within a ToastProvider");
71089
- }
71090
- return value;
71091
- }
71092
- function ToastProvider({ children }) {
71093
- const [currentToast, setCurrentToast] = import_react19.useState(null);
71094
- const timeoutHandleRef = import_react19.useRef(null);
71095
- const clearCurrentTimeout = import_react19.useCallback(() => {
71096
- if (timeoutHandleRef.current) {
71097
- clearTimeout(timeoutHandleRef.current);
71098
- timeoutHandleRef.current = null;
71035
+ },
71036
+ {
71037
+ name: "Everforest Dark",
71038
+ colors: {
71039
+ primary: "#7FBBB3",
71040
+ planMode: "#DBBC7F",
71041
+ selectMode: "#D699B6",
71042
+ thinking: "#A7C080",
71043
+ success: "#A7C080",
71044
+ error: "#E67E80",
71045
+ info: "#83C092",
71046
+ background: "#2D353B",
71047
+ surface: "#343F44",
71048
+ dialogSurface: "#3D484D",
71049
+ thinkingBorder: "#A7C080",
71050
+ dimSeparator: "#4F585E"
71099
71051
  }
71100
- }, []);
71101
- const show = import_react19.useCallback((options) => {
71102
- const duration = options.duration ?? DEFAULT_DURATION;
71103
- clearCurrentTimeout();
71104
- setCurrentToast({
71105
- variant: options.variant ?? "info",
71106
- ...options,
71107
- duration
71108
- });
71109
- timeoutHandleRef.current = setTimeout(() => {
71110
- setCurrentToast(null);
71111
- }, duration).unref();
71112
- }, [clearCurrentTimeout]);
71113
- const value = import_react19.useMemo(() => ({ show }), [show]);
71114
- return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ToastContext.Provider, {
71115
- value,
71116
- children: [
71117
- children,
71118
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(Toast, {
71119
- currentToast
71120
- }, undefined, false, undefined, this)
71121
- ]
71122
- }, undefined, true, undefined, this);
71123
- }
71124
- function Toast({ currentToast }) {
71125
- const { width } = useTerminalDimensions();
71126
- const { colors } = useTheme();
71127
- if (!currentToast) {
71128
- return null;
71129
- }
71130
- const variantColors = {
71131
- success: colors.success,
71132
- error: colors.error,
71133
- info: colors.info
71134
- };
71135
- const borderColor = currentToast.variant ? variantColors[currentToast.variant] : variantColors.info;
71136
- return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
71137
- position: "absolute",
71138
- justifyContent: "center",
71139
- alignItems: "flex-start",
71140
- top: 2,
71141
- right: 2,
71142
- width: Math.max(1, Math.min(60, width - 6)),
71143
- paddingLeft: 2,
71144
- paddingRight: 2,
71145
- paddingTop: 1,
71146
- paddingBottom: 1,
71147
- backgroundColor: colors.surface,
71148
- borderColor,
71149
- border: ["left", "right"],
71150
- customBorderChars: SplitBorderChars,
71151
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
71152
- flexDirection: "column",
71153
- gap: 1,
71154
- width: "100%",
71155
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
71156
- fg: "#E1E1E1",
71157
- wrapMode: "word",
71158
- width: "100%",
71159
- children: currentToast.message
71160
- }, undefined, false, undefined, this)
71161
- }, undefined, false, undefined, this)
71162
- }, undefined, false, undefined, this);
71163
- }
71164
-
71165
- // src/providers/dialog/index.tsx
71166
- var import_react24 = __toESM(require_react(), 1);
71167
-
71168
- // src/providers/keyboard-layer/index.tsx
71169
- var import_react21 = __toESM(require_react(), 1);
71170
- var KeyboardLayerContext = import_react21.createContext(null);
71171
- function KeyboardLayerProvider({ children }) {
71172
- const [stack, setStack] = import_react21.useState(["base"]);
71173
- const stackRef = import_react21.useRef(stack);
71174
- stackRef.current = stack;
71175
- const responders = import_react21.useRef(new Map);
71176
- const renderer = useRenderer();
71177
- const push = import_react21.useCallback((id, responder) => {
71178
- if (responder) {
71179
- responders.current.set(id, responder);
71052
+ },
71053
+ {
71054
+ name: "Kanagawa Wave",
71055
+ colors: {
71056
+ primary: "#7E9CD8",
71057
+ planMode: "#E6C384",
71058
+ selectMode: "#957FB8",
71059
+ thinking: "#98BB6C",
71060
+ success: "#98BB6C",
71061
+ error: "#E46876",
71062
+ info: "#7FB4CA",
71063
+ background: "#1F1F28",
71064
+ surface: "#2A2A37",
71065
+ dialogSurface: "#363646",
71066
+ thinkingBorder: "#98BB6C",
71067
+ dimSeparator: "#54546D"
71180
71068
  }
71181
- setStack((prev) => {
71182
- if (prev.includes(id)) {
71183
- return prev;
71184
- }
71185
- return [...prev, id];
71186
- });
71187
- }, []);
71188
- const pop = import_react21.useCallback((id) => {
71189
- responders.current.delete(id);
71190
- setStack((prev) => prev.filter((layer) => layer !== id));
71191
- }, []);
71192
- const isTopLayer = import_react21.useCallback((id) => {
71193
- return stack.length === 0 || stack[stack.length - 1] === id;
71194
- }, [stack]);
71195
- const setResponder = import_react21.useCallback((id, responder) => {
71196
- if (responder) {
71197
- responders.current.set(id, responder);
71198
- } else {
71199
- responders.current.delete(id);
71069
+ },
71070
+ {
71071
+ name: "Monokai Pro",
71072
+ colors: {
71073
+ primary: "#78DCE8",
71074
+ planMode: "#FFD866",
71075
+ selectMode: "#AB9DF2",
71076
+ thinking: "#A9DC76",
71077
+ success: "#A9DC76",
71078
+ error: "#FF6188",
71079
+ info: "#78DCE8",
71080
+ background: "#2D2A2E",
71081
+ surface: "#36333A",
71082
+ dialogSurface: "#403E41",
71083
+ thinkingBorder: "#A9DC76",
71084
+ dimSeparator: "#727072"
71200
71085
  }
71201
- }, []);
71202
- useKeyboard((key) => {
71203
- if (!key.ctrl || key.name !== "c")
71204
- return;
71205
- const currentStack = stackRef.current;
71206
- for (let i = currentStack.length - 1;i >= 0; i--) {
71207
- const layerId = currentStack[i];
71208
- const responder = responders.current.get(layerId);
71209
- if (responder && responder()) {
71210
- return;
71211
- }
71086
+ },
71087
+ {
71088
+ name: "Material Theme",
71089
+ colors: {
71090
+ primary: "#82AAFF",
71091
+ planMode: "#FFCB6B",
71092
+ selectMode: "#C792EA",
71093
+ thinking: "#C3E88D",
71094
+ success: "#C3E88D",
71095
+ error: "#F07178",
71096
+ info: "#89DDFF",
71097
+ background: "#263238",
71098
+ surface: "#2F3B43",
71099
+ dialogSurface: "#37474F",
71100
+ thinkingBorder: "#C3E88D",
71101
+ dimSeparator: "#546E7A"
71212
71102
  }
71213
- renderer.destroy();
71214
- });
71215
- return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(KeyboardLayerContext.Provider, {
71216
- value: { push, pop, isTopLayer, setResponder },
71217
- children
71218
- }, undefined, false, undefined, this);
71219
- }
71220
- function useKeyboardLayer() {
71221
- const context = import_react21.useContext(KeyboardLayerContext);
71222
- if (!context) {
71223
- throw new Error("useKeyboardLayer must be used within a KeyboardLayerProvider");
71224
- }
71225
- return context;
71226
- }
71227
-
71228
- // src/components/error-boundary.tsx
71229
- var import_react23 = __toESM(require_react(), 1);
71230
- class ErrorBoundary2 extends import_react23.Component {
71231
- constructor(props) {
71232
- super(props);
71233
- this.state = { error: null };
71234
- }
71235
- static getDerivedStateFromError(error) {
71236
- return { error };
71237
- }
71238
- componentDidCatch(error, errorInfo) {
71239
- console.error("[ErrorBoundary] Caught error:", error, errorInfo.componentStack);
71240
- }
71241
- render() {
71242
- if (this.state.error) {
71243
- console.error("[ErrorBoundary] Rendering fallback for:", this.state.error.message);
71244
- return this.props.fallback ?? /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
71245
- flexDirection: "column",
71246
- padding: 1,
71247
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
71248
- children: [
71249
- "Error: ",
71250
- this.state.error.message
71251
- ]
71252
- }, undefined, true, undefined, this)
71253
- }, undefined, false, undefined, this);
71103
+ },
71104
+ {
71105
+ name: "Rose Pine Moon",
71106
+ colors: {
71107
+ primary: "#C4A7E7",
71108
+ planMode: "#F6C177",
71109
+ selectMode: "#9CCFD8",
71110
+ thinking: "#31748F",
71111
+ success: "#9CCFD8",
71112
+ error: "#EB6F92",
71113
+ info: "#C4A7E7",
71114
+ background: "#232136",
71115
+ surface: "#2A273F",
71116
+ dialogSurface: "#393552",
71117
+ thinkingBorder: "#31748F",
71118
+ dimSeparator: "#59546D"
71254
71119
  }
71255
- return this.props.children;
71256
- }
71257
- }
71258
-
71259
- // src/providers/dialog/index.tsx
71260
- var DialogContext = import_react24.createContext(null);
71261
- function useDialog() {
71262
- const value = import_react24.useContext(DialogContext);
71263
- if (!value) {
71264
- throw new Error("useDialog must be used within a DialogProvider");
71265
- }
71266
- return value;
71267
- }
71268
- function DialogProvider({ children }) {
71269
- const [currentDialog, setCurrentDialog] = import_react24.useState(null);
71270
- const { push, pop } = useKeyboardLayer();
71271
- const close = import_react24.useCallback(() => {
71272
- setCurrentDialog(null);
71273
- pop("dialog");
71274
- }, [pop]);
71275
- const open = import_react24.useCallback((config) => {
71276
- setCurrentDialog(config);
71277
- push("dialog", () => {
71278
- close();
71279
- return true;
71280
- });
71281
- }, [push, close]);
71282
- const value = {
71283
- open,
71284
- close
71285
- };
71286
- return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(DialogContext.Provider, {
71287
- value,
71288
- children: [
71289
- children,
71290
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(Dialog, {
71291
- currentDialog,
71292
- close
71293
- }, undefined, false, undefined, this)
71294
- ]
71295
- }, undefined, true, undefined, this);
71296
- }
71297
- function Dialog({ currentDialog, close }) {
71298
- const { isTopLayer } = useKeyboardLayer();
71299
- const dimensions = useTerminalDimensions();
71300
- const { colors } = useTheme();
71301
- useKeyboard((key) => {
71302
- if (!currentDialog || !isTopLayer("dialog"))
71303
- return;
71304
- if (key.name === "escape") {
71305
- close();
71120
+ },
71121
+ {
71122
+ name: "GitHub Dark",
71123
+ colors: {
71124
+ primary: "#58A6FF",
71125
+ planMode: "#E3B341",
71126
+ selectMode: "#A371F7",
71127
+ thinking: "#3FB950",
71128
+ success: "#3FB950",
71129
+ error: "#F85149",
71130
+ info: "#79C0FF",
71131
+ background: "#0D1117",
71132
+ surface: "#161B22",
71133
+ dialogSurface: "#21262D",
71134
+ thinkingBorder: "#3FB950",
71135
+ dimSeparator: "#30363D"
71136
+ }
71137
+ },
71138
+ {
71139
+ name: "SynthWave '84",
71140
+ colors: {
71141
+ primary: "#36F9F6",
71142
+ planMode: "#FFE347",
71143
+ selectMode: "#FF7EDB",
71144
+ thinking: "#72F1B8",
71145
+ success: "#72F1B8",
71146
+ error: "#FE4450",
71147
+ info: "#36F9F6",
71148
+ background: "#262335",
71149
+ surface: "#34294F",
71150
+ dialogSurface: "#433C68",
71151
+ thinkingBorder: "#72F1B8",
71152
+ dimSeparator: "#5B5478"
71153
+ }
71154
+ },
71155
+ {
71156
+ name: "Horizon",
71157
+ colors: {
71158
+ primary: "#26BBD9",
71159
+ planMode: "#FAC29A",
71160
+ selectMode: "#EE64AC",
71161
+ thinking: "#29D398",
71162
+ success: "#29D398",
71163
+ error: "#E95678",
71164
+ info: "#59E3E3",
71165
+ background: "#1C1E26",
71166
+ surface: "#232530",
71167
+ dialogSurface: "#2E303E",
71168
+ thinkingBorder: "#29D398",
71169
+ dimSeparator: "#6C6F93"
71170
+ }
71171
+ },
71172
+ {
71173
+ name: "Arctic Ice",
71174
+ colors: {
71175
+ primary: "#88C0D0",
71176
+ planMode: "#EBCB8B",
71177
+ selectMode: "#81A1C1",
71178
+ thinking: "#A3BE8C",
71179
+ success: "#A3BE8C",
71180
+ error: "#BF616A",
71181
+ info: "#5E81AC",
71182
+ background: "#242933",
71183
+ surface: "#2E3440",
71184
+ dialogSurface: "#3B4252",
71185
+ thinkingBorder: "#A3BE8C",
71186
+ dimSeparator: "#4C566A"
71187
+ }
71188
+ },
71189
+ {
71190
+ name: "Midnight Purple",
71191
+ colors: {
71192
+ primary: "#9D7DFF",
71193
+ planMode: "#F8C555",
71194
+ selectMode: "#B388FF",
71195
+ thinking: "#43D787",
71196
+ success: "#43D787",
71197
+ error: "#FF5D73",
71198
+ info: "#59C8FF",
71199
+ background: "#11111B",
71200
+ surface: "#1B1B2A",
71201
+ dialogSurface: "#25253A",
71202
+ thinkingBorder: "#43D787",
71203
+ dimSeparator: "#47475A"
71204
+ }
71205
+ },
71206
+ {
71207
+ name: "Poimandres",
71208
+ colors: {
71209
+ primary: "#89DDFF",
71210
+ planMode: "#FFE66D",
71211
+ selectMode: "#C792EA",
71212
+ thinking: "#5DE4C7",
71213
+ success: "#5DE4C7",
71214
+ error: "#D0679D",
71215
+ info: "#91B4D5",
71216
+ background: "#1B1E28",
71217
+ surface: "#222635",
71218
+ dialogSurface: "#2A3042",
71219
+ thinkingBorder: "#5DE4C7",
71220
+ dimSeparator: "#4F5B76"
71221
+ }
71222
+ },
71223
+ {
71224
+ name: "Vesper",
71225
+ colors: {
71226
+ primary: "#99FFE4",
71227
+ planMode: "#FFC799",
71228
+ selectMode: "#FCA7EA",
71229
+ thinking: "#A1EFD3",
71230
+ success: "#A1EFD3",
71231
+ error: "#F07178",
71232
+ info: "#8BD5FF",
71233
+ background: "#101010",
71234
+ surface: "#181818",
71235
+ dialogSurface: "#222222",
71236
+ thinkingBorder: "#A1EFD3",
71237
+ dimSeparator: "#3B3B3B"
71238
+ }
71239
+ },
71240
+ {
71241
+ name: "Oxocarbon",
71242
+ colors: {
71243
+ primary: "#78A9FF",
71244
+ planMode: "#FF7EB6",
71245
+ selectMode: "#BE95FF",
71246
+ thinking: "#42BE65",
71247
+ success: "#42BE65",
71248
+ error: "#EE5396",
71249
+ info: "#33B1FF",
71250
+ background: "#161616",
71251
+ surface: "#262626",
71252
+ dialogSurface: "#393939",
71253
+ thinkingBorder: "#42BE65",
71254
+ dimSeparator: "#525252"
71255
+ }
71256
+ },
71257
+ {
71258
+ name: "Flexoki Dark",
71259
+ colors: {
71260
+ primary: "#4385BE",
71261
+ planMode: "#DA702C",
71262
+ selectMode: "#8B7EC8",
71263
+ thinking: "#879A39",
71264
+ success: "#879A39",
71265
+ error: "#D14D41",
71266
+ info: "#3AA99F",
71267
+ background: "#100F0F",
71268
+ surface: "#1C1B1A",
71269
+ dialogSurface: "#282726",
71270
+ thinkingBorder: "#879A39",
71271
+ dimSeparator: "#575653"
71272
+ }
71273
+ },
71274
+ {
71275
+ name: "Dark Plus",
71276
+ colors: {
71277
+ primary: "#569CD6",
71278
+ planMode: "#DCDCAA",
71279
+ selectMode: "#C586C0",
71280
+ thinking: "#6A9955",
71281
+ success: "#6A9955",
71282
+ error: "#F44747",
71283
+ info: "#4FC1FF",
71284
+ background: "#1E1E1E",
71285
+ surface: "#252526",
71286
+ dialogSurface: "#2D2D30",
71287
+ thinkingBorder: "#6A9955",
71288
+ dimSeparator: "#3E3E42"
71289
+ }
71290
+ },
71291
+ {
71292
+ name: "Dark Modern",
71293
+ colors: {
71294
+ primary: "#4FC1FF",
71295
+ planMode: "#E5C07B",
71296
+ selectMode: "#C678DD",
71297
+ thinking: "#7EE787",
71298
+ success: "#7EE787",
71299
+ error: "#FF7B72",
71300
+ info: "#79C0FF",
71301
+ background: "#181818",
71302
+ surface: "#212121",
71303
+ dialogSurface: "#2B2B2B",
71304
+ thinkingBorder: "#7EE787",
71305
+ dimSeparator: "#444444"
71306
+ }
71307
+ },
71308
+ {
71309
+ name: "Dark+ (Default Dark)",
71310
+ colors: {
71311
+ primary: "#569CD6",
71312
+ planMode: "#DCDCAA",
71313
+ selectMode: "#C586C0",
71314
+ thinking: "#6A9955",
71315
+ success: "#6A9955",
71316
+ error: "#F44747",
71317
+ info: "#4FC1FF",
71318
+ background: "#1E1E1E",
71319
+ surface: "#252526",
71320
+ dialogSurface: "#2D2D30",
71321
+ thinkingBorder: "#6A9955",
71322
+ dimSeparator: "#3E3E42"
71323
+ }
71324
+ },
71325
+ {
71326
+ name: "Dark Modern",
71327
+ colors: {
71328
+ primary: "#75BEFF",
71329
+ planMode: "#E5C07B",
71330
+ selectMode: "#D2A8FF",
71331
+ thinking: "#7EE787",
71332
+ success: "#7EE787",
71333
+ error: "#FF7B72",
71334
+ info: "#79C0FF",
71335
+ background: "#181818",
71336
+ surface: "#202020",
71337
+ dialogSurface: "#2A2A2A",
71338
+ thinkingBorder: "#7EE787",
71339
+ dimSeparator: "#3F3F46"
71340
+ }
71341
+ },
71342
+ {
71343
+ name: "High Contrast Dark",
71344
+ colors: {
71345
+ primary: "#3B8EEA",
71346
+ planMode: "#FFCC00",
71347
+ selectMode: "#B180D7",
71348
+ thinking: "#89D185",
71349
+ success: "#89D185",
71350
+ error: "#F14C4C",
71351
+ info: "#75BEFF",
71352
+ background: "#000000",
71353
+ surface: "#0F0F0F",
71354
+ dialogSurface: "#1A1A1A",
71355
+ thinkingBorder: "#89D185",
71356
+ dimSeparator: "#6B6B6B"
71357
+ }
71358
+ },
71359
+ {
71360
+ name: "High Contrast Black",
71361
+ colors: {
71362
+ primary: "#3794FF",
71363
+ planMode: "#FFD700",
71364
+ selectMode: "#C586C0",
71365
+ thinking: "#00FF00",
71366
+ success: "#00FF00",
71367
+ error: "#FF5555",
71368
+ info: "#4FC1FF",
71369
+ background: "#000000",
71370
+ surface: "#111111",
71371
+ dialogSurface: "#1C1C1C",
71372
+ thinkingBorder: "#00FF00",
71373
+ dimSeparator: "#666666"
71306
71374
  }
71307
- });
71308
- if (!currentDialog) {
71309
- return null;
71310
71375
  }
71311
- const { title, children } = currentDialog;
71312
- return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
71313
- position: "absolute",
71314
- left: 0,
71315
- top: 0,
71316
- width: dimensions.width,
71317
- height: dimensions.height,
71318
- justifyContent: "center",
71319
- alignItems: "center",
71320
- backgroundColor: RGBA.fromInts(0, 0, 0, 150),
71321
- zIndex: 100,
71322
- onMouseDown: () => close(),
71323
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
71324
- width: Math.min(60, dimensions.width - 4),
71325
- height: "auto",
71326
- backgroundColor: colors.dialogSurface,
71327
- paddingX: 4,
71328
- paddingY: 1,
71329
- flexDirection: "column",
71330
- gap: 1,
71331
- onMouseDown: (e) => e.stopPropagation(),
71332
- children: [
71333
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
71334
- paddingBottom: 1,
71335
- flexDirection: "row",
71336
- alignItems: "center",
71337
- justifyContent: "space-between",
71338
- children: [
71339
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
71340
- attributes: TextAttributes.BOLD,
71341
- children: title
71342
- }, undefined, false, undefined, this),
71343
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
71344
- attributes: TextAttributes.DIM,
71345
- onMouseDown: () => close(),
71346
- children: "esc"
71347
- }, undefined, false, undefined, this)
71348
- ]
71349
- }, undefined, true, undefined, this),
71350
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
71351
- flexGrow: 1,
71352
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ErrorBoundary2, {
71353
- children
71354
- }, undefined, false, undefined, this)
71355
- }, undefined, false, undefined, this)
71356
- ]
71357
- }, undefined, true, undefined, this)
71358
- }, undefined, false, undefined, this);
71359
- }
71360
-
71361
- // src/layouts/themed-root.tsx
71362
- function ThemedRoot({ children }) {
71363
- const { colors } = useTheme();
71364
- return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
71365
- backgroundColor: colors.background,
71366
- width: "100%",
71367
- height: "100%",
71368
- flexGrow: 1,
71369
- children
71370
- }, undefined, false, undefined, this);
71371
- }
71376
+ ];
71377
+ var DEFAULT_THEME = THEMES.find((t2) => t2.name === "Nightfox");
71372
71378
 
71373
- // src/providers/prompt-config/index.tsx
71374
- var import_react26 = __toESM(require_react(), 1);
71379
+ // src/lib/config.ts
71380
+ import { mkdirSync, readFileSync, writeFileSync as writeFileSync2 } from "fs";
71381
+ import { homedir } from "os";
71382
+ import { join as join2 } from "path";
71375
71383
 
71376
71384
  // ../../node_modules/zod/v4/classic/external.js
71377
71385
  var exports_external = {};
@@ -85685,6 +85693,12 @@ var toolInputSchemas = {
85685
85693
  command: exports_external.string().describe("Shell command to run"),
85686
85694
  description: exports_external.string().optional().describe("Short description of the command"),
85687
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")
85688
85702
  })
85689
85703
  };
85690
85704
  // node_modules/ai/node_modules/@ai-sdk/provider/dist/index.js
@@ -93393,10 +93407,18 @@ var readOnlyToolContracts = {
93393
93407
  grep: tool({
93394
93408
  description: "Search file contents with a regular expression under the current project directory.",
93395
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
93396
93414
  })
93397
93415
  };
93398
93416
  var buildToolContracts = {
93399
93417
  ...readOnlyToolContracts,
93418
+ deleteFile: tool({
93419
+ description: "Delete a file under the current project directory.",
93420
+ inputSchema: toolInputSchemas.deleteFile
93421
+ }),
93400
93422
  writeFile: tool({
93401
93423
  description: "Create or overwrite a file under the current project directory.",
93402
93424
  inputSchema: toolInputSchemas.writeFile
@@ -93413,7 +93435,7 @@ var buildToolContracts = {
93413
93435
  // src/lib/shared/models.ts
93414
93436
  var SUPPORTED_CHAT_MODELS = [
93415
93437
  {
93416
- id: "claude-sonnet-4-20250514",
93438
+ id: "claude-sonnet-5",
93417
93439
  provider: "anthropic",
93418
93440
  pricing: {
93419
93441
  inputUsdPerMillionTokens: 3,
@@ -93421,7 +93443,7 @@ var SUPPORTED_CHAT_MODELS = [
93421
93443
  }
93422
93444
  },
93423
93445
  {
93424
- id: "claude-3-5-haiku-latest",
93446
+ id: "claude-haiku-4-5",
93425
93447
  provider: "anthropic",
93426
93448
  pricing: {
93427
93449
  inputUsdPerMillionTokens: 1,
@@ -93429,7 +93451,7 @@ var SUPPORTED_CHAT_MODELS = [
93429
93451
  }
93430
93452
  },
93431
93453
  {
93432
- id: "claude-opus-4-20250514",
93454
+ id: "claude-opus-4-8",
93433
93455
  provider: "anthropic",
93434
93456
  pricing: {
93435
93457
  inputUsdPerMillionTokens: 5,
@@ -93437,7 +93459,7 @@ var SUPPORTED_CHAT_MODELS = [
93437
93459
  }
93438
93460
  },
93439
93461
  {
93440
- id: "gpt-4.1",
93462
+ id: "gpt-5.5",
93441
93463
  provider: "openai",
93442
93464
  pricing: {
93443
93465
  inputUsdPerMillionTokens: 2.5,
@@ -93445,7 +93467,7 @@ var SUPPORTED_CHAT_MODELS = [
93445
93467
  }
93446
93468
  },
93447
93469
  {
93448
- id: "gpt-4.1-mini",
93470
+ id: "gpt-5.4-mini",
93449
93471
  provider: "openai",
93450
93472
  pricing: {
93451
93473
  inputUsdPerMillionTokens: 0.75,
@@ -93453,7 +93475,7 @@ var SUPPORTED_CHAT_MODELS = [
93453
93475
  }
93454
93476
  },
93455
93477
  {
93456
- id: "gpt-4.1-nano",
93478
+ id: "gpt-5.4-nano",
93457
93479
  provider: "openai",
93458
93480
  pricing: {
93459
93481
  inputUsdPerMillionTokens: 0.2,
@@ -93461,19 +93483,19 @@ var SUPPORTED_CHAT_MODELS = [
93461
93483
  }
93462
93484
  },
93463
93485
  {
93464
- id: "qwen/qwen3-coder",
93486
+ id: "cohere/north-mini-code:free",
93465
93487
  provider: "openrouter",
93466
93488
  pricing: {
93467
- inputUsdPerMillionTokens: 0.3,
93468
- outputUsdPerMillionTokens: 1.2
93489
+ inputUsdPerMillionTokens: 0,
93490
+ outputUsdPerMillionTokens: 0
93469
93491
  }
93470
93492
  },
93471
93493
  {
93472
- id: "qwen/qwen3-32b",
93473
- provider: "groq",
93494
+ id: "deepseek/deepseek-chat-v3",
93495
+ provider: "openrouter",
93474
93496
  pricing: {
93475
- inputUsdPerMillionTokens: 0.29,
93476
- outputUsdPerMillionTokens: 0.59
93497
+ inputUsdPerMillionTokens: 0.27,
93498
+ outputUsdPerMillionTokens: 1.1
93477
93499
  }
93478
93500
  },
93479
93501
  {
@@ -93485,11 +93507,67 @@ var SUPPORTED_CHAT_MODELS = [
93485
93507
  }
93486
93508
  },
93487
93509
  {
93488
- id: "deepseek/deepseek-chat-v3",
93510
+ id: "deepseek/deepseek-v3.2",
93489
93511
  provider: "openrouter",
93490
93512
  pricing: {
93491
- inputUsdPerMillionTokens: 0.27,
93492
- 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
93493
93571
  }
93494
93572
  },
93495
93573
  {
@@ -93500,6 +93578,22 @@ var SUPPORTED_CHAT_MODELS = [
93500
93578
  outputUsdPerMillionTokens: 0
93501
93579
  }
93502
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
+ },
93503
93597
  {
93504
93598
  id: "llama-3.3-70b-versatile",
93505
93599
  provider: "groq",
@@ -93547,7 +93641,7 @@ function findSupportedChatModel(modelId) {
93547
93641
  function isFreeModel(model) {
93548
93642
  return model.pricing.inputUsdPerMillionTokens === 0 && model.pricing.outputUsdPerMillionTokens === 0;
93549
93643
  }
93550
- var DEFAULT_CHAT_MODEL_ID = "qwen/qwen3-32b";
93644
+ var DEFAULT_CHAT_MODEL_ID = "deepseek/deepseek-v4-flash";
93551
93645
  // src/lib/shared/domains.ts
93552
93646
  var DOMAINS = [
93553
93647
  {
@@ -93565,7 +93659,357 @@ function findDomain(id) {
93565
93659
  return DOMAINS.find((d2) => d2.id === id);
93566
93660
  }
93567
93661
  var DEFAULT_DOMAIN_ID = "coding";
93662
+ // src/lib/config.ts
93663
+ var CONFIG_DIR = join2(homedir(), ".lupacode");
93664
+ var CONFIG_PATH = join2(CONFIG_DIR, "preferences.json");
93665
+ var DEFAULTS = {
93666
+ themeName: "Nightfox",
93667
+ mode: Mode.BUILD,
93668
+ model: DEFAULT_CHAT_MODEL_ID,
93669
+ domain: DEFAULT_DOMAIN_ID
93670
+ };
93671
+ function ensureDir() {
93672
+ mkdirSync(CONFIG_DIR, { recursive: true });
93673
+ }
93674
+ function readPreferences() {
93675
+ try {
93676
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
93677
+ } catch {
93678
+ return {};
93679
+ }
93680
+ }
93681
+ function writePreferences(prefs) {
93682
+ try {
93683
+ const current = readPreferences();
93684
+ ensureDir();
93685
+ writeFileSync2(CONFIG_PATH, JSON.stringify({ ...current, ...prefs }, null, 2), "utf8");
93686
+ } catch {}
93687
+ }
93688
+
93689
+ // ../../node_modules/@opentui/react/jsx-dev-runtime.js
93690
+ var import_jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
93691
+
93692
+ // src/providers/theme/index.tsx
93693
+ function getInitialTheme() {
93694
+ const prefs = readPreferences();
93695
+ const savedTheme = typeof prefs.themeName === "string" ? THEMES.find((t2) => t2.name === prefs.themeName) : undefined;
93696
+ return savedTheme ?? DEFAULT_THEME;
93697
+ }
93698
+ function persistTheme(theme) {
93699
+ writePreferences({ themeName: theme.name });
93700
+ }
93701
+ var ThemeContext = import_react18.createContext(null);
93702
+ function useTheme() {
93703
+ const value = import_react18.useContext(ThemeContext);
93704
+ if (!value) {
93705
+ throw new Error("useTheme must be used within a ThemeProvider");
93706
+ }
93707
+ return value;
93708
+ }
93709
+ function ThemeProvider({ children }) {
93710
+ const [currentTheme, setCurrentTheme] = import_react18.useState(getInitialTheme);
93711
+ const setTheme = import_react18.useCallback((theme) => {
93712
+ setCurrentTheme(theme);
93713
+ persistTheme(theme);
93714
+ }, []);
93715
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ThemeContext.Provider, {
93716
+ value: { colors: currentTheme.colors, currentTheme, setTheme },
93717
+ children
93718
+ }, undefined, false, undefined, this);
93719
+ }
93720
+
93721
+ // src/providers/toast/index.tsx
93722
+ var ToastContext = import_react19.createContext(null);
93723
+ function useToast() {
93724
+ const value = import_react19.useContext(ToastContext);
93725
+ if (!value) {
93726
+ throw new Error("useToast must be used within a ToastProvider");
93727
+ }
93728
+ return value;
93729
+ }
93730
+ function ToastProvider({ children }) {
93731
+ const [currentToast, setCurrentToast] = import_react19.useState(null);
93732
+ const timeoutHandleRef = import_react19.useRef(null);
93733
+ const clearCurrentTimeout = import_react19.useCallback(() => {
93734
+ if (timeoutHandleRef.current) {
93735
+ clearTimeout(timeoutHandleRef.current);
93736
+ timeoutHandleRef.current = null;
93737
+ }
93738
+ }, []);
93739
+ const show = import_react19.useCallback((options) => {
93740
+ const duration3 = options.duration ?? DEFAULT_DURATION;
93741
+ clearCurrentTimeout();
93742
+ setCurrentToast({
93743
+ variant: options.variant ?? "info",
93744
+ ...options,
93745
+ duration: duration3
93746
+ });
93747
+ timeoutHandleRef.current = setTimeout(() => {
93748
+ setCurrentToast(null);
93749
+ }, duration3).unref();
93750
+ }, [clearCurrentTimeout]);
93751
+ const value = import_react19.useMemo(() => ({ show }), [show]);
93752
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ToastContext.Provider, {
93753
+ value,
93754
+ children: [
93755
+ children,
93756
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(Toast, {
93757
+ currentToast
93758
+ }, undefined, false, undefined, this)
93759
+ ]
93760
+ }, undefined, true, undefined, this);
93761
+ }
93762
+ function Toast({ currentToast }) {
93763
+ const { width } = useTerminalDimensions();
93764
+ const { colors } = useTheme();
93765
+ if (!currentToast) {
93766
+ return null;
93767
+ }
93768
+ const variantColors = {
93769
+ success: colors.success,
93770
+ error: colors.error,
93771
+ info: colors.info
93772
+ };
93773
+ const borderColor = currentToast.variant ? variantColors[currentToast.variant] : variantColors.info;
93774
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
93775
+ position: "absolute",
93776
+ justifyContent: "center",
93777
+ alignItems: "flex-start",
93778
+ top: 2,
93779
+ right: 2,
93780
+ width: Math.max(1, Math.min(60, width - 6)),
93781
+ paddingLeft: 2,
93782
+ paddingRight: 2,
93783
+ paddingTop: 1,
93784
+ paddingBottom: 1,
93785
+ backgroundColor: colors.surface,
93786
+ borderColor,
93787
+ border: ["left", "right"],
93788
+ customBorderChars: SplitBorderChars,
93789
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
93790
+ flexDirection: "column",
93791
+ gap: 1,
93792
+ width: "100%",
93793
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
93794
+ fg: "#E1E1E1",
93795
+ wrapMode: "word",
93796
+ width: "100%",
93797
+ children: currentToast.message
93798
+ }, undefined, false, undefined, this)
93799
+ }, undefined, false, undefined, this)
93800
+ }, undefined, false, undefined, this);
93801
+ }
93802
+
93803
+ // src/providers/dialog/index.tsx
93804
+ var import_react24 = __toESM(require_react(), 1);
93805
+
93806
+ // src/providers/keyboard-layer/index.tsx
93807
+ var import_react21 = __toESM(require_react(), 1);
93808
+ var KeyboardLayerContext = import_react21.createContext(null);
93809
+ function KeyboardLayerProvider({ children }) {
93810
+ const [stack, setStack] = import_react21.useState(["base"]);
93811
+ const stackRef = import_react21.useRef(stack);
93812
+ stackRef.current = stack;
93813
+ const responders = import_react21.useRef(new Map);
93814
+ const renderer = useRenderer();
93815
+ const push = import_react21.useCallback((id, responder) => {
93816
+ if (responder) {
93817
+ responders.current.set(id, responder);
93818
+ }
93819
+ setStack((prev) => [...prev, id]);
93820
+ }, []);
93821
+ const pop = import_react21.useCallback((id) => {
93822
+ setStack((prev) => {
93823
+ const idx = prev.lastIndexOf(id);
93824
+ if (idx === -1)
93825
+ return prev;
93826
+ if (prev.indexOf(id) === idx) {
93827
+ responders.current.delete(id);
93828
+ }
93829
+ return prev.slice(0, idx);
93830
+ });
93831
+ }, []);
93832
+ const isTopLayer = import_react21.useCallback((id) => {
93833
+ return stack.length === 0 || stack[stack.length - 1] === id;
93834
+ }, [stack]);
93835
+ const setResponder = import_react21.useCallback((id, responder) => {
93836
+ if (responder) {
93837
+ responders.current.set(id, responder);
93838
+ } else {
93839
+ responders.current.delete(id);
93840
+ }
93841
+ }, []);
93842
+ useKeyboard((key) => {
93843
+ if (!key.ctrl || key.name !== "c")
93844
+ return;
93845
+ const currentStack = stackRef.current;
93846
+ for (let i = currentStack.length - 1;i >= 0; i--) {
93847
+ const layerId = currentStack[i];
93848
+ const responder = responders.current.get(layerId);
93849
+ if (responder && responder()) {
93850
+ return;
93851
+ }
93852
+ }
93853
+ renderer.destroy();
93854
+ });
93855
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(KeyboardLayerContext.Provider, {
93856
+ value: { push, pop, isTopLayer, setResponder },
93857
+ children
93858
+ }, undefined, false, undefined, this);
93859
+ }
93860
+ function useKeyboardLayer() {
93861
+ const context = import_react21.useContext(KeyboardLayerContext);
93862
+ if (!context) {
93863
+ throw new Error("useKeyboardLayer must be used within a KeyboardLayerProvider");
93864
+ }
93865
+ return context;
93866
+ }
93867
+
93868
+ // src/components/error-boundary.tsx
93869
+ var import_react23 = __toESM(require_react(), 1);
93870
+ class ErrorBoundary2 extends import_react23.Component {
93871
+ constructor(props) {
93872
+ super(props);
93873
+ this.state = { error: null };
93874
+ }
93875
+ static getDerivedStateFromError(error51) {
93876
+ return { error: error51 };
93877
+ }
93878
+ componentDidCatch(error51, errorInfo) {
93879
+ console.error("[ErrorBoundary] Caught error:", error51, errorInfo.componentStack);
93880
+ }
93881
+ render() {
93882
+ if (this.state.error) {
93883
+ console.error("[ErrorBoundary] Rendering fallback for:", this.state.error.message);
93884
+ return this.props.fallback ?? /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
93885
+ flexDirection: "column",
93886
+ padding: 1,
93887
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
93888
+ children: [
93889
+ "Error: ",
93890
+ this.state.error.message
93891
+ ]
93892
+ }, undefined, true, undefined, this)
93893
+ }, undefined, false, undefined, this);
93894
+ }
93895
+ return this.props.children;
93896
+ }
93897
+ }
93898
+
93899
+ // src/providers/dialog/index.tsx
93900
+ var DialogContext = import_react24.createContext(null);
93901
+ function useDialog() {
93902
+ const value = import_react24.useContext(DialogContext);
93903
+ if (!value) {
93904
+ throw new Error("useDialog must be used within a DialogProvider");
93905
+ }
93906
+ return value;
93907
+ }
93908
+ function DialogProvider({ children }) {
93909
+ const [dialogStack, setDialogStack] = import_react24.useState([]);
93910
+ const { push, pop } = useKeyboardLayer();
93911
+ const close = import_react24.useCallback(() => {
93912
+ setDialogStack((prev) => prev.slice(0, -1));
93913
+ pop("dialog");
93914
+ }, [pop]);
93915
+ const open = import_react24.useCallback((config2) => {
93916
+ setDialogStack((prev) => [...prev, config2]);
93917
+ push("dialog", () => {
93918
+ close();
93919
+ return true;
93920
+ });
93921
+ }, [push, close]);
93922
+ const value = {
93923
+ open,
93924
+ close
93925
+ };
93926
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(DialogContext.Provider, {
93927
+ value,
93928
+ children: [
93929
+ children,
93930
+ dialogStack.map((config2, i) => /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(Dialog, {
93931
+ config: config2,
93932
+ close: i === dialogStack.length - 1 ? close : undefined,
93933
+ isBack: i < dialogStack.length - 1
93934
+ }, `dialog-${i}`, false, undefined, this))
93935
+ ]
93936
+ }, undefined, true, undefined, this);
93937
+ }
93938
+ function Dialog({ config: config2, close, isBack = false }) {
93939
+ const { isTopLayer } = useKeyboardLayer();
93940
+ const dimensions = useTerminalDimensions();
93941
+ const { colors } = useTheme();
93942
+ useKeyboard((key) => {
93943
+ if (!close || !isTopLayer("dialog"))
93944
+ return;
93945
+ if (key.name === "escape") {
93946
+ close();
93947
+ }
93948
+ });
93949
+ const { title, children } = config2;
93950
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
93951
+ position: "absolute",
93952
+ left: 0,
93953
+ top: 0,
93954
+ width: dimensions.width,
93955
+ height: dimensions.height,
93956
+ justifyContent: "center",
93957
+ alignItems: "center",
93958
+ backgroundColor: RGBA.fromInts(0, 0, 0, 150),
93959
+ zIndex: 100,
93960
+ onMouseDown: close,
93961
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
93962
+ width: Math.min(60, dimensions.width - 4),
93963
+ height: "auto",
93964
+ backgroundColor: colors.dialogSurface,
93965
+ paddingX: 4,
93966
+ paddingY: 1,
93967
+ flexDirection: "column",
93968
+ gap: 1,
93969
+ onMouseDown: (e) => e.stopPropagation(),
93970
+ children: [
93971
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
93972
+ paddingBottom: 1,
93973
+ flexDirection: "row",
93974
+ alignItems: "center",
93975
+ justifyContent: "space-between",
93976
+ children: [
93977
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
93978
+ attributes: TextAttributes.BOLD,
93979
+ children: title
93980
+ }, undefined, false, undefined, this),
93981
+ close && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
93982
+ attributes: TextAttributes.DIM,
93983
+ onMouseDown: close,
93984
+ children: isBack ? "back (esc)" : "esc"
93985
+ }, undefined, false, undefined, this)
93986
+ ]
93987
+ }, undefined, true, undefined, this),
93988
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
93989
+ flexGrow: 1,
93990
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ErrorBoundary2, {
93991
+ children
93992
+ }, undefined, false, undefined, this)
93993
+ }, undefined, false, undefined, this)
93994
+ ]
93995
+ }, undefined, true, undefined, this)
93996
+ }, undefined, false, undefined, this);
93997
+ }
93998
+
93999
+ // src/layouts/themed-root.tsx
94000
+ function ThemedRoot({ children }) {
94001
+ const { colors } = useTheme();
94002
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
94003
+ backgroundColor: colors.background,
94004
+ width: "100%",
94005
+ height: "100%",
94006
+ flexGrow: 1,
94007
+ children
94008
+ }, undefined, false, undefined, this);
94009
+ }
94010
+
93568
94011
  // src/providers/prompt-config/index.tsx
94012
+ var import_react26 = __toESM(require_react(), 1);
93569
94013
  var PromptConfigContext = import_react26.createContext(null);
93570
94014
  function usePromptConfig() {
93571
94015
  const value = import_react26.useContext(PromptConfigContext);
@@ -93574,12 +94018,32 @@ function usePromptConfig() {
93574
94018
  }
93575
94019
  return value;
93576
94020
  }
94021
+ function getInitial(key, fallback) {
94022
+ const prefs = readPreferences();
94023
+ return prefs[key] !== undefined ? prefs[key] : fallback;
94024
+ }
93577
94025
  function PromptConfigProvider({ children }) {
93578
- const [mode, setMode] = import_react26.useState(Mode.BUILD);
93579
- const [model, setModel] = import_react26.useState(DEFAULT_CHAT_MODEL_ID);
93580
- const [domain2, setDomain] = import_react26.useState(DEFAULT_DOMAIN_ID);
94026
+ const [mode, setModeState] = import_react26.useState(() => getInitial("mode", Mode.BUILD));
94027
+ const [model, setModelState] = import_react26.useState(() => getInitial("model", DEFAULT_CHAT_MODEL_ID));
94028
+ const [domain2, setDomainState] = import_react26.useState(() => getInitial("domain", DEFAULT_DOMAIN_ID));
94029
+ const setMode = import_react26.useCallback((m2) => {
94030
+ setModeState(m2);
94031
+ writePreferences({ mode: m2 });
94032
+ }, []);
94033
+ const setModel = import_react26.useCallback((m2) => {
94034
+ setModelState(m2);
94035
+ writePreferences({ model: m2 });
94036
+ }, []);
94037
+ const setDomain = import_react26.useCallback((d2) => {
94038
+ setDomainState(d2);
94039
+ writePreferences({ domain: d2 });
94040
+ }, []);
93581
94041
  const toggleMode = import_react26.useCallback(() => {
93582
- setMode((m2) => m2 === Mode.BUILD ? Mode.PLAN : Mode.BUILD);
94042
+ setModeState((m2) => {
94043
+ const next = m2 === Mode.BUILD ? Mode.PLAN : Mode.BUILD;
94044
+ writePreferences({ mode: next });
94045
+ return next;
94046
+ });
93583
94047
  }, []);
93584
94048
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(PromptConfigContext.Provider, {
93585
94049
  value: {
@@ -93600,8 +94064,8 @@ function RootLayout() {
93600
94064
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ThemeProvider, {
93601
94065
  children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ToastProvider, {
93602
94066
  children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(KeyboardLayerProvider, {
93603
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(DialogProvider, {
93604
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(PromptConfigProvider, {
94067
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(PromptConfigProvider, {
94068
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(DialogProvider, {
93605
94069
  children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ThemedRoot, {
93606
94070
  children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(Outlet, {}, undefined, false, undefined, this)
93607
94071
  }, undefined, false, undefined, this)
@@ -93613,7 +94077,7 @@ function RootLayout() {
93613
94077
  }
93614
94078
 
93615
94079
  // src/screens/home.tsx
93616
- var import_react38 = __toESM(require_react(), 1);
94080
+ var import_react42 = __toESM(require_react(), 1);
93617
94081
 
93618
94082
  // src/components/header.tsx
93619
94083
  function Header() {
@@ -93641,7 +94105,7 @@ function Header() {
93641
94105
  }
93642
94106
 
93643
94107
  // src/components/input-bar.tsx
93644
- var import_react36 = __toESM(require_react(), 1);
94108
+ var import_react40 = __toESM(require_react(), 1);
93645
94109
  import { readdir } from "fs/promises";
93646
94110
  import { isAbsolute as isAbsolute3, relative, resolve as resolve5 } from "path";
93647
94111
 
@@ -93659,7 +94123,8 @@ function DialogSearchList({
93659
94123
  renderItem,
93660
94124
  getKey,
93661
94125
  placeholder = "Search",
93662
- emptyText = "No results"
94126
+ emptyText = "No results",
94127
+ onEndReached
93663
94128
  }) {
93664
94129
  const [selectedIndex, setSelectedIndex] = import_react27.useState(0);
93665
94130
  const [searchValue, setSearchValue] = import_react27.useState("");
@@ -93667,6 +94132,26 @@ function DialogSearchList({
93667
94132
  const scrollRef = import_react27.useRef(null);
93668
94133
  const { isTopLayer } = useKeyboardLayer();
93669
94134
  const { colors } = useTheme();
94135
+ const itemsRef = import_react27.useRef(items);
94136
+ itemsRef.current = items;
94137
+ const filterFnRef = import_react27.useRef(filterFn);
94138
+ filterFnRef.current = filterFn;
94139
+ const onSelectRef = import_react27.useRef(onSelect);
94140
+ onSelectRef.current = onSelect;
94141
+ const onHighlightRef = import_react27.useRef(onHighlight);
94142
+ onHighlightRef.current = onHighlight;
94143
+ const selectedIndexRef = import_react27.useRef(selectedIndex);
94144
+ selectedIndexRef.current = selectedIndex;
94145
+ const searchValueRef = import_react27.useRef(searchValue);
94146
+ searchValueRef.current = searchValue;
94147
+ const handleSubmit = import_react27.useCallback(() => {
94148
+ const text2 = inputRef.current?.value ?? "";
94149
+ const currentFiltered = text2 ? itemsRef.current.filter((item2) => filterFnRef.current(item2, text2)) : itemsRef.current;
94150
+ const item = currentFiltered[selectedIndexRef.current];
94151
+ if (item) {
94152
+ onSelectRef.current(item);
94153
+ }
94154
+ }, []);
93670
94155
  const handleContentChange = import_react27.useCallback(() => {
93671
94156
  const text2 = inputRef.current?.value ?? "";
93672
94157
  setSearchValue(text2);
@@ -93694,8 +94179,8 @@ function DialogSearchList({
93694
94179
  sb.scrollTo(newIndex);
93695
94180
  }
93696
94181
  const item = filtered[newIndex];
93697
- if (item && onHighlight)
93698
- onHighlight(item);
94182
+ if (item && onHighlightRef.current)
94183
+ onHighlightRef.current(item);
93699
94184
  return newIndex;
93700
94185
  });
93701
94186
  } else if (key.name === "down") {
@@ -93710,8 +94195,11 @@ function DialogSearchList({
93710
94195
  }
93711
94196
  }
93712
94197
  const item = filtered[newIndex];
93713
- if (item && onHighlight)
93714
- onHighlight(item);
94198
+ if (item && onHighlightRef.current)
94199
+ onHighlightRef.current(item);
94200
+ if (newIndex >= filtered.length - 2 && onEndReached) {
94201
+ onEndReached();
94202
+ }
93715
94203
  return newIndex;
93716
94204
  });
93717
94205
  }
@@ -93724,7 +94212,8 @@ function DialogSearchList({
93724
94212
  ref: inputRef,
93725
94213
  placeholder,
93726
94214
  focused: true,
93727
- onContentChange: handleContentChange
94215
+ onContentChange: handleContentChange,
94216
+ onSubmit: handleSubmit
93728
94217
  }, undefined, false, undefined, this),
93729
94218
  filtered.length === 0 ? /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
93730
94219
  attributes: TextAttributes.DIM,
@@ -93741,10 +94230,10 @@ function DialogSearchList({
93741
94230
  backgroundColor: isSelected ? colors.selectMode : undefined,
93742
94231
  onMouseMove: () => {
93743
94232
  setSelectedIndex(i);
93744
- if (onHighlight)
93745
- onHighlight(item);
94233
+ if (onHighlightRef.current)
94234
+ onHighlightRef.current(item);
93746
94235
  },
93747
- onMouseDown: () => onSelect(item),
94236
+ onMouseDown: () => onSelectRef.current(item),
93748
94237
  children: renderItem(item, isSelected)
93749
94238
  }, getKey(item), false, undefined, this);
93750
94239
  })
@@ -93768,8 +94257,8 @@ var ThemeDialogContent = () => {
93768
94257
  }, [setTheme]);
93769
94258
  const handleSelect = import_react29.useCallback((theme) => {
93770
94259
  confirmedRef.current = true;
93771
- setTheme(theme);
93772
94260
  dialog.close();
94261
+ setTheme(theme);
93773
94262
  }, [setTheme, dialog]);
93774
94263
  const handleHighlight = import_react29.useCallback((theme) => {
93775
94264
  setTheme(theme);
@@ -94119,6 +94608,27 @@ function clearAuth() {
94119
94608
  }
94120
94609
 
94121
94610
  // src/lib/api-client.ts
94611
+ var RETRY_ATTEMPTS = 3;
94612
+ var RETRY_BASE_DELAY_MS = 1000;
94613
+ async function sleep2(ms) {
94614
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
94615
+ }
94616
+ async function withRetry(fn, options) {
94617
+ const maxAttempts = options?.attempts ?? RETRY_ATTEMPTS;
94618
+ let lastError;
94619
+ for (let attempt = 0;attempt < maxAttempts; attempt++) {
94620
+ try {
94621
+ return await fn(attempt);
94622
+ } catch (err) {
94623
+ lastError = err;
94624
+ if (attempt < maxAttempts - 1) {
94625
+ const delay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt);
94626
+ await sleep2(delay);
94627
+ }
94628
+ }
94629
+ }
94630
+ throw lastError;
94631
+ }
94122
94632
  var apiClient = hc(process.env.API_URL ?? "https://lupacodeserver-production.up.railway.app", {
94123
94633
  fetch: async (input, init) => {
94124
94634
  const headers = new Headers(init?.headers);
@@ -95577,44 +96087,126 @@ function cleanEscapedString(input) {
95577
96087
  }
95578
96088
 
95579
96089
  // src/components/dialogs/sessions-dialog.tsx
96090
+ var PAGE_SIZE = 20;
95580
96091
  var SessionsDialogContent = () => {
95581
96092
  const [sessions, setSessions] = import_react30.useState([]);
95582
96093
  const [loading, setLoading] = import_react30.useState(true);
96094
+ const [hasMore, setHasMore] = import_react30.useState(false);
96095
+ const [cursor, setCursor] = import_react30.useState();
96096
+ const [highlightedSession, setHighlightedSession] = import_react30.useState(null);
96097
+ const [view, setView] = import_react30.useState({ type: "list" });
95583
96098
  const { close } = useDialog();
95584
96099
  const navigate = useNavigate();
95585
96100
  const { show } = useToast();
95586
- import_react30.useEffect(() => {
95587
- let ignore = false;
95588
- const fetchSessions = async () => {
95589
- try {
95590
- const res = await apiClient.sessions.$get();
95591
- if (!res.ok) {
95592
- throw new Error(await getErrorMessage2(res));
95593
- }
95594
- const data2 = await res.json();
95595
- if (!ignore) {
95596
- setSessions(data2);
95597
- setLoading(false);
95598
- }
95599
- } catch (error51) {
95600
- if (!ignore) {
95601
- show({
95602
- variant: "error",
95603
- message: error51 instanceof Error ? error51.message : "Failed to fetch sessions"
95604
- });
95605
- close();
95606
- }
96101
+ const { isTopLayer } = useKeyboardLayer();
96102
+ const { colors } = useTheme();
96103
+ const fetchSessions = import_react30.useCallback(async (cursorVal) => {
96104
+ try {
96105
+ const params = new URLSearchParams;
96106
+ params.set("limit", String(PAGE_SIZE));
96107
+ if (cursorVal)
96108
+ params.set("cursor", cursorVal);
96109
+ const res = await apiClient.sessions.$get({ query: Object.fromEntries(params) });
96110
+ if (!res.ok) {
96111
+ throw new Error(await getErrorMessage2(res));
96112
+ }
96113
+ const raw = await res.json();
96114
+ let list;
96115
+ let more;
96116
+ if (Array.isArray(raw)) {
96117
+ list = raw;
96118
+ more = false;
96119
+ } else {
96120
+ const parsed = raw;
96121
+ list = parsed.sessions ?? [];
96122
+ more = parsed.hasMore ?? false;
95607
96123
  }
95608
- };
95609
- fetchSessions();
95610
- return () => {
95611
- ignore = true;
95612
- };
96124
+ if (cursorVal) {
96125
+ setSessions((prev) => [...prev, ...list]);
96126
+ } else {
96127
+ setSessions(list);
96128
+ }
96129
+ setHasMore(more);
96130
+ if (list.length > 0) {
96131
+ setCursor(list[list.length - 1].createdAt);
96132
+ }
96133
+ setLoading(false);
96134
+ } catch (error51) {
96135
+ show({
96136
+ variant: "error",
96137
+ message: error51 instanceof Error ? error51.message : "Failed to fetch sessions"
96138
+ });
96139
+ close();
96140
+ }
95613
96141
  }, [close, show]);
96142
+ import_react30.useEffect(() => {
96143
+ fetchSessions();
96144
+ }, [fetchSessions]);
95614
96145
  const handleSelect = import_react30.useCallback((session) => {
95615
96146
  close();
95616
96147
  navigate(`/sessions/${session.id}`);
95617
96148
  }, [close, navigate]);
96149
+ const handleDelete = import_react30.useCallback(async (session) => {
96150
+ try {
96151
+ const res = await withRetry(() => apiClient.sessions[":id"].$delete({ param: { id: session.id } }));
96152
+ if (!res.ok)
96153
+ throw new Error(await getErrorMessage2(res));
96154
+ show({ variant: "success", message: "Session deleted" });
96155
+ setSessions((prev) => prev.filter((s) => s.id !== session.id));
96156
+ setView({ type: "list" });
96157
+ } catch (error51) {
96158
+ show({
96159
+ variant: "error",
96160
+ message: error51 instanceof Error ? error51.message : "Failed to delete session"
96161
+ });
96162
+ setView({ type: "list" });
96163
+ }
96164
+ }, [show]);
96165
+ const handleRename = import_react30.useCallback(async (session, newTitle) => {
96166
+ try {
96167
+ const res = await withRetry(() => apiClient.sessions[":id"].$patch({
96168
+ param: { id: session.id },
96169
+ json: { title: newTitle }
96170
+ }));
96171
+ if (!res.ok)
96172
+ throw new Error(await getErrorMessage2(res));
96173
+ show({ variant: "success", message: "Session renamed" });
96174
+ setSessions((prev) => prev.map((s) => s.id === session.id ? { ...s, title: newTitle } : s));
96175
+ setView({ type: "list" });
96176
+ } catch (error51) {
96177
+ show({
96178
+ variant: "error",
96179
+ message: error51 instanceof Error ? error51.message : "Failed to rename session"
96180
+ });
96181
+ setView({ type: "list" });
96182
+ }
96183
+ }, [show]);
96184
+ const handleLoadMore = import_react30.useCallback(async () => {
96185
+ if (hasMore && cursor) {
96186
+ await fetchSessions(cursor);
96187
+ }
96188
+ }, [hasMore, cursor, fetchSessions]);
96189
+ useKeyboard((key) => {
96190
+ if (!isTopLayer("dialog"))
96191
+ return;
96192
+ if (view.type === "list") {
96193
+ if (key.name === "d" && highlightedSession) {
96194
+ setView({ type: "confirm-delete", session: highlightedSession });
96195
+ } else if (key.name === "r" && highlightedSession) {
96196
+ setView({ type: "rename", session: highlightedSession });
96197
+ }
96198
+ } else if (view.type === "confirm-delete") {
96199
+ if (key.name === "y" || key.name === "enter") {
96200
+ handleDelete(view.session);
96201
+ } else if (key.name === "n" || key.name === "escape") {
96202
+ setView({ type: "list" });
96203
+ }
96204
+ } else if (view.type === "rename") {
96205
+ if (key.name === "escape") {
96206
+ setView({ type: "list" });
96207
+ }
96208
+ }
96209
+ });
95618
96210
  if (loading) {
95619
96211
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
95620
96212
  flexDirection: "column",
@@ -95633,35 +96225,94 @@ var SessionsDialogContent = () => {
95633
96225
  }, undefined, false, undefined, this)
95634
96226
  }, undefined, false, undefined, this);
95635
96227
  }
95636
- return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(DialogSearchList, {
95637
- items: sessions,
95638
- onSelect: handleSelect,
95639
- filterFn: (s, query) => (s.title || "").toLowerCase().includes(query.toLowerCase()),
95640
- renderItem: (session, isSelected) => /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(import_jsx_dev_runtime2.Fragment, {
96228
+ if (view.type === "confirm-delete") {
96229
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96230
+ flexDirection: "column",
96231
+ gap: 1,
95641
96232
  children: [
95642
96233
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
95643
- selectable: false,
95644
- fg: isSelected ? "black" : "white",
95645
- children: session.title
95646
- }, undefined, false, undefined, this),
96234
+ children: [
96235
+ 'Delete "',
96236
+ view.session.title,
96237
+ '"?'
96238
+ ]
96239
+ }, undefined, true, undefined, this),
95647
96240
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
95648
- flexGrow: 1
95649
- }, undefined, false, undefined, this),
96241
+ flexDirection: "row",
96242
+ gap: 2,
96243
+ children: [
96244
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
96245
+ selectable: true,
96246
+ fg: colors.error,
96247
+ onMouseDown: () => handleDelete(view.session),
96248
+ children: "yes (y)"
96249
+ }, undefined, false, undefined, this),
96250
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
96251
+ selectable: true,
96252
+ onMouseDown: () => setView({ type: "list" }),
96253
+ children: "no (n)"
96254
+ }, undefined, false, undefined, this)
96255
+ ]
96256
+ }, undefined, true, undefined, this)
96257
+ ]
96258
+ }, undefined, true, undefined, this);
96259
+ }
96260
+ if (view.type === "rename") {
96261
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96262
+ flexDirection: "column",
96263
+ gap: 1,
96264
+ children: [
95650
96265
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
95651
- selectable: false,
95652
- fg: isSelected ? "black" : undefined,
95653
96266
  attributes: TextAttributes.DIM,
95654
- children: format(new Date(session.createdAt), "hh:mm a")
96267
+ children: "Rename session:"
96268
+ }, undefined, false, undefined, this),
96269
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("input", {
96270
+ focused: true,
96271
+ initialValue: view.session.title,
96272
+ onSubmit: (value) => {
96273
+ if (value.trim()) {
96274
+ handleRename(view.session, value.trim());
96275
+ }
96276
+ }
95655
96277
  }, undefined, false, undefined, this)
95656
96278
  ]
95657
- }, undefined, true, undefined, this),
95658
- getKey: (s) => s.id,
95659
- placeholder: "Search sessions",
95660
- emptyText: "No matching sessions"
96279
+ }, undefined, true, undefined, this);
96280
+ }
96281
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96282
+ flexDirection: "column",
96283
+ gap: 1,
96284
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(DialogSearchList, {
96285
+ items: sessions,
96286
+ onSelect: handleSelect,
96287
+ onHighlight: (s) => setHighlightedSession(s),
96288
+ filterFn: (s, query) => (s.title || "").toLowerCase().includes(query.toLowerCase()),
96289
+ renderItem: (session, isSelected) => /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(import_jsx_dev_runtime2.Fragment, {
96290
+ children: [
96291
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
96292
+ selectable: false,
96293
+ fg: isSelected ? "black" : "white",
96294
+ children: session.title
96295
+ }, undefined, false, undefined, this),
96296
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96297
+ flexGrow: 1
96298
+ }, undefined, false, undefined, this),
96299
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
96300
+ selectable: false,
96301
+ fg: isSelected ? "black" : undefined,
96302
+ attributes: TextAttributes.DIM,
96303
+ children: format(new Date(session.createdAt), "hh:mm a")
96304
+ }, undefined, false, undefined, this)
96305
+ ]
96306
+ }, undefined, true, undefined, this),
96307
+ getKey: (s) => s.id,
96308
+ placeholder: "Search sessions (r) rename (d) delete",
96309
+ emptyText: "No matching sessions",
96310
+ onEndReached: handleLoadMore
96311
+ }, undefined, false, undefined, this)
95661
96312
  }, undefined, false, undefined, this);
95662
96313
  };
95663
96314
  // src/components/dialogs/agents-dialog.tsx
95664
- var import_react31 = __toESM(require_react(), 1);
96315
+ var import_react32 = __toESM(require_react(), 1);
95665
96316
  var AVAILABLE_MODES = [Mode.BUILD, Mode.PLAN];
95666
96317
  function getModeLabel(mode) {
95667
96318
  return mode === Mode.PLAN ? "Plan" : "Build";
@@ -95671,9 +96322,9 @@ var AgentsDialogContent = ({
95671
96322
  onSelectMode
95672
96323
  }) => {
95673
96324
  const dialog = useDialog();
95674
- const handleSelect = import_react31.useCallback((nextMode) => {
95675
- onSelectMode(nextMode);
96325
+ const handleSelect = import_react32.useCallback((nextMode) => {
95676
96326
  dialog.close();
96327
+ onSelectMode(nextMode);
95677
96328
  }, [onSelectMode, dialog]);
95678
96329
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(DialogSearchList, {
95679
96330
  items: AVAILABLE_MODES,
@@ -95693,15 +96344,15 @@ var AgentsDialogContent = ({
95693
96344
  }, undefined, false, undefined, this);
95694
96345
  };
95695
96346
  // src/components/dialogs/models-dialog.tsx
95696
- var import_react32 = __toESM(require_react(), 1);
96347
+ var import_react33 = __toESM(require_react(), 1);
95697
96348
  var ModelsDialogContent = ({
95698
96349
  models,
95699
96350
  onSelectModel
95700
96351
  }) => {
95701
96352
  const dialog = useDialog();
95702
- const handleSelect = import_react32.useCallback((model) => {
95703
- onSelectModel(model.id);
96353
+ const handleSelect = import_react33.useCallback((model) => {
95704
96354
  dialog.close();
96355
+ onSelectModel(model.id);
95705
96356
  }, [dialog, onSelectModel]);
95706
96357
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(DialogSearchList, {
95707
96358
  items: models,
@@ -95737,14 +96388,14 @@ var ModelsDialogContent = ({
95737
96388
  }, undefined, false, undefined, this);
95738
96389
  };
95739
96390
  // src/components/dialogs/domains-dialog.tsx
95740
- var import_react33 = __toESM(require_react(), 1);
96391
+ var import_react34 = __toESM(require_react(), 1);
95741
96392
  var DomainsDialogContent = ({
95742
96393
  onSelectDomain
95743
96394
  }) => {
95744
96395
  const dialog = useDialog();
95745
- const handleSelect = import_react33.useCallback((domain2) => {
95746
- onSelectDomain(domain2.id);
96396
+ const handleSelect = import_react34.useCallback((domain2) => {
95747
96397
  dialog.close();
96398
+ onSelectDomain(domain2.id);
95748
96399
  }, [dialog, onSelectDomain]);
95749
96400
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(DialogSearchList, {
95750
96401
  items: [...DOMAINS],
@@ -95760,6 +96411,274 @@ var DomainsDialogContent = ({
95760
96411
  emptyText: "No matching domains"
95761
96412
  }, undefined, false, undefined, this);
95762
96413
  };
96414
+ // src/components/dialogs/settings-dialog.tsx
96415
+ var import_react35 = __toESM(require_react(), 1);
96416
+ // package.json
96417
+ var package_default2 = {
96418
+ name: "lupacode",
96419
+ version: "1.0.1",
96420
+ description: "AI-powered terminal coding assistant",
96421
+ type: "module",
96422
+ bin: {
96423
+ lupacode: "bin/lupacode"
96424
+ },
96425
+ files: [
96426
+ "bin/",
96427
+ "dist/",
96428
+ "README.md"
96429
+ ],
96430
+ scripts: {
96431
+ dev: "bun run --watch src/index.tsx",
96432
+ build: 'bun build src/index.tsx --outdir dist --target bun --external "@opentui/core-*"',
96433
+ prepublishOnly: "bun run build",
96434
+ typecheck: "tsc -p tsconfig.json --noEmit"
96435
+ },
96436
+ engines: {
96437
+ bun: ">=1.0.0"
96438
+ },
96439
+ devDependencies: {
96440
+ "@types/bun": "latest",
96441
+ "@types/react": "^19.2.17"
96442
+ },
96443
+ peerDependencies: {
96444
+ typescript: "^5"
96445
+ },
96446
+ peerDependenciesMeta: {
96447
+ typescript: {
96448
+ optional: true
96449
+ }
96450
+ },
96451
+ dependencies: {
96452
+ "@ai-sdk/react": "^4.0.4",
96453
+ "@opentui/core": "^0.3.4",
96454
+ "@opentui/react": "^0.3.4",
96455
+ ai: "^7.0.3",
96456
+ "date-fns": "^4.4.0",
96457
+ dotenv: "^16.4.7",
96458
+ hono: "^4.12.26",
96459
+ open: "^11.0.0",
96460
+ "opentui-spinner": "^0.0.7",
96461
+ "pretty-ms": "^9.3.0",
96462
+ react: "^19.2.6",
96463
+ "react-router": "^8.0.1",
96464
+ zod: "^4.4.3"
96465
+ }
96466
+ };
96467
+
96468
+ // src/lib/update-check.ts
96469
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
96470
+ import { homedir as homedir3 } from "os";
96471
+ import { join as join4 } from "path";
96472
+ var DIR = join4(homedir3(), ".lupacode");
96473
+ var FILE = join4(DIR, "update-check.json");
96474
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
96475
+ function readCache() {
96476
+ try {
96477
+ return JSON.parse(readFileSync3(FILE, "utf-8"));
96478
+ } catch {
96479
+ return null;
96480
+ }
96481
+ }
96482
+ function writeCache(data2) {
96483
+ if (!existsSync5(DIR)) {
96484
+ mkdirSync3(DIR, { mode: 448 });
96485
+ }
96486
+ writeFileSync4(FILE, JSON.stringify(data2));
96487
+ }
96488
+ var cachedLatest = null;
96489
+ var cachedCurrent = "0.0.0";
96490
+ function setCurrentVersion(v2) {
96491
+ cachedCurrent = v2;
96492
+ }
96493
+ function getLatestVersion() {
96494
+ return cachedLatest;
96495
+ }
96496
+ async function checkForUpdate(currentVersion) {
96497
+ cachedCurrent = currentVersion;
96498
+ const cache = readCache();
96499
+ if (cache && Date.now() - cache.checkedAt < CHECK_INTERVAL_MS) {
96500
+ cachedLatest = cache.latest;
96501
+ return isNewer(cache.latest, currentVersion) ? cache.latest : null;
96502
+ }
96503
+ try {
96504
+ const res = await fetch("https://registry.npmjs.org/lupacode/latest");
96505
+ if (!res.ok)
96506
+ return null;
96507
+ const data2 = await res.json();
96508
+ const latest = data2.version;
96509
+ const result = { latest, checkedAt: Date.now() };
96510
+ writeCache(result);
96511
+ cachedLatest = latest;
96512
+ return isNewer(latest, currentVersion) ? latest : null;
96513
+ } catch {
96514
+ return null;
96515
+ }
96516
+ }
96517
+ function isNewer(latest, current) {
96518
+ if (!latest)
96519
+ return false;
96520
+ const a = parseSemver(latest);
96521
+ const b2 = parseSemver(current);
96522
+ if (!a || !b2)
96523
+ return false;
96524
+ if (a.major !== b2.major)
96525
+ return a.major > b2.major;
96526
+ if (a.minor !== b2.minor)
96527
+ return a.minor > b2.minor;
96528
+ return a.patch > b2.patch;
96529
+ }
96530
+ function parseSemver(v2) {
96531
+ const m2 = /^v?(\d+)\.(\d+)\.(\d+)/.exec(v2.trim());
96532
+ if (!m2 || m2[1] == null || m2[2] == null || m2[3] == null)
96533
+ return null;
96534
+ return { major: Number(m2[1]), minor: Number(m2[2]), patch: Number(m2[3]) };
96535
+ }
96536
+ function hasUpdate() {
96537
+ return isNewer(cachedLatest, cachedCurrent);
96538
+ }
96539
+
96540
+ // src/components/dialogs/settings-dialog.tsx
96541
+ var ITEMS = ["theme", "mode", "model", "domain"];
96542
+ var SettingsDialogContent = () => {
96543
+ const dialog = useDialog();
96544
+ const { currentTheme, setTheme } = useTheme();
96545
+ const { mode, setMode, model, setModel, domain: domain2, setDomain } = usePromptConfig();
96546
+ const { isTopLayer } = useKeyboardLayer();
96547
+ const [selectedIndex, setSelectedIndex] = import_react35.useState(0);
96548
+ const selectedIndexRef = import_react35.useRef(0);
96549
+ const currentModel = findSupportedChatModel(model);
96550
+ const currentDomain = findDomain(domain2);
96551
+ const openDialogRef = import_react35.useRef(undefined);
96552
+ const openDialog = import_react35.useCallback((item) => {
96553
+ switch (item) {
96554
+ case "theme":
96555
+ dialog.open({ title: "Select Theme", children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ThemeDialogContent, {}, undefined, false, undefined, this) });
96556
+ break;
96557
+ case "mode":
96558
+ dialog.open({ title: "Select Mode", children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(AgentsDialogContent, {
96559
+ currentMode: mode,
96560
+ onSelectMode: setMode
96561
+ }, undefined, false, undefined, this) });
96562
+ break;
96563
+ case "model":
96564
+ dialog.open({ title: "Select Model", children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ModelsDialogContent, {
96565
+ models: [...SUPPORTED_CHAT_MODELS],
96566
+ onSelectModel: setModel
96567
+ }, undefined, false, undefined, this) });
96568
+ break;
96569
+ case "domain":
96570
+ dialog.open({ title: "Select Domain", children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(DomainsDialogContent, {
96571
+ onSelectDomain: setDomain
96572
+ }, undefined, false, undefined, this) });
96573
+ break;
96574
+ }
96575
+ }, [dialog, mode, setMode, setModel, setDomain]);
96576
+ openDialogRef.current = openDialog;
96577
+ useKeyboard((key) => {
96578
+ if (!isTopLayer("dialog"))
96579
+ return;
96580
+ if (key.name === "down") {
96581
+ setSelectedIndex((i) => {
96582
+ const next = Math.min(ITEMS.length - 1, i + 1);
96583
+ selectedIndexRef.current = next;
96584
+ return next;
96585
+ });
96586
+ } else if (key.name === "up") {
96587
+ setSelectedIndex((i) => {
96588
+ const next = Math.max(0, i - 1);
96589
+ selectedIndexRef.current = next;
96590
+ return next;
96591
+ });
96592
+ } else if (key.name === "enter" || key.name === "return") {
96593
+ openDialogRef.current?.(ITEMS[selectedIndexRef.current]);
96594
+ }
96595
+ });
96596
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96597
+ flexDirection: "column",
96598
+ gap: 1,
96599
+ children: [
96600
+ ITEMS.map((item, i) => {
96601
+ const isSelected = i === selectedIndex;
96602
+ const bgColor = isSelected ? currentTheme.colors.selectMode : undefined;
96603
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96604
+ flexDirection: "row",
96605
+ justifyContent: "space-between",
96606
+ alignItems: "center",
96607
+ backgroundColor: bgColor,
96608
+ onMouseDown: () => openDialogRef.current?.(item),
96609
+ children: [
96610
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
96611
+ selectable: false,
96612
+ fg: isSelected ? "black" : undefined,
96613
+ children: item.charAt(0).toUpperCase() + item.slice(1)
96614
+ }, undefined, false, undefined, this),
96615
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96616
+ flexDirection: "row",
96617
+ gap: 1,
96618
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
96619
+ selectable: false,
96620
+ fg: isSelected ? "black" : currentTheme.colors.primary,
96621
+ children: [
96622
+ item === "theme" && currentTheme.name,
96623
+ item === "mode" && mode,
96624
+ item === "model" && (currentModel?.id ?? model),
96625
+ item === "domain" && (currentDomain?.name ?? domain2)
96626
+ ]
96627
+ }, undefined, true, undefined, this)
96628
+ }, undefined, false, undefined, this)
96629
+ ]
96630
+ }, item, true, undefined, this);
96631
+ }),
96632
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96633
+ paddingTop: 1,
96634
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
96635
+ attributes: TextAttributes.DIM,
96636
+ children: [
96637
+ "v",
96638
+ package_default2.version,
96639
+ hasUpdate() ? ` \u2192 v${getLatestVersion()} available` : "",
96640
+ " | ",
96641
+ "LupaCode AI Coding Assistant"
96642
+ ]
96643
+ }, undefined, true, undefined, this)
96644
+ }, undefined, false, undefined, this)
96645
+ ]
96646
+ }, undefined, true, undefined, this);
96647
+ };
96648
+ // src/components/dialogs/keyboard-help-dialog.tsx
96649
+ var shortcuts = [
96650
+ { key: "tab", description: "Toggle Build/Plan mode" },
96651
+ { key: "ctrl+d", description: "Switch domain" },
96652
+ { key: "ctrl+s", description: "Open settings" },
96653
+ { key: "ctrl+r", description: "Regenerate last assistant response" },
96654
+ { key: "ctrl+e", description: "Edit last user message" },
96655
+ { key: "escape", description: "Interrupt response / Cancel / Close dialog" }
96656
+ ];
96657
+ var KeyboardHelpDialogContent = () => {
96658
+ const { colors } = useTheme();
96659
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96660
+ flexDirection: "column",
96661
+ gap: 1,
96662
+ children: shortcuts.map(({ key, description }) => /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96663
+ flexDirection: "row",
96664
+ gap: 2,
96665
+ children: [
96666
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96667
+ width: 14,
96668
+ flexShrink: 0,
96669
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
96670
+ fg: colors.primary,
96671
+ children: key
96672
+ }, undefined, false, undefined, this)
96673
+ }, undefined, false, undefined, this),
96674
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
96675
+ attributes: TextAttributes.DIM,
96676
+ children: description
96677
+ }, undefined, false, undefined, this)
96678
+ ]
96679
+ }, key, true, undefined, this))
96680
+ }, undefined, false, undefined, this);
96681
+ };
95763
96682
  // src/components/status-bar.tsx
95764
96683
  function StatusBar() {
95765
96684
  const { colors } = useTheme();
@@ -95799,6 +96718,9 @@ function StatusBar() {
95799
96718
  }, undefined, true, undefined, this);
95800
96719
  }
95801
96720
 
96721
+ // src/components/command-menu/commands.tsx
96722
+ var import_react37 = __toESM(require_react(), 1);
96723
+
95802
96724
  // ../../node_modules/open/index.js
95803
96725
  import process9 from "process";
95804
96726
  import path4 from "path";
@@ -96623,6 +97545,85 @@ var COMMANDS = [
96623
97545
  }
96624
97546
  }
96625
97547
  },
97548
+ {
97549
+ name: "rename",
97550
+ description: "Rename the current session",
97551
+ value: "/rename",
97552
+ action: (ctx) => {
97553
+ if (!ctx.sessionId) {
97554
+ ctx.toast.show({ variant: "error", message: "No active session" });
97555
+ return;
97556
+ }
97557
+ const RenameDialog = () => {
97558
+ const [text2, setText] = import_react37.useState("");
97559
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
97560
+ flexDirection: "column",
97561
+ gap: 1,
97562
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("input", {
97563
+ focused: true,
97564
+ placeholder: "New session name",
97565
+ onContentChange: (event) => setText(String(event.value ?? event)),
97566
+ onSubmit: () => {
97567
+ const name23 = text2.trim();
97568
+ if (!name23)
97569
+ return;
97570
+ const baseUrl = process.env.API_URL ?? "https://lupacodeserver-production.up.railway.app";
97571
+ const token = getAuth()?.token ?? "";
97572
+ fetch(`${baseUrl}/sessions/${ctx.sessionId}`, {
97573
+ method: "PATCH",
97574
+ headers: {
97575
+ "Content-Type": "application/json",
97576
+ ...token ? { Authorization: `Bearer ${token}` } : {}
97577
+ },
97578
+ body: JSON.stringify({ title: name23 })
97579
+ }).then((res) => {
97580
+ if (!res.ok)
97581
+ throw new Error("Failed to rename");
97582
+ ctx.toast.show({ variant: "success", message: `Renamed to "${name23}"` });
97583
+ }).catch(() => {
97584
+ ctx.toast.show({ variant: "error", message: "Failed to rename session" });
97585
+ });
97586
+ ctx.dialog.close();
97587
+ }
97588
+ }, undefined, false, undefined, this)
97589
+ }, undefined, false, undefined, this);
97590
+ };
97591
+ ctx.dialog.open({ title: "Rename Session", children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(RenameDialog, {}, undefined, false, undefined, this) });
97592
+ }
97593
+ },
97594
+ {
97595
+ name: "export",
97596
+ description: "Export conversation as markdown",
97597
+ value: "/export",
97598
+ action: (ctx) => {
97599
+ if (!ctx.messages || ctx.messages.length === 0) {
97600
+ ctx.toast.show({ variant: "error", message: "No messages to export" });
97601
+ return;
97602
+ }
97603
+ const lines = [];
97604
+ lines.push("# Chat Export");
97605
+ lines.push("");
97606
+ for (const msg of ctx.messages) {
97607
+ const role = msg.role === "user" ? "User" : "Assistant";
97608
+ const text2 = msg.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
97609
+ if (!text2)
97610
+ continue;
97611
+ lines.push(`**${role}:**`);
97612
+ lines.push("");
97613
+ lines.push(text2);
97614
+ lines.push("");
97615
+ }
97616
+ const content = lines.join(`
97617
+ `);
97618
+ const filename = `chat-export-${Date.now()}.md`;
97619
+ try {
97620
+ Bun.write(filename, content);
97621
+ ctx.toast.show({ variant: "success", message: `Exported to ${filename}` });
97622
+ } catch {
97623
+ ctx.toast.show({ variant: "error", message: "Failed to write export file" });
97624
+ }
97625
+ }
97626
+ },
96626
97627
  {
96627
97628
  name: "exit",
96628
97629
  description: "Quit the application",
@@ -96703,15 +97704,15 @@ function CommandMenu({
96703
97704
  }
96704
97705
 
96705
97706
  // src/components/command-menu/use-command-menu.ts
96706
- var import_react34 = __toESM(require_react(), 1);
97707
+ var import_react38 = __toESM(require_react(), 1);
96707
97708
  function useCommandMenu() {
96708
- const [textValue, setTextValue] = import_react34.useState("");
96709
- const [selectedIndex, setSelectedIndex] = import_react34.useState(0);
96710
- const [showCommandMenu, setShowCommandMenu] = import_react34.useState(false);
96711
- const scrollRef = import_react34.useRef(null);
97709
+ const [textValue, setTextValue] = import_react38.useState("");
97710
+ const [selectedIndex, setSelectedIndex] = import_react38.useState(0);
97711
+ const [showCommandMenu, setShowCommandMenu] = import_react38.useState(false);
97712
+ const scrollRef = import_react38.useRef(null);
96712
97713
  const { push, pop, isTopLayer } = useKeyboardLayer();
96713
97714
  const commandQuery = showCommandMenu && textValue.startsWith("/") ? textValue.slice(1) : "";
96714
- const filteredCommands = import_react34.useMemo(() => getFilteredCommands(commandQuery), [commandQuery]);
97715
+ const filteredCommands = import_react38.useMemo(() => getFilteredCommands(commandQuery), [commandQuery]);
96715
97716
  const close = () => {
96716
97717
  setShowCommandMenu(false);
96717
97718
  pop("command");
@@ -96724,13 +97725,15 @@ function useCommandMenu() {
96724
97725
  scrollbox.scrollTo(0);
96725
97726
  }
96726
97727
  const prefix = text2.startsWith("/") ? text2.slice(1) : null;
96727
- if (prefix !== null && !prefix.includes(" ")) {
97728
+ const wasShowing = showCommandMenu;
97729
+ const shouldShow = prefix !== null && !prefix.includes(" ");
97730
+ if (shouldShow && !wasShowing) {
96728
97731
  setShowCommandMenu(true);
96729
97732
  push("command", () => {
96730
97733
  close();
96731
97734
  return true;
96732
97735
  });
96733
- } else {
97736
+ } else if (!shouldShow && wasShowing) {
96734
97737
  close();
96735
97738
  }
96736
97739
  };
@@ -96966,21 +97969,21 @@ var TEXTAREA_KEY_BINDINGS = [
96966
97969
  { name: "return", shift: true, action: "newline" },
96967
97970
  { name: "enter", shift: true, action: "newline" }
96968
97971
  ];
96969
- function InputBar({ onSubmit, disabled = false }) {
97972
+ function InputBar({ onSubmit, disabled = false, sessionId, messages }) {
96970
97973
  const { mode, toggleMode, setMode, setModel, domain: domain2, setDomain } = usePromptConfig();
96971
- const textareaRef = import_react36.useRef(null);
96972
- const onSubmitRef = import_react36.useRef(() => {});
96973
- const activeMentionRef = import_react36.useRef(null);
96974
- const mentionScrollRef = import_react36.useRef(null);
97974
+ const textareaRef = import_react40.useRef(null);
97975
+ const onSubmitRef = import_react40.useRef(() => {});
97976
+ const activeMentionRef = import_react40.useRef(null);
97977
+ const mentionScrollRef = import_react40.useRef(null);
96975
97978
  const renderer = useRenderer();
96976
97979
  const toast = useToast();
96977
97980
  const navigate = useNavigate();
96978
97981
  const dialog = useDialog();
96979
97982
  const { colors } = useTheme();
96980
97983
  const { isTopLayer, setResponder, push, pop } = useKeyboardLayer();
96981
- const [activeMention, setActiveMention] = import_react36.useState(null);
96982
- const [mentionCandidates, setMentionCandidates] = import_react36.useState([]);
96983
- const [mentionSelectedIndex, setMentionSelectedIndex] = import_react36.useState(0);
97984
+ const [activeMention, setActiveMention] = import_react40.useState(null);
97985
+ const [mentionCandidates, setMentionCandidates] = import_react40.useState([]);
97986
+ const [mentionSelectedIndex, setMentionSelectedIndex] = import_react40.useState(0);
96984
97987
  const {
96985
97988
  showCommandMenu,
96986
97989
  commandQuery,
@@ -96991,13 +97994,13 @@ function InputBar({ onSubmit, disabled = false }) {
96991
97994
  setSelectedIndex
96992
97995
  } = useCommandMenu();
96993
97996
  const showMentionMenu = activeMention !== null;
96994
- const closeMentionMenu = import_react36.useCallback(() => {
97997
+ const closeMentionMenu = import_react40.useCallback(() => {
96995
97998
  activeMentionRef.current = null;
96996
97999
  setActiveMention(null);
96997
98000
  setMentionCandidates([]);
96998
98001
  pop("mention");
96999
98002
  }, [pop]);
97000
- const syncMentionMenu = import_react36.useCallback((text2, cursorOffset) => {
98003
+ const syncMentionMenu = import_react40.useCallback((text2, cursorOffset) => {
97001
98004
  const nextMention = findActiveMention(text2, cursorOffset);
97002
98005
  const previousMention = activeMentionRef.current;
97003
98006
  const mentionChanged = previousMention?.start !== nextMention?.start || previousMention?.end !== nextMention?.end || previousMention?.query !== nextMention?.query;
@@ -97007,18 +98010,20 @@ function InputBar({ onSubmit, disabled = false }) {
97007
98010
  }
97008
98011
  return;
97009
98012
  }
98013
+ if (!previousMention) {
98014
+ push("mention", () => {
98015
+ closeMentionMenu();
98016
+ return true;
98017
+ });
98018
+ }
97010
98019
  activeMentionRef.current = nextMention;
97011
98020
  setActiveMention(nextMention);
97012
- push("mention", () => {
97013
- closeMentionMenu();
97014
- return true;
97015
- });
97016
98021
  if (mentionChanged) {
97017
98022
  setMentionSelectedIndex(0);
97018
98023
  mentionScrollRef.current?.scrollTo(0);
97019
98024
  }
97020
98025
  }, [closeMentionMenu, push]);
97021
- const handleTextareaContentChange = import_react36.useCallback(() => {
98026
+ const handleTextareaContentChange = import_react40.useCallback(() => {
97022
98027
  const textarea = textareaRef.current;
97023
98028
  if (!textarea)
97024
98029
  return;
@@ -97026,7 +98031,7 @@ function InputBar({ onSubmit, disabled = false }) {
97026
98031
  handleContentChange(textarea.plainText);
97027
98032
  syncMentionMenu(text2, textarea.cursorOffset);
97028
98033
  }, [handleContentChange, syncMentionMenu]);
97029
- const handleSubmit = import_react36.useCallback(() => {
98034
+ const handleSubmit = import_react40.useCallback(() => {
97030
98035
  if (disabled)
97031
98036
  return;
97032
98037
  const textarea = textareaRef.current;
@@ -97038,7 +98043,7 @@ function InputBar({ onSubmit, disabled = false }) {
97038
98043
  onSubmit(text2);
97039
98044
  textarea.setText("");
97040
98045
  }, [disabled, onSubmit]);
97041
- const handleMentionExecute = import_react36.useCallback((index) => {
98046
+ const handleMentionExecute = import_react40.useCallback((index) => {
97042
98047
  const textarea = textareaRef.current;
97043
98048
  const mention = activeMentionRef.current;
97044
98049
  const candidate = mentionCandidates[index];
@@ -97050,13 +98055,13 @@ function InputBar({ onSubmit, disabled = false }) {
97050
98055
  textarea.cursorOffset = mention.start + insertion.length + 1;
97051
98056
  syncMentionMenu(nextText, textarea.cursorOffset);
97052
98057
  }, [mentionCandidates, syncMentionMenu]);
97053
- const handleTextareaCursorChange = import_react36.useCallback(() => {
98058
+ const handleTextareaCursorChange = import_react40.useCallback(() => {
97054
98059
  const textarea = textareaRef.current;
97055
98060
  if (!textarea)
97056
98061
  return;
97057
98062
  syncMentionMenu(textarea.plainText, textarea.cursorOffset);
97058
98063
  }, [syncMentionMenu]);
97059
- const handleCommand = import_react36.useCallback((command) => {
98064
+ const handleCommand = import_react40.useCallback((command) => {
97060
98065
  const textarea = textareaRef.current;
97061
98066
  if (!textarea || !command)
97062
98067
  return;
@@ -97070,17 +98075,19 @@ function InputBar({ onSubmit, disabled = false }) {
97070
98075
  mode,
97071
98076
  setMode,
97072
98077
  setModel,
97073
- setDomain
98078
+ setDomain,
98079
+ sessionId,
98080
+ messages
97074
98081
  });
97075
98082
  } else {
97076
98083
  textarea.insertText(command.value + " ");
97077
98084
  }
97078
98085
  }, [renderer, toast, dialog, navigate, mode, setMode, setModel, setDomain]);
97079
- const handleCommandExecute = import_react36.useCallback((index) => {
98086
+ const handleCommandExecute = import_react40.useCallback((index) => {
97080
98087
  const command = resolveCommand(index);
97081
98088
  handleCommand(command);
97082
98089
  }, [resolveCommand, handleCommand]);
97083
- import_react36.useEffect(() => {
98090
+ import_react40.useEffect(() => {
97084
98091
  if (!activeMention) {
97085
98092
  setMentionCandidates([]);
97086
98093
  return;
@@ -97103,7 +98110,7 @@ function InputBar({ onSubmit, disabled = false }) {
97103
98110
  ignore = true;
97104
98111
  };
97105
98112
  }, [activeMention]);
97106
- import_react36.useEffect(() => {
98113
+ import_react40.useEffect(() => {
97107
98114
  const textare = textareaRef.current;
97108
98115
  if (!textare)
97109
98116
  return;
@@ -97146,8 +98153,22 @@ function InputBar({ onSubmit, disabled = false }) {
97146
98153
  }, undefined, false, undefined, this)
97147
98154
  });
97148
98155
  }
98156
+ if (key.name === "s" && key.ctrl) {
98157
+ key.preventDefault();
98158
+ dialog.open({
98159
+ title: "Settings",
98160
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(SettingsDialogContent, {}, undefined, false, undefined, this)
98161
+ });
98162
+ }
98163
+ if (key.name === "?" && !key.ctrl) {
98164
+ key.preventDefault();
98165
+ dialog.open({
98166
+ title: "Keyboard Shortcuts",
98167
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(KeyboardHelpDialogContent, {}, undefined, false, undefined, this)
98168
+ });
98169
+ }
97149
98170
  });
97150
- import_react36.useEffect(() => {
98171
+ import_react40.useEffect(() => {
97151
98172
  setResponder("base", () => {
97152
98173
  if (disabled)
97153
98174
  return false;
@@ -97260,63 +98281,12 @@ function InputBar({ onSubmit, disabled = false }) {
97260
98281
  }, undefined, false, undefined, this)
97261
98282
  }, undefined, false, undefined, this);
97262
98283
  }
97263
- // package.json
97264
- var package_default2 = {
97265
- name: "lupacode",
97266
- version: "0.2.5",
97267
- description: "AI-powered terminal coding assistant",
97268
- type: "module",
97269
- bin: {
97270
- lupacode: "bin/lupacode"
97271
- },
97272
- files: [
97273
- "bin/",
97274
- "dist/",
97275
- "README.md"
97276
- ],
97277
- scripts: {
97278
- dev: "bun run --watch src/index.tsx",
97279
- build: 'bun build src/index.tsx --outdir dist --target bun --external "@opentui/core-*"',
97280
- prepublishOnly: "bun run build",
97281
- typecheck: "tsc -p tsconfig.json --noEmit"
97282
- },
97283
- engines: {
97284
- bun: ">=1.0.0"
97285
- },
97286
- devDependencies: {
97287
- "@types/bun": "latest",
97288
- "@types/react": "^19.2.17"
97289
- },
97290
- peerDependencies: {
97291
- typescript: "^5"
97292
- },
97293
- peerDependenciesMeta: {
97294
- typescript: {
97295
- optional: true
97296
- }
97297
- },
97298
- dependencies: {
97299
- "@ai-sdk/react": "^4.0.4",
97300
- "@opentui/core": "^0.3.4",
97301
- "@opentui/react": "^0.3.4",
97302
- ai: "^7.0.3",
97303
- "date-fns": "^4.4.0",
97304
- dotenv: "^16.4.7",
97305
- hono: "^4.12.26",
97306
- open: "^11.0.0",
97307
- "opentui-spinner": "^0.0.7",
97308
- "pretty-ms": "^9.3.0",
97309
- react: "^19.2.6",
97310
- "react-router": "^8.0.1",
97311
- zod: "^4.4.3"
97312
- }
97313
- };
97314
98284
 
97315
98285
  // src/screens/home.tsx
97316
98286
  function Home() {
97317
98287
  const navigate = useNavigate();
97318
98288
  const { mode, model, domain: domain2 } = usePromptConfig();
97319
- const handleSubmit = import_react38.useCallback((text2) => {
98289
+ const handleSubmit = import_react42.useCallback((text2) => {
97320
98290
  navigate("/sessions/new", { state: { message: text2, mode, model, domain: domain2 } });
97321
98291
  }, [navigate, mode, model, domain2]);
97322
98292
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
@@ -97347,7 +98317,8 @@ function Home() {
97347
98317
  attributes: TextAttributes.DIM,
97348
98318
  children: [
97349
98319
  "v",
97350
- package_default2.version
98320
+ package_default2.version,
98321
+ hasUpdate() ? ` \u2192 v${getLatestVersion() ?? ""} (npm i -g lupacode)` : ""
97351
98322
  ]
97352
98323
  }, undefined, true, undefined, this),
97353
98324
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
@@ -97371,6 +98342,16 @@ function Home() {
97371
98342
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
97372
98343
  attributes: TextAttributes.DIM,
97373
98344
  children: "domains"
98345
+ }, undefined, false, undefined, this),
98346
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
98347
+ children: "\xB7"
98348
+ }, undefined, false, undefined, this),
98349
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
98350
+ children: "ctrl+s"
98351
+ }, undefined, false, undefined, this),
98352
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
98353
+ attributes: TextAttributes.DIM,
98354
+ children: "settings"
97374
98355
  }, undefined, false, undefined, this)
97375
98356
  ]
97376
98357
  }, undefined, true, undefined, this)
@@ -97383,7 +98364,7 @@ function Home() {
97383
98364
  }
97384
98365
 
97385
98366
  // src/screens/new-session.tsx
97386
- var import_react41 = __toESM(require_react(), 1);
98367
+ var import_react45 = __toESM(require_react(), 1);
97387
98368
 
97388
98369
  // src/components/messages/error-message.tsx
97389
98370
  function ErrorMessage({ message: message2 }) {
@@ -97574,6 +98555,45 @@ function prettyMilliseconds(milliseconds, options) {
97574
98555
  }
97575
98556
 
97576
98557
  // src/components/messages/bot-message.tsx
98558
+ var globalSyntaxStyle = SyntaxStyle.fromStyles({
98559
+ default: { fg: "#D4D4D4" },
98560
+ keyword: { fg: "#569CD6" },
98561
+ "keyword.control": { fg: "#C586C0" },
98562
+ "keyword.import": { fg: "#C586C0" },
98563
+ "keyword.function": { fg: "#DCDCAA" },
98564
+ string: { fg: "#CE9178" },
98565
+ "string.special": { fg: "#CE9178" },
98566
+ number: { fg: "#B5CEA8" },
98567
+ "number.float": { fg: "#B5CEA8" },
98568
+ comment: { fg: "#6A9955", italic: true },
98569
+ function: { fg: "#DCDCAA" },
98570
+ "function.call": { fg: "#DCDCAA" },
98571
+ "function.method": { fg: "#DCDCAA" },
98572
+ type: { fg: "#4EC9B0" },
98573
+ "type.builtin": { fg: "#4EC9B0" },
98574
+ variable: { fg: "#9CDCFE" },
98575
+ "variable.parameter": { fg: "#9CDCFE" },
98576
+ "variable.builtin": { fg: "#9CDCFE" },
98577
+ constant: { fg: "#4FC1FF" },
98578
+ "constant.builtin": { fg: "#569CD6" },
98579
+ property: { fg: "#9CDCFE" },
98580
+ operator: { fg: "#D4D4D4" },
98581
+ "punctuation.delimiter": { fg: "#D4D4D4" },
98582
+ "punctuation.bracket": { fg: "#D4D4D4" },
98583
+ boolean: { fg: "#569CD6" },
98584
+ label: { fg: "#DCDCAA" },
98585
+ "markup.heading": { fg: "#569CD6", bold: true },
98586
+ "markup.italic": { italic: true },
98587
+ "markup.strong": { fg: "#DCDCAA", bold: true },
98588
+ "markup.strikethrough": { fg: "#888888", italic: true, dim: true },
98589
+ "markup.raw": { fg: "#CE9178" },
98590
+ "markup.link": { fg: "#569CD6", underline: true },
98591
+ "markup.link.label": { fg: "#569CD6", underline: true },
98592
+ "markup.link.url": { fg: "#4FC1FF" },
98593
+ "markup.list": { fg: "#569CD6" },
98594
+ "markup.quote": { fg: "#6A9955", italic: true },
98595
+ conceal: { fg: "#555555" }
98596
+ });
97577
98597
  function formatToolName(name23) {
97578
98598
  return name23.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (c) => c.toUpperCase());
97579
98599
  }
@@ -97587,6 +98607,79 @@ function formatToolArgs(tc) {
97587
98607
  return String(tc.input);
97588
98608
  return Object.values(tc.input).map(String).join(" ");
97589
98609
  }
98610
+ function isBashTool(tc) {
98611
+ const name23 = tc.type === "dynamic-tool" ? tc.toolName : tc.type.slice("tool-".length);
98612
+ return name23 === "bash";
98613
+ }
98614
+ function isToolRunning(tc) {
98615
+ return tc.state !== "output-available" && tc.state !== "output-error";
98616
+ }
98617
+ function renderToolCall(tc, colors, j2, renderKey) {
98618
+ const toolName = tc.type === "dynamic-tool" ? tc.toolName : tc.type.slice("tool-".length);
98619
+ const running = isToolRunning(tc);
98620
+ const done = tc.state === "output-available";
98621
+ if (isBashTool(tc)) {
98622
+ const command = typeof tc.input === "object" && tc.input != null ? String(tc.input.command ?? "") : "";
98623
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
98624
+ width: "100%",
98625
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
98626
+ attributes: TextAttributes.DIM,
98627
+ children: [
98628
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("em", {
98629
+ fg: colors.success,
98630
+ attributes: TextAttributes.BOLD,
98631
+ children: "$"
98632
+ }, undefined, false, undefined, this),
98633
+ " ",
98634
+ command,
98635
+ running ? "" : "",
98636
+ tc.state === "output-error" ? /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("em", {
98637
+ fg: colors.error,
98638
+ children: [
98639
+ " ",
98640
+ "\u2716",
98641
+ " ",
98642
+ tc.errorText
98643
+ ]
98644
+ }, undefined, true, undefined, this) : null
98645
+ ]
98646
+ }, undefined, true, undefined, this)
98647
+ }, renderKey, false, undefined, this);
98648
+ }
98649
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
98650
+ width: "100%",
98651
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
98652
+ attributes: TextAttributes.DIM,
98653
+ children: [
98654
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("em", {
98655
+ fg: colors.info,
98656
+ children: [
98657
+ formatToolName(toolName),
98658
+ ":"
98659
+ ]
98660
+ }, undefined, true, undefined, this),
98661
+ " ",
98662
+ formatToolArgs(tc),
98663
+ running ? "..." : done ? " \u2713" : "",
98664
+ tc.state === "output-error" ? /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("em", {
98665
+ fg: colors.error,
98666
+ children: [
98667
+ " ",
98668
+ "\u2716",
98669
+ " ",
98670
+ tc.errorText
98671
+ ]
98672
+ }, undefined, true, undefined, this) : null
98673
+ ]
98674
+ }, undefined, true, undefined, this)
98675
+ }, renderKey, false, undefined, this);
98676
+ }
98677
+ function formatTokens(usage) {
98678
+ const total = usage.totalTokens ?? usage.inputTokens ?? usage.outputTokens;
98679
+ if (total == null)
98680
+ return "";
98681
+ return `${total.toLocaleString()} tok`;
98682
+ }
97590
98683
  function mergeReasoningParts(parts) {
97591
98684
  const reasoningParts = parts.filter((p) => p.type === "reasoning");
97592
98685
  if (reasoningParts.length <= 1)
@@ -97624,6 +98717,7 @@ function BotMessage({
97624
98717
  model,
97625
98718
  mode,
97626
98719
  durationMs,
98720
+ usage,
97627
98721
  streaming = false
97628
98722
  }) {
97629
98723
  const { colors } = useTheme();
@@ -97649,22 +98743,7 @@ function BotMessage({
97649
98743
  flexDirection: "column",
97650
98744
  children: group.parts.map((part, j2) => {
97651
98745
  const tc = part;
97652
- const toolName = tc.type === "dynamic-tool" ? tc.toolName : tc.type.slice("tool-".length);
97653
- return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
97654
- attributes: TextAttributes.DIM,
97655
- children: [
97656
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("em", {
97657
- fg: colors.info,
97658
- children: [
97659
- formatToolName(toolName),
97660
- ":"
97661
- ]
97662
- }, undefined, true, undefined, this),
97663
- formatToolArgs(tc),
97664
- tc.state !== "output-available" && tc.state !== "output-error" ? "..." : "",
97665
- tc.state === "output-error" ? ` ${tc.errorText}` : ""
97666
- ]
97667
- }, tc.toolCallId, true, undefined, this);
98746
+ return renderToolCall(tc, colors, j2, tc.toolCallId);
97668
98747
  })
97669
98748
  }, undefined, false, undefined, this)
97670
98749
  }, group.key, false, undefined, this);
@@ -97697,7 +98776,6 @@ function BotMessage({
97697
98776
  }, `reasoning-${j2}`, false, undefined, this);
97698
98777
  }
97699
98778
  if (isToolPart(part)) {
97700
- const toolName = part.type === "dynamic-tool" ? part.toolName : part.type.slice("tool-".length);
97701
98779
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
97702
98780
  border: ["left"],
97703
98781
  borderColor: colors.thinkingBorder,
@@ -97707,29 +98785,27 @@ function BotMessage({
97707
98785
  },
97708
98786
  width: "100%",
97709
98787
  paddingX: 2,
97710
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
97711
- attributes: TextAttributes.DIM,
97712
- children: [
97713
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("em", {
97714
- fg: colors.info,
97715
- children: [
97716
- formatToolName(toolName),
97717
- ":"
97718
- ]
97719
- }, undefined, true, undefined, this),
97720
- formatToolArgs(part),
97721
- part.state !== "output-available" && part.state !== "output-error" ? "..." : "",
97722
- part.state === "output-error" ? ` ${part.errorText}` : ""
97723
- ]
97724
- }, undefined, true, undefined, this)
98788
+ children: renderToolCall(part, colors, j2, part.toolCallId)
97725
98789
  }, part.toolCallId, false, undefined, this);
97726
98790
  }
97727
98791
  if (part.type === "text") {
97728
98792
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
97729
98793
  paddingX: 3,
97730
98794
  width: "100%",
97731
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
97732
- children: part.text
98795
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("markdown", {
98796
+ content: part.text,
98797
+ syntaxStyle: globalSyntaxStyle,
98798
+ conceal: true,
98799
+ concealCode: true,
98800
+ streaming,
98801
+ tableOptions: {
98802
+ style: "grid",
98803
+ borders: true,
98804
+ outerBorder: true,
98805
+ borderStyle: "rounded",
98806
+ cellPadding: 1,
98807
+ borderColor: "#555555"
98808
+ }
97733
98809
  }, undefined, false, undefined, this)
97734
98810
  }, `text-${j2}`, false, undefined, this);
97735
98811
  }
@@ -97778,6 +98854,19 @@ function BotMessage({
97778
98854
  children: prettyMilliseconds(durationMs)
97779
98855
  }, undefined, false, undefined, this)
97780
98856
  ]
98857
+ }, undefined, true, undefined, this),
98858
+ usage && formatTokens(usage) && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(import_jsx_dev_runtime2.Fragment, {
98859
+ children: [
98860
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
98861
+ attributes: TextAttributes.DIM,
98862
+ fg: colors.dimSeparator,
98863
+ children: ">"
98864
+ }, undefined, false, undefined, this),
98865
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
98866
+ attributes: TextAttributes.DIM,
98867
+ children: formatTokens(usage)
98868
+ }, undefined, false, undefined, this)
98869
+ ]
97781
98870
  }, undefined, true, undefined, this)
97782
98871
  ]
97783
98872
  }, undefined, true, undefined, this)
@@ -99772,7 +100861,9 @@ function SessionShell({
99772
100861
  onSubmit,
99773
100862
  inputDisabled = false,
99774
100863
  loading = false,
99775
- interruptible = false
100864
+ interruptible = false,
100865
+ sessionId,
100866
+ messages
99776
100867
  }) {
99777
100868
  const { mode } = usePromptConfig();
99778
100869
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
@@ -99797,7 +100888,9 @@ function SessionShell({
99797
100888
  flexShrink: 0,
99798
100889
  children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(InputBar, {
99799
100890
  onSubmit,
99800
- disabled: inputDisabled
100891
+ disabled: inputDisabled,
100892
+ sessionId,
100893
+ messages
99801
100894
  }, undefined, false, undefined, this)
99802
100895
  }, undefined, false, undefined, this),
99803
100896
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
@@ -99818,7 +100911,8 @@ function SessionShell({
99818
100911
  attributes: TextAttributes.DIM,
99819
100912
  children: [
99820
100913
  "v",
99821
- package_default2.version
100914
+ package_default2.version,
100915
+ hasUpdate() ? ` \u2192 v${getLatestVersion() ?? ""} (npm i -g lupacode)` : ""
99822
100916
  ]
99823
100917
  }, undefined, true, undefined, this),
99824
100918
  loading ? /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(import_jsx_dev_runtime2.Fragment, {
@@ -99855,6 +100949,16 @@ function SessionShell({
99855
100949
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
99856
100950
  attributes: TextAttributes.DIM,
99857
100951
  children: "domains"
100952
+ }, undefined, false, undefined, this),
100953
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
100954
+ children: "\xB7"
100955
+ }, undefined, false, undefined, this),
100956
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
100957
+ children: "ctrl+s"
100958
+ }, undefined, false, undefined, this),
100959
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
100960
+ attributes: TextAttributes.DIM,
100961
+ children: "settings"
99858
100962
  }, undefined, false, undefined, this)
99859
100963
  ]
99860
100964
  }, undefined, true, undefined, this)
@@ -99875,17 +100979,17 @@ function NewSession() {
99875
100979
  const navigate = useNavigate();
99876
100980
  const location = useLocation();
99877
100981
  const toast = useToast();
99878
- const hasStartedRef = import_react41.useRef(false);
99879
- const state = import_react41.useMemo(() => {
100982
+ const hasStartedRef = import_react45.useRef(false);
100983
+ const state = import_react45.useMemo(() => {
99880
100984
  const parsed = newSessionStateSchema.safeParse(location.state);
99881
100985
  return parsed.success ? parsed.data : null;
99882
100986
  }, [location.state]);
99883
- import_react41.useEffect(() => {
100987
+ import_react45.useEffect(() => {
99884
100988
  if (!state?.message) {
99885
100989
  navigate("/", { replace: true });
99886
100990
  }
99887
100991
  }, [state, navigate]);
99888
- import_react41.useEffect(() => {
100992
+ import_react45.useEffect(() => {
99889
100993
  if (!state || hasStartedRef.current)
99890
100994
  return;
99891
100995
  hasStartedRef.current = true;
@@ -99933,13 +101037,13 @@ function NewSession() {
99933
101037
  }
99934
101038
 
99935
101039
  // src/screens/sessions.tsx
99936
- var import_react50 = __toESM(require_react(), 1);
101040
+ var import_react54 = __toESM(require_react(), 1);
99937
101041
 
99938
101042
  // src/hooks/use-chat.ts
99939
- var import_react48 = __toESM(require_react(), 1);
101043
+ var import_react52 = __toESM(require_react(), 1);
99940
101044
 
99941
101045
  // ../../node_modules/@ai-sdk/react/dist/index.js
99942
- var import_react42 = __toESM(require_react(), 1);
101046
+ var import_react46 = __toESM(require_react(), 1);
99943
101047
 
99944
101048
  // ../../node_modules/@ai-sdk/react/node_modules/@ai-sdk/provider/dist/index.js
99945
101049
  var marker23 = "vercel.ai.error";
@@ -104692,11 +105796,11 @@ var AbstractChat = class {
104692
105796
 
104693
105797
  // ../../node_modules/@ai-sdk/react/dist/index.js
104694
105798
  var import_throttleit = __toESM(require_throttleit(), 1);
104695
- var import_react43 = __toESM(require_react(), 1);
104696
- var import_react44 = __toESM(require_react(), 1);
104697
- var import_react45 = __toESM(require_react(), 1);
104698
- var import_react46 = __toESM(require_react(), 1);
104699
105799
  var import_react47 = __toESM(require_react(), 1);
105800
+ var import_react48 = __toESM(require_react(), 1);
105801
+ var import_react49 = __toESM(require_react(), 1);
105802
+ var import_react50 = __toESM(require_react(), 1);
105803
+ var import_react51 = __toESM(require_react(), 1);
104700
105804
  var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
104701
105805
  var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
104702
105806
  var __accessCheck = (obj, member, msg) => {
@@ -104833,7 +105937,7 @@ function useChat({
104833
105937
  resume = false,
104834
105938
  ...options
104835
105939
  } = {}) {
104836
- const callbacksRef = import_react42.useRef(!("chat" in options) ? {
105940
+ const callbacksRef = import_react46.useRef(!("chat" in options) ? {
104837
105941
  onToolCall: options.onToolCall,
104838
105942
  onData: options.onData,
104839
105943
  onFinish: options.onFinish,
@@ -104872,22 +105976,22 @@ function useChat({
104872
105976
  return (_c = (_b17 = (_a29 = callbacksRef.current).sendAutomaticallyWhen) == null ? undefined : _b17.call(_a29, arg)) != null ? _c : false;
104873
105977
  }
104874
105978
  };
104875
- const chatRef = import_react42.useRef("chat" in options ? options.chat : new Chat(optionsWithCallbacks));
105979
+ const chatRef = import_react46.useRef("chat" in options ? options.chat : new Chat(optionsWithCallbacks));
104876
105980
  const shouldRecreateChat = "chat" in options && options.chat !== chatRef.current || "id" in options && chatRef.current.id !== options.id;
104877
105981
  if (shouldRecreateChat) {
104878
105982
  chatRef.current = "chat" in options ? options.chat : new Chat(optionsWithCallbacks);
104879
105983
  }
104880
- const subscribeToMessages = import_react42.useCallback((update) => chatRef.current["~registerMessagesCallback"](update, throttleWaitMs), [throttleWaitMs, chatRef.current.id]);
104881
- const messages = import_react42.useSyncExternalStore(subscribeToMessages, () => chatRef.current.messages, () => chatRef.current.messages);
104882
- const status = import_react42.useSyncExternalStore(chatRef.current["~registerStatusCallback"], () => chatRef.current.status, () => chatRef.current.status);
104883
- const error51 = import_react42.useSyncExternalStore(chatRef.current["~registerErrorCallback"], () => chatRef.current.error, () => chatRef.current.error);
104884
- const setMessages = import_react42.useCallback((messagesParam) => {
105984
+ const subscribeToMessages = import_react46.useCallback((update) => chatRef.current["~registerMessagesCallback"](update, throttleWaitMs), [throttleWaitMs, chatRef.current.id]);
105985
+ const messages = import_react46.useSyncExternalStore(subscribeToMessages, () => chatRef.current.messages, () => chatRef.current.messages);
105986
+ const status = import_react46.useSyncExternalStore(chatRef.current["~registerStatusCallback"], () => chatRef.current.status, () => chatRef.current.status);
105987
+ const error51 = import_react46.useSyncExternalStore(chatRef.current["~registerErrorCallback"], () => chatRef.current.error, () => chatRef.current.error);
105988
+ const setMessages = import_react46.useCallback((messagesParam) => {
104885
105989
  if (typeof messagesParam === "function") {
104886
105990
  messagesParam = messagesParam(chatRef.current.messages);
104887
105991
  }
104888
105992
  chatRef.current.messages = messagesParam;
104889
105993
  }, [chatRef]);
104890
- import_react42.useEffect(() => {
105994
+ import_react46.useEffect(() => {
104891
105995
  if (resume) {
104892
105996
  chatRef.current.resumeStream();
104893
105997
  }
@@ -104910,8 +106014,8 @@ function useChat({
104910
106014
  }
104911
106015
 
104912
106016
  // src/lib/local-tools.ts
104913
- import { mkdir as mkdir2, readFile as readFile2, readdir as readdir2, stat, writeFile as writeFile2 } from "fs/promises";
104914
- import { dirname as dirname2, isAbsolute as isAbsolute5, join as join4, relative as relative2, resolve as resolve7 } from "path";
106017
+ import { mkdir as mkdir2, readFile as readFile2, readdir as readdir2, stat, writeFile as writeFile2, unlink as unlink2 } from "fs/promises";
106018
+ import { dirname as dirname2, isAbsolute as isAbsolute5, join as join5, relative as relative2, resolve as resolve7 } from "path";
104915
106019
  var MAX_FILE_SIZE = 1e4;
104916
106020
  var MAX_RESULTS = 200;
104917
106021
  var MAX_MATCHES = 50;
@@ -104930,7 +106034,7 @@ function truncate(value, limit) {
104930
106034
  return value.length > limit ? `${value.slice(0, limit)}n... (truncated, ${value.length} total chars)` : value;
104931
106035
  }
104932
106036
  async function executeLocalTool(toolName, input, mode) {
104933
- if (mode === Mode.PLAN && !["readFile", "listDirectory", "glob", "grep"].includes(toolName)) {
106037
+ if (mode === Mode.PLAN && !["readFile", "listDirectory", "glob", "grep", "webSearch"].includes(toolName)) {
104934
106038
  throw new Error(`Tool ${toolName} is not available in PLAN mode`);
104935
106039
  }
104936
106040
  switch (toolName) {
@@ -104948,7 +106052,7 @@ async function executeLocalTool(toolName, input, mode) {
104948
106052
  for (const entry of entries) {
104949
106053
  if (entry.startsWith(".") || entry === "node_modules")
104950
106054
  continue;
104951
- const info = await stat(join4(resolved, entry));
106055
+ const info = await stat(join5(resolved, entry));
104952
106056
  results.push({ name: entry, type: info.isDirectory() ? "directory" : "file" });
104953
106057
  }
104954
106058
  results.sort((a2, b2) => a2.type !== b2.type ? a2.type === "directory" ? -1 : 1 : a2.name.localeCompare(b2.name));
@@ -105040,11 +106144,15 @@ async function executeLocalTool(toolName, input, mode) {
105040
106144
  }
105041
106145
  case "bash": {
105042
106146
  const { command, timeout = DEFAULT_TIMEOUT } = toolInputSchemas.bash.parse(input);
105043
- const proc = Bun.spawn(["bash", "-c", command], {
106147
+ const isWindows = process.platform === "win32";
106148
+ const shell = isWindows ? "cmd.exe" : "bash";
106149
+ const shellFlag = isWindows ? "/c" : "-c";
106150
+ const env2 = isWindows ? { ...process.env } : { ...process.env, TERM: "dumb" };
106151
+ const proc = Bun.spawn([shell, shellFlag, command], {
105044
106152
  cwd: resolveInsideCwd(".").resolved,
105045
106153
  stdout: "pipe",
105046
106154
  stderr: "pipe",
105047
- env: { ...process.env, TERM: "dumb" }
106155
+ env: env2
105048
106156
  });
105049
106157
  const timer = setTimeout(() => proc.kill(), timeout);
105050
106158
  const [stdout, stderr] = await Promise.all([
@@ -105059,6 +106167,40 @@ async function executeLocalTool(toolName, input, mode) {
105059
106167
  exitCode
105060
106168
  };
105061
106169
  }
106170
+ case "deleteFile": {
106171
+ const { path: path5 } = toolInputSchemas.deleteFile.parse(input);
106172
+ const { cwd, resolved } = resolveInsideCwd(path5);
106173
+ await unlink2(resolved);
106174
+ return { success: true, path: relative2(cwd, resolved) };
106175
+ }
106176
+ case "webSearch": {
106177
+ const { query } = toolInputSchemas.webSearch.parse(input);
106178
+ const apiKey = process.env.TAVILY_API_KEY;
106179
+ if (!apiKey)
106180
+ throw new Error("TAVILY_API_KEY is not set");
106181
+ const res = await fetch("https://api.tavily.com/search", {
106182
+ method: "POST",
106183
+ headers: { "Content-Type": "application/json" },
106184
+ body: JSON.stringify({
106185
+ api_key: apiKey,
106186
+ query,
106187
+ search_depth: "advanced",
106188
+ include_answer: true,
106189
+ max_results: 5
106190
+ })
106191
+ });
106192
+ if (!res.ok)
106193
+ throw new Error(`Web search failed: ${res.status} ${res.statusText}`);
106194
+ const data2 = await res.json();
106195
+ return {
106196
+ results: data2.results.map((r) => ({
106197
+ title: r.title,
106198
+ url: r.url,
106199
+ content: r.content
106200
+ })),
106201
+ answer: data2.answer
106202
+ };
106203
+ }
105062
106204
  default:
105063
106205
  throw new Error(`Unknown tool ${toolName}`);
105064
106206
  }
@@ -105066,7 +106208,7 @@ async function executeLocalTool(toolName, input, mode) {
105066
106208
 
105067
106209
  // src/hooks/use-chat.ts
105068
106210
  function useChat2(sessionId, initialMessages) {
105069
- const transport = import_react48.useMemo(() => {
106211
+ const transport = import_react52.useMemo(() => {
105070
106212
  return new DefaultChatTransport({
105071
106213
  api: apiClient.chat.$url().toString(),
105072
106214
  headers() {
@@ -105124,6 +106266,9 @@ function useChat2(sessionId, initialMessages) {
105124
106266
  }
105125
106267
  });
105126
106268
  },
106269
+ append: chat.append,
106270
+ setMessages: chat.setMessages,
106271
+ reload: chat.reload,
105127
106272
  abort: chat.stop,
105128
106273
  interrupt: chat.stop
105129
106274
  };
@@ -105151,19 +106296,38 @@ function ChatMessage({ msg }) {
105151
106296
  model: msg.metadata?.model ?? "unknown",
105152
106297
  mode: msg.metadata?.mode ?? "BUILD",
105153
106298
  durationMs: msg.metadata?.durationMs,
106299
+ usage: msg.metadata?.usage,
105154
106300
  streaming: false
105155
106301
  }, undefined, false, undefined, this);
105156
106302
  }
106303
+ function EditMessageDialog({
106304
+ initialText,
106305
+ onResolve,
106306
+ onSubmit
106307
+ }) {
106308
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
106309
+ flexDirection: "column",
106310
+ gap: 1,
106311
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("input", {
106312
+ focused: true,
106313
+ initialValue: initialText,
106314
+ onContentChange: (v2) => onResolve(v2),
106315
+ onSubmit
106316
+ }, undefined, false, undefined, this)
106317
+ }, undefined, false, undefined, this);
106318
+ }
105157
106319
  function SessionChat({
105158
106320
  session,
105159
106321
  initialPrompt
105160
106322
  }) {
105161
- const [initialMessages] = import_react50.useState(() => session.messages);
106323
+ const [initialMessages] = import_react54.useState(() => session.messages);
105162
106324
  const { mode, model, domain: domain2 } = usePromptConfig();
105163
106325
  const { isTopLayer } = useKeyboardLayer();
105164
- const { messages, status, submit, abort, interrupt, error: error51 } = useChat2(session.id, initialMessages);
105165
- const hasSubmittedInitialPromptRef = import_react50.useRef(false);
105166
- import_react50.useEffect(() => {
106326
+ const { messages, status, submit, setMessages, reload, abort, interrupt, error: error51 } = useChat2(session.id, initialMessages);
106327
+ const hasSubmittedInitialPromptRef = import_react54.useRef(false);
106328
+ const toast = useToast();
106329
+ const dialog = useDialog();
106330
+ import_react54.useEffect(() => {
105167
106331
  return () => {
105168
106332
  abort();
105169
106333
  };
@@ -105174,7 +106338,55 @@ function SessionChat({
105174
106338
  interrupt();
105175
106339
  }
105176
106340
  });
105177
- import_react50.useEffect(() => {
106341
+ useKeyboard((key) => {
106342
+ if (key.name === "r" && key.ctrl && isTopLayer("base") && status !== "streaming" && status !== "submitted") {
106343
+ const lastMsg = messages[messages.length - 1];
106344
+ if (lastMsg?.role === "assistant") {
106345
+ key.preventDefault();
106346
+ reload();
106347
+ }
106348
+ }
106349
+ });
106350
+ useKeyboard((key) => {
106351
+ if (key.name === "e" && key.ctrl && isTopLayer("base") && status !== "streaming" && status !== "submitted") {
106352
+ const lastUserMsg = messages.findLast((m2) => m2.role === "user");
106353
+ if (!lastUserMsg)
106354
+ return;
106355
+ key.preventDefault();
106356
+ const currentText = lastUserMsg.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
106357
+ let inputValue = currentText;
106358
+ dialog.open({
106359
+ title: "Edit message",
106360
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(EditMessageDialog, {
106361
+ initialText: currentText,
106362
+ onResolve: (text3) => {
106363
+ inputValue = text3;
106364
+ },
106365
+ onSubmit: () => {
106366
+ const idx = messages.findIndex((m2) => m2.id === lastUserMsg.id);
106367
+ if (idx === -1)
106368
+ return;
106369
+ const text3 = inputValue.trim();
106370
+ if (!text3)
106371
+ return;
106372
+ const truncated = messages.slice(0, idx);
106373
+ const editedMsg = {
106374
+ ...messages[idx],
106375
+ parts: [{ type: "text", text: text3 }]
106376
+ };
106377
+ setMessages([...truncated, editedMsg]);
106378
+ submit({
106379
+ userText: text3,
106380
+ mode: editedMsg.metadata?.mode ?? mode,
106381
+ model: editedMsg.metadata?.model ?? model
106382
+ });
106383
+ dialog.close();
106384
+ }
106385
+ }, undefined, false, undefined, this)
106386
+ });
106387
+ }
106388
+ });
106389
+ import_react54.useEffect(() => {
105178
106390
  if (!initialPrompt || hasSubmittedInitialPromptRef.current)
105179
106391
  return;
105180
106392
  hasSubmittedInitialPromptRef.current = true;
@@ -105188,6 +106400,8 @@ function SessionChat({
105188
106400
  onSubmit: (text3) => submit({ userText: text3, mode, model, domain: domain2 }),
105189
106401
  loading: status === "submitted" || status === "streaming",
105190
106402
  interruptible: status === "submitted" || status === "streaming",
106403
+ sessionId: session.id,
106404
+ messages,
105191
106405
  children: [
105192
106406
  messages.map((msg) => /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ChatMessage, {
105193
106407
  msg
@@ -105203,12 +106417,12 @@ function Session() {
105203
106417
  const location = useLocation();
105204
106418
  const navigate = useNavigate();
105205
106419
  const toast = useToast();
105206
- const prefetched = import_react50.useMemo(() => {
106420
+ const prefetched = import_react54.useMemo(() => {
105207
106421
  const parsed = sessionLocationSchema.safeParse(location.state);
105208
106422
  return parsed.success ? parsed.data : null;
105209
106423
  }, [location.state]);
105210
- const [session, setSession] = import_react50.useState(prefetched?.session ?? null);
105211
- import_react50.useEffect(() => {
106424
+ const [session, setSession] = import_react54.useState(prefetched?.session ?? null);
106425
+ import_react54.useEffect(() => {
105212
106426
  if (prefetched?.session)
105213
106427
  return;
105214
106428
  setSession(null);
@@ -105275,6 +106489,8 @@ function App() {
105275
106489
  router
105276
106490
  }, undefined, false, undefined, this);
105277
106491
  }
106492
+ setCurrentVersion(package_default2.version);
106493
+ checkForUpdate(package_default2.version);
105278
106494
  var renderer = await createCliRenderer({
105279
106495
  targetFps: 60,
105280
106496
  exitOnCtrlC: false