lupacode 0.2.5 → 1.0.0

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 +1401 -521
  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 = {};
@@ -93565,7 +93573,360 @@ function findDomain(id) {
93565
93573
  return DOMAINS.find((d2) => d2.id === id);
93566
93574
  }
93567
93575
  var DEFAULT_DOMAIN_ID = "coding";
93576
+ // src/lib/config.ts
93577
+ var CONFIG_DIR = join2(homedir(), ".lupacode");
93578
+ var CONFIG_PATH = join2(CONFIG_DIR, "preferences.json");
93579
+ var DEFAULTS = {
93580
+ themeName: "Nightfox",
93581
+ mode: Mode.BUILD,
93582
+ model: DEFAULT_CHAT_MODEL_ID,
93583
+ domain: DEFAULT_DOMAIN_ID
93584
+ };
93585
+ function ensureDir() {
93586
+ mkdirSync(CONFIG_DIR, { recursive: true });
93587
+ }
93588
+ function readPreferences() {
93589
+ try {
93590
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
93591
+ } catch {
93592
+ return {};
93593
+ }
93594
+ }
93595
+ function writePreferences(prefs) {
93596
+ try {
93597
+ const current = readPreferences();
93598
+ ensureDir();
93599
+ writeFileSync2(CONFIG_PATH, JSON.stringify({ ...current, ...prefs }, null, 2), "utf8");
93600
+ } catch {}
93601
+ }
93602
+
93603
+ // ../../node_modules/@opentui/react/jsx-dev-runtime.js
93604
+ var import_jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
93605
+
93606
+ // src/providers/theme/index.tsx
93607
+ function getInitialTheme() {
93608
+ const prefs = readPreferences();
93609
+ const savedTheme = typeof prefs.themeName === "string" ? THEMES.find((t2) => t2.name === prefs.themeName) : undefined;
93610
+ return savedTheme ?? DEFAULT_THEME;
93611
+ }
93612
+ function persistTheme(theme) {
93613
+ writePreferences({ themeName: theme.name });
93614
+ }
93615
+ var ThemeContext = import_react18.createContext(null);
93616
+ function useTheme() {
93617
+ const value = import_react18.useContext(ThemeContext);
93618
+ if (!value) {
93619
+ throw new Error("useTheme must be used within a ThemeProvider");
93620
+ }
93621
+ return value;
93622
+ }
93623
+ function ThemeProvider({ children }) {
93624
+ const [currentTheme, setCurrentTheme] = import_react18.useState(getInitialTheme);
93625
+ const setTheme = import_react18.useCallback((theme) => {
93626
+ setCurrentTheme(theme);
93627
+ persistTheme(theme);
93628
+ }, []);
93629
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ThemeContext.Provider, {
93630
+ value: { colors: currentTheme.colors, currentTheme, setTheme },
93631
+ children
93632
+ }, undefined, false, undefined, this);
93633
+ }
93634
+
93635
+ // src/providers/toast/index.tsx
93636
+ var ToastContext = import_react19.createContext(null);
93637
+ function useToast() {
93638
+ const value = import_react19.useContext(ToastContext);
93639
+ if (!value) {
93640
+ throw new Error("useToast must be used within a ToastProvider");
93641
+ }
93642
+ return value;
93643
+ }
93644
+ function ToastProvider({ children }) {
93645
+ const [currentToast, setCurrentToast] = import_react19.useState(null);
93646
+ const timeoutHandleRef = import_react19.useRef(null);
93647
+ const clearCurrentTimeout = import_react19.useCallback(() => {
93648
+ if (timeoutHandleRef.current) {
93649
+ clearTimeout(timeoutHandleRef.current);
93650
+ timeoutHandleRef.current = null;
93651
+ }
93652
+ }, []);
93653
+ const show = import_react19.useCallback((options) => {
93654
+ const duration3 = options.duration ?? DEFAULT_DURATION;
93655
+ clearCurrentTimeout();
93656
+ setCurrentToast({
93657
+ variant: options.variant ?? "info",
93658
+ ...options,
93659
+ duration: duration3
93660
+ });
93661
+ timeoutHandleRef.current = setTimeout(() => {
93662
+ setCurrentToast(null);
93663
+ }, duration3).unref();
93664
+ }, [clearCurrentTimeout]);
93665
+ const value = import_react19.useMemo(() => ({ show }), [show]);
93666
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ToastContext.Provider, {
93667
+ value,
93668
+ children: [
93669
+ children,
93670
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(Toast, {
93671
+ currentToast
93672
+ }, undefined, false, undefined, this)
93673
+ ]
93674
+ }, undefined, true, undefined, this);
93675
+ }
93676
+ function Toast({ currentToast }) {
93677
+ const { width } = useTerminalDimensions();
93678
+ const { colors } = useTheme();
93679
+ if (!currentToast) {
93680
+ return null;
93681
+ }
93682
+ const variantColors = {
93683
+ success: colors.success,
93684
+ error: colors.error,
93685
+ info: colors.info
93686
+ };
93687
+ const borderColor = currentToast.variant ? variantColors[currentToast.variant] : variantColors.info;
93688
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
93689
+ position: "absolute",
93690
+ justifyContent: "center",
93691
+ alignItems: "flex-start",
93692
+ top: 2,
93693
+ right: 2,
93694
+ width: Math.max(1, Math.min(60, width - 6)),
93695
+ paddingLeft: 2,
93696
+ paddingRight: 2,
93697
+ paddingTop: 1,
93698
+ paddingBottom: 1,
93699
+ backgroundColor: colors.surface,
93700
+ borderColor,
93701
+ border: ["left", "right"],
93702
+ customBorderChars: SplitBorderChars,
93703
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
93704
+ flexDirection: "column",
93705
+ gap: 1,
93706
+ width: "100%",
93707
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
93708
+ fg: "#E1E1E1",
93709
+ wrapMode: "word",
93710
+ width: "100%",
93711
+ children: currentToast.message
93712
+ }, undefined, false, undefined, this)
93713
+ }, undefined, false, undefined, this)
93714
+ }, undefined, false, undefined, this);
93715
+ }
93716
+
93717
+ // src/providers/dialog/index.tsx
93718
+ var import_react24 = __toESM(require_react(), 1);
93719
+
93720
+ // src/providers/keyboard-layer/index.tsx
93721
+ var import_react21 = __toESM(require_react(), 1);
93722
+ var KeyboardLayerContext = import_react21.createContext(null);
93723
+ function KeyboardLayerProvider({ children }) {
93724
+ const [stack, setStack] = import_react21.useState(["base"]);
93725
+ const stackRef = import_react21.useRef(stack);
93726
+ stackRef.current = stack;
93727
+ const responders = import_react21.useRef(new Map);
93728
+ const renderer = useRenderer();
93729
+ const push = import_react21.useCallback((id, responder) => {
93730
+ if (responder) {
93731
+ responders.current.set(id, responder);
93732
+ }
93733
+ setStack((prev) => [...prev, id]);
93734
+ }, []);
93735
+ const pop = import_react21.useCallback((id) => {
93736
+ setStack((prev) => {
93737
+ const idx = prev.lastIndexOf(id);
93738
+ if (idx === -1)
93739
+ return prev;
93740
+ if (prev.indexOf(id) === idx) {
93741
+ responders.current.delete(id);
93742
+ }
93743
+ return prev.slice(0, idx);
93744
+ });
93745
+ }, []);
93746
+ const isTopLayer = import_react21.useCallback((id) => {
93747
+ return stack.length === 0 || stack[stack.length - 1] === id;
93748
+ }, [stack]);
93749
+ const setResponder = import_react21.useCallback((id, responder) => {
93750
+ if (responder) {
93751
+ responders.current.set(id, responder);
93752
+ } else {
93753
+ responders.current.delete(id);
93754
+ }
93755
+ }, []);
93756
+ useKeyboard((key) => {
93757
+ if (!key.ctrl || key.name !== "c")
93758
+ return;
93759
+ const currentStack = stackRef.current;
93760
+ for (let i = currentStack.length - 1;i >= 0; i--) {
93761
+ const layerId = currentStack[i];
93762
+ const responder = responders.current.get(layerId);
93763
+ if (responder && responder()) {
93764
+ return;
93765
+ }
93766
+ }
93767
+ renderer.destroy();
93768
+ });
93769
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(KeyboardLayerContext.Provider, {
93770
+ value: { push, pop, isTopLayer, setResponder },
93771
+ children
93772
+ }, undefined, false, undefined, this);
93773
+ }
93774
+ function useKeyboardLayer() {
93775
+ const context = import_react21.useContext(KeyboardLayerContext);
93776
+ if (!context) {
93777
+ throw new Error("useKeyboardLayer must be used within a KeyboardLayerProvider");
93778
+ }
93779
+ return context;
93780
+ }
93781
+
93782
+ // src/components/error-boundary.tsx
93783
+ var import_react23 = __toESM(require_react(), 1);
93784
+ class ErrorBoundary2 extends import_react23.Component {
93785
+ constructor(props) {
93786
+ super(props);
93787
+ this.state = { error: null };
93788
+ }
93789
+ static getDerivedStateFromError(error51) {
93790
+ return { error: error51 };
93791
+ }
93792
+ componentDidCatch(error51, errorInfo) {
93793
+ console.error("[ErrorBoundary] Caught error:", error51, errorInfo.componentStack);
93794
+ }
93795
+ render() {
93796
+ if (this.state.error) {
93797
+ console.error("[ErrorBoundary] Rendering fallback for:", this.state.error.message);
93798
+ return this.props.fallback ?? /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
93799
+ flexDirection: "column",
93800
+ padding: 1,
93801
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
93802
+ children: [
93803
+ "Error: ",
93804
+ this.state.error.message
93805
+ ]
93806
+ }, undefined, true, undefined, this)
93807
+ }, undefined, false, undefined, this);
93808
+ }
93809
+ return this.props.children;
93810
+ }
93811
+ }
93812
+
93813
+ // src/providers/dialog/index.tsx
93814
+ var DialogContext = import_react24.createContext(null);
93815
+ function useDialog() {
93816
+ const value = import_react24.useContext(DialogContext);
93817
+ if (!value) {
93818
+ throw new Error("useDialog must be used within a DialogProvider");
93819
+ }
93820
+ return value;
93821
+ }
93822
+ function DialogProvider({ children }) {
93823
+ const [dialogStack, setDialogStack] = import_react24.useState([]);
93824
+ const { push, pop } = useKeyboardLayer();
93825
+ const close = import_react24.useCallback(() => {
93826
+ setDialogStack((prev) => {
93827
+ const next = prev.slice(0, -1);
93828
+ pop("dialog");
93829
+ return next;
93830
+ });
93831
+ }, [pop]);
93832
+ const open = import_react24.useCallback((config2) => {
93833
+ setDialogStack((prev) => [...prev, config2]);
93834
+ push("dialog", () => {
93835
+ close();
93836
+ return true;
93837
+ });
93838
+ }, [push, close]);
93839
+ const value = {
93840
+ open,
93841
+ close
93842
+ };
93843
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(DialogContext.Provider, {
93844
+ value,
93845
+ children: [
93846
+ children,
93847
+ dialogStack.map((config2, i) => /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(Dialog, {
93848
+ config: config2,
93849
+ close: i === dialogStack.length - 1 ? close : undefined,
93850
+ isBack: i < dialogStack.length - 1
93851
+ }, `dialog-${i}`, false, undefined, this))
93852
+ ]
93853
+ }, undefined, true, undefined, this);
93854
+ }
93855
+ function Dialog({ config: config2, close, isBack = false }) {
93856
+ const { isTopLayer } = useKeyboardLayer();
93857
+ const dimensions = useTerminalDimensions();
93858
+ const { colors } = useTheme();
93859
+ useKeyboard((key) => {
93860
+ if (!close || !isTopLayer("dialog"))
93861
+ return;
93862
+ if (key.name === "escape") {
93863
+ close();
93864
+ }
93865
+ });
93866
+ const { title, children } = config2;
93867
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
93868
+ position: "absolute",
93869
+ left: 0,
93870
+ top: 0,
93871
+ width: dimensions.width,
93872
+ height: dimensions.height,
93873
+ justifyContent: "center",
93874
+ alignItems: "center",
93875
+ backgroundColor: RGBA.fromInts(0, 0, 0, 150),
93876
+ zIndex: 100,
93877
+ onMouseDown: close,
93878
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
93879
+ width: Math.min(60, dimensions.width - 4),
93880
+ height: "auto",
93881
+ backgroundColor: colors.dialogSurface,
93882
+ paddingX: 4,
93883
+ paddingY: 1,
93884
+ flexDirection: "column",
93885
+ gap: 1,
93886
+ onMouseDown: (e) => e.stopPropagation(),
93887
+ children: [
93888
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
93889
+ paddingBottom: 1,
93890
+ flexDirection: "row",
93891
+ alignItems: "center",
93892
+ justifyContent: "space-between",
93893
+ children: [
93894
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
93895
+ attributes: TextAttributes.BOLD,
93896
+ children: title
93897
+ }, undefined, false, undefined, this),
93898
+ close && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
93899
+ attributes: TextAttributes.DIM,
93900
+ onMouseDown: close,
93901
+ children: isBack ? "back (esc)" : "esc"
93902
+ }, undefined, false, undefined, this)
93903
+ ]
93904
+ }, undefined, true, undefined, this),
93905
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
93906
+ flexGrow: 1,
93907
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ErrorBoundary2, {
93908
+ children
93909
+ }, undefined, false, undefined, this)
93910
+ }, undefined, false, undefined, this)
93911
+ ]
93912
+ }, undefined, true, undefined, this)
93913
+ }, undefined, false, undefined, this);
93914
+ }
93915
+
93916
+ // src/layouts/themed-root.tsx
93917
+ function ThemedRoot({ children }) {
93918
+ const { colors } = useTheme();
93919
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
93920
+ backgroundColor: colors.background,
93921
+ width: "100%",
93922
+ height: "100%",
93923
+ flexGrow: 1,
93924
+ children
93925
+ }, undefined, false, undefined, this);
93926
+ }
93927
+
93568
93928
  // src/providers/prompt-config/index.tsx
93929
+ var import_react26 = __toESM(require_react(), 1);
93569
93930
  var PromptConfigContext = import_react26.createContext(null);
93570
93931
  function usePromptConfig() {
93571
93932
  const value = import_react26.useContext(PromptConfigContext);
@@ -93574,12 +93935,32 @@ function usePromptConfig() {
93574
93935
  }
93575
93936
  return value;
93576
93937
  }
93938
+ function getInitial(key, fallback) {
93939
+ const prefs = readPreferences();
93940
+ return prefs[key] !== undefined ? prefs[key] : fallback;
93941
+ }
93577
93942
  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);
93943
+ const [mode, setModeState] = import_react26.useState(() => getInitial("mode", Mode.BUILD));
93944
+ const [model, setModelState] = import_react26.useState(() => getInitial("model", DEFAULT_CHAT_MODEL_ID));
93945
+ const [domain2, setDomainState] = import_react26.useState(() => getInitial("domain", DEFAULT_DOMAIN_ID));
93946
+ const setMode = import_react26.useCallback((m2) => {
93947
+ setModeState(m2);
93948
+ writePreferences({ mode: m2 });
93949
+ }, []);
93950
+ const setModel = import_react26.useCallback((m2) => {
93951
+ setModelState(m2);
93952
+ writePreferences({ model: m2 });
93953
+ }, []);
93954
+ const setDomain = import_react26.useCallback((d2) => {
93955
+ setDomainState(d2);
93956
+ writePreferences({ domain: d2 });
93957
+ }, []);
93581
93958
  const toggleMode = import_react26.useCallback(() => {
93582
- setMode((m2) => m2 === Mode.BUILD ? Mode.PLAN : Mode.BUILD);
93959
+ setModeState((m2) => {
93960
+ const next = m2 === Mode.BUILD ? Mode.PLAN : Mode.BUILD;
93961
+ writePreferences({ mode: next });
93962
+ return next;
93963
+ });
93583
93964
  }, []);
93584
93965
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(PromptConfigContext.Provider, {
93585
93966
  value: {
@@ -93600,8 +93981,8 @@ function RootLayout() {
93600
93981
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ThemeProvider, {
93601
93982
  children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ToastProvider, {
93602
93983
  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, {
93984
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(PromptConfigProvider, {
93985
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(DialogProvider, {
93605
93986
  children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ThemedRoot, {
93606
93987
  children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(Outlet, {}, undefined, false, undefined, this)
93607
93988
  }, undefined, false, undefined, this)
@@ -93613,7 +93994,7 @@ function RootLayout() {
93613
93994
  }
93614
93995
 
93615
93996
  // src/screens/home.tsx
93616
- var import_react38 = __toESM(require_react(), 1);
93997
+ var import_react41 = __toESM(require_react(), 1);
93617
93998
 
93618
93999
  // src/components/header.tsx
93619
94000
  function Header() {
@@ -93641,7 +94022,7 @@ function Header() {
93641
94022
  }
93642
94023
 
93643
94024
  // src/components/input-bar.tsx
93644
- var import_react36 = __toESM(require_react(), 1);
94025
+ var import_react39 = __toESM(require_react(), 1);
93645
94026
  import { readdir } from "fs/promises";
93646
94027
  import { isAbsolute as isAbsolute3, relative, resolve as resolve5 } from "path";
93647
94028
 
@@ -93659,7 +94040,8 @@ function DialogSearchList({
93659
94040
  renderItem,
93660
94041
  getKey,
93661
94042
  placeholder = "Search",
93662
- emptyText = "No results"
94043
+ emptyText = "No results",
94044
+ onEndReached
93663
94045
  }) {
93664
94046
  const [selectedIndex, setSelectedIndex] = import_react27.useState(0);
93665
94047
  const [searchValue, setSearchValue] = import_react27.useState("");
@@ -93667,6 +94049,26 @@ function DialogSearchList({
93667
94049
  const scrollRef = import_react27.useRef(null);
93668
94050
  const { isTopLayer } = useKeyboardLayer();
93669
94051
  const { colors } = useTheme();
94052
+ const itemsRef = import_react27.useRef(items);
94053
+ itemsRef.current = items;
94054
+ const filterFnRef = import_react27.useRef(filterFn);
94055
+ filterFnRef.current = filterFn;
94056
+ const onSelectRef = import_react27.useRef(onSelect);
94057
+ onSelectRef.current = onSelect;
94058
+ const onHighlightRef = import_react27.useRef(onHighlight);
94059
+ onHighlightRef.current = onHighlight;
94060
+ const selectedIndexRef = import_react27.useRef(selectedIndex);
94061
+ selectedIndexRef.current = selectedIndex;
94062
+ const searchValueRef = import_react27.useRef(searchValue);
94063
+ searchValueRef.current = searchValue;
94064
+ const handleSubmit = import_react27.useCallback(() => {
94065
+ const text2 = inputRef.current?.value ?? "";
94066
+ const currentFiltered = text2 ? itemsRef.current.filter((item2) => filterFnRef.current(item2, text2)) : itemsRef.current;
94067
+ const item = currentFiltered[selectedIndexRef.current];
94068
+ if (item) {
94069
+ onSelectRef.current(item);
94070
+ }
94071
+ }, []);
93670
94072
  const handleContentChange = import_react27.useCallback(() => {
93671
94073
  const text2 = inputRef.current?.value ?? "";
93672
94074
  setSearchValue(text2);
@@ -93694,8 +94096,8 @@ function DialogSearchList({
93694
94096
  sb.scrollTo(newIndex);
93695
94097
  }
93696
94098
  const item = filtered[newIndex];
93697
- if (item && onHighlight)
93698
- onHighlight(item);
94099
+ if (item && onHighlightRef.current)
94100
+ onHighlightRef.current(item);
93699
94101
  return newIndex;
93700
94102
  });
93701
94103
  } else if (key.name === "down") {
@@ -93710,8 +94112,11 @@ function DialogSearchList({
93710
94112
  }
93711
94113
  }
93712
94114
  const item = filtered[newIndex];
93713
- if (item && onHighlight)
93714
- onHighlight(item);
94115
+ if (item && onHighlightRef.current)
94116
+ onHighlightRef.current(item);
94117
+ if (newIndex >= filtered.length - 2 && onEndReached) {
94118
+ onEndReached();
94119
+ }
93715
94120
  return newIndex;
93716
94121
  });
93717
94122
  }
@@ -93724,7 +94129,8 @@ function DialogSearchList({
93724
94129
  ref: inputRef,
93725
94130
  placeholder,
93726
94131
  focused: true,
93727
- onContentChange: handleContentChange
94132
+ onContentChange: handleContentChange,
94133
+ onSubmit: handleSubmit
93728
94134
  }, undefined, false, undefined, this),
93729
94135
  filtered.length === 0 ? /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
93730
94136
  attributes: TextAttributes.DIM,
@@ -93741,10 +94147,10 @@ function DialogSearchList({
93741
94147
  backgroundColor: isSelected ? colors.selectMode : undefined,
93742
94148
  onMouseMove: () => {
93743
94149
  setSelectedIndex(i);
93744
- if (onHighlight)
93745
- onHighlight(item);
94150
+ if (onHighlightRef.current)
94151
+ onHighlightRef.current(item);
93746
94152
  },
93747
- onMouseDown: () => onSelect(item),
94153
+ onMouseDown: () => onSelectRef.current(item),
93748
94154
  children: renderItem(item, isSelected)
93749
94155
  }, getKey(item), false, undefined, this);
93750
94156
  })
@@ -93768,8 +94174,8 @@ var ThemeDialogContent = () => {
93768
94174
  }, [setTheme]);
93769
94175
  const handleSelect = import_react29.useCallback((theme) => {
93770
94176
  confirmedRef.current = true;
93771
- setTheme(theme);
93772
94177
  dialog.close();
94178
+ setTheme(theme);
93773
94179
  }, [setTheme, dialog]);
93774
94180
  const handleHighlight = import_react29.useCallback((theme) => {
93775
94181
  setTheme(theme);
@@ -94119,6 +94525,27 @@ function clearAuth() {
94119
94525
  }
94120
94526
 
94121
94527
  // src/lib/api-client.ts
94528
+ var RETRY_ATTEMPTS = 3;
94529
+ var RETRY_BASE_DELAY_MS = 1000;
94530
+ async function sleep2(ms) {
94531
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
94532
+ }
94533
+ async function withRetry(fn, options) {
94534
+ const maxAttempts = options?.attempts ?? RETRY_ATTEMPTS;
94535
+ let lastError;
94536
+ for (let attempt = 0;attempt < maxAttempts; attempt++) {
94537
+ try {
94538
+ return await fn(attempt);
94539
+ } catch (err) {
94540
+ lastError = err;
94541
+ if (attempt < maxAttempts - 1) {
94542
+ const delay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt);
94543
+ await sleep2(delay);
94544
+ }
94545
+ }
94546
+ }
94547
+ throw lastError;
94548
+ }
94122
94549
  var apiClient = hc(process.env.API_URL ?? "https://lupacodeserver-production.up.railway.app", {
94123
94550
  fetch: async (input, init) => {
94124
94551
  const headers = new Headers(init?.headers);
@@ -95577,44 +96004,126 @@ function cleanEscapedString(input) {
95577
96004
  }
95578
96005
 
95579
96006
  // src/components/dialogs/sessions-dialog.tsx
96007
+ var PAGE_SIZE = 20;
95580
96008
  var SessionsDialogContent = () => {
95581
96009
  const [sessions, setSessions] = import_react30.useState([]);
95582
96010
  const [loading, setLoading] = import_react30.useState(true);
96011
+ const [hasMore, setHasMore] = import_react30.useState(false);
96012
+ const [cursor, setCursor] = import_react30.useState();
96013
+ const [highlightedSession, setHighlightedSession] = import_react30.useState(null);
96014
+ const [view, setView] = import_react30.useState({ type: "list" });
95583
96015
  const { close } = useDialog();
95584
96016
  const navigate = useNavigate();
95585
96017
  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
- }
96018
+ const { isTopLayer } = useKeyboardLayer();
96019
+ const { colors } = useTheme();
96020
+ const fetchSessions = import_react30.useCallback(async (cursorVal) => {
96021
+ try {
96022
+ const params = new URLSearchParams;
96023
+ params.set("limit", String(PAGE_SIZE));
96024
+ if (cursorVal)
96025
+ params.set("cursor", cursorVal);
96026
+ const res = await apiClient.sessions.$get({ query: Object.fromEntries(params) });
96027
+ if (!res.ok) {
96028
+ throw new Error(await getErrorMessage2(res));
96029
+ }
96030
+ const raw = await res.json();
96031
+ let list;
96032
+ let more;
96033
+ if (Array.isArray(raw)) {
96034
+ list = raw;
96035
+ more = false;
96036
+ } else {
96037
+ const parsed = raw;
96038
+ list = parsed.sessions ?? [];
96039
+ more = parsed.hasMore ?? false;
95607
96040
  }
95608
- };
95609
- fetchSessions();
95610
- return () => {
95611
- ignore = true;
95612
- };
96041
+ if (cursorVal) {
96042
+ setSessions((prev) => [...prev, ...list]);
96043
+ } else {
96044
+ setSessions(list);
96045
+ }
96046
+ setHasMore(more);
96047
+ if (list.length > 0) {
96048
+ setCursor(list[list.length - 1].createdAt);
96049
+ }
96050
+ setLoading(false);
96051
+ } catch (error51) {
96052
+ show({
96053
+ variant: "error",
96054
+ message: error51 instanceof Error ? error51.message : "Failed to fetch sessions"
96055
+ });
96056
+ close();
96057
+ }
95613
96058
  }, [close, show]);
96059
+ import_react30.useEffect(() => {
96060
+ fetchSessions();
96061
+ }, [fetchSessions]);
95614
96062
  const handleSelect = import_react30.useCallback((session) => {
95615
96063
  close();
95616
96064
  navigate(`/sessions/${session.id}`);
95617
96065
  }, [close, navigate]);
96066
+ const handleDelete = import_react30.useCallback(async (session) => {
96067
+ try {
96068
+ const res = await withRetry(() => apiClient.sessions[":id"].$delete({ param: { id: session.id } }));
96069
+ if (!res.ok)
96070
+ throw new Error(await getErrorMessage2(res));
96071
+ show({ variant: "success", message: "Session deleted" });
96072
+ setSessions((prev) => prev.filter((s) => s.id !== session.id));
96073
+ setView({ type: "list" });
96074
+ } catch (error51) {
96075
+ show({
96076
+ variant: "error",
96077
+ message: error51 instanceof Error ? error51.message : "Failed to delete session"
96078
+ });
96079
+ setView({ type: "list" });
96080
+ }
96081
+ }, [show]);
96082
+ const handleRename = import_react30.useCallback(async (session, newTitle) => {
96083
+ try {
96084
+ const res = await withRetry(() => apiClient.sessions[":id"].$patch({
96085
+ param: { id: session.id },
96086
+ json: { title: newTitle }
96087
+ }));
96088
+ if (!res.ok)
96089
+ throw new Error(await getErrorMessage2(res));
96090
+ show({ variant: "success", message: "Session renamed" });
96091
+ setSessions((prev) => prev.map((s) => s.id === session.id ? { ...s, title: newTitle } : s));
96092
+ setView({ type: "list" });
96093
+ } catch (error51) {
96094
+ show({
96095
+ variant: "error",
96096
+ message: error51 instanceof Error ? error51.message : "Failed to rename session"
96097
+ });
96098
+ setView({ type: "list" });
96099
+ }
96100
+ }, [show]);
96101
+ const handleLoadMore = import_react30.useCallback(async () => {
96102
+ if (hasMore && cursor) {
96103
+ await fetchSessions(cursor);
96104
+ }
96105
+ }, [hasMore, cursor, fetchSessions]);
96106
+ useKeyboard((key) => {
96107
+ if (!isTopLayer("dialog"))
96108
+ return;
96109
+ if (view.type === "list") {
96110
+ if (key.name === "d" && highlightedSession) {
96111
+ setView({ type: "confirm-delete", session: highlightedSession });
96112
+ } else if (key.name === "r" && highlightedSession) {
96113
+ setView({ type: "rename", session: highlightedSession });
96114
+ }
96115
+ } else if (view.type === "confirm-delete") {
96116
+ if (key.name === "y" || key.name === "enter") {
96117
+ handleDelete(view.session);
96118
+ } else if (key.name === "n" || key.name === "escape") {
96119
+ setView({ type: "list" });
96120
+ }
96121
+ } else if (view.type === "rename") {
96122
+ if (key.name === "escape") {
96123
+ setView({ type: "list" });
96124
+ }
96125
+ }
96126
+ });
95618
96127
  if (loading) {
95619
96128
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
95620
96129
  flexDirection: "column",
@@ -95633,35 +96142,94 @@ var SessionsDialogContent = () => {
95633
96142
  }, undefined, false, undefined, this)
95634
96143
  }, undefined, false, undefined, this);
95635
96144
  }
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, {
96145
+ if (view.type === "confirm-delete") {
96146
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96147
+ flexDirection: "column",
96148
+ gap: 1,
95641
96149
  children: [
95642
96150
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
95643
- selectable: false,
95644
- fg: isSelected ? "black" : "white",
95645
- children: session.title
95646
- }, undefined, false, undefined, this),
96151
+ children: [
96152
+ 'Delete "',
96153
+ view.session.title,
96154
+ '"?'
96155
+ ]
96156
+ }, undefined, true, undefined, this),
95647
96157
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
95648
- flexGrow: 1
95649
- }, undefined, false, undefined, this),
96158
+ flexDirection: "row",
96159
+ gap: 2,
96160
+ children: [
96161
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
96162
+ selectable: true,
96163
+ fg: colors.error,
96164
+ onMouseDown: () => handleDelete(view.session),
96165
+ children: "yes (y)"
96166
+ }, undefined, false, undefined, this),
96167
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
96168
+ selectable: true,
96169
+ onMouseDown: () => setView({ type: "list" }),
96170
+ children: "no (n)"
96171
+ }, undefined, false, undefined, this)
96172
+ ]
96173
+ }, undefined, true, undefined, this)
96174
+ ]
96175
+ }, undefined, true, undefined, this);
96176
+ }
96177
+ if (view.type === "rename") {
96178
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96179
+ flexDirection: "column",
96180
+ gap: 1,
96181
+ children: [
95650
96182
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
95651
- selectable: false,
95652
- fg: isSelected ? "black" : undefined,
95653
96183
  attributes: TextAttributes.DIM,
95654
- children: format(new Date(session.createdAt), "hh:mm a")
96184
+ children: "Rename session:"
96185
+ }, undefined, false, undefined, this),
96186
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("input", {
96187
+ focused: true,
96188
+ initialValue: view.session.title,
96189
+ onSubmit: (value) => {
96190
+ if (value.trim()) {
96191
+ handleRename(view.session, value.trim());
96192
+ }
96193
+ }
95655
96194
  }, undefined, false, undefined, this)
95656
96195
  ]
95657
- }, undefined, true, undefined, this),
95658
- getKey: (s) => s.id,
95659
- placeholder: "Search sessions",
95660
- emptyText: "No matching sessions"
96196
+ }, undefined, true, undefined, this);
96197
+ }
96198
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96199
+ flexDirection: "column",
96200
+ gap: 1,
96201
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(DialogSearchList, {
96202
+ items: sessions,
96203
+ onSelect: handleSelect,
96204
+ onHighlight: (s) => setHighlightedSession(s),
96205
+ filterFn: (s, query) => (s.title || "").toLowerCase().includes(query.toLowerCase()),
96206
+ renderItem: (session, isSelected) => /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(import_jsx_dev_runtime2.Fragment, {
96207
+ children: [
96208
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
96209
+ selectable: false,
96210
+ fg: isSelected ? "black" : "white",
96211
+ children: session.title
96212
+ }, undefined, false, undefined, this),
96213
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96214
+ flexGrow: 1
96215
+ }, undefined, false, undefined, this),
96216
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
96217
+ selectable: false,
96218
+ fg: isSelected ? "black" : undefined,
96219
+ attributes: TextAttributes.DIM,
96220
+ children: format(new Date(session.createdAt), "hh:mm a")
96221
+ }, undefined, false, undefined, this)
96222
+ ]
96223
+ }, undefined, true, undefined, this),
96224
+ getKey: (s) => s.id,
96225
+ placeholder: "Search sessions (r) rename (d) delete",
96226
+ emptyText: "No matching sessions",
96227
+ onEndReached: handleLoadMore
96228
+ }, undefined, false, undefined, this)
95661
96229
  }, undefined, false, undefined, this);
95662
96230
  };
95663
96231
  // src/components/dialogs/agents-dialog.tsx
95664
- var import_react31 = __toESM(require_react(), 1);
96232
+ var import_react32 = __toESM(require_react(), 1);
95665
96233
  var AVAILABLE_MODES = [Mode.BUILD, Mode.PLAN];
95666
96234
  function getModeLabel(mode) {
95667
96235
  return mode === Mode.PLAN ? "Plan" : "Build";
@@ -95671,9 +96239,9 @@ var AgentsDialogContent = ({
95671
96239
  onSelectMode
95672
96240
  }) => {
95673
96241
  const dialog = useDialog();
95674
- const handleSelect = import_react31.useCallback((nextMode) => {
95675
- onSelectMode(nextMode);
96242
+ const handleSelect = import_react32.useCallback((nextMode) => {
95676
96243
  dialog.close();
96244
+ onSelectMode(nextMode);
95677
96245
  }, [onSelectMode, dialog]);
95678
96246
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(DialogSearchList, {
95679
96247
  items: AVAILABLE_MODES,
@@ -95693,15 +96261,15 @@ var AgentsDialogContent = ({
95693
96261
  }, undefined, false, undefined, this);
95694
96262
  };
95695
96263
  // src/components/dialogs/models-dialog.tsx
95696
- var import_react32 = __toESM(require_react(), 1);
96264
+ var import_react33 = __toESM(require_react(), 1);
95697
96265
  var ModelsDialogContent = ({
95698
96266
  models,
95699
96267
  onSelectModel
95700
96268
  }) => {
95701
96269
  const dialog = useDialog();
95702
- const handleSelect = import_react32.useCallback((model) => {
95703
- onSelectModel(model.id);
96270
+ const handleSelect = import_react33.useCallback((model) => {
95704
96271
  dialog.close();
96272
+ onSelectModel(model.id);
95705
96273
  }, [dialog, onSelectModel]);
95706
96274
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(DialogSearchList, {
95707
96275
  items: models,
@@ -95737,14 +96305,14 @@ var ModelsDialogContent = ({
95737
96305
  }, undefined, false, undefined, this);
95738
96306
  };
95739
96307
  // src/components/dialogs/domains-dialog.tsx
95740
- var import_react33 = __toESM(require_react(), 1);
96308
+ var import_react34 = __toESM(require_react(), 1);
95741
96309
  var DomainsDialogContent = ({
95742
96310
  onSelectDomain
95743
96311
  }) => {
95744
96312
  const dialog = useDialog();
95745
- const handleSelect = import_react33.useCallback((domain2) => {
95746
- onSelectDomain(domain2.id);
96313
+ const handleSelect = import_react34.useCallback((domain2) => {
95747
96314
  dialog.close();
96315
+ onSelectDomain(domain2.id);
95748
96316
  }, [dialog, onSelectDomain]);
95749
96317
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(DialogSearchList, {
95750
96318
  items: [...DOMAINS],
@@ -95760,6 +96328,255 @@ var DomainsDialogContent = ({
95760
96328
  emptyText: "No matching domains"
95761
96329
  }, undefined, false, undefined, this);
95762
96330
  };
96331
+ // src/components/dialogs/settings-dialog.tsx
96332
+ var import_react35 = __toESM(require_react(), 1);
96333
+ // package.json
96334
+ var package_default2 = {
96335
+ name: "lupacode",
96336
+ version: "1.0.0",
96337
+ description: "AI-powered terminal coding assistant",
96338
+ type: "module",
96339
+ bin: {
96340
+ lupacode: "bin/lupacode"
96341
+ },
96342
+ files: [
96343
+ "bin/",
96344
+ "dist/",
96345
+ "README.md"
96346
+ ],
96347
+ scripts: {
96348
+ dev: "bun run --watch src/index.tsx",
96349
+ build: 'bun build src/index.tsx --outdir dist --target bun --external "@opentui/core-*"',
96350
+ prepublishOnly: "bun run build",
96351
+ typecheck: "tsc -p tsconfig.json --noEmit"
96352
+ },
96353
+ engines: {
96354
+ bun: ">=1.0.0"
96355
+ },
96356
+ devDependencies: {
96357
+ "@types/bun": "latest",
96358
+ "@types/react": "^19.2.17"
96359
+ },
96360
+ peerDependencies: {
96361
+ typescript: "^5"
96362
+ },
96363
+ peerDependenciesMeta: {
96364
+ typescript: {
96365
+ optional: true
96366
+ }
96367
+ },
96368
+ dependencies: {
96369
+ "@ai-sdk/react": "^4.0.4",
96370
+ "@opentui/core": "^0.3.4",
96371
+ "@opentui/react": "^0.3.4",
96372
+ ai: "^7.0.3",
96373
+ "date-fns": "^4.4.0",
96374
+ dotenv: "^16.4.7",
96375
+ hono: "^4.12.26",
96376
+ open: "^11.0.0",
96377
+ "opentui-spinner": "^0.0.7",
96378
+ "pretty-ms": "^9.3.0",
96379
+ react: "^19.2.6",
96380
+ "react-router": "^8.0.1",
96381
+ zod: "^4.4.3"
96382
+ }
96383
+ };
96384
+
96385
+ // src/lib/update-check.ts
96386
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
96387
+ import { homedir as homedir3 } from "os";
96388
+ import { join as join4 } from "path";
96389
+ var DIR = join4(homedir3(), ".lupacode");
96390
+ var FILE = join4(DIR, "update-check.json");
96391
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
96392
+ function readCache() {
96393
+ try {
96394
+ return JSON.parse(readFileSync3(FILE, "utf-8"));
96395
+ } catch {
96396
+ return null;
96397
+ }
96398
+ }
96399
+ function writeCache(data2) {
96400
+ if (!existsSync5(DIR)) {
96401
+ mkdirSync3(DIR, { mode: 448 });
96402
+ }
96403
+ writeFileSync4(FILE, JSON.stringify(data2));
96404
+ }
96405
+ var cachedLatest = null;
96406
+ var cachedCurrent = "0.0.0";
96407
+ function setCurrentVersion(v2) {
96408
+ cachedCurrent = v2;
96409
+ }
96410
+ function getLatestVersion() {
96411
+ return cachedLatest;
96412
+ }
96413
+ async function checkForUpdate(currentVersion) {
96414
+ cachedCurrent = currentVersion;
96415
+ const cache = readCache();
96416
+ if (cache && Date.now() - cache.checkedAt < CHECK_INTERVAL_MS) {
96417
+ cachedLatest = cache.latest;
96418
+ return cache.latest && cache.latest !== currentVersion ? cache.latest : null;
96419
+ }
96420
+ try {
96421
+ const res = await fetch("https://registry.npmjs.org/lupacode/latest");
96422
+ if (!res.ok)
96423
+ return null;
96424
+ const data2 = await res.json();
96425
+ const latest = data2.version;
96426
+ const result = { latest, checkedAt: Date.now() };
96427
+ writeCache(result);
96428
+ cachedLatest = latest;
96429
+ return latest !== currentVersion ? latest : null;
96430
+ } catch {
96431
+ return null;
96432
+ }
96433
+ }
96434
+ function hasUpdate() {
96435
+ return cachedLatest !== null && cachedLatest !== cachedCurrent;
96436
+ }
96437
+
96438
+ // src/components/dialogs/settings-dialog.tsx
96439
+ var ITEMS = ["theme", "mode", "model", "domain"];
96440
+ var SettingsDialogContent = () => {
96441
+ const dialog = useDialog();
96442
+ const { currentTheme, setTheme } = useTheme();
96443
+ const { mode, setMode, model, setModel, domain: domain2, setDomain } = usePromptConfig();
96444
+ const { isTopLayer } = useKeyboardLayer();
96445
+ const [selectedIndex, setSelectedIndex] = import_react35.useState(0);
96446
+ const selectedIndexRef = import_react35.useRef(0);
96447
+ const currentModel = findSupportedChatModel(model);
96448
+ const currentDomain = findDomain(domain2);
96449
+ const openDialogRef = import_react35.useRef(undefined);
96450
+ const openDialog = import_react35.useCallback((item) => {
96451
+ switch (item) {
96452
+ case "theme":
96453
+ dialog.open({ title: "Select Theme", children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ThemeDialogContent, {}, undefined, false, undefined, this) });
96454
+ break;
96455
+ case "mode":
96456
+ dialog.open({ title: "Select Mode", children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(AgentsDialogContent, {
96457
+ currentMode: mode,
96458
+ onSelectMode: setMode
96459
+ }, undefined, false, undefined, this) });
96460
+ break;
96461
+ case "model":
96462
+ dialog.open({ title: "Select Model", children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ModelsDialogContent, {
96463
+ models: SUPPORTED_CHAT_MODELS,
96464
+ onSelectModel: setModel
96465
+ }, undefined, false, undefined, this) });
96466
+ break;
96467
+ case "domain":
96468
+ dialog.open({ title: "Select Domain", children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(DomainsDialogContent, {
96469
+ onSelectDomain: setDomain
96470
+ }, undefined, false, undefined, this) });
96471
+ break;
96472
+ }
96473
+ }, [dialog, mode, setMode, setModel, setDomain]);
96474
+ openDialogRef.current = openDialog;
96475
+ useKeyboard((key) => {
96476
+ if (!isTopLayer("dialog"))
96477
+ return;
96478
+ if (key.name === "down") {
96479
+ setSelectedIndex((i) => {
96480
+ const next = Math.min(ITEMS.length - 1, i + 1);
96481
+ selectedIndexRef.current = next;
96482
+ return next;
96483
+ });
96484
+ } else if (key.name === "up") {
96485
+ setSelectedIndex((i) => {
96486
+ const next = Math.max(0, i - 1);
96487
+ selectedIndexRef.current = next;
96488
+ return next;
96489
+ });
96490
+ } else if (key.name === "enter" || key.name === "return") {
96491
+ openDialogRef.current?.(ITEMS[selectedIndexRef.current]);
96492
+ }
96493
+ });
96494
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96495
+ flexDirection: "column",
96496
+ gap: 1,
96497
+ children: [
96498
+ ITEMS.map((item, i) => {
96499
+ const isSelected = i === selectedIndex;
96500
+ const bgColor = isSelected ? currentTheme.colors.selectMode : undefined;
96501
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96502
+ flexDirection: "row",
96503
+ justifyContent: "space-between",
96504
+ alignItems: "center",
96505
+ backgroundColor: bgColor,
96506
+ onMouseDown: () => openDialogRef.current?.(item),
96507
+ children: [
96508
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
96509
+ selectable: false,
96510
+ fg: isSelected ? "black" : undefined,
96511
+ children: item.charAt(0).toUpperCase() + item.slice(1)
96512
+ }, undefined, false, undefined, this),
96513
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96514
+ flexDirection: "row",
96515
+ gap: 1,
96516
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
96517
+ selectable: false,
96518
+ fg: isSelected ? "black" : currentTheme.colors.primary,
96519
+ children: [
96520
+ item === "theme" && currentTheme.name,
96521
+ item === "mode" && mode,
96522
+ item === "model" && (currentModel?.id ?? model),
96523
+ item === "domain" && (currentDomain?.name ?? domain2)
96524
+ ]
96525
+ }, undefined, true, undefined, this)
96526
+ }, undefined, false, undefined, this)
96527
+ ]
96528
+ }, item, true, undefined, this);
96529
+ }),
96530
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96531
+ paddingTop: 1,
96532
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
96533
+ attributes: TextAttributes.DIM,
96534
+ children: [
96535
+ "v",
96536
+ package_default2.version,
96537
+ hasUpdate() ? ` \u2192 v${getLatestVersion()} available` : "",
96538
+ " | ",
96539
+ "LupaCode AI Coding Assistant"
96540
+ ]
96541
+ }, undefined, true, undefined, this)
96542
+ }, undefined, false, undefined, this)
96543
+ ]
96544
+ }, undefined, true, undefined, this);
96545
+ };
96546
+ // src/components/dialogs/keyboard-help-dialog.tsx
96547
+ var shortcuts = [
96548
+ { key: "tab", description: "Toggle Build/Plan mode" },
96549
+ { key: "ctrl+d", description: "Switch domain" },
96550
+ { key: "ctrl+s", description: "Open settings" },
96551
+ { key: "ctrl+r", description: "Regenerate last assistant response" },
96552
+ { key: "e", description: "Edit last user message" },
96553
+ { key: "escape", description: "Interrupt response / Cancel / Close dialog" }
96554
+ ];
96555
+ var KeyboardHelpDialogContent = () => {
96556
+ const { colors } = useTheme();
96557
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96558
+ flexDirection: "column",
96559
+ gap: 1,
96560
+ children: shortcuts.map(({ key, description }) => /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96561
+ flexDirection: "row",
96562
+ gap: 2,
96563
+ children: [
96564
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
96565
+ width: 14,
96566
+ flexShrink: 0,
96567
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
96568
+ fg: colors.primary,
96569
+ children: key
96570
+ }, undefined, false, undefined, this)
96571
+ }, undefined, false, undefined, this),
96572
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
96573
+ attributes: TextAttributes.DIM,
96574
+ children: description
96575
+ }, undefined, false, undefined, this)
96576
+ ]
96577
+ }, key, true, undefined, this))
96578
+ }, undefined, false, undefined, this);
96579
+ };
95763
96580
  // src/components/status-bar.tsx
95764
96581
  function StatusBar() {
95765
96582
  const { colors } = useTheme();
@@ -96703,15 +97520,15 @@ function CommandMenu({
96703
97520
  }
96704
97521
 
96705
97522
  // src/components/command-menu/use-command-menu.ts
96706
- var import_react34 = __toESM(require_react(), 1);
97523
+ var import_react37 = __toESM(require_react(), 1);
96707
97524
  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);
97525
+ const [textValue, setTextValue] = import_react37.useState("");
97526
+ const [selectedIndex, setSelectedIndex] = import_react37.useState(0);
97527
+ const [showCommandMenu, setShowCommandMenu] = import_react37.useState(false);
97528
+ const scrollRef = import_react37.useRef(null);
96712
97529
  const { push, pop, isTopLayer } = useKeyboardLayer();
96713
97530
  const commandQuery = showCommandMenu && textValue.startsWith("/") ? textValue.slice(1) : "";
96714
- const filteredCommands = import_react34.useMemo(() => getFilteredCommands(commandQuery), [commandQuery]);
97531
+ const filteredCommands = import_react37.useMemo(() => getFilteredCommands(commandQuery), [commandQuery]);
96715
97532
  const close = () => {
96716
97533
  setShowCommandMenu(false);
96717
97534
  pop("command");
@@ -96724,13 +97541,15 @@ function useCommandMenu() {
96724
97541
  scrollbox.scrollTo(0);
96725
97542
  }
96726
97543
  const prefix = text2.startsWith("/") ? text2.slice(1) : null;
96727
- if (prefix !== null && !prefix.includes(" ")) {
97544
+ const wasShowing = showCommandMenu;
97545
+ const shouldShow = prefix !== null && !prefix.includes(" ");
97546
+ if (shouldShow && !wasShowing) {
96728
97547
  setShowCommandMenu(true);
96729
97548
  push("command", () => {
96730
97549
  close();
96731
97550
  return true;
96732
97551
  });
96733
- } else {
97552
+ } else if (!shouldShow && wasShowing) {
96734
97553
  close();
96735
97554
  }
96736
97555
  };
@@ -96968,19 +97787,19 @@ var TEXTAREA_KEY_BINDINGS = [
96968
97787
  ];
96969
97788
  function InputBar({ onSubmit, disabled = false }) {
96970
97789
  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);
97790
+ const textareaRef = import_react39.useRef(null);
97791
+ const onSubmitRef = import_react39.useRef(() => {});
97792
+ const activeMentionRef = import_react39.useRef(null);
97793
+ const mentionScrollRef = import_react39.useRef(null);
96975
97794
  const renderer = useRenderer();
96976
97795
  const toast = useToast();
96977
97796
  const navigate = useNavigate();
96978
97797
  const dialog = useDialog();
96979
97798
  const { colors } = useTheme();
96980
97799
  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);
97800
+ const [activeMention, setActiveMention] = import_react39.useState(null);
97801
+ const [mentionCandidates, setMentionCandidates] = import_react39.useState([]);
97802
+ const [mentionSelectedIndex, setMentionSelectedIndex] = import_react39.useState(0);
96984
97803
  const {
96985
97804
  showCommandMenu,
96986
97805
  commandQuery,
@@ -96991,13 +97810,13 @@ function InputBar({ onSubmit, disabled = false }) {
96991
97810
  setSelectedIndex
96992
97811
  } = useCommandMenu();
96993
97812
  const showMentionMenu = activeMention !== null;
96994
- const closeMentionMenu = import_react36.useCallback(() => {
97813
+ const closeMentionMenu = import_react39.useCallback(() => {
96995
97814
  activeMentionRef.current = null;
96996
97815
  setActiveMention(null);
96997
97816
  setMentionCandidates([]);
96998
97817
  pop("mention");
96999
97818
  }, [pop]);
97000
- const syncMentionMenu = import_react36.useCallback((text2, cursorOffset) => {
97819
+ const syncMentionMenu = import_react39.useCallback((text2, cursorOffset) => {
97001
97820
  const nextMention = findActiveMention(text2, cursorOffset);
97002
97821
  const previousMention = activeMentionRef.current;
97003
97822
  const mentionChanged = previousMention?.start !== nextMention?.start || previousMention?.end !== nextMention?.end || previousMention?.query !== nextMention?.query;
@@ -97007,18 +97826,20 @@ function InputBar({ onSubmit, disabled = false }) {
97007
97826
  }
97008
97827
  return;
97009
97828
  }
97829
+ if (!previousMention) {
97830
+ push("mention", () => {
97831
+ closeMentionMenu();
97832
+ return true;
97833
+ });
97834
+ }
97010
97835
  activeMentionRef.current = nextMention;
97011
97836
  setActiveMention(nextMention);
97012
- push("mention", () => {
97013
- closeMentionMenu();
97014
- return true;
97015
- });
97016
97837
  if (mentionChanged) {
97017
97838
  setMentionSelectedIndex(0);
97018
97839
  mentionScrollRef.current?.scrollTo(0);
97019
97840
  }
97020
97841
  }, [closeMentionMenu, push]);
97021
- const handleTextareaContentChange = import_react36.useCallback(() => {
97842
+ const handleTextareaContentChange = import_react39.useCallback(() => {
97022
97843
  const textarea = textareaRef.current;
97023
97844
  if (!textarea)
97024
97845
  return;
@@ -97026,7 +97847,7 @@ function InputBar({ onSubmit, disabled = false }) {
97026
97847
  handleContentChange(textarea.plainText);
97027
97848
  syncMentionMenu(text2, textarea.cursorOffset);
97028
97849
  }, [handleContentChange, syncMentionMenu]);
97029
- const handleSubmit = import_react36.useCallback(() => {
97850
+ const handleSubmit = import_react39.useCallback(() => {
97030
97851
  if (disabled)
97031
97852
  return;
97032
97853
  const textarea = textareaRef.current;
@@ -97038,7 +97859,7 @@ function InputBar({ onSubmit, disabled = false }) {
97038
97859
  onSubmit(text2);
97039
97860
  textarea.setText("");
97040
97861
  }, [disabled, onSubmit]);
97041
- const handleMentionExecute = import_react36.useCallback((index) => {
97862
+ const handleMentionExecute = import_react39.useCallback((index) => {
97042
97863
  const textarea = textareaRef.current;
97043
97864
  const mention = activeMentionRef.current;
97044
97865
  const candidate = mentionCandidates[index];
@@ -97050,13 +97871,13 @@ function InputBar({ onSubmit, disabled = false }) {
97050
97871
  textarea.cursorOffset = mention.start + insertion.length + 1;
97051
97872
  syncMentionMenu(nextText, textarea.cursorOffset);
97052
97873
  }, [mentionCandidates, syncMentionMenu]);
97053
- const handleTextareaCursorChange = import_react36.useCallback(() => {
97874
+ const handleTextareaCursorChange = import_react39.useCallback(() => {
97054
97875
  const textarea = textareaRef.current;
97055
97876
  if (!textarea)
97056
97877
  return;
97057
97878
  syncMentionMenu(textarea.plainText, textarea.cursorOffset);
97058
97879
  }, [syncMentionMenu]);
97059
- const handleCommand = import_react36.useCallback((command) => {
97880
+ const handleCommand = import_react39.useCallback((command) => {
97060
97881
  const textarea = textareaRef.current;
97061
97882
  if (!textarea || !command)
97062
97883
  return;
@@ -97076,11 +97897,11 @@ function InputBar({ onSubmit, disabled = false }) {
97076
97897
  textarea.insertText(command.value + " ");
97077
97898
  }
97078
97899
  }, [renderer, toast, dialog, navigate, mode, setMode, setModel, setDomain]);
97079
- const handleCommandExecute = import_react36.useCallback((index) => {
97900
+ const handleCommandExecute = import_react39.useCallback((index) => {
97080
97901
  const command = resolveCommand(index);
97081
97902
  handleCommand(command);
97082
97903
  }, [resolveCommand, handleCommand]);
97083
- import_react36.useEffect(() => {
97904
+ import_react39.useEffect(() => {
97084
97905
  if (!activeMention) {
97085
97906
  setMentionCandidates([]);
97086
97907
  return;
@@ -97103,7 +97924,7 @@ function InputBar({ onSubmit, disabled = false }) {
97103
97924
  ignore = true;
97104
97925
  };
97105
97926
  }, [activeMention]);
97106
- import_react36.useEffect(() => {
97927
+ import_react39.useEffect(() => {
97107
97928
  const textare = textareaRef.current;
97108
97929
  if (!textare)
97109
97930
  return;
@@ -97146,8 +97967,22 @@ function InputBar({ onSubmit, disabled = false }) {
97146
97967
  }, undefined, false, undefined, this)
97147
97968
  });
97148
97969
  }
97970
+ if (key.name === "s" && key.ctrl) {
97971
+ key.preventDefault();
97972
+ dialog.open({
97973
+ title: "Settings",
97974
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(SettingsDialogContent, {}, undefined, false, undefined, this)
97975
+ });
97976
+ }
97977
+ if (key.name === "?" && !key.ctrl) {
97978
+ key.preventDefault();
97979
+ dialog.open({
97980
+ title: "Keyboard Shortcuts",
97981
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(KeyboardHelpDialogContent, {}, undefined, false, undefined, this)
97982
+ });
97983
+ }
97149
97984
  });
97150
- import_react36.useEffect(() => {
97985
+ import_react39.useEffect(() => {
97151
97986
  setResponder("base", () => {
97152
97987
  if (disabled)
97153
97988
  return false;
@@ -97260,63 +98095,12 @@ function InputBar({ onSubmit, disabled = false }) {
97260
98095
  }, undefined, false, undefined, this)
97261
98096
  }, undefined, false, undefined, this);
97262
98097
  }
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
98098
 
97315
98099
  // src/screens/home.tsx
97316
98100
  function Home() {
97317
98101
  const navigate = useNavigate();
97318
98102
  const { mode, model, domain: domain2 } = usePromptConfig();
97319
- const handleSubmit = import_react38.useCallback((text2) => {
98103
+ const handleSubmit = import_react41.useCallback((text2) => {
97320
98104
  navigate("/sessions/new", { state: { message: text2, mode, model, domain: domain2 } });
97321
98105
  }, [navigate, mode, model, domain2]);
97322
98106
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
@@ -97347,7 +98131,8 @@ function Home() {
97347
98131
  attributes: TextAttributes.DIM,
97348
98132
  children: [
97349
98133
  "v",
97350
- package_default2.version
98134
+ package_default2.version,
98135
+ hasUpdate() ? ` \u2192 v${getLatestVersion() ?? ""} (npm i -g lupacode)` : ""
97351
98136
  ]
97352
98137
  }, undefined, true, undefined, this),
97353
98138
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
@@ -97371,6 +98156,16 @@ function Home() {
97371
98156
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
97372
98157
  attributes: TextAttributes.DIM,
97373
98158
  children: "domains"
98159
+ }, undefined, false, undefined, this),
98160
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
98161
+ children: "\xB7"
98162
+ }, undefined, false, undefined, this),
98163
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
98164
+ children: "ctrl+s"
98165
+ }, undefined, false, undefined, this),
98166
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
98167
+ attributes: TextAttributes.DIM,
98168
+ children: "settings"
97374
98169
  }, undefined, false, undefined, this)
97375
98170
  ]
97376
98171
  }, undefined, true, undefined, this)
@@ -97383,7 +98178,7 @@ function Home() {
97383
98178
  }
97384
98179
 
97385
98180
  // src/screens/new-session.tsx
97386
- var import_react41 = __toESM(require_react(), 1);
98181
+ var import_react44 = __toESM(require_react(), 1);
97387
98182
 
97388
98183
  // src/components/messages/error-message.tsx
97389
98184
  function ErrorMessage({ message: message2 }) {
@@ -97574,6 +98369,7 @@ function prettyMilliseconds(milliseconds, options) {
97574
98369
  }
97575
98370
 
97576
98371
  // src/components/messages/bot-message.tsx
98372
+ var globalSyntaxStyle = SyntaxStyle.create();
97577
98373
  function formatToolName(name23) {
97578
98374
  return name23.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (c) => c.toUpperCase());
97579
98375
  }
@@ -97728,8 +98524,10 @@ function BotMessage({
97728
98524
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
97729
98525
  paddingX: 3,
97730
98526
  width: "100%",
97731
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
97732
- children: part.text
98527
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("markdown", {
98528
+ content: part.text,
98529
+ syntaxStyle: globalSyntaxStyle,
98530
+ conceal: true
97733
98531
  }, undefined, false, undefined, this)
97734
98532
  }, `text-${j2}`, false, undefined, this);
97735
98533
  }
@@ -99818,7 +100616,8 @@ function SessionShell({
99818
100616
  attributes: TextAttributes.DIM,
99819
100617
  children: [
99820
100618
  "v",
99821
- package_default2.version
100619
+ package_default2.version,
100620
+ hasUpdate() ? ` \u2192 v${getLatestVersion() ?? ""} (npm i -g lupacode)` : ""
99822
100621
  ]
99823
100622
  }, undefined, true, undefined, this),
99824
100623
  loading ? /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(import_jsx_dev_runtime2.Fragment, {
@@ -99855,6 +100654,16 @@ function SessionShell({
99855
100654
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
99856
100655
  attributes: TextAttributes.DIM,
99857
100656
  children: "domains"
100657
+ }, undefined, false, undefined, this),
100658
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
100659
+ children: "\xB7"
100660
+ }, undefined, false, undefined, this),
100661
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
100662
+ children: "ctrl+s"
100663
+ }, undefined, false, undefined, this),
100664
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
100665
+ attributes: TextAttributes.DIM,
100666
+ children: "settings"
99858
100667
  }, undefined, false, undefined, this)
99859
100668
  ]
99860
100669
  }, undefined, true, undefined, this)
@@ -99875,17 +100684,17 @@ function NewSession() {
99875
100684
  const navigate = useNavigate();
99876
100685
  const location = useLocation();
99877
100686
  const toast = useToast();
99878
- const hasStartedRef = import_react41.useRef(false);
99879
- const state = import_react41.useMemo(() => {
100687
+ const hasStartedRef = import_react44.useRef(false);
100688
+ const state = import_react44.useMemo(() => {
99880
100689
  const parsed = newSessionStateSchema.safeParse(location.state);
99881
100690
  return parsed.success ? parsed.data : null;
99882
100691
  }, [location.state]);
99883
- import_react41.useEffect(() => {
100692
+ import_react44.useEffect(() => {
99884
100693
  if (!state?.message) {
99885
100694
  navigate("/", { replace: true });
99886
100695
  }
99887
100696
  }, [state, navigate]);
99888
- import_react41.useEffect(() => {
100697
+ import_react44.useEffect(() => {
99889
100698
  if (!state || hasStartedRef.current)
99890
100699
  return;
99891
100700
  hasStartedRef.current = true;
@@ -99933,13 +100742,13 @@ function NewSession() {
99933
100742
  }
99934
100743
 
99935
100744
  // src/screens/sessions.tsx
99936
- var import_react50 = __toESM(require_react(), 1);
100745
+ var import_react53 = __toESM(require_react(), 1);
99937
100746
 
99938
100747
  // src/hooks/use-chat.ts
99939
- var import_react48 = __toESM(require_react(), 1);
100748
+ var import_react51 = __toESM(require_react(), 1);
99940
100749
 
99941
100750
  // ../../node_modules/@ai-sdk/react/dist/index.js
99942
- var import_react42 = __toESM(require_react(), 1);
100751
+ var import_react45 = __toESM(require_react(), 1);
99943
100752
 
99944
100753
  // ../../node_modules/@ai-sdk/react/node_modules/@ai-sdk/provider/dist/index.js
99945
100754
  var marker23 = "vercel.ai.error";
@@ -104692,11 +105501,11 @@ var AbstractChat = class {
104692
105501
 
104693
105502
  // ../../node_modules/@ai-sdk/react/dist/index.js
104694
105503
  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
105504
  var import_react46 = __toESM(require_react(), 1);
104699
105505
  var import_react47 = __toESM(require_react(), 1);
105506
+ var import_react48 = __toESM(require_react(), 1);
105507
+ var import_react49 = __toESM(require_react(), 1);
105508
+ var import_react50 = __toESM(require_react(), 1);
104700
105509
  var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
104701
105510
  var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
104702
105511
  var __accessCheck = (obj, member, msg) => {
@@ -104833,7 +105642,7 @@ function useChat({
104833
105642
  resume = false,
104834
105643
  ...options
104835
105644
  } = {}) {
104836
- const callbacksRef = import_react42.useRef(!("chat" in options) ? {
105645
+ const callbacksRef = import_react45.useRef(!("chat" in options) ? {
104837
105646
  onToolCall: options.onToolCall,
104838
105647
  onData: options.onData,
104839
105648
  onFinish: options.onFinish,
@@ -104872,22 +105681,22 @@ function useChat({
104872
105681
  return (_c = (_b17 = (_a29 = callbacksRef.current).sendAutomaticallyWhen) == null ? undefined : _b17.call(_a29, arg)) != null ? _c : false;
104873
105682
  }
104874
105683
  };
104875
- const chatRef = import_react42.useRef("chat" in options ? options.chat : new Chat(optionsWithCallbacks));
105684
+ const chatRef = import_react45.useRef("chat" in options ? options.chat : new Chat(optionsWithCallbacks));
104876
105685
  const shouldRecreateChat = "chat" in options && options.chat !== chatRef.current || "id" in options && chatRef.current.id !== options.id;
104877
105686
  if (shouldRecreateChat) {
104878
105687
  chatRef.current = "chat" in options ? options.chat : new Chat(optionsWithCallbacks);
104879
105688
  }
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) => {
105689
+ const subscribeToMessages = import_react45.useCallback((update) => chatRef.current["~registerMessagesCallback"](update, throttleWaitMs), [throttleWaitMs, chatRef.current.id]);
105690
+ const messages = import_react45.useSyncExternalStore(subscribeToMessages, () => chatRef.current.messages, () => chatRef.current.messages);
105691
+ const status = import_react45.useSyncExternalStore(chatRef.current["~registerStatusCallback"], () => chatRef.current.status, () => chatRef.current.status);
105692
+ const error51 = import_react45.useSyncExternalStore(chatRef.current["~registerErrorCallback"], () => chatRef.current.error, () => chatRef.current.error);
105693
+ const setMessages = import_react45.useCallback((messagesParam) => {
104885
105694
  if (typeof messagesParam === "function") {
104886
105695
  messagesParam = messagesParam(chatRef.current.messages);
104887
105696
  }
104888
105697
  chatRef.current.messages = messagesParam;
104889
105698
  }, [chatRef]);
104890
- import_react42.useEffect(() => {
105699
+ import_react45.useEffect(() => {
104891
105700
  if (resume) {
104892
105701
  chatRef.current.resumeStream();
104893
105702
  }
@@ -104911,7 +105720,7 @@ function useChat({
104911
105720
 
104912
105721
  // src/lib/local-tools.ts
104913
105722
  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";
105723
+ import { dirname as dirname2, isAbsolute as isAbsolute5, join as join5, relative as relative2, resolve as resolve7 } from "path";
104915
105724
  var MAX_FILE_SIZE = 1e4;
104916
105725
  var MAX_RESULTS = 200;
104917
105726
  var MAX_MATCHES = 50;
@@ -104948,7 +105757,7 @@ async function executeLocalTool(toolName, input, mode) {
104948
105757
  for (const entry of entries) {
104949
105758
  if (entry.startsWith(".") || entry === "node_modules")
104950
105759
  continue;
104951
- const info = await stat(join4(resolved, entry));
105760
+ const info = await stat(join5(resolved, entry));
104952
105761
  results.push({ name: entry, type: info.isDirectory() ? "directory" : "file" });
104953
105762
  }
104954
105763
  results.sort((a2, b2) => a2.type !== b2.type ? a2.type === "directory" ? -1 : 1 : a2.name.localeCompare(b2.name));
@@ -105066,7 +105875,7 @@ async function executeLocalTool(toolName, input, mode) {
105066
105875
 
105067
105876
  // src/hooks/use-chat.ts
105068
105877
  function useChat2(sessionId, initialMessages) {
105069
- const transport = import_react48.useMemo(() => {
105878
+ const transport = import_react51.useMemo(() => {
105070
105879
  return new DefaultChatTransport({
105071
105880
  api: apiClient.chat.$url().toString(),
105072
105881
  headers() {
@@ -105124,6 +105933,9 @@ function useChat2(sessionId, initialMessages) {
105124
105933
  }
105125
105934
  });
105126
105935
  },
105936
+ append: chat.append,
105937
+ setMessages: chat.setMessages,
105938
+ reload: chat.reload,
105127
105939
  abort: chat.stop,
105128
105940
  interrupt: chat.stop
105129
105941
  };
@@ -105154,16 +105966,34 @@ function ChatMessage({ msg }) {
105154
105966
  streaming: false
105155
105967
  }, undefined, false, undefined, this);
105156
105968
  }
105969
+ function EditMessageDialog({
105970
+ initialText,
105971
+ onResolve,
105972
+ onSubmit
105973
+ }) {
105974
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
105975
+ flexDirection: "column",
105976
+ gap: 1,
105977
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("input", {
105978
+ focused: true,
105979
+ initialValue: initialText,
105980
+ onContentChange: (v2) => onResolve(v2),
105981
+ onSubmit
105982
+ }, undefined, false, undefined, this)
105983
+ }, undefined, false, undefined, this);
105984
+ }
105157
105985
  function SessionChat({
105158
105986
  session,
105159
105987
  initialPrompt
105160
105988
  }) {
105161
- const [initialMessages] = import_react50.useState(() => session.messages);
105989
+ const [initialMessages] = import_react53.useState(() => session.messages);
105162
105990
  const { mode, model, domain: domain2 } = usePromptConfig();
105163
105991
  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(() => {
105992
+ const { messages, status, submit, setMessages, reload, abort, interrupt, error: error51 } = useChat2(session.id, initialMessages);
105993
+ const hasSubmittedInitialPromptRef = import_react53.useRef(false);
105994
+ const toast = useToast();
105995
+ const dialog = useDialog();
105996
+ import_react53.useEffect(() => {
105167
105997
  return () => {
105168
105998
  abort();
105169
105999
  };
@@ -105174,7 +106004,55 @@ function SessionChat({
105174
106004
  interrupt();
105175
106005
  }
105176
106006
  });
105177
- import_react50.useEffect(() => {
106007
+ useKeyboard((key) => {
106008
+ if (key.name === "r" && key.ctrl && isTopLayer("base") && status !== "streaming" && status !== "submitted") {
106009
+ const lastMsg = messages[messages.length - 1];
106010
+ if (lastMsg?.role === "assistant") {
106011
+ key.preventDefault();
106012
+ reload();
106013
+ }
106014
+ }
106015
+ });
106016
+ useKeyboard((key) => {
106017
+ if (key.name === "e" && !key.ctrl && isTopLayer("base") && status !== "streaming" && status !== "submitted") {
106018
+ const lastUserMsg = messages.findLast((m2) => m2.role === "user");
106019
+ if (!lastUserMsg)
106020
+ return;
106021
+ key.preventDefault();
106022
+ const currentText = lastUserMsg.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
106023
+ let inputValue = currentText;
106024
+ dialog.open({
106025
+ title: "Edit message",
106026
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(EditMessageDialog, {
106027
+ initialText: currentText,
106028
+ onResolve: (text3) => {
106029
+ inputValue = text3;
106030
+ },
106031
+ onSubmit: () => {
106032
+ const idx = messages.findIndex((m2) => m2.id === lastUserMsg.id);
106033
+ if (idx === -1)
106034
+ return;
106035
+ const text3 = inputValue.trim();
106036
+ if (!text3)
106037
+ return;
106038
+ const truncated = messages.slice(0, idx);
106039
+ const editedMsg = {
106040
+ ...messages[idx],
106041
+ parts: [{ type: "text", text: text3 }]
106042
+ };
106043
+ setMessages([...truncated, editedMsg]);
106044
+ submit({
106045
+ userText: text3,
106046
+ mode: editedMsg.metadata?.mode ?? mode,
106047
+ model: editedMsg.metadata?.model ?? model
106048
+ });
106049
+ dialog.close();
106050
+ }
106051
+ }, undefined, false, undefined, this)
106052
+ });
106053
+ }
106054
+ });
106055
+ import_react53.useEffect(() => {
105178
106056
  if (!initialPrompt || hasSubmittedInitialPromptRef.current)
105179
106057
  return;
105180
106058
  hasSubmittedInitialPromptRef.current = true;
@@ -105203,12 +106081,12 @@ function Session() {
105203
106081
  const location = useLocation();
105204
106082
  const navigate = useNavigate();
105205
106083
  const toast = useToast();
105206
- const prefetched = import_react50.useMemo(() => {
106084
+ const prefetched = import_react53.useMemo(() => {
105207
106085
  const parsed = sessionLocationSchema.safeParse(location.state);
105208
106086
  return parsed.success ? parsed.data : null;
105209
106087
  }, [location.state]);
105210
- const [session, setSession] = import_react50.useState(prefetched?.session ?? null);
105211
- import_react50.useEffect(() => {
106088
+ const [session, setSession] = import_react53.useState(prefetched?.session ?? null);
106089
+ import_react53.useEffect(() => {
105212
106090
  if (prefetched?.session)
105213
106091
  return;
105214
106092
  setSession(null);
@@ -105275,6 +106153,8 @@ function App() {
105275
106153
  router
105276
106154
  }, undefined, false, undefined, this);
105277
106155
  }
106156
+ setCurrentVersion(package_default2.version);
106157
+ checkForUpdate(package_default2.version);
105278
106158
  var renderer = await createCliRenderer({
105279
106159
  targetFps: 60,
105280
106160
  exitOnCtrlC: false