code-ollama 0.23.2 → 0.24.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 +287 -49
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
2
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, writeFileSync } from "node:fs";
3
3
  import cac from "cac";
4
4
  import { homedir, tmpdir } from "node:os";
5
5
  import { join } from "node:path";
@@ -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.23.2";
53
+ var version = "0.24.0";
54
54
  //#endregion
55
55
  //#region src/constants/package.ts
56
56
  var NAME = name;
@@ -107,13 +107,16 @@ var BACK = {
107
107
  //#endregion
108
108
  //#region src/constants/tool.ts
109
109
  var tool_exports = /* @__PURE__ */ __exportAll({
110
+ CREATE_DIRECTORY: () => CREATE_DIRECTORY,
111
+ DELETE_PATH: () => DELETE_PATH,
110
112
  EDIT_FILE: () => EDIT_FILE,
113
+ FIND_FILES: () => FIND_FILES,
111
114
  GREP_SEARCH: () => GREP_SEARCH,
112
115
  LIST_DIR: () => LIST_DIR,
113
116
  READ_FILE: () => READ_FILE,
114
117
  READ_TOOL_NAMES: () => READ_TOOL_NAMES,
118
+ RENAME_PATH: () => RENAME_PATH,
115
119
  RUN_SHELL: () => RUN_SHELL,
116
- VIEW_RANGE: () => VIEW_RANGE,
117
120
  WEB_FETCH: () => WEB_FETCH,
118
121
  WEB_SEARCH: () => WEB_SEARCH,
119
122
  WRITE_FILE: () => WRITE_FILE,
@@ -122,23 +125,29 @@ var tool_exports = /* @__PURE__ */ __exportAll({
122
125
  var READ_FILE = "read_file";
123
126
  var WRITE_FILE = "write_file";
124
127
  var EDIT_FILE = "edit_file";
128
+ var CREATE_DIRECTORY = "create_directory";
129
+ var RENAME_PATH = "rename_path";
130
+ var DELETE_PATH = "delete_path";
125
131
  var RUN_SHELL = "run_shell";
126
132
  var LIST_DIR = "list_dir";
133
+ var FIND_FILES = "find_files";
127
134
  var GREP_SEARCH = "grep_search";
128
- var VIEW_RANGE = "view_range";
129
135
  var WEB_SEARCH = "web_search";
130
136
  var WEB_FETCH = "web_fetch";
131
137
  var READ_TOOL_NAMES = [
132
138
  READ_FILE,
133
139
  LIST_DIR,
140
+ FIND_FILES,
134
141
  GREP_SEARCH,
135
- VIEW_RANGE,
136
142
  WEB_SEARCH,
137
143
  WEB_FETCH
138
144
  ];
139
145
  var WRITE_TOOL_NAMES = [
140
146
  WRITE_FILE,
141
147
  EDIT_FILE,
148
+ CREATE_DIRECTORY,
149
+ RENAME_PATH,
150
+ DELETE_PATH,
142
151
  RUN_SHELL
143
152
  ];
144
153
  //#endregion
@@ -157,10 +166,14 @@ Follow these rules:
157
166
 
158
167
  When tools return results, incorporate them into your response naturally`;
159
168
  var TOOL_INSTRUCTIONS = `Available tools:
160
- - read_file: Read file contents at a path
169
+ - read_file: Read file contents at a path; supports startLine, endLine, and maxLines options
161
170
  - write_file: Write content to a file (requires approval)
162
171
  - edit_file: Replace one exact text match in a file (requires approval)
172
+ - create_directory: Create a directory and missing parent directories (requires approval)
173
+ - rename_path: Rename or move a file or directory without overwriting existing destinations (requires approval)
174
+ - delete_path: Delete a file or directory; non-empty directories require recursive=true (requires approval)
163
175
  - list_dir: List files in a directory
176
+ - find_files: Recursively find files by optional substring or wildcard path pattern; supports includeHidden and ignoredDirs options
164
177
  - grep_search: Search code with regex
165
178
  - web_search: Search the web for current or external information
166
179
  - run_shell: Execute shell commands (requires approval)
@@ -815,10 +828,24 @@ function defineTool(name, description, params, required) {
815
828
  * Tool definitions for Ollama API
816
829
  */
817
830
  var TOOLS = [
818
- defineTool(READ_FILE, "Read the contents of a file at the specified path", { path: {
819
- type: "string",
820
- description: "The path to the file to read"
821
- } }, ["path"]),
831
+ defineTool(READ_FILE, "Read the contents of a file at the specified path, optionally limited by line range", {
832
+ path: {
833
+ type: "string",
834
+ description: "The path to the file to read"
835
+ },
836
+ startLine: {
837
+ type: "number",
838
+ description: "Optional starting line number to read from (1-indexed)"
839
+ },
840
+ endLine: {
841
+ type: "number",
842
+ description: "Optional ending line number to read through (inclusive)"
843
+ },
844
+ maxLines: {
845
+ type: "number",
846
+ description: "Optional maximum number of lines to read; cannot be combined with endLine"
847
+ }
848
+ }, ["path"]),
822
849
  defineTool(WRITE_FILE, "Write content to a file at the specified path", {
823
850
  path: {
824
851
  type: "string",
@@ -847,6 +874,30 @@ var TOOLS = [
847
874
  "oldText",
848
875
  "newText"
849
876
  ]),
877
+ defineTool(CREATE_DIRECTORY, "Create a directory and any missing parent directories at the specified path", { path: {
878
+ type: "string",
879
+ description: "The directory path to create"
880
+ } }, ["path"]),
881
+ defineTool(RENAME_PATH, "Rename or move an existing file or directory to a new path", {
882
+ from: {
883
+ type: "string",
884
+ description: "The existing file or directory path to rename or move"
885
+ },
886
+ to: {
887
+ type: "string",
888
+ description: "The destination path for the renamed or moved item"
889
+ }
890
+ }, ["from", "to"]),
891
+ defineTool(DELETE_PATH, "Delete a file or directory at the specified path", {
892
+ path: {
893
+ type: "string",
894
+ description: "The file or directory path to delete"
895
+ },
896
+ recursive: {
897
+ type: "boolean",
898
+ description: "Whether to delete non-empty directories recursively; use false for files and empty directories"
899
+ }
900
+ }, ["path", "recursive"]),
850
901
  defineTool(RUN_SHELL, "Execute a shell command", { command: {
851
902
  type: "string",
852
903
  description: "The shell command to execute"
@@ -855,6 +906,28 @@ var TOOLS = [
855
906
  type: "string",
856
907
  description: "The path to the directory to list"
857
908
  } }, ["path"]),
909
+ defineTool(FIND_FILES, "Recursively find files under a directory, optionally matching a simple substring or wildcard pattern", {
910
+ path: {
911
+ type: "string",
912
+ description: "The directory path to search in"
913
+ },
914
+ pattern: {
915
+ type: "string",
916
+ description: "Optional case-insensitive substring or wildcard pattern to match against file paths"
917
+ },
918
+ includeHidden: {
919
+ type: "boolean",
920
+ description: "Whether to include hidden files and directories; defaults to false"
921
+ },
922
+ ignoredDirs: {
923
+ type: "array",
924
+ description: "Optional directory names or simple wildcard patterns to skip instead of the default ignored directory list; .git is always skipped",
925
+ items: {
926
+ type: "string",
927
+ description: "Directory name or wildcard pattern to skip"
928
+ }
929
+ }
930
+ }, ["path"]),
858
931
  defineTool(GREP_SEARCH, "Search files within a directory; multi-word queries also match common code identifier forms", {
859
932
  pattern: {
860
933
  type: "string",
@@ -865,24 +938,6 @@ var TOOLS = [
865
938
  description: "The directory path to search in"
866
939
  }
867
940
  }, ["pattern", "path"]),
868
- defineTool(VIEW_RANGE, "View a specific range of lines from a file", {
869
- path: {
870
- type: "string",
871
- description: "The path to the file"
872
- },
873
- start: {
874
- type: "number",
875
- description: "The starting line number (1-indexed)"
876
- },
877
- end: {
878
- type: "number",
879
- description: "The ending line number (inclusive)"
880
- }
881
- }, [
882
- "path",
883
- "start",
884
- "end"
885
- ]),
886
941
  defineTool(WEB_SEARCH, "Search the web for external or current information", { query: {
887
942
  type: "string",
888
943
  description: "The search query to look up"
@@ -926,6 +981,17 @@ async function runShell(command) {
926
981
  var DIFF_CONTEXT_LINES = 3;
927
982
  var DIFF_MAX_LINES = 120;
928
983
  var DIFF_MAX_CHARS = 12e3;
984
+ var DEFAULT_FIND_FILES_IGNORED_DIRS = [
985
+ "node_modules",
986
+ "__pycache__",
987
+ ".*cache",
988
+ ".tox",
989
+ ".venv",
990
+ "venv",
991
+ "dist",
992
+ "build",
993
+ "coverage"
994
+ ];
929
995
  function splitLines(content) {
930
996
  return content.split("\n");
931
997
  }
@@ -993,16 +1059,63 @@ function buildSearchPatterns(pattern) {
993
1059
  function capitalize(value) {
994
1060
  return value.charAt(0).toUpperCase() + value.slice(1);
995
1061
  }
1062
+ function escapeRegExp(value) {
1063
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1064
+ }
1065
+ function fileMatchesPattern(filePath, pattern) {
1066
+ const trimmedPattern = pattern?.trim();
1067
+ if (!trimmedPattern) return true;
1068
+ const normalizedPath = filePath.toLowerCase();
1069
+ const normalizedFileName = normalizedPath.slice(Math.max(normalizedPath.lastIndexOf("/"), normalizedPath.lastIndexOf("\\")) + 1);
1070
+ const normalizedPattern = trimmedPattern.toLowerCase();
1071
+ if (!normalizedPattern.includes("*") && !normalizedPattern.includes("?")) return normalizedPath.includes(normalizedPattern);
1072
+ const regexPattern = normalizedPattern.split("").map((char) => {
1073
+ if (char === "*") return ".*";
1074
+ if (char === "?") return ".";
1075
+ return escapeRegExp(char);
1076
+ }).join("");
1077
+ const regex = new RegExp(`^${regexPattern}$`);
1078
+ return regex.test(normalizedPath) || regex.test(normalizedFileName);
1079
+ }
1080
+ function valueMatchesWildcardPattern(value, pattern) {
1081
+ const normalizedValue = value.toLowerCase();
1082
+ const normalizedPattern = pattern.trim().toLowerCase();
1083
+ if (!normalizedPattern.includes("*") && !normalizedPattern.includes("?")) return normalizedValue === normalizedPattern;
1084
+ const regexPattern = normalizedPattern.split("").map((char) => {
1085
+ if (char === "*") return ".*";
1086
+ if (char === "?") return ".";
1087
+ return escapeRegExp(char);
1088
+ }).join("");
1089
+ return new RegExp(`^${regexPattern}$`).test(normalizedValue);
1090
+ }
1091
+ function directoryMatchesIgnoredPattern(dirName, ignoredDirs) {
1092
+ for (const ignoredDir of ignoredDirs) if (valueMatchesWildcardPattern(dirName, ignoredDir)) return true;
1093
+ return false;
1094
+ }
1095
+ function formatNumberedLines(lines, startLine) {
1096
+ return lines.map((line, index) => `${String(startLine + index)}: ${line}`).join("\n");
1097
+ }
996
1098
  /**
997
1099
  * Read file contents
998
1100
  */
999
- function readFile(filePath) {
1101
+ function readFile(filePath, options = {}) {
1000
1102
  try {
1001
1103
  if (!existsSync(filePath)) return {
1002
1104
  content: "",
1003
1105
  error: `File not found: ${filePath}`
1004
1106
  };
1005
- return { content: readFileSync(filePath, "utf8") };
1107
+ const content = readFileSync(filePath, "utf8");
1108
+ if (!(options.startLine !== void 0 || options.endLine !== void 0 || options.maxLines !== void 0)) return { content };
1109
+ const lines = content.split("\n");
1110
+ const startLine = options.startLine ?? 1;
1111
+ const endLine = options.endLine ?? startLine + (options.maxLines ?? lines.length) - 1;
1112
+ const startIndex = startLine - 1;
1113
+ const endIndex = Math.min(lines.length, endLine);
1114
+ if (startIndex >= lines.length) return {
1115
+ content: "",
1116
+ error: "Invalid line range"
1117
+ };
1118
+ return { content: formatNumberedLines(lines.slice(startIndex, endIndex), startLine) };
1006
1119
  } catch (error) {
1007
1120
  return {
1008
1121
  content: "",
@@ -1067,26 +1180,73 @@ function editFile(filePath, oldText, newText) {
1067
1180
  }
1068
1181
  }
1069
1182
  /**
1070
- * View specific line range from file
1183
+ * Create a directory and any missing parent directories
1071
1184
  */
1072
- function viewRange(filePath, start, end) {
1185
+ function createDirectory(dirPath) {
1073
1186
  try {
1074
- if (!existsSync(filePath)) return {
1187
+ if (existsSync(dirPath)) {
1188
+ if (statSync(dirPath).isDirectory()) return { content: `Directory already exists: ${dirPath}` };
1189
+ return {
1190
+ content: "",
1191
+ error: `Path already exists and is not a directory: ${dirPath}`
1192
+ };
1193
+ }
1194
+ mkdirSync(dirPath, { recursive: true });
1195
+ return { content: `Directory created successfully: ${dirPath}` };
1196
+ } catch (error) {
1197
+ return {
1075
1198
  content: "",
1076
- error: `File not found: ${filePath}`
1199
+ error: `Failed to create directory: ${error instanceof Error ? error.message : String(error)}`
1077
1200
  };
1078
- const lines = readFileSync(filePath, "utf8").split("\n");
1079
- const startIdx = Math.max(0, start - 1);
1080
- const endIdx = Math.min(lines.length, end);
1081
- if (startIdx >= lines.length || startIdx > endIdx) return {
1201
+ }
1202
+ }
1203
+ /**
1204
+ * Rename or move an existing file or directory
1205
+ */
1206
+ function renamePath(fromPath, toPath) {
1207
+ try {
1208
+ if (!existsSync(fromPath)) return {
1082
1209
  content: "",
1083
- error: "Invalid line range"
1210
+ error: `Source path not found: ${fromPath}`
1211
+ };
1212
+ if (existsSync(toPath)) return {
1213
+ content: "",
1214
+ error: `Destination path already exists: ${toPath}`
1084
1215
  };
1085
- return { content: lines.slice(startIdx, endIdx).join("\n") };
1216
+ renameSync(fromPath, toPath);
1217
+ return { content: `Path renamed successfully: ${fromPath} -> ${toPath}` };
1086
1218
  } catch (error) {
1087
1219
  return {
1088
1220
  content: "",
1089
- error: `Failed to view range: ${error instanceof Error ? error.message : String(error)}`
1221
+ error: `Failed to rename path: ${error instanceof Error ? error.message : String(error)}`
1222
+ };
1223
+ }
1224
+ }
1225
+ /**
1226
+ * Delete a file or directory
1227
+ */
1228
+ function deletePath(path, recursive) {
1229
+ try {
1230
+ if (!existsSync(path)) return {
1231
+ content: "",
1232
+ error: `Path not found: ${path}`
1233
+ };
1234
+ if (statSync(path).isDirectory()) {
1235
+ if (readdirSync(path).length > 0 && !recursive) return {
1236
+ content: "",
1237
+ error: `Directory is not empty; set recursive to true to delete: ${path}`
1238
+ };
1239
+ if (recursive) rmSync(path, {
1240
+ recursive: true,
1241
+ force: false
1242
+ });
1243
+ else rmdirSync(path);
1244
+ } else rmSync(path, { force: false });
1245
+ return { content: `Path deleted successfully: ${path}` };
1246
+ } catch (error) {
1247
+ return {
1248
+ content: "",
1249
+ error: `Failed to delete path: ${error instanceof Error ? error.message : String(error)}`
1090
1250
  };
1091
1251
  }
1092
1252
  }
@@ -1110,6 +1270,40 @@ function listDir(dirPath) {
1110
1270
  }
1111
1271
  }
1112
1272
  /**
1273
+ * Recursively find files by path
1274
+ */
1275
+ function findFiles(dirPath, options = {}) {
1276
+ try {
1277
+ if (!existsSync(dirPath)) return {
1278
+ content: "",
1279
+ error: `Directory not found: ${dirPath}`
1280
+ };
1281
+ if (!statSync(dirPath).isDirectory()) return {
1282
+ content: "",
1283
+ error: `Path is not a directory: ${dirPath}`
1284
+ };
1285
+ const results = [];
1286
+ const includeHidden = options.includeHidden ?? false;
1287
+ const ignoredDirs = new Set(options.ignoredDirs ?? DEFAULT_FIND_FILES_IGNORED_DIRS);
1288
+ function searchDirectory(currentPath) {
1289
+ const entries = readdirSync(currentPath, { withFileTypes: true });
1290
+ for (const entry of entries) {
1291
+ const fullPath = join(currentPath, entry.name);
1292
+ if (entry.isDirectory()) {
1293
+ if (entry.name !== ".git" && !directoryMatchesIgnoredPattern(entry.name, ignoredDirs) && (includeHidden || !entry.name.startsWith("."))) searchDirectory(fullPath);
1294
+ } else if (entry.isFile() && (includeHidden || !entry.name.startsWith(".")) && fileMatchesPattern(fullPath, options.pattern)) results.push(fullPath);
1295
+ }
1296
+ }
1297
+ searchDirectory(dirPath);
1298
+ return { content: results.join("\n") };
1299
+ } catch (error) {
1300
+ return {
1301
+ content: "",
1302
+ error: `Failed to find files: ${error instanceof Error ? error.message : String(error)}`
1303
+ };
1304
+ }
1305
+ }
1306
+ /**
1113
1307
  * Search for pattern in files using ripgrep if available, fallback to Node.js
1114
1308
  */
1115
1309
  async function grepSearch(pattern, dirPath) {
@@ -1333,10 +1527,13 @@ var REQUIRED_STRING_ARGS = {
1333
1527
  "oldText",
1334
1528
  "newText"
1335
1529
  ],
1530
+ [CREATE_DIRECTORY]: ["path"],
1531
+ [RENAME_PATH]: ["from", "to"],
1532
+ [DELETE_PATH]: ["path"],
1336
1533
  [RUN_SHELL]: ["command"],
1337
1534
  [LIST_DIR]: ["path"],
1535
+ [FIND_FILES]: ["path"],
1338
1536
  [GREP_SEARCH]: ["pattern", "path"],
1339
- [VIEW_RANGE]: ["path"],
1340
1537
  [WEB_SEARCH]: ["query"],
1341
1538
  [WEB_FETCH]: ["url"]
1342
1539
  };
@@ -1351,14 +1548,44 @@ function validateArgs(name, args) {
1351
1548
  content: "",
1352
1549
  error: `Missing required argument: ${key} (received keys: ${received})`
1353
1550
  };
1354
- if (name === "view_range") {
1355
- if (!Number.isInteger(args.start) || !Number.isInteger(args.end)) return {
1551
+ if (name === "read_file") {
1552
+ for (const key of [
1553
+ "startLine",
1554
+ "endLine",
1555
+ "maxLines"
1556
+ ]) if (args[key] !== void 0 && !Number.isInteger(args[key])) return {
1557
+ content: "",
1558
+ error: `Invalid optional numeric argument: ${key} (received keys: ${received})`
1559
+ };
1560
+ if (typeof args.startLine === "number" && args.startLine < 1 || typeof args.endLine === "number" && args.endLine < 1 || typeof args.maxLines === "number" && args.maxLines < 1) return {
1561
+ content: "",
1562
+ error: "Invalid read range: startLine, endLine, and maxLines must be >= 1"
1563
+ };
1564
+ if (args.endLine !== void 0 && args.maxLines !== void 0) return {
1356
1565
  content: "",
1357
- error: `Missing required numeric arguments: start, end (received keys: ${received})`
1566
+ error: "Invalid read range: endLine cannot be combined with maxLines"
1358
1567
  };
1359
- if (args.start < 1 || args.end < args.start) return {
1568
+ if (typeof args.startLine === "number" && typeof args.endLine === "number" && args.endLine < args.startLine) return {
1569
+ content: "",
1570
+ error: "Invalid read range: endLine must be >= startLine"
1571
+ };
1572
+ }
1573
+ if (name === "delete_path" && typeof args.recursive !== "boolean") return {
1574
+ content: "",
1575
+ error: `Missing required boolean argument: recursive (received keys: ${received})`
1576
+ };
1577
+ if (name === "find_files" && args.pattern !== void 0 && typeof args.pattern !== "string") return {
1578
+ content: "",
1579
+ error: `Invalid optional argument: pattern must be a string (received keys: ${received})`
1580
+ };
1581
+ if (name === "find_files" && args.includeHidden !== void 0 && typeof args.includeHidden !== "boolean") return {
1582
+ content: "",
1583
+ error: `Invalid optional argument: includeHidden must be a boolean (received keys: ${received})`
1584
+ };
1585
+ if (name === "find_files" && args.ignoredDirs !== void 0) {
1586
+ if (!Array.isArray(args.ignoredDirs) || !args.ignoredDirs.every((value) => typeof value === "string")) return {
1360
1587
  content: "",
1361
- error: "Invalid line range: start must be >= 1 and end must be >= start"
1588
+ error: `Invalid optional argument: ignoredDirs must be an array of strings (received keys: ${received})`
1362
1589
  };
1363
1590
  }
1364
1591
  if (name === "web_fetch") try {
@@ -1435,13 +1662,24 @@ async function executeTool(name, args, options) {
1435
1662
  if (invalid) return invalid;
1436
1663
  const stringArgs = args;
1437
1664
  switch (name) {
1438
- case READ_FILE: return readFile(stringArgs.path);
1665
+ case READ_FILE: return readFile(stringArgs.path, {
1666
+ endLine: args.endLine,
1667
+ maxLines: args.maxLines,
1668
+ startLine: args.startLine
1669
+ });
1439
1670
  case WRITE_FILE: return writeFile(stringArgs.path, stringArgs.content);
1440
1671
  case EDIT_FILE: return editFile(stringArgs.path, stringArgs.oldText, stringArgs.newText);
1672
+ case CREATE_DIRECTORY: return createDirectory(stringArgs.path);
1673
+ case RENAME_PATH: return renamePath(stringArgs.from, stringArgs.to);
1674
+ case DELETE_PATH: return deletePath(stringArgs.path, args.recursive);
1441
1675
  case RUN_SHELL: return runShell(stringArgs.command);
1442
1676
  case LIST_DIR: return listDir(stringArgs.path);
1677
+ case FIND_FILES: return findFiles(stringArgs.path, {
1678
+ ignoredDirs: args.ignoredDirs,
1679
+ includeHidden: args.includeHidden,
1680
+ pattern: stringArgs.pattern
1681
+ });
1443
1682
  case GREP_SEARCH: return await grepSearch(stringArgs.pattern, stringArgs.path);
1444
- case VIEW_RANGE: return viewRange(stringArgs.path, args.start, args.end);
1445
1683
  case WEB_SEARCH: return await webSearch(stringArgs.query);
1446
1684
  case WEB_FETCH: return await webFetch(stringArgs.url);
1447
1685
  // v8 ignore next 2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-ollama",
3
- "version": "0.23.2",
3
+ "version": "0.24.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",