code-ollama 0.25.0 → 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.
Files changed (2) hide show
  1. package/dist/cli.js +214 -188
  2. package/package.json +1 -1
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.25.0";
53
+ var version = "0.26.0";
54
54
  //#endregion
55
55
  //#region src/constants/package.ts
56
56
  var NAME = name;
@@ -859,6 +859,10 @@ var TOOLS = [
859
859
  maxLines: {
860
860
  type: "number",
861
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`
862
866
  }
863
867
  }, ["path"]),
864
868
  defineTool(WRITE_FILE, "Write content to a file at the specified path", {
@@ -965,56 +969,10 @@ var TOOLS = [
965
969
  var READ_TOOLS = new Set(READ_TOOL_NAMES);
966
970
  var WRITE_TOOLS = new Set(WRITE_TOOL_NAMES);
967
971
  //#endregion
968
- //#region src/utils/tools/shell.ts
969
- var execAsync = promisify(exec);
970
- var SHELL_EXEC_OPTIONS = {
971
- timeout: 3e4,
972
- maxBuffer: 1024 * 1024
973
- };
974
- function getErrorOutput(error) {
975
- if (typeof error !== "object" || error === null) return "";
976
- const output = error;
977
- return [output.stdout, output.stderr].filter((value) => typeof value === "string" && !!value).join("\n");
978
- }
979
- /**
980
- * Execute shell command with shared options (throws on error)
981
- */
982
- function execShell(command) {
983
- return execAsync(command, SHELL_EXEC_OPTIONS);
984
- }
985
- /**
986
- * Execute shell command
987
- */
988
- async function runShell(command) {
989
- try {
990
- const { stdout, stderr } = await execShell(command);
991
- return { content: stdout || stderr };
992
- } catch (error) {
993
- const message = error instanceof Error ? error.message : String(error);
994
- return {
995
- content: getErrorOutput(error),
996
- error: `Command failed: ${message}`,
997
- // v8 ignore next
998
- ...error instanceof Error && error.stack ? { stack: error.stack } : {}
999
- };
1000
- }
1001
- }
1002
- //#endregion
1003
- //#region src/utils/tools/filesystem.ts
972
+ //#region src/utils/tools/filesystem/diff.ts
1004
973
  var DIFF_CONTEXT_LINES = 3;
1005
974
  var DIFF_MAX_LINES = 120;
1006
975
  var DIFF_MAX_CHARS = 12e3;
1007
- var DEFAULT_FIND_FILES_IGNORED_DIRS = [
1008
- "node_modules",
1009
- "__pycache__",
1010
- ".*cache",
1011
- ".tox",
1012
- ".venv",
1013
- "venv",
1014
- "dist",
1015
- "build",
1016
- "coverage"
1017
- ];
1018
976
  function splitLines(content) {
1019
977
  return content.split("\n");
1020
978
  }
@@ -1062,59 +1020,9 @@ function truncateDiff(diff) {
1062
1020
  visibleLines: Math.min(visibleLines.length, lines.length)
1063
1021
  };
1064
1022
  }
1065
- function buildSearchPatterns(pattern) {
1066
- const words = pattern.trim().split(/[^a-zA-Z0-9]+/).filter(Boolean);
1067
- if (words.length < 2) return [pattern];
1068
- const camelCase = words.map((word, index) => index === 0 ? word.toLowerCase() : capitalize(word.toLowerCase())).join("");
1069
- const pascalCase = words.map((word) => capitalize(word.toLowerCase())).join("");
1070
- const snakeCase = words.map((word) => word.toLowerCase()).join("_");
1071
- const upperSnakeCase = snakeCase.toUpperCase();
1072
- const flexibleWhitespace = words.join(String.raw`\s+`);
1073
- return Array.from(new Set([
1074
- pattern,
1075
- flexibleWhitespace,
1076
- snakeCase,
1077
- upperSnakeCase,
1078
- camelCase,
1079
- pascalCase
1080
- ]));
1081
- }
1082
- function capitalize(value) {
1083
- return value.charAt(0).toUpperCase() + value.slice(1);
1084
- }
1085
- function escapeRegExp(value) {
1086
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1087
- }
1088
- function fileMatchesPattern(filePath, pattern) {
1089
- const trimmedPattern = pattern?.trim();
1090
- if (!trimmedPattern) return true;
1091
- const normalizedPath = filePath.toLowerCase();
1092
- const normalizedFileName = normalizedPath.slice(Math.max(normalizedPath.lastIndexOf("/"), normalizedPath.lastIndexOf("\\")) + 1);
1093
- const normalizedPattern = trimmedPattern.toLowerCase();
1094
- if (!normalizedPattern.includes("*") && !normalizedPattern.includes("?")) return normalizedPath.includes(normalizedPattern);
1095
- const regexPattern = normalizedPattern.split("").map((char) => {
1096
- if (char === "*") return ".*";
1097
- if (char === "?") return ".";
1098
- return escapeRegExp(char);
1099
- }).join("");
1100
- const regex = new RegExp(`^${regexPattern}$`);
1101
- return regex.test(normalizedPath) || regex.test(normalizedFileName);
1102
- }
1103
- function valueMatchesWildcardPattern(value, pattern) {
1104
- const normalizedValue = value.toLowerCase();
1105
- const normalizedPattern = pattern.trim().toLowerCase();
1106
- if (!normalizedPattern.includes("*") && !normalizedPattern.includes("?")) return normalizedValue === normalizedPattern;
1107
- const regexPattern = normalizedPattern.split("").map((char) => {
1108
- if (char === "*") return ".*";
1109
- if (char === "?") return ".";
1110
- return escapeRegExp(char);
1111
- }).join("");
1112
- return new RegExp(`^${regexPattern}$`).test(normalizedValue);
1113
- }
1114
- function directoryMatchesIgnoredPattern(dirName, ignoredDirs) {
1115
- for (const ignoredDir of ignoredDirs) if (valueMatchesWildcardPattern(dirName, ignoredDir)) return true;
1116
- return false;
1117
- }
1023
+ //#endregion
1024
+ //#region src/utils/tools/filesystem/files.ts
1025
+ var READ_FILE_MAX_CHARS = 5e4;
1118
1026
  function formatNumberedLines(lines, startLine) {
1119
1027
  return lines.map((line, index) => `${String(startLine + index)}: ${line}`).join("\n");
1120
1028
  }
@@ -1128,17 +1036,24 @@ function readFile(filePath, options = {}) {
1128
1036
  error: `File not found: ${filePath}`
1129
1037
  };
1130
1038
  const content = readFileSync(filePath, "utf8");
1131
- if (!(options.startLine !== void 0 || options.endLine !== void 0 || options.maxLines !== void 0)) return { content };
1132
- const lines = content.split("\n");
1133
- const startLine = options.startLine ?? 1;
1134
- const endLine = options.endLine ?? startLine + (options.maxLines ?? lines.length) - 1;
1135
- const startIndex = startLine - 1;
1136
- const endIndex = Math.min(lines.length, endLine);
1137
- if (startIndex >= lines.length) return {
1138
- content: "",
1139
- error: "Invalid line range"
1140
- };
1141
- 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 };
1142
1057
  } catch (error) {
1143
1058
  return {
1144
1059
  content: "",
@@ -1202,6 +1117,186 @@ function editFile(filePath, oldText, newText) {
1202
1117
  };
1203
1118
  }
1204
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
1205
1300
  /**
1206
1301
  * Create a directory and any missing parent directories
1207
1302
  */
@@ -1292,81 +1387,6 @@ function listDir(dirPath) {
1292
1387
  };
1293
1388
  }
1294
1389
  }
1295
- /**
1296
- * Recursively find files by path
1297
- */
1298
- function findFiles(dirPath, options = {}) {
1299
- try {
1300
- if (!existsSync(dirPath)) return {
1301
- content: "",
1302
- error: `Directory not found: ${dirPath}`
1303
- };
1304
- if (!statSync(dirPath).isDirectory()) return {
1305
- content: "",
1306
- error: `Path is not a directory: ${dirPath}`
1307
- };
1308
- const results = [];
1309
- const includeHidden = options.includeHidden ?? false;
1310
- const ignoredDirs = new Set(options.ignoredDirs ?? DEFAULT_FIND_FILES_IGNORED_DIRS);
1311
- function searchDirectory(currentPath) {
1312
- const entries = readdirSync(currentPath, { withFileTypes: true });
1313
- for (const entry of entries) {
1314
- const fullPath = join(currentPath, entry.name);
1315
- if (entry.isDirectory()) {
1316
- if (entry.name !== ".git" && !directoryMatchesIgnoredPattern(entry.name, ignoredDirs) && (includeHidden || !entry.name.startsWith("."))) searchDirectory(fullPath);
1317
- } else if (entry.isFile() && (includeHidden || !entry.name.startsWith(".")) && fileMatchesPattern(fullPath, options.pattern)) results.push(fullPath);
1318
- }
1319
- }
1320
- searchDirectory(dirPath);
1321
- return { content: results.join("\n") };
1322
- } catch (error) {
1323
- return {
1324
- content: "",
1325
- error: `Failed to find files: ${error instanceof Error ? error.message : String(error)}`
1326
- };
1327
- }
1328
- }
1329
- /**
1330
- * Search for pattern in files using ripgrep if available, fallback to Node.js
1331
- */
1332
- async function grepSearch(pattern, dirPath) {
1333
- const patterns = buildSearchPatterns(pattern);
1334
- for (const searchPattern of patterns) try {
1335
- const { stdout } = await execShell(`rg --line-number --no-heading --smart-case "${searchPattern.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}" "${dirPath.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`);
1336
- if (stdout) return { content: stdout };
1337
- } catch {}
1338
- try {
1339
- if (!existsSync(dirPath)) return {
1340
- content: "",
1341
- error: `Directory not found: ${dirPath}`
1342
- };
1343
- const regexes = patterns.map((searchPattern) => new RegExp(searchPattern));
1344
- const results = [];
1345
- function searchDirectory(currentPath) {
1346
- const entries = readdirSync(currentPath, { withFileTypes: true });
1347
- for (const entry of entries) {
1348
- const fullPath = join(currentPath, entry.name);
1349
- if (entry.isDirectory()) {
1350
- if (!entry.name.startsWith(".") && entry.name !== "node_modules") searchDirectory(fullPath);
1351
- } else if (entry.isFile()) try {
1352
- const lines = readFileSync(fullPath, "utf8").split("\n");
1353
- for (let i = 0; i < lines.length; i++) for (const regex of regexes) if (regex.test(lines[i])) {
1354
- results.push(`${fullPath}:${(i + 1).toString()}: ${lines[i].trim()}`);
1355
- break;
1356
- }
1357
- } catch {}
1358
- }
1359
- }
1360
- searchDirectory(dirPath);
1361
- if (!results.length) return { content: "No matches found" };
1362
- return { content: results.join("\n") };
1363
- } catch (error) {
1364
- return {
1365
- content: "",
1366
- error: `Search failed: ${error instanceof Error ? error.message : String(error)}`
1367
- };
1368
- }
1369
- }
1370
1390
  //#endregion
1371
1391
  //#region src/utils/tools/web/fetch.ts
1372
1392
  var FETCH_TIMEOUT_MS = 1e4;
@@ -1575,7 +1595,8 @@ function validateArgs(name, args) {
1575
1595
  for (const key of [
1576
1596
  "startLine",
1577
1597
  "endLine",
1578
- "maxLines"
1598
+ "maxLines",
1599
+ "maxChars"
1579
1600
  ]) if (args[key] !== void 0 && !Number.isInteger(args[key])) return {
1580
1601
  content: "",
1581
1602
  error: `Invalid optional numeric argument: ${key} (received keys: ${received})`
@@ -1584,6 +1605,10 @@ function validateArgs(name, args) {
1584
1605
  content: "",
1585
1606
  error: "Invalid read range: startLine, endLine, and maxLines must be >= 1"
1586
1607
  };
1608
+ if (typeof args.maxChars === "number" && args.maxChars < 1) return {
1609
+ content: "",
1610
+ error: "Invalid read range: maxChars must be >= 1"
1611
+ };
1587
1612
  if (args.endLine !== void 0 && args.maxLines !== void 0) return {
1588
1613
  content: "",
1589
1614
  error: "Invalid read range: endLine cannot be combined with maxLines"
@@ -1691,6 +1716,7 @@ async function executeTool(name, args, options) {
1691
1716
  switch (name) {
1692
1717
  case READ_FILE: return readFile(stringArgs.path, {
1693
1718
  endLine: args.endLine,
1719
+ maxChars: args.maxChars,
1694
1720
  maxLines: args.maxLines,
1695
1721
  startLine: args.startLine
1696
1722
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-ollama",
3
- "version": "0.25.0",
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",