code-ollama 0.24.1 → 0.26.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.
@@ -1,4 +1,4 @@
1
- import { $ as NAME, A as loadConfig, B as ASSISTANT, C as checkHealth, D as pullModel, E as listModels, F as withSystemMessage, G as BACK, H as USER, I as HEADER_PREFIX, J as LABEL, K as CATALOG, L as WARNING, M as removeClipboardImage, N as saveClipboardImage, O as sanitizeAssistantContent, P as resetSystemMessage, Q as REJECT, R as LIST$1, S as TOOL_INTENT_CORRECTION, T as hasUncalledToolIntent, U as PLAN_GENERATION_INSTRUCTION, V as SYSTEM, W as PLAN_INSTRUCTION, X as SAFE, Y as PLAN, Z as APPROVE, _ as loadSession, a as normalizeToolCall, b as reset, c as WRITE_TOOLS, d as write, et as VERSION, f as appendMessage, g as listSessions, h as deleteSessionIfEmpty, i as formatToolResultContent, j as saveConfig, k as streamChat, l as tick, m as deleteSession, n as executeTool, o as READ_TOOLS, p as createSession, q as AUTO, r as executeToolCall, s as TOOLS, t as checkForUpdate, tt as LIST, u as color, v as updateSessionModel, w as deleteModel, x as setClearHandler, y as clear, z as getTheme } from "../cli.js";
1
+ import { $ as LABEL, A as loadConfig, B as CHAT, C as checkHealth, D as pullModel, E as listModels, F as withSystemMessage, G as ASSISTANT, H as SEARCH_SETTINGS, I as HEADER_PREFIX, J as PLAN_GENERATION_INSTRUCTION, K as SYSTEM, L as WARNING, M as removeClipboardImage, N as saveClipboardImage, O as sanitizeAssistantContent, P as resetSystemMessage, Q as AUTO, R as LIST$1, S as TOOL_INTENT_CORRECTION, T as hasUncalledToolIntent, U as SESSION_MANAGER, V as MODEL_MANAGER, W as THEME_SETTINGS, X as BACK, Y as PLAN_INSTRUCTION, Z as CATALOG, _ as loadSession, a as normalizeToolCall, at as VERSION, b as reset, c as WRITE_TOOLS, d as write, et as PLAN, f as appendMessage, g as listSessions, h as deleteSessionIfEmpty, i as formatToolResultContent, it as NAME, j as saveConfig, k as streamChat, l as tick, m as deleteSession, n as executeTool, nt as APPROVE, o as READ_TOOLS, ot as LIST, p as createSession, q as USER, r as executeToolCall, rt as REJECT, s as TOOLS, t as checkForUpdate, tt as SAFE, u as color, v as updateSessionModel, w as deleteModel, x as setClearHandler, y as clear, z as getTheme } from "../cli.js";
2
2
  import { existsSync, readdirSync, statSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { basename, extname, isAbsolute, join, relative, resolve } from "node:path";
@@ -3255,46 +3255,36 @@ function UpdateBanner({ onLoad, theme }) {
3255
3255
  });
3256
3256
  }
3257
3257
  //#endregion
3258
- //#region src/components/App/constants.ts
3259
- var Screen = /* @__PURE__ */ function(Screen) {
3260
- Screen["Chat"] = "chat";
3261
- Screen["ModelManager"] = "model-manager";
3262
- Screen["SearchSettings"] = "search-settings";
3263
- Screen["SessionManager"] = "session-manager";
3264
- Screen["ThemeSettings"] = "theme-settings";
3265
- return Screen;
3266
- }({});
3267
- //#endregion
3268
3258
  //#region src/components/App/hooks/useScreenRouter.ts
3269
- function useScreenRouter() {
3259
+ function useScreenRouter({ initialScreen } = {}) {
3270
3260
  const { exit } = useApp();
3271
- const [currentScreen, setScreen] = useState(Screen.Chat);
3261
+ const [currentScreen, setScreen] = useState(initialScreen ?? "chat");
3272
3262
  return {
3273
3263
  currentScreen,
3274
3264
  setScreen,
3275
3265
  handleClose: useCallback(() => {
3276
- setScreen(Screen.Chat);
3266
+ setScreen(CHAT);
3277
3267
  }, []),
3278
3268
  handleCommand: useCallback((command, callbacks) => {
3279
3269
  const { onCreateSession, onSetPreviewThemeId, model, theme } = callbacks;
3280
3270
  switch (command) {
3281
3271
  case "/session":
3282
- setScreen(Screen.SessionManager);
3272
+ setScreen(SESSION_MANAGER);
3283
3273
  break;
3284
3274
  case "/model":
3285
- setScreen(Screen.ModelManager);
3275
+ setScreen(MODEL_MANAGER);
3286
3276
  break;
3287
3277
  case "/search":
3288
- setScreen(Screen.SearchSettings);
3278
+ setScreen(SEARCH_SETTINGS);
3289
3279
  break;
3290
3280
  case "/theme":
3291
3281
  onSetPreviewThemeId(theme);
3292
- setScreen(Screen.ThemeSettings);
3282
+ setScreen(THEME_SETTINGS);
3293
3283
  break;
3294
3284
  case "/clear": {
3295
3285
  resetSystemMessage();
3296
3286
  const nextSession = onCreateSession(model);
3297
- setScreen(Screen.Chat);
3287
+ setScreen(CHAT);
3298
3288
  clear(nextSession.metadata.id);
3299
3289
  break;
3300
3290
  }
@@ -3381,7 +3371,7 @@ function useThemeSettings({ currentTheme, onUpdateConfig, setScreen }) {
3381
3371
  activeTheme,
3382
3372
  handleThemeClose: useCallback(() => {
3383
3373
  setPreviewThemeId(null);
3384
- setScreen(Screen.Chat);
3374
+ setScreen(CHAT);
3385
3375
  }, [setScreen]),
3386
3376
  handleThemePreview,
3387
3377
  handleThemeSave: useCallback((themeId) => {
@@ -3473,13 +3463,13 @@ function ReadinessCheck({ errorMessage, onCommand, setupState, theme = getTheme(
3473
3463
  }
3474
3464
  //#endregion
3475
3465
  //#region src/components/App/App.tsx
3476
- function App({ sessionId }) {
3466
+ function App({ sessionId, initialScreen }) {
3477
3467
  const [appConfig, setConfig] = useState(() => loadConfig());
3478
3468
  const [mode, setMode] = useState(SAFE);
3479
3469
  const [isLoaded, setIsLoaded] = useState(false);
3480
3470
  const [setupState, setSetupState] = useState(() => appConfig.model ? ReadinessState.Ready : ReadinessState.MissingModelConfig);
3481
3471
  const [setupErrorMessage, setSetupErrorMessage] = useState(null);
3482
- const { currentScreen, setScreen, handleClose, handleCommand } = useScreenRouter();
3472
+ const { currentScreen, setScreen, handleClose, handleCommand } = useScreenRouter({ initialScreen });
3483
3473
  const { activeSession, setSession, handleCreateSession, handleOpenSession, handleDeleteSession, handleMessagesChange } = useSessionManager({
3484
3474
  sessionId,
3485
3475
  model: appConfig.model ?? "",
@@ -3496,7 +3486,7 @@ function App({ sessionId }) {
3496
3486
  }
3497
3487
  return;
3498
3488
  }
3499
- if (currentScreen !== Screen.Chat) return;
3489
+ if (currentScreen !== "chat") return;
3500
3490
  // v8 ignore next
3501
3491
  if (isMounted) {
3502
3492
  setSetupErrorMessage(null);
@@ -3533,7 +3523,7 @@ function App({ sessionId }) {
3533
3523
  ...current,
3534
3524
  metadata: updateSessionModel(current.metadata.id, newModel)
3535
3525
  }));
3536
- setScreen(Screen.Chat);
3526
+ setScreen(CHAT);
3537
3527
  }, [setScreen, setSession]);
3538
3528
  const { activeTheme, handleThemeClose, handleThemePreview, handleThemeSave, setPreviewThemeId } = useThemeSettings({
3539
3529
  currentTheme: appConfig.theme,
@@ -3556,7 +3546,7 @@ function App({ sessionId }) {
3556
3546
  ]);
3557
3547
  let screenContent;
3558
3548
  switch (currentScreen) {
3559
- case Screen.ModelManager:
3549
+ case MODEL_MANAGER:
3560
3550
  screenContent = /* @__PURE__ */ jsx(ModelManager, {
3561
3551
  currentModel: appConfig.model ?? "",
3562
3552
  onSelect: handleUpdateConfig,
@@ -3564,7 +3554,7 @@ function App({ sessionId }) {
3564
3554
  theme: activeTheme
3565
3555
  });
3566
3556
  break;
3567
- case Screen.SearchSettings:
3557
+ case SEARCH_SETTINGS:
3568
3558
  screenContent = /* @__PURE__ */ jsx(SearchSettings, {
3569
3559
  currentUrl: appConfig.searxngBaseUrl,
3570
3560
  onSave: handleUpdateConfig,
@@ -3572,26 +3562,26 @@ function App({ sessionId }) {
3572
3562
  theme: activeTheme
3573
3563
  });
3574
3564
  break;
3575
- case Screen.SessionManager:
3565
+ case SESSION_MANAGER:
3576
3566
  screenContent = /* @__PURE__ */ jsx(SessionManager, {
3577
3567
  currentSessionId: activeSession.metadata.id,
3578
3568
  onClose: handleClose,
3579
3569
  onDelete: (sessionId) => {
3580
3570
  handleDeleteSession(sessionId);
3581
- setScreen(Screen.SessionManager);
3571
+ setScreen(SESSION_MANAGER);
3582
3572
  },
3583
3573
  onNew: () => {
3584
3574
  handleCreateSession();
3585
- setScreen(Screen.Chat);
3575
+ setScreen(CHAT);
3586
3576
  },
3587
3577
  onOpen: (sessionId) => {
3588
3578
  handleOpenSession(sessionId);
3589
- setScreen(Screen.Chat);
3579
+ setScreen(CHAT);
3590
3580
  },
3591
3581
  theme: activeTheme
3592
3582
  });
3593
3583
  break;
3594
- case Screen.ThemeSettings:
3584
+ case THEME_SETTINGS:
3595
3585
  screenContent = /* @__PURE__ */ jsx(ThemeSettings, {
3596
3586
  currentTheme: appConfig.theme,
3597
3587
  onClose: handleThemeClose,
@@ -3599,7 +3589,7 @@ function App({ sessionId }) {
3599
3589
  onSave: handleThemeSave
3600
3590
  });
3601
3591
  break;
3602
- case Screen.Chat:
3592
+ case CHAT:
3603
3593
  screenContent = setupState === ReadinessState.Ready ? /* @__PURE__ */ jsx(Chat, {
3604
3594
  initialMessages: activeSession.messages,
3605
3595
  model: appConfig.model,
@@ -3687,15 +3677,18 @@ function ExitHint({ action = "go back" }) {
3687
3677
  }
3688
3678
  //#endregion
3689
3679
  //#region src/tui.tsx
3690
- function renderApp(sessionId) {
3680
+ function renderApp(options = {}) {
3691
3681
  let resetKey = 0;
3692
- const app = render(/* @__PURE__ */ jsx(App, { sessionId }, resetKey), {
3682
+ const app = render(/* @__PURE__ */ jsx(App, {
3683
+ sessionId: options.sessionId,
3684
+ initialScreen: options.initialScreen
3685
+ }, resetKey), {
3693
3686
  exitOnCtrlC: false,
3694
3687
  maxFps: 60
3695
3688
  });
3696
3689
  setClearHandler((nextSessionId) => {
3697
3690
  reset();
3698
- app.rerender(/* @__PURE__ */ jsx(App, { sessionId: nextSessionId ?? sessionId }, ++resetKey));
3691
+ app.rerender(/* @__PURE__ */ jsx(App, { sessionId: nextSessionId ?? options.sessionId }, ++resetKey));
3699
3692
  });
3700
3693
  }
3701
3694
  //#endregion
package/dist/cli.js CHANGED
@@ -50,7 +50,7 @@ var LIST$1 = [
50
50
  //#endregion
51
51
  //#region package.json
52
52
  var name = "code-ollama";
53
- var version = "0.24.1";
53
+ var version = "0.26.0";
54
54
  //#endregion
55
55
  //#region src/constants/package.ts
56
56
  var NAME = name;
@@ -252,6 +252,13 @@ var USER = "user";
252
252
  var ASSISTANT = "assistant";
253
253
  var SYSTEM = "system";
254
254
  //#endregion
255
+ //#region src/constants/screen.ts
256
+ var CHAT = "chat";
257
+ var MODEL_MANAGER = "model-manager";
258
+ var SEARCH_SETTINGS = "search-settings";
259
+ var SESSION_MANAGER = "session-manager";
260
+ var THEME_SETTINGS = "theme-settings";
261
+ //#endregion
255
262
  //#region src/constants/theme.ts
256
263
  var DEFAULT_THEME_ID = "github-dark";
257
264
  var LIST = [
@@ -852,6 +859,10 @@ var TOOLS = [
852
859
  maxLines: {
853
860
  type: "number",
854
861
  description: "Optional maximum number of lines to read; cannot be combined with endLine"
862
+ },
863
+ maxChars: {
864
+ type: "number",
865
+ description: `Optional maximum number of characters to return; defaults to 50000; applies after any line-range selection`
855
866
  }
856
867
  }, ["path"]),
857
868
  defineTool(WRITE_FILE, "Write content to a file at the specified path", {
@@ -958,56 +969,10 @@ var TOOLS = [
958
969
  var READ_TOOLS = new Set(READ_TOOL_NAMES);
959
970
  var WRITE_TOOLS = new Set(WRITE_TOOL_NAMES);
960
971
  //#endregion
961
- //#region src/utils/tools/shell.ts
962
- var execAsync = promisify(exec);
963
- var SHELL_EXEC_OPTIONS = {
964
- timeout: 3e4,
965
- maxBuffer: 1024 * 1024
966
- };
967
- function getErrorOutput(error) {
968
- if (typeof error !== "object" || error === null) return "";
969
- const output = error;
970
- return [output.stdout, output.stderr].filter((value) => typeof value === "string" && !!value).join("\n");
971
- }
972
- /**
973
- * Execute shell command with shared options (throws on error)
974
- */
975
- function execShell(command) {
976
- return execAsync(command, SHELL_EXEC_OPTIONS);
977
- }
978
- /**
979
- * Execute shell command
980
- */
981
- async function runShell(command) {
982
- try {
983
- const { stdout, stderr } = await execShell(command);
984
- return { content: stdout || stderr };
985
- } catch (error) {
986
- const message = error instanceof Error ? error.message : String(error);
987
- return {
988
- content: getErrorOutput(error),
989
- error: `Command failed: ${message}`,
990
- // v8 ignore next
991
- ...error instanceof Error && error.stack ? { stack: error.stack } : {}
992
- };
993
- }
994
- }
995
- //#endregion
996
- //#region src/utils/tools/filesystem.ts
972
+ //#region src/utils/tools/filesystem/diff.ts
997
973
  var DIFF_CONTEXT_LINES = 3;
998
974
  var DIFF_MAX_LINES = 120;
999
975
  var DIFF_MAX_CHARS = 12e3;
1000
- var DEFAULT_FIND_FILES_IGNORED_DIRS = [
1001
- "node_modules",
1002
- "__pycache__",
1003
- ".*cache",
1004
- ".tox",
1005
- ".venv",
1006
- "venv",
1007
- "dist",
1008
- "build",
1009
- "coverage"
1010
- ];
1011
976
  function splitLines(content) {
1012
977
  return content.split("\n");
1013
978
  }
@@ -1055,59 +1020,9 @@ function truncateDiff(diff) {
1055
1020
  visibleLines: Math.min(visibleLines.length, lines.length)
1056
1021
  };
1057
1022
  }
1058
- function buildSearchPatterns(pattern) {
1059
- const words = pattern.trim().split(/[^a-zA-Z0-9]+/).filter(Boolean);
1060
- if (words.length < 2) return [pattern];
1061
- const camelCase = words.map((word, index) => index === 0 ? word.toLowerCase() : capitalize(word.toLowerCase())).join("");
1062
- const pascalCase = words.map((word) => capitalize(word.toLowerCase())).join("");
1063
- const snakeCase = words.map((word) => word.toLowerCase()).join("_");
1064
- const upperSnakeCase = snakeCase.toUpperCase();
1065
- const flexibleWhitespace = words.join(String.raw`\s+`);
1066
- return Array.from(new Set([
1067
- pattern,
1068
- flexibleWhitespace,
1069
- snakeCase,
1070
- upperSnakeCase,
1071
- camelCase,
1072
- pascalCase
1073
- ]));
1074
- }
1075
- function capitalize(value) {
1076
- return value.charAt(0).toUpperCase() + value.slice(1);
1077
- }
1078
- function escapeRegExp(value) {
1079
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1080
- }
1081
- function fileMatchesPattern(filePath, pattern) {
1082
- const trimmedPattern = pattern?.trim();
1083
- if (!trimmedPattern) return true;
1084
- const normalizedPath = filePath.toLowerCase();
1085
- const normalizedFileName = normalizedPath.slice(Math.max(normalizedPath.lastIndexOf("/"), normalizedPath.lastIndexOf("\\")) + 1);
1086
- const normalizedPattern = trimmedPattern.toLowerCase();
1087
- if (!normalizedPattern.includes("*") && !normalizedPattern.includes("?")) return normalizedPath.includes(normalizedPattern);
1088
- const regexPattern = normalizedPattern.split("").map((char) => {
1089
- if (char === "*") return ".*";
1090
- if (char === "?") return ".";
1091
- return escapeRegExp(char);
1092
- }).join("");
1093
- const regex = new RegExp(`^${regexPattern}$`);
1094
- return regex.test(normalizedPath) || regex.test(normalizedFileName);
1095
- }
1096
- function valueMatchesWildcardPattern(value, pattern) {
1097
- const normalizedValue = value.toLowerCase();
1098
- const normalizedPattern = pattern.trim().toLowerCase();
1099
- if (!normalizedPattern.includes("*") && !normalizedPattern.includes("?")) return normalizedValue === normalizedPattern;
1100
- const regexPattern = normalizedPattern.split("").map((char) => {
1101
- if (char === "*") return ".*";
1102
- if (char === "?") return ".";
1103
- return escapeRegExp(char);
1104
- }).join("");
1105
- return new RegExp(`^${regexPattern}$`).test(normalizedValue);
1106
- }
1107
- function directoryMatchesIgnoredPattern(dirName, ignoredDirs) {
1108
- for (const ignoredDir of ignoredDirs) if (valueMatchesWildcardPattern(dirName, ignoredDir)) return true;
1109
- return false;
1110
- }
1023
+ //#endregion
1024
+ //#region src/utils/tools/filesystem/files.ts
1025
+ var READ_FILE_MAX_CHARS = 5e4;
1111
1026
  function formatNumberedLines(lines, startLine) {
1112
1027
  return lines.map((line, index) => `${String(startLine + index)}: ${line}`).join("\n");
1113
1028
  }
@@ -1121,17 +1036,24 @@ function readFile(filePath, options = {}) {
1121
1036
  error: `File not found: ${filePath}`
1122
1037
  };
1123
1038
  const content = readFileSync(filePath, "utf8");
1124
- if (!(options.startLine !== void 0 || options.endLine !== void 0 || options.maxLines !== void 0)) return { content };
1125
- const lines = content.split("\n");
1126
- const startLine = options.startLine ?? 1;
1127
- const endLine = options.endLine ?? startLine + (options.maxLines ?? lines.length) - 1;
1128
- const startIndex = startLine - 1;
1129
- const endIndex = Math.min(lines.length, endLine);
1130
- if (startIndex >= lines.length) return {
1131
- content: "",
1132
- error: "Invalid line range"
1133
- };
1134
- return { content: formatNumberedLines(lines.slice(startIndex, endIndex), startLine) };
1039
+ const isPartialRead = options.startLine !== void 0 || options.endLine !== void 0 || options.maxLines !== void 0;
1040
+ let output;
1041
+ if (!isPartialRead) output = content;
1042
+ else {
1043
+ const lines = content.split("\n");
1044
+ const startLine = options.startLine ?? 1;
1045
+ const endLine = options.endLine ?? startLine + (options.maxLines ?? lines.length) - 1;
1046
+ const startIndex = startLine - 1;
1047
+ const endIndex = Math.min(lines.length, endLine);
1048
+ if (startIndex >= lines.length) return {
1049
+ content: "",
1050
+ error: "Invalid line range"
1051
+ };
1052
+ output = formatNumberedLines(lines.slice(startIndex, endIndex), startLine);
1053
+ }
1054
+ const cap = options.maxChars ?? READ_FILE_MAX_CHARS;
1055
+ if (output.length > cap) return { content: `${output.slice(0, cap)}\n[file truncated: showing ${String(cap)} of ${String(output.length)} chars]` };
1056
+ return { content: output };
1135
1057
  } catch (error) {
1136
1058
  return {
1137
1059
  content: "",
@@ -1195,6 +1117,186 @@ function editFile(filePath, oldText, newText) {
1195
1117
  };
1196
1118
  }
1197
1119
  }
1120
+ //#endregion
1121
+ //#region src/utils/tools/filesystem/find.ts
1122
+ var DEFAULT_FIND_FILES_IGNORED_DIRS = [
1123
+ "node_modules",
1124
+ "__pycache__",
1125
+ ".*cache",
1126
+ ".tox",
1127
+ ".venv",
1128
+ "venv",
1129
+ "dist",
1130
+ "build",
1131
+ "coverage"
1132
+ ];
1133
+ function escapeRegExp(value) {
1134
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1135
+ }
1136
+ function fileMatchesPattern(filePath, pattern) {
1137
+ const trimmedPattern = pattern?.trim();
1138
+ if (!trimmedPattern) return true;
1139
+ const normalizedPath = filePath.toLowerCase();
1140
+ const normalizedFileName = normalizedPath.slice(Math.max(normalizedPath.lastIndexOf("/"), normalizedPath.lastIndexOf("\\")) + 1);
1141
+ const normalizedPattern = trimmedPattern.toLowerCase();
1142
+ if (!normalizedPattern.includes("*") && !normalizedPattern.includes("?")) return normalizedPath.includes(normalizedPattern);
1143
+ const regexPattern = normalizedPattern.split("").map((char) => {
1144
+ if (char === "*") return ".*";
1145
+ if (char === "?") return ".";
1146
+ return escapeRegExp(char);
1147
+ }).join("");
1148
+ const regex = new RegExp(`^${regexPattern}$`);
1149
+ return regex.test(normalizedPath) || regex.test(normalizedFileName);
1150
+ }
1151
+ function valueMatchesWildcardPattern(value, pattern) {
1152
+ const normalizedValue = value.toLowerCase();
1153
+ const normalizedPattern = pattern.trim().toLowerCase();
1154
+ if (!normalizedPattern.includes("*") && !normalizedPattern.includes("?")) return normalizedValue === normalizedPattern;
1155
+ const regexPattern = normalizedPattern.split("").map((char) => {
1156
+ if (char === "*") return ".*";
1157
+ if (char === "?") return ".";
1158
+ return escapeRegExp(char);
1159
+ }).join("");
1160
+ return new RegExp(`^${regexPattern}$`).test(normalizedValue);
1161
+ }
1162
+ function directoryMatchesIgnoredPattern(dirName, ignoredDirs) {
1163
+ for (const ignoredDir of ignoredDirs) if (valueMatchesWildcardPattern(dirName, ignoredDir)) return true;
1164
+ return false;
1165
+ }
1166
+ /**
1167
+ * Recursively find files by path
1168
+ */
1169
+ function findFiles(dirPath, options = {}) {
1170
+ try {
1171
+ if (!existsSync(dirPath)) return {
1172
+ content: "",
1173
+ error: `Directory not found: ${dirPath}`
1174
+ };
1175
+ if (!statSync(dirPath).isDirectory()) return {
1176
+ content: "",
1177
+ error: `Path is not a directory: ${dirPath}`
1178
+ };
1179
+ const results = [];
1180
+ const includeHidden = options.includeHidden ?? false;
1181
+ const ignoredDirs = new Set(options.ignoredDirs ?? DEFAULT_FIND_FILES_IGNORED_DIRS);
1182
+ function searchDirectory(currentPath) {
1183
+ const entries = readdirSync(currentPath, { withFileTypes: true });
1184
+ for (const entry of entries) {
1185
+ const fullPath = join(currentPath, entry.name);
1186
+ if (entry.isDirectory()) {
1187
+ if (entry.name !== ".git" && !directoryMatchesIgnoredPattern(entry.name, ignoredDirs) && (includeHidden || !entry.name.startsWith("."))) searchDirectory(fullPath);
1188
+ } else if (entry.isFile() && (includeHidden || !entry.name.startsWith(".")) && fileMatchesPattern(fullPath, options.pattern)) results.push(fullPath);
1189
+ }
1190
+ }
1191
+ searchDirectory(dirPath);
1192
+ return { content: results.join("\n") };
1193
+ } catch (error) {
1194
+ return {
1195
+ content: "",
1196
+ error: `Failed to find files: ${error instanceof Error ? error.message : String(error)}`
1197
+ };
1198
+ }
1199
+ }
1200
+ //#endregion
1201
+ //#region src/utils/tools/shell.ts
1202
+ var execAsync = promisify(exec);
1203
+ var SHELL_EXEC_OPTIONS = {
1204
+ timeout: 3e4,
1205
+ maxBuffer: 1024 * 1024
1206
+ };
1207
+ function getErrorOutput(error) {
1208
+ if (typeof error !== "object" || error === null) return "";
1209
+ const output = error;
1210
+ return [output.stdout, output.stderr].filter((value) => typeof value === "string" && !!value).join("\n");
1211
+ }
1212
+ /**
1213
+ * Execute shell command with shared options (throws on error)
1214
+ */
1215
+ function execShell(command) {
1216
+ return execAsync(command, SHELL_EXEC_OPTIONS);
1217
+ }
1218
+ /**
1219
+ * Execute shell command
1220
+ */
1221
+ async function runShell(command) {
1222
+ try {
1223
+ const { stdout, stderr } = await execShell(command);
1224
+ return { content: stdout || stderr };
1225
+ } catch (error) {
1226
+ const message = error instanceof Error ? error.message : String(error);
1227
+ return {
1228
+ content: getErrorOutput(error),
1229
+ error: `Command failed: ${message}`,
1230
+ // v8 ignore next
1231
+ ...error instanceof Error && error.stack ? { stack: error.stack } : {}
1232
+ };
1233
+ }
1234
+ }
1235
+ //#endregion
1236
+ //#region src/utils/tools/filesystem/grep.ts
1237
+ function capitalize(value) {
1238
+ return value.charAt(0).toUpperCase() + value.slice(1);
1239
+ }
1240
+ function buildSearchPatterns(pattern) {
1241
+ const words = pattern.trim().split(/[^a-zA-Z0-9]+/).filter(Boolean);
1242
+ if (words.length < 2) return [pattern];
1243
+ const camelCase = words.map((word, index) => index === 0 ? word.toLowerCase() : capitalize(word.toLowerCase())).join("");
1244
+ const pascalCase = words.map((word) => capitalize(word.toLowerCase())).join("");
1245
+ const snakeCase = words.map((word) => word.toLowerCase()).join("_");
1246
+ const upperSnakeCase = snakeCase.toUpperCase();
1247
+ const flexibleWhitespace = words.join(String.raw`\s+`);
1248
+ return Array.from(new Set([
1249
+ pattern,
1250
+ flexibleWhitespace,
1251
+ snakeCase,
1252
+ upperSnakeCase,
1253
+ camelCase,
1254
+ pascalCase
1255
+ ]));
1256
+ }
1257
+ /**
1258
+ * Search for pattern in files using ripgrep if available, fallback to Node.js
1259
+ */
1260
+ async function grepSearch(pattern, dirPath) {
1261
+ const patterns = buildSearchPatterns(pattern);
1262
+ for (const searchPattern of patterns) try {
1263
+ const { stdout } = await execShell(`rg --line-number --no-heading --smart-case "${searchPattern.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}" "${dirPath.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`);
1264
+ if (stdout) return { content: stdout };
1265
+ } catch {}
1266
+ try {
1267
+ if (!existsSync(dirPath)) return {
1268
+ content: "",
1269
+ error: `Directory not found: ${dirPath}`
1270
+ };
1271
+ const regexes = patterns.map((searchPattern) => new RegExp(searchPattern));
1272
+ const results = [];
1273
+ function searchDirectory(currentPath) {
1274
+ const entries = readdirSync(currentPath, { withFileTypes: true });
1275
+ for (const entry of entries) {
1276
+ const fullPath = join(currentPath, entry.name);
1277
+ if (entry.isDirectory()) {
1278
+ if (!entry.name.startsWith(".") && entry.name !== "node_modules") searchDirectory(fullPath);
1279
+ } else if (entry.isFile()) try {
1280
+ const lines = readFileSync(fullPath, "utf8").split("\n");
1281
+ for (let i = 0; i < lines.length; i++) for (const regex of regexes) if (regex.test(lines[i])) {
1282
+ results.push(`${fullPath}:${(i + 1).toString()}: ${lines[i].trim()}`);
1283
+ break;
1284
+ }
1285
+ } catch {}
1286
+ }
1287
+ }
1288
+ searchDirectory(dirPath);
1289
+ if (!results.length) return { content: "No matches found" };
1290
+ return { content: results.join("\n") };
1291
+ } catch (error) {
1292
+ return {
1293
+ content: "",
1294
+ error: `Search failed: ${error instanceof Error ? error.message : String(error)}`
1295
+ };
1296
+ }
1297
+ }
1298
+ //#endregion
1299
+ //#region src/utils/tools/filesystem/paths.ts
1198
1300
  /**
1199
1301
  * Create a directory and any missing parent directories
1200
1302
  */
@@ -1285,81 +1387,6 @@ function listDir(dirPath) {
1285
1387
  };
1286
1388
  }
1287
1389
  }
1288
- /**
1289
- * Recursively find files by path
1290
- */
1291
- function findFiles(dirPath, options = {}) {
1292
- try {
1293
- if (!existsSync(dirPath)) return {
1294
- content: "",
1295
- error: `Directory not found: ${dirPath}`
1296
- };
1297
- if (!statSync(dirPath).isDirectory()) return {
1298
- content: "",
1299
- error: `Path is not a directory: ${dirPath}`
1300
- };
1301
- const results = [];
1302
- const includeHidden = options.includeHidden ?? false;
1303
- const ignoredDirs = new Set(options.ignoredDirs ?? DEFAULT_FIND_FILES_IGNORED_DIRS);
1304
- function searchDirectory(currentPath) {
1305
- const entries = readdirSync(currentPath, { withFileTypes: true });
1306
- for (const entry of entries) {
1307
- const fullPath = join(currentPath, entry.name);
1308
- if (entry.isDirectory()) {
1309
- if (entry.name !== ".git" && !directoryMatchesIgnoredPattern(entry.name, ignoredDirs) && (includeHidden || !entry.name.startsWith("."))) searchDirectory(fullPath);
1310
- } else if (entry.isFile() && (includeHidden || !entry.name.startsWith(".")) && fileMatchesPattern(fullPath, options.pattern)) results.push(fullPath);
1311
- }
1312
- }
1313
- searchDirectory(dirPath);
1314
- return { content: results.join("\n") };
1315
- } catch (error) {
1316
- return {
1317
- content: "",
1318
- error: `Failed to find files: ${error instanceof Error ? error.message : String(error)}`
1319
- };
1320
- }
1321
- }
1322
- /**
1323
- * Search for pattern in files using ripgrep if available, fallback to Node.js
1324
- */
1325
- async function grepSearch(pattern, dirPath) {
1326
- const patterns = buildSearchPatterns(pattern);
1327
- for (const searchPattern of patterns) try {
1328
- const { stdout } = await execShell(`rg --line-number --no-heading --smart-case "${searchPattern.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}" "${dirPath.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`);
1329
- if (stdout) return { content: stdout };
1330
- } catch {}
1331
- try {
1332
- if (!existsSync(dirPath)) return {
1333
- content: "",
1334
- error: `Directory not found: ${dirPath}`
1335
- };
1336
- const regexes = patterns.map((searchPattern) => new RegExp(searchPattern));
1337
- const results = [];
1338
- function searchDirectory(currentPath) {
1339
- const entries = readdirSync(currentPath, { withFileTypes: true });
1340
- for (const entry of entries) {
1341
- const fullPath = join(currentPath, entry.name);
1342
- if (entry.isDirectory()) {
1343
- if (!entry.name.startsWith(".") && entry.name !== "node_modules") searchDirectory(fullPath);
1344
- } else if (entry.isFile()) try {
1345
- const lines = readFileSync(fullPath, "utf8").split("\n");
1346
- for (let i = 0; i < lines.length; i++) for (const regex of regexes) if (regex.test(lines[i])) {
1347
- results.push(`${fullPath}:${(i + 1).toString()}: ${lines[i].trim()}`);
1348
- break;
1349
- }
1350
- } catch {}
1351
- }
1352
- }
1353
- searchDirectory(dirPath);
1354
- if (!results.length) return { content: "No matches found" };
1355
- return { content: results.join("\n") };
1356
- } catch (error) {
1357
- return {
1358
- content: "",
1359
- error: `Search failed: ${error instanceof Error ? error.message : String(error)}`
1360
- };
1361
- }
1362
- }
1363
1390
  //#endregion
1364
1391
  //#region src/utils/tools/web/fetch.ts
1365
1392
  var FETCH_TIMEOUT_MS = 1e4;
@@ -1568,7 +1595,8 @@ function validateArgs(name, args) {
1568
1595
  for (const key of [
1569
1596
  "startLine",
1570
1597
  "endLine",
1571
- "maxLines"
1598
+ "maxLines",
1599
+ "maxChars"
1572
1600
  ]) if (args[key] !== void 0 && !Number.isInteger(args[key])) return {
1573
1601
  content: "",
1574
1602
  error: `Invalid optional numeric argument: ${key} (received keys: ${received})`
@@ -1577,6 +1605,10 @@ function validateArgs(name, args) {
1577
1605
  content: "",
1578
1606
  error: "Invalid read range: startLine, endLine, and maxLines must be >= 1"
1579
1607
  };
1608
+ if (typeof args.maxChars === "number" && args.maxChars < 1) return {
1609
+ content: "",
1610
+ error: "Invalid read range: maxChars must be >= 1"
1611
+ };
1580
1612
  if (args.endLine !== void 0 && args.maxLines !== void 0) return {
1581
1613
  content: "",
1582
1614
  error: "Invalid read range: endLine cannot be combined with maxLines"
@@ -1684,6 +1716,7 @@ async function executeTool(name, args, options) {
1684
1716
  switch (name) {
1685
1717
  case READ_FILE: return readFile(stringArgs.path, {
1686
1718
  endLine: args.endLine,
1719
+ maxChars: args.maxChars,
1687
1720
  maxLines: args.maxLines,
1688
1721
  startLine: args.startLine
1689
1722
  });
@@ -1756,15 +1789,19 @@ cli.command("run <model> <prompt>", "Run a one-off prompt").action(async (model,
1756
1789
  process.exitCode = 1;
1757
1790
  }
1758
1791
  });
1759
- cli.command("resume <sessionId>", "Resume a saved session").action(async (sessionId) => {
1792
+ cli.command("resume [sessionId]", "Resume a saved session").action(async (sessionId) => {
1760
1793
  try {
1794
+ if (!sessionId) {
1795
+ await launchTui({ initialScreen: SESSION_MANAGER });
1796
+ return;
1797
+ }
1761
1798
  const loaded = loadSession(sessionId);
1762
1799
  if (loaded.metadata.directory && loaded.metadata.directory !== process.cwd()) {
1763
1800
  writeError(color(`${WARNING} Cannot resume: session belongs to ${loaded.metadata.directory}\n`, "yellow"));
1764
1801
  process.exitCode = 1;
1765
1802
  return;
1766
1803
  }
1767
- await launchTui(sessionId);
1804
+ await launchTui({ sessionId });
1768
1805
  } catch (error) {
1769
1806
  writeError(`Error: ${error instanceof Error ? error.message : "Unknown error"}\n`);
1770
1807
  process.exitCode = 1;
@@ -1839,10 +1876,10 @@ async function main(args = process.argv.slice(2)) {
1839
1876
  ]);
1840
1877
  else await launchTui();
1841
1878
  }
1842
- async function launchTui(sessionId) {
1843
- const { renderApp } = await import("./assets/tui-CboegfoT.js");
1879
+ async function launchTui(options = {}) {
1880
+ const { renderApp } = await import("./assets/tui-c76QePyQ.js");
1844
1881
  reset();
1845
- renderApp(sessionId);
1882
+ renderApp(options);
1846
1883
  }
1847
1884
  // v8 ignore start
1848
1885
  function isEntrypoint(argv1 = process.argv[1]) {
@@ -1856,4 +1893,4 @@ function isEntrypoint(argv1 = process.argv[1]) {
1856
1893
  if (isEntrypoint()) main();
1857
1894
  // v8 ignore stop
1858
1895
  //#endregion
1859
- export { NAME as $, loadConfig as A, ASSISTANT as B, checkHealth as C, pullModel as D, listModels as E, withSystemMessage as F, BACK as G, USER as H, HEADER_PREFIX as I, LABEL as J, CATALOG as K, WARNING as L, removeClipboardImage as M, saveClipboardImage as N, sanitizeAssistantContent as O, resetSystemMessage as P, REJECT as Q, LIST as R, TOOL_INTENT_CORRECTION as S, hasUncalledToolIntent as T, PLAN_GENERATION_INSTRUCTION as U, SYSTEM as V, PLAN_INSTRUCTION as W, SAFE as X, PLAN as Y, APPROVE as Z, loadSession as _, normalizeToolCall as a, reset as b, WRITE_TOOLS as c, write as d, VERSION as et, appendMessage as f, listSessions as g, deleteSessionIfEmpty as h, formatToolResultContent as i, saveConfig as j, streamChat as k, tick as l, deleteSession as m, main, executeTool as n, READ_TOOLS as o, createSession as p, AUTO as q, executeToolCall as r, TOOLS as s, checkForUpdate as t, LIST$1 as tt, color as u, updateSessionModel as v, deleteModel as w, setClearHandler as x, clear as y, getTheme as z };
1896
+ export { LABEL as $, loadConfig as A, CHAT as B, checkHealth as C, pullModel as D, listModels as E, withSystemMessage as F, ASSISTANT as G, SEARCH_SETTINGS as H, HEADER_PREFIX as I, PLAN_GENERATION_INSTRUCTION as J, SYSTEM as K, WARNING as L, removeClipboardImage as M, saveClipboardImage as N, sanitizeAssistantContent as O, resetSystemMessage as P, AUTO as Q, LIST as R, TOOL_INTENT_CORRECTION as S, hasUncalledToolIntent as T, SESSION_MANAGER as U, MODEL_MANAGER as V, THEME_SETTINGS as W, BACK as X, PLAN_INSTRUCTION as Y, CATALOG as Z, loadSession as _, normalizeToolCall as a, VERSION as at, reset as b, WRITE_TOOLS as c, write as d, PLAN as et, appendMessage as f, listSessions as g, deleteSessionIfEmpty as h, formatToolResultContent as i, NAME as it, saveConfig as j, streamChat as k, tick as l, deleteSession as m, main, executeTool as n, APPROVE as nt, READ_TOOLS as o, LIST$1 as ot, createSession as p, USER as q, executeToolCall as r, REJECT as rt, TOOLS as s, checkForUpdate as t, SAFE as tt, color as u, updateSessionModel as v, deleteModel as w, setClearHandler as x, clear as y, getTheme as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-ollama",
3
- "version": "0.24.1",
3
+ "version": "0.26.0",
4
4
  "description": "Ollama coding agent that runs in your terminal",
5
5
  "author": "Mark <mark@remarkablemark.org> (https://remarkablemark.org)",
6
6
  "type": "module",