@zvec/zvec-grep 0.1.4 → 0.1.5

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 (135) hide show
  1. package/README.md +127 -42
  2. package/README_CN.md +124 -42
  3. package/dist/cli/args.d.ts +3 -0
  4. package/dist/cli/args.js +490 -192
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/commands.d.ts +3 -1
  7. package/dist/cli/commands.js +590 -56
  8. package/dist/cli/commands.js.map +1 -1
  9. package/dist/cli/errors.js +6 -5
  10. package/dist/cli/errors.js.map +1 -1
  11. package/dist/cli/format/context.js +33 -29
  12. package/dist/cli/format/context.js.map +1 -1
  13. package/dist/cli/format/debug.js +12 -14
  14. package/dist/cli/format/debug.js.map +1 -1
  15. package/dist/cli/format/highlight.js.map +1 -1
  16. package/dist/cli/format/progress.js +8 -3
  17. package/dist/cli/format/progress.js.map +1 -1
  18. package/dist/cli/format/range.js.map +1 -1
  19. package/dist/cli/format/status.js +59 -26
  20. package/dist/cli/format/status.js.map +1 -1
  21. package/dist/cli/help.d.ts +1 -1
  22. package/dist/cli/help.js +170 -85
  23. package/dist/cli/help.js.map +1 -1
  24. package/dist/cli/index.js +4 -4
  25. package/dist/cli/index.js.map +1 -1
  26. package/dist/cli/mcp.d.ts +2 -0
  27. package/dist/cli/mcp.js +354 -0
  28. package/dist/cli/mcp.js.map +1 -0
  29. package/dist/cli/types.d.ts +22 -9
  30. package/dist/cli/types.js.map +1 -1
  31. package/dist/cli/version.js +3 -1
  32. package/dist/cli/version.js.map +1 -1
  33. package/dist/engine/collection/index.js +15 -15
  34. package/dist/engine/collection/index.js.map +1 -1
  35. package/dist/engine/config.d.ts +28 -0
  36. package/dist/engine/config.js +195 -0
  37. package/dist/engine/config.js.map +1 -0
  38. package/dist/engine/errors/details.js.map +1 -1
  39. package/dist/engine/errors/index.js.map +1 -1
  40. package/dist/engine/extraction/code/adapter.js.map +1 -1
  41. package/dist/engine/extraction/code/extractor.js +87 -73
  42. package/dist/engine/extraction/code/extractor.js.map +1 -1
  43. package/dist/engine/extraction/code/families/c-family.js +12 -6
  44. package/dist/engine/extraction/code/families/c-family.js.map +1 -1
  45. package/dist/engine/extraction/code/families/js-ts.js +24 -18
  46. package/dist/engine/extraction/code/families/js-ts.js.map +1 -1
  47. package/dist/engine/extraction/code/families/metadata.js +11 -8
  48. package/dist/engine/extraction/code/families/metadata.js.map +1 -1
  49. package/dist/engine/extraction/code/families/name-field.js +5 -5
  50. package/dist/engine/extraction/code/families/name-field.js.map +1 -1
  51. package/dist/engine/extraction/code/languages/c.js +1 -4
  52. package/dist/engine/extraction/code/languages/c.js.map +1 -1
  53. package/dist/engine/extraction/code/languages/cpp.js.map +1 -1
  54. package/dist/engine/extraction/code/languages/go.js +1 -3
  55. package/dist/engine/extraction/code/languages/go.js.map +1 -1
  56. package/dist/engine/extraction/code/languages/java.js.map +1 -1
  57. package/dist/engine/extraction/code/languages/javascript.js +1 -3
  58. package/dist/engine/extraction/code/languages/javascript.js.map +1 -1
  59. package/dist/engine/extraction/code/languages/python.js +5 -6
  60. package/dist/engine/extraction/code/languages/python.js.map +1 -1
  61. package/dist/engine/extraction/code/languages/rust.js +1 -5
  62. package/dist/engine/extraction/code/languages/rust.js.map +1 -1
  63. package/dist/engine/extraction/code/languages/typescript.js.map +1 -1
  64. package/dist/engine/extraction/ids.js.map +1 -1
  65. package/dist/engine/extraction/index.js +3 -3
  66. package/dist/engine/extraction/index.js.map +1 -1
  67. package/dist/engine/extraction/markdown/extractor.js +4 -2
  68. package/dist/engine/extraction/markdown/extractor.js.map +1 -1
  69. package/dist/engine/extraction/tree-sitter/grammar.js.map +1 -1
  70. package/dist/engine/extraction/tree-sitter/nodes.js +2 -1
  71. package/dist/engine/extraction/tree-sitter/nodes.js.map +1 -1
  72. package/dist/engine/extraction/tree-sitter/parser.js.map +1 -1
  73. package/dist/engine/files/file-type.js.map +1 -1
  74. package/dist/engine/models/catalog.js +7 -23
  75. package/dist/engine/models/catalog.js.map +1 -1
  76. package/dist/engine/models/embeddings.js +3 -3
  77. package/dist/engine/models/embeddings.js.map +1 -1
  78. package/dist/engine/models/factory.js.map +1 -1
  79. package/dist/engine/models/index.d.ts +1 -1
  80. package/dist/engine/models/index.js.map +1 -1
  81. package/dist/engine/models/providers/index.d.ts +1 -1
  82. package/dist/engine/models/providers/index.js +1 -1
  83. package/dist/engine/models/providers/index.js.map +1 -1
  84. package/dist/engine/models/providers/llama-cpp/embedding.js +49 -18
  85. package/dist/engine/models/providers/llama-cpp/embedding.js.map +1 -1
  86. package/dist/engine/models/providers/qwen/embedding.js +16 -11
  87. package/dist/engine/models/providers/qwen/embedding.js.map +1 -1
  88. package/dist/engine/models/providers/qwen/index.d.ts +1 -1
  89. package/dist/engine/models/providers/qwen/index.js +1 -1
  90. package/dist/engine/models/providers/qwen/index.js.map +1 -1
  91. package/dist/engine/pipeline/indexing/index.js +46 -33
  92. package/dist/engine/pipeline/indexing/index.js.map +1 -1
  93. package/dist/engine/pipeline/indexing/root-paths.js +10 -13
  94. package/dist/engine/pipeline/indexing/root-paths.js.map +1 -1
  95. package/dist/engine/pipeline/indexing/scanner/index.js +91 -43
  96. package/dist/engine/pipeline/indexing/scanner/index.js.map +1 -1
  97. package/dist/engine/pipeline/search/index.js +68 -24
  98. package/dist/engine/pipeline/search/index.js.map +1 -1
  99. package/dist/engine/service/index.d.ts +1 -1
  100. package/dist/engine/service/lexical.d.ts +14 -15
  101. package/dist/engine/service/lexical.js +78 -119
  102. package/dist/engine/service/lexical.js.map +1 -1
  103. package/dist/engine/service/root.js +2 -2
  104. package/dist/engine/service/root.js.map +1 -1
  105. package/dist/engine/service/structure-enrichment.js +17 -11
  106. package/dist/engine/service/structure-enrichment.js.map +1 -1
  107. package/dist/engine/service/types.d.ts +39 -8
  108. package/dist/engine/service/zvec-grep.js +303 -160
  109. package/dist/engine/service/zvec-grep.js.map +1 -1
  110. package/dist/engine/storage/index.js.map +1 -1
  111. package/dist/engine/storage/zvec.js +15 -11
  112. package/dist/engine/storage/zvec.js.map +1 -1
  113. package/dist/engine/types.d.ts +14 -0
  114. package/dist/engine/types.js.map +1 -1
  115. package/dist/engine/utils/file-selection.d.ts +10 -0
  116. package/dist/engine/utils/file-selection.js +120 -0
  117. package/dist/engine/utils/file-selection.js.map +1 -0
  118. package/dist/engine/utils/glob.d.ts +3 -0
  119. package/dist/engine/utils/glob.js +116 -18
  120. package/dist/engine/utils/glob.js.map +1 -1
  121. package/dist/engine/utils/hash.js.map +1 -1
  122. package/dist/engine/utils/json.d.ts +6 -2
  123. package/dist/engine/utils/json.js +50 -12
  124. package/dist/engine/utils/json.js.map +1 -1
  125. package/dist/engine/utils/lock.js +6 -6
  126. package/dist/engine/utils/lock.js.map +1 -1
  127. package/dist/engine/utils/path.js +2 -2
  128. package/dist/engine/utils/path.js.map +1 -1
  129. package/dist/engine/utils/timing.js.map +1 -1
  130. package/dist/index.d.ts +3 -3
  131. package/dist/index.js +2 -2
  132. package/dist/index.js.map +1 -1
  133. package/package.json +34 -7
  134. package/skills/zvec-grep/SKILL.md +58 -48
  135. package/skills/zvec-grep/agents/openai.yaml +1 -1
@@ -1,35 +1,219 @@
1
- import { resolve } from "node:path";
1
+ import { randomUUID } from "node:crypto";
2
+ import { chmod, lstat, mkdir, open, readFile, readlink, rename, stat, unlink, } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { dirname, resolve } from "node:path";
5
+ import { createInterface } from "node:readline/promises";
2
6
  import { createZvecGrep, } from "../index.js";
7
+ import { globalConfigPath, updateGlobalConfigFromExplicitOptions, } from "../engine/config.js";
3
8
  import { findNearestAnonymousWorkspace } from "../engine/service/root.js";
4
- import { contextWarningLines, printAgentContextResult, printHumanContextResult } from "./format/context.js";
9
+ import { contextWarningLines, printAgentContextResult, printHumanContextResult, } from "./format/context.js";
5
10
  import { printDebug } from "./format/debug.js";
6
11
  import { createIndexProgressReporter } from "./format/progress.js";
7
12
  import { printAnonymousInfo, printCollectionInfo, printCollectionList, printCollectionRemoveResult, printIndexPathFilterTip, printIndexResult, } from "./format/status.js";
13
+ const AGENT_INSTALLERS = [
14
+ {
15
+ id: "codex",
16
+ label: "Codex",
17
+ description: "configure zvec-grep MCP and Codex guidance",
18
+ install: installCodexIntegration,
19
+ uninstall: uninstallCodexIntegration,
20
+ },
21
+ ];
22
+ const ZVEC_GREP_CONFIG_START = "# ZVEC_GREP_START";
23
+ const ZVEC_GREP_CONFIG_END = "# ZVEC_GREP_END";
24
+ const ZVEC_GREP_AGENTS_START = "<!-- ZVEC_GREP_START -->";
25
+ const ZVEC_GREP_AGENTS_END = "<!-- ZVEC_GREP_END -->";
26
+ const DEFAULT_MCP_TOOL_TIMEOUT_SECONDS = 600;
8
27
  export async function runParsedCommand(parsed) {
9
- if (parsed.options.index) {
10
- await runIndex(parsed);
11
- return;
28
+ switch (parsed.command) {
29
+ case "query":
30
+ await runQuery(parsed);
31
+ return;
32
+ case "index":
33
+ await runIndex(parsed);
34
+ return;
35
+ case "status":
36
+ await runStatus(parsed);
37
+ return;
38
+ case "collections":
39
+ await runCollections(parsed);
40
+ return;
41
+ case "install":
42
+ await runInstall(parsed);
43
+ return;
44
+ case "uninstall":
45
+ await runUninstall(parsed);
46
+ return;
47
+ case "serve":
48
+ await runServe(parsed);
49
+ return;
50
+ case "help":
51
+ case "version":
52
+ throw new Error(`${parsed.command} must be handled before dispatch`);
12
53
  }
13
- if (parsed.options.disableIndex) {
14
- await runDisableIndex(parsed);
54
+ }
55
+ async function runInstall(parsed) {
56
+ const installers = await resolveInstallers(parsed, "install");
57
+ if (installers.length === 0) {
58
+ console.log("No agents selected.");
15
59
  return;
16
60
  }
17
- if (parsed.options.status) {
18
- await runStatus(parsed);
19
- return;
61
+ console.log(`Installing zvec-grep for: ${installers.map((installer) => installer.label).join(", ")}`);
62
+ for (const installer of installers) {
63
+ const result = await installer.install({
64
+ force: parsed.options.force === true,
65
+ mcpToolTimeoutSeconds: parsed.options.installMcpToolTimeoutSeconds ??
66
+ DEFAULT_MCP_TOOL_TIMEOUT_SECONDS,
67
+ });
68
+ console.log(`Installed ${installer.label}:`);
69
+ for (const file of result.files) {
70
+ console.log(` ${file}`);
71
+ }
20
72
  }
21
- if (parsed.options.collections) {
22
- await runCollections(parsed);
73
+ console.log("Restart the selected agent or start a new session to pick up the integration.");
74
+ if (installers.some((installer) => installer.id === "codex")) {
75
+ console.log("Codex MCP server: zg serve --mcp");
76
+ }
77
+ }
78
+ async function runUninstall(parsed) {
79
+ const installers = await resolveInstallers(parsed, "uninstall");
80
+ if (installers.length === 0) {
81
+ console.log("No agents selected.");
23
82
  return;
24
83
  }
25
- await runQuery(parsed);
84
+ console.log(`Removing zvec-grep from: ${installers.map((installer) => installer.label).join(", ")}`);
85
+ for (const installer of installers) {
86
+ const result = await installer.uninstall();
87
+ console.log(`Removed ${installer.label} integration:`);
88
+ for (const file of result.files) {
89
+ console.log(` ${file}`);
90
+ }
91
+ }
92
+ console.log("Restart the selected agent or start a new session to apply the change.");
93
+ }
94
+ async function installCodexIntegration(options) {
95
+ const codexHome = resolveCodexHome();
96
+ const configPath = resolve(codexHome, "config.toml");
97
+ const agentsPath = resolve(codexHome, "AGENTS.md");
98
+ await writeMarkedFile({
99
+ path: configPath,
100
+ startMarker: ZVEC_GREP_CONFIG_START,
101
+ endMarker: ZVEC_GREP_CONFIG_END,
102
+ block: codexConfigBlock(options.mcpToolTimeoutSeconds),
103
+ force: options.force,
104
+ hasConflict: hasCodexMcpServerConfig,
105
+ conflictMessage: `Existing [mcp_servers.zvec_grep] found in ${configPath}. Re-run with --force after removing or moving that table into the zvec-grep managed block.`,
106
+ removeConflict: removeCodexMcpServerConfig,
107
+ });
108
+ await writeMarkedFile({
109
+ path: agentsPath,
110
+ startMarker: ZVEC_GREP_AGENTS_START,
111
+ endMarker: ZVEC_GREP_AGENTS_END,
112
+ block: codexAgentsBlock(),
113
+ force: true,
114
+ });
115
+ return { files: [configPath, agentsPath] };
116
+ }
117
+ async function uninstallCodexIntegration() {
118
+ const codexHome = resolveCodexHome();
119
+ const configPath = resolve(codexHome, "config.toml");
120
+ const agentsPath = resolve(codexHome, "AGENTS.md");
121
+ await removeMarkedFile({
122
+ path: configPath,
123
+ startMarker: ZVEC_GREP_CONFIG_START,
124
+ endMarker: ZVEC_GREP_CONFIG_END,
125
+ });
126
+ await removeMarkedFile({
127
+ path: agentsPath,
128
+ startMarker: ZVEC_GREP_AGENTS_START,
129
+ endMarker: ZVEC_GREP_AGENTS_END,
130
+ });
131
+ return { files: [configPath, agentsPath] };
132
+ }
133
+ async function resolveInstallers(parsed, action) {
134
+ const targetTokens = [
135
+ ...(parsed.options.installTargets ?? []),
136
+ ...parsed.positionals,
137
+ ];
138
+ if (targetTokens.length > 0) {
139
+ return installersFromTokens(targetTokens);
140
+ }
141
+ if (parsed.options.yes === true ||
142
+ !process.stdin.isTTY ||
143
+ !process.stdout.isTTY) {
144
+ return installersFromTokens(["auto"]);
145
+ }
146
+ return promptInstallers(action);
147
+ }
148
+ async function promptInstallers(action) {
149
+ console.log(`Select agents whose zvec-grep integration should be ${action === "install" ? "installed" : "removed"}:`);
150
+ AGENT_INSTALLERS.forEach((installer, index) => {
151
+ console.log(` ${index + 1}. ${installer.label} - ${installer.description}`);
152
+ });
153
+ console.log(" all. All supported agents");
154
+ console.log(" none. Cancel");
155
+ const readline = createInterface({
156
+ input: process.stdin,
157
+ output: process.stdout,
158
+ });
159
+ try {
160
+ const answer = await readline.question("Agents [codex]: ");
161
+ const normalized = answer.trim();
162
+ return installersFromTokens(normalized.length > 0 ? splitTargetTokens(normalized) : ["codex"]);
163
+ }
164
+ finally {
165
+ readline.close();
166
+ }
167
+ }
168
+ function installersFromTokens(tokens) {
169
+ const normalized = tokens.flatMap(splitTargetTokens);
170
+ if (normalized.length === 0) {
171
+ return installersFromTokens(["auto"]);
172
+ }
173
+ const selected = new Map();
174
+ for (const token of normalized) {
175
+ const lower = token.toLowerCase();
176
+ if (lower === "none") {
177
+ return [];
178
+ }
179
+ if (lower === "auto" || lower === "all") {
180
+ for (const installer of AGENT_INSTALLERS) {
181
+ selected.set(installer.id, installer);
182
+ }
183
+ continue;
184
+ }
185
+ const numbered = /^\d+$/.test(lower) ? Number(lower) : Number.NaN;
186
+ if (Number.isInteger(numbered) &&
187
+ numbered >= 1 &&
188
+ numbered <= AGENT_INSTALLERS.length) {
189
+ const installer = AGENT_INSTALLERS[numbered - 1];
190
+ selected.set(installer.id, installer);
191
+ continue;
192
+ }
193
+ const installer = AGENT_INSTALLERS.find((candidate) => candidate.id === lower || candidate.label.toLowerCase() === lower);
194
+ if (!installer) {
195
+ throw new Error(`Unknown install target: ${token}`);
196
+ }
197
+ selected.set(installer.id, installer);
198
+ }
199
+ return [...selected.values()];
200
+ }
201
+ function splitTargetTokens(value) {
202
+ return value
203
+ .split(/[,\s]+/)
204
+ .map((target) => target.trim())
205
+ .filter((target) => target.length > 0);
26
206
  }
27
207
  async function runIndex(parsed) {
28
208
  const explicitRoot = parsed.positionals.length > 0;
29
209
  const root = resolveIndexRoot(parsed.positionals[0]);
30
210
  const rootPath = indexRootPath(root, parsed.options);
31
211
  if (parsed.positionals.length > 1) {
32
- throw new Error("zg --index accepts at most one root path");
212
+ throw new Error("zg index accepts at most one root path");
213
+ }
214
+ if (parsed.options.drop) {
215
+ await runDropIndex(parsed, rootPath.absolutePath);
216
+ return;
33
217
  }
34
218
  const zvecGrep = await createZvecGrep(createServiceOptions(parsed.options, rootPath.absolutePath));
35
219
  const progress = createIndexProgressReporter();
@@ -40,14 +224,23 @@ async function runIndex(parsed) {
40
224
  rootPaths: explicitRoot ? [rootPath] : undefined,
41
225
  rebuild: parsed.options.rebuild,
42
226
  resetPaths: parsed.options.resetPaths,
43
- includePaths: parsed.options.includePaths,
44
- excludePaths: parsed.options.excludePaths,
227
+ globs: parsed.options.globs,
228
+ insensitiveGlobs: parsed.options.insensitiveGlobs,
229
+ fileTypes: parsed.options.fileTypes,
230
+ excludedFileTypes: parsed.options.excludedFileTypes,
231
+ hidden: parsed.options.hidden,
232
+ noIgnore: parsed.options.noIgnore,
233
+ ignoreFiles: parsed.options.ignoreFiles,
234
+ maxDepth: parsed.options.maxDepth,
235
+ maxFileSizeBytes: parsed.options.maxFileSizeBytes,
236
+ follow: parsed.options.follow,
45
237
  embeddingConcurrency: parsed.options.embeddingConcurrency,
46
238
  onProgress: progress.report,
47
239
  });
48
240
  progress.finish();
49
241
  const info = await zvecGrep.info({ root: rootPath.absolutePath });
50
242
  printIndexResult("Indexed anonymous workspace", result, parsed.options, info.collection?.rootPaths);
243
+ persistExplicitGlobalConfig(parsed.options, info.collection?.embedding?.provider);
51
244
  }
52
245
  catch (error) {
53
246
  progress.finish();
@@ -57,24 +250,50 @@ async function runIndex(parsed) {
57
250
  await zvecGrep.close();
58
251
  }
59
252
  }
60
- async function runDisableIndex(parsed) {
61
- const root = resolveIndexRoot(parsed.positionals[0]);
62
- if (parsed.positionals.length > 1) {
63
- throw new Error("zg --disable-index accepts at most one root path");
253
+ async function runDropIndex(parsed, root) {
254
+ if (!(await confirmIndexDrop(root, parsed.options.yes === true))) {
255
+ console.log("Index drop cancelled.");
256
+ return;
64
257
  }
65
258
  const zvecGrep = await createZvecGrep(createServiceOptions(parsed.options, root));
66
259
  try {
67
- const info = await zvecGrep.disableIndex({ root });
68
- printAnonymousInfo(info, parsed.options);
260
+ const removed = await zvecGrep.dropIndex({ root });
261
+ console.log(removed ? `Dropped index for ${root}` : `No index found for ${root}`);
69
262
  }
70
263
  finally {
71
264
  await zvecGrep.close();
72
265
  }
73
266
  }
267
+ async function confirmIndexDrop(root, accepted) {
268
+ if (accepted) {
269
+ return true;
270
+ }
271
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
272
+ throw new Error("zg index --drop requires --yes in a non-interactive shell");
273
+ }
274
+ const readline = createInterface({
275
+ input: process.stdin,
276
+ output: process.stdout,
277
+ });
278
+ try {
279
+ const answer = await readline.question(`Drop the index for ${root}? [y/N] `);
280
+ return ["y", "yes"].includes(answer.trim().toLowerCase());
281
+ }
282
+ finally {
283
+ readline.close();
284
+ }
285
+ }
286
+ async function runServe(parsed) {
287
+ if (parsed.positionals.length > 0) {
288
+ throw new Error("zg serve --mcp does not accept positional arguments");
289
+ }
290
+ const { runMcpServer } = await import("./mcp.js");
291
+ await runMcpServer(createServiceOptions(parsed.options, process.cwd()));
292
+ }
74
293
  async function runStatus(parsed) {
75
294
  const root = parsed.positionals[0] ?? process.cwd();
76
295
  if (parsed.positionals.length > 1) {
77
- throw new Error("zg --status accepts at most one root path");
296
+ throw new Error("zg status accepts at most one root path");
78
297
  }
79
298
  const zvecGrep = await createZvecGrep(createServiceOptions(parsed.options, root));
80
299
  try {
@@ -87,21 +306,26 @@ async function runStatus(parsed) {
87
306
  }
88
307
  async function runCollections(parsed) {
89
308
  const [action = "list", name, root] = parsed.positionals;
309
+ validateCollectionArguments(action, parsed.positionals);
310
+ const indexOption = collectionIndexOption(parsed.options);
311
+ if (action !== "index" && indexOption) {
312
+ throw new Error(`${indexOption} can only be used with zg collections index`);
313
+ }
90
314
  const zvecGrep = await createZvecGrep(createServiceOptions(parsed.options, undefined));
91
315
  try {
92
316
  if (action === "list") {
93
317
  if (parsed.options.resetPaths) {
94
- throw new Error("--reset-paths can only be used with --collections index");
318
+ throw new Error("--reset-paths can only be used with zg collections index");
95
319
  }
96
320
  printCollectionList(await zvecGrep.collections.list(), parsed.options);
97
321
  return;
98
322
  }
99
323
  if (action === "info") {
100
324
  if (parsed.options.resetPaths) {
101
- throw new Error("--reset-paths can only be used with --collections index");
325
+ throw new Error("--reset-paths can only be used with zg collections index");
102
326
  }
103
327
  if (!name) {
104
- throw new Error("zg --collections info requires <name>");
328
+ throw new Error("zg collections info requires <name>");
105
329
  }
106
330
  const [info, status] = await Promise.all([
107
331
  zvecGrep.collections.info(name),
@@ -115,7 +339,7 @@ async function runCollections(parsed) {
115
339
  }
116
340
  if (action === "index") {
117
341
  if (!name) {
118
- throw new Error("zg --collections index requires <name>");
342
+ throw new Error("zg collections index requires <name>");
119
343
  }
120
344
  const explicitRoot = root !== undefined;
121
345
  const rootPath = indexRootPath(root ?? process.cwd(), parsed.options);
@@ -125,14 +349,23 @@ async function runCollections(parsed) {
125
349
  const result = await zvecGrep.collections.index(name, rootPaths, {
126
350
  rebuild: parsed.options.rebuild,
127
351
  resetPaths: parsed.options.resetPaths,
128
- includePaths: parsed.options.includePaths,
129
- excludePaths: parsed.options.excludePaths,
352
+ globs: parsed.options.globs,
353
+ insensitiveGlobs: parsed.options.insensitiveGlobs,
354
+ fileTypes: parsed.options.fileTypes,
355
+ excludedFileTypes: parsed.options.excludedFileTypes,
356
+ hidden: parsed.options.hidden,
357
+ noIgnore: parsed.options.noIgnore,
358
+ ignoreFiles: parsed.options.ignoreFiles,
359
+ maxDepth: parsed.options.maxDepth,
360
+ maxFileSizeBytes: parsed.options.maxFileSizeBytes,
361
+ follow: parsed.options.follow,
130
362
  embeddingConcurrency: parsed.options.embeddingConcurrency,
131
363
  onProgress: progress.report,
132
364
  });
133
365
  progress.finish();
134
366
  const info = await zvecGrep.collections.info(name);
135
367
  printIndexResult(`Indexed collection ${name}`, result, parsed.options, info?.rootPaths);
368
+ persistExplicitGlobalConfig(parsed.options, info?.embedding?.provider);
136
369
  }
137
370
  catch (error) {
138
371
  progress.finish();
@@ -142,17 +375,17 @@ async function runCollections(parsed) {
142
375
  }
143
376
  if (action === "remove") {
144
377
  if (parsed.options.resetPaths) {
145
- throw new Error("--reset-paths can only be used with --collections index");
378
+ throw new Error("--reset-paths can only be used with zg collections index");
146
379
  }
147
380
  if (!name) {
148
- throw new Error("zg --collections remove requires <name>");
381
+ throw new Error("zg collections remove requires <name>");
149
382
  }
150
383
  const removed = await zvecGrep.collections.remove(name);
151
384
  printCollectionRemoveResult(name, removed, parsed.options);
152
385
  return;
153
386
  }
154
387
  if (parsed.options.resetPaths) {
155
- throw new Error("--reset-paths can only be used with --collections index");
388
+ throw new Error("--reset-paths can only be used with zg collections index");
156
389
  }
157
390
  throw new Error(`Unknown collections action: ${action}`);
158
391
  }
@@ -160,19 +393,67 @@ async function runCollections(parsed) {
160
393
  await zvecGrep.close();
161
394
  }
162
395
  }
396
+ function validateCollectionArguments(action, positionals) {
397
+ if (action === "list") {
398
+ if (positionals.length > 1) {
399
+ throw new Error("zg collections list does not accept arguments");
400
+ }
401
+ return;
402
+ }
403
+ if (action === "info" || action === "remove") {
404
+ if (positionals.length > 2) {
405
+ throw new Error(`zg collections ${action} accepts only <name>`);
406
+ }
407
+ return;
408
+ }
409
+ if (action === "index") {
410
+ if (positionals.length > 3) {
411
+ throw new Error("zg collections index accepts only <name> and [root]");
412
+ }
413
+ return;
414
+ }
415
+ throw new Error(`Unknown collections action: ${action}`);
416
+ }
417
+ function collectionIndexOption(options) {
418
+ const candidates = [
419
+ [options.rebuild, "--rebuild"],
420
+ [options.resetPaths, "--reset-paths"],
421
+ [options.embedding, "--embedding"],
422
+ [options.modelCacheDir, "--model-cache"],
423
+ [options.llamaGpu, "--llama-gpu"],
424
+ [options.embeddingParallelism, "--embedding-parallelism"],
425
+ [options.apiKey, "--api-key"],
426
+ [options.endpoint, "--endpoint"],
427
+ [options.embeddingConcurrency, "--embedding-concurrency"],
428
+ [options.globs?.length, "--glob"],
429
+ [options.insensitiveGlobs?.length, "--iglob"],
430
+ [options.fileTypes?.length, "--type"],
431
+ [options.excludedFileTypes?.length, "--type-not"],
432
+ [options.hidden, "--hidden"],
433
+ [options.noIgnore, "--no-ignore"],
434
+ [options.ignoreFiles?.length, "--ignore-file"],
435
+ [options.maxDepth, "--max-depth"],
436
+ [options.maxFileSizeBytes, "--max-filesize"],
437
+ [options.follow, "--follow"],
438
+ ];
439
+ return candidates.find(([value]) => Array.isArray(value) ? value.length > 0 : value !== undefined)?.[1];
440
+ }
163
441
  async function runQuery(parsed) {
164
- const rgInput = parsed.options.rg
165
- ? normalizeRgInput(parsed)
166
- : undefined;
442
+ const rgInput = parsed.options.rg ? normalizeRgInput(parsed) : undefined;
167
443
  const commandOptions = rgInput?.options ?? parsed.options;
168
- const queries = (rgInput?.queries ?? parsed.positionals)
444
+ const queries = (rgInput?.queries ?? [
445
+ ...parsed.positionals,
446
+ ...(parsed.options.hybridQueries ?? []),
447
+ ])
169
448
  .map((query) => query.trim())
170
449
  .filter((query) => query.length > 0);
171
450
  const routes = parsed.options.routes ?? [];
172
- if (queries.length === 0 && routes.length === 0) {
451
+ if (queries.length === 0 &&
452
+ routes.length === 0 &&
453
+ (parsed.options.rgOptions?.patternFiles?.length ?? 0) === 0) {
173
454
  throw new Error(parsed.options.rg
174
- ? "zg --rg requires a pattern. Use --help for examples."
175
- : "zg query requires text or --fts/--vector routes. Use --help for examples.");
455
+ ? "zg query --rg requires a pattern. Use zg help query for examples."
456
+ : "zg query requires text or --hybrid/--fts/--vector routes. Use zg help query for examples.");
176
457
  }
177
458
  const zvecGrep = await createZvecGrep(createServiceOptions(commandOptions, undefined));
178
459
  const progress = createIndexProgressReporter();
@@ -213,15 +494,23 @@ function contextOptions(options, queries, onAutoUpdateProgress) {
213
494
  rgOptions: options.rgOptions,
214
495
  rgPaths: options.rgPaths,
215
496
  routes: options.routes,
497
+ fuse: options.fuse,
216
498
  collection: options.collection,
217
499
  limit: options.limit,
218
- fallback: "disabled",
219
500
  autoUpdate: !options.noAutoUpdate,
220
501
  onAutoUpdateProgress,
221
502
  trace: options.trace,
222
503
  preferSymbol: options.preferSymbol,
223
- includePaths: options.includePaths,
224
- excludePaths: options.excludePaths,
504
+ globs: options.globs,
505
+ insensitiveGlobs: options.insensitiveGlobs,
506
+ fileTypes: options.fileTypes,
507
+ excludedFileTypes: options.excludedFileTypes,
508
+ hidden: options.hidden,
509
+ noIgnore: options.noIgnore,
510
+ ignoreFiles: options.ignoreFiles,
511
+ maxDepth: options.maxDepth,
512
+ maxFileSizeBytes: options.maxFileSizeBytes,
513
+ follow: options.follow,
225
514
  modifiedAfter: options.modifiedAfter,
226
515
  modifiedBefore: options.modifiedBefore,
227
516
  symbolTypes: options.symbolTypes,
@@ -230,10 +519,11 @@ function contextOptions(options, queries, onAutoUpdateProgress) {
230
519
  }
231
520
  function normalizeRgInput(parsed) {
232
521
  const explicitPatterns = parsed.options.rgOptions?.patterns ?? [];
233
- const queries = explicitPatterns.length > 0
522
+ const hasPatternFiles = (parsed.options.rgOptions?.patternFiles?.length ?? 0) > 0;
523
+ const queries = explicitPatterns.length > 0 || hasPatternFiles
234
524
  ? explicitPatterns
235
525
  : parsed.positionals.slice(0, 1);
236
- const paths = explicitPatterns.length > 0
526
+ const paths = explicitPatterns.length > 0 || hasPatternFiles
237
527
  ? parsed.positionals
238
528
  : parsed.positionals.slice(1);
239
529
  return {
@@ -244,12 +534,12 @@ function normalizeRgInput(parsed) {
244
534
  },
245
535
  };
246
536
  }
247
- function createServiceOptions(options, root) {
537
+ export function createServiceOptions(options, root) {
248
538
  const embedding = options.embedding ?? process.env.ZVEC_GREP_EMBEDDING;
249
- const apiKey = options.apiKey
250
- ?? process.env.ZVEC_GREP_API_KEY
251
- ?? process.env.DASHSCOPE_API_KEY
252
- ?? process.env.QWEN_API_KEY;
539
+ const apiKey = options.apiKey ??
540
+ process.env.ZVEC_GREP_API_KEY ??
541
+ process.env.DASHSCOPE_API_KEY ??
542
+ process.env.QWEN_API_KEY;
253
543
  const endpoint = options.endpoint ?? process.env.ZVEC_GREP_ENDPOINT;
254
544
  return {
255
545
  root,
@@ -259,15 +549,25 @@ function createServiceOptions(options, root) {
259
549
  endpoint,
260
550
  modelCacheDir: options.modelCacheDir ?? process.env.ZVEC_GREP_MODEL_CACHE,
261
551
  llamaGpu: options.llamaGpu ?? parseEnvLlamaGpu(process.env.ZVEC_GREP_LLAMA_GPU),
262
- embeddingParallelism: options.embeddingParallelism ?? parseEnvPositiveInteger(process.env.ZVEC_GREP_EMBED_PARALLELISM),
552
+ embeddingParallelism: options.embeddingParallelism ??
553
+ parseEnvPositiveInteger(process.env.ZVEC_GREP_EMBED_PARALLELISM),
263
554
  };
264
555
  }
556
+ function persistExplicitGlobalConfig(options, indexedProvider) {
557
+ if (!updateGlobalConfigFromExplicitOptions(options, indexedProvider)) {
558
+ return;
559
+ }
560
+ console.log(`Global config: ${globalConfigPath()}`);
561
+ }
265
562
  function parseEnvLlamaGpu(value) {
266
563
  const normalized = value?.trim().toLowerCase() ?? "";
267
564
  if (!normalized) {
268
565
  return undefined;
269
566
  }
270
- if (normalized === "auto" || normalized === "metal" || normalized === "vulkan" || normalized === "cuda") {
567
+ if (normalized === "auto" ||
568
+ normalized === "metal" ||
569
+ normalized === "vulkan" ||
570
+ normalized === "cuda") {
271
571
  return normalized;
272
572
  }
273
573
  if (["false", "off", "none", "disable", "disabled", "0"].includes(normalized)) {
@@ -277,11 +577,237 @@ function parseEnvLlamaGpu(value) {
277
577
  }
278
578
  function parseEnvPositiveInteger(value) {
279
579
  const normalized = value?.trim() ?? "";
280
- if (!normalized) {
580
+ if (!/^\d+$/.test(normalized)) {
281
581
  return undefined;
282
582
  }
283
- const parsed = Number.parseInt(normalized, 10);
284
- return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
583
+ const parsed = Number(normalized);
584
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
585
+ }
586
+ function resolveCodexHome() {
587
+ return resolve(process.env.CODEX_HOME ?? resolve(homedir(), ".codex"));
588
+ }
589
+ async function writeMarkedFile(options) {
590
+ let existing = await readTextFileIfExists(options.path);
591
+ const next = replaceMarkedBlock(existing, options.startMarker, options.endMarker, options.block);
592
+ if (next === null) {
593
+ existing = removeOrphanedMarkers(existing, options.startMarker, options.endMarker);
594
+ }
595
+ if (next === null && options.hasConflict?.(existing)) {
596
+ if (!options.force) {
597
+ throw new Error(options.conflictMessage ??
598
+ `Existing unmanaged configuration found in ${options.path}`);
599
+ }
600
+ existing = options.removeConflict
601
+ ? options.removeConflict(existing)
602
+ : existing;
603
+ }
604
+ await writeTextFileAtomic(options.path, next ?? appendMarkedBlock(existing, options.block));
605
+ }
606
+ async function removeMarkedFile(options) {
607
+ const existing = await readTextFileIfExists(options.path);
608
+ if (!existing) {
609
+ return;
610
+ }
611
+ const next = replaceMarkedBlock(existing, options.startMarker, options.endMarker, "") ??
612
+ removeOrphanedMarkers(existing, options.startMarker, options.endMarker);
613
+ if (next === existing) {
614
+ return;
615
+ }
616
+ await writeTextFileAtomic(options.path, next);
617
+ }
618
+ async function writeTextFileAtomic(path, content) {
619
+ const targetPath = await resolveAtomicWriteTarget(path);
620
+ await mkdir(dirname(targetPath), { recursive: true });
621
+ const mode = await fileModeIfExists(targetPath);
622
+ const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;
623
+ let temporaryFile;
624
+ try {
625
+ temporaryFile = await open(temporaryPath, "wx", mode);
626
+ await temporaryFile.writeFile(content, "utf8");
627
+ await temporaryFile.sync();
628
+ await temporaryFile.close();
629
+ temporaryFile = undefined;
630
+ if (mode !== undefined) {
631
+ await chmod(temporaryPath, mode);
632
+ }
633
+ await rename(temporaryPath, targetPath);
634
+ }
635
+ catch (error) {
636
+ await temporaryFile?.close().catch(() => undefined);
637
+ await unlink(temporaryPath).catch(() => undefined);
638
+ throw error;
639
+ }
640
+ }
641
+ async function resolveAtomicWriteTarget(path, visited = new Set()) {
642
+ const absolutePath = resolve(path);
643
+ if (visited.has(absolutePath)) {
644
+ throw new Error(`Cannot atomically write through circular symbolic link: ${path}`);
645
+ }
646
+ try {
647
+ const file = await lstat(absolutePath);
648
+ if (!file.isSymbolicLink()) {
649
+ return absolutePath;
650
+ }
651
+ visited.add(absolutePath);
652
+ const link = await readlink(absolutePath);
653
+ return resolveAtomicWriteTarget(resolve(dirname(absolutePath), link), visited);
654
+ }
655
+ catch (error) {
656
+ if (isNodeError(error) && error.code === "ENOENT") {
657
+ return absolutePath;
658
+ }
659
+ throw error;
660
+ }
661
+ }
662
+ async function fileModeIfExists(path) {
663
+ try {
664
+ return (await stat(path)).mode & 0o777;
665
+ }
666
+ catch (error) {
667
+ if (isNodeError(error) && error.code === "ENOENT") {
668
+ return undefined;
669
+ }
670
+ throw error;
671
+ }
672
+ }
673
+ async function readTextFileIfExists(path) {
674
+ try {
675
+ return await readFile(path, "utf8");
676
+ }
677
+ catch (error) {
678
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
679
+ return "";
680
+ }
681
+ throw error;
682
+ }
683
+ }
684
+ function isNodeError(error) {
685
+ return typeof error === "object" && error !== null && "code" in error;
686
+ }
687
+ function replaceMarkedBlock(existing, startMarker, endMarker, block) {
688
+ const lines = existing.split(/\r?\n/);
689
+ const markerLines = new Set();
690
+ const ranges = [];
691
+ let pendingStart;
692
+ for (const [index, line] of lines.entries()) {
693
+ const trimmed = line.trim();
694
+ if (trimmed === startMarker) {
695
+ markerLines.add(index);
696
+ pendingStart = index;
697
+ continue;
698
+ }
699
+ if (trimmed === endMarker) {
700
+ markerLines.add(index);
701
+ if (pendingStart !== undefined) {
702
+ ranges.push({ start: pendingStart, end: index });
703
+ pendingStart = undefined;
704
+ }
705
+ }
706
+ }
707
+ const first = ranges[0];
708
+ if (!first) {
709
+ return null;
710
+ }
711
+ const before = retainedMarkedLines(lines, 0, first.start, markerLines, [])
712
+ .join("\n")
713
+ .trimEnd();
714
+ const after = retainedMarkedLines(lines, first.end + 1, lines.length, markerLines, ranges.slice(1))
715
+ .join("\n")
716
+ .trim();
717
+ return ([before, block.trim(), after.trim()]
718
+ .filter((part) => part.length > 0)
719
+ .join("\n\n") + "\n");
720
+ }
721
+ function retainedMarkedLines(lines, start, end, markerLines, removedRanges) {
722
+ const retained = [];
723
+ for (let index = start; index < end; index += 1) {
724
+ if (markerLines.has(index)) {
725
+ continue;
726
+ }
727
+ if (removedRanges.some((range) => index >= range.start && index <= range.end)) {
728
+ continue;
729
+ }
730
+ retained.push(lines[index]);
731
+ }
732
+ return retained;
733
+ }
734
+ function removeOrphanedMarkers(existing, startMarker, endMarker) {
735
+ return (existing
736
+ .split(/\r?\n/)
737
+ .filter((line) => {
738
+ const trimmed = line.trim();
739
+ return trimmed !== startMarker && trimmed !== endMarker;
740
+ })
741
+ .join("\n")
742
+ .trimEnd() + "\n");
743
+ }
744
+ function appendMarkedBlock(existing, block) {
745
+ const prefix = existing.trimEnd();
746
+ return `${prefix.length > 0 ? `${prefix}\n\n` : ""}${block.trim()}\n`;
747
+ }
748
+ function hasCodexMcpServerConfig(existing) {
749
+ return existing.split(/\r?\n/).some((line) => {
750
+ const tableName = tomlTableName(line);
751
+ return tableName !== undefined && isCodexMcpServerTableName(tableName);
752
+ });
753
+ }
754
+ function removeCodexMcpServerConfig(existing) {
755
+ const lines = existing.split(/\r?\n/);
756
+ const kept = [];
757
+ let skipping = false;
758
+ for (const line of lines) {
759
+ const tableName = tomlTableName(line);
760
+ if (tableName !== undefined) {
761
+ skipping = isCodexMcpServerTableName(tableName);
762
+ }
763
+ if (!skipping) {
764
+ kept.push(line);
765
+ }
766
+ }
767
+ return kept.join("\n").trimEnd() + "\n";
768
+ }
769
+ function tomlTableName(line) {
770
+ return line.match(/^\s*\[([^\]]+)\]\s*(?:#.*)?$/)?.[1]?.trim();
771
+ }
772
+ function isCodexMcpServerTableName(tableName) {
773
+ return /^(?:mcp_servers|"mcp_servers"|'mcp_servers')\s*\.\s*(?:zvec_grep|"zvec_grep"|'zvec_grep')(?:\s*\.|$)/.test(tableName);
774
+ }
775
+ function codexConfigBlock(mcpToolTimeoutSeconds) {
776
+ return `${ZVEC_GREP_CONFIG_START}
777
+ [mcp_servers.zvec_grep]
778
+ command = "zg"
779
+ args = ["serve", "--mcp"]
780
+ env_vars = [
781
+ "DASHSCOPE_API_KEY",
782
+ "QWEN_API_KEY",
783
+ "ZVEC_GREP_API_KEY",
784
+ "ZVEC_GREP_EMBEDDING",
785
+ "ZVEC_GREP_ENDPOINT",
786
+ "ZVEC_GREP_HOME",
787
+ "ZVEC_GREP_MODEL_CACHE",
788
+ "ZVEC_GREP_LLAMA_GPU",
789
+ "ZVEC_GREP_EMBED_PARALLELISM",
790
+ "NODE_LLAMA_CPP_CMAKE_OPTION_GGML_OPENMP",
791
+ "NODE_LLAMA_CPP_CMAKE_OPTION_GGML_NATIVE",
792
+ "NODE_LLAMA_CPP_CMAKE_OPTION_GGML_CPU_ARM_ARCH"
793
+ ]
794
+ startup_timeout_sec = 20
795
+ tool_timeout_sec = ${mcpToolTimeoutSeconds}
796
+ default_tools_approval_mode = "auto"
797
+ ${ZVEC_GREP_CONFIG_END}`;
798
+ }
799
+ function codexAgentsBlock() {
800
+ return `${ZVEC_GREP_AGENTS_START}
801
+ ## zvec-grep
802
+
803
+ Use zvec-grep before grep, rg, or broad file reads when you need to understand or locate code.
804
+
805
+ - **MCP tools**: Use \`zvec_grep_search\` for indexed semantic/lexical code search and \`zvec_grep_rg\` for explicit no-index lexical search.
806
+ - **Indexing and status**: These are CLI-only operations. If an index is missing, use \`zvec_grep_rg\` and mention \`zg index\` as the opt-in setup path; use \`zg status\` when status inspection is needed.
807
+ - **Shell fallback**: If the MCP server is unavailable, use \`zg status\`, \`zg query "<query>"\`, and \`zg query --rg "<pattern>"\`.
808
+
809
+ Prefer focused -g/--glob and -t/--type filters, and exclude dependencies, generated output, caches, build artifacts, and logs unless the task is about those files.
810
+ ${ZVEC_GREP_AGENTS_END}`;
285
811
  }
286
812
  function resolveIndexRoot(root) {
287
813
  if (root !== undefined) {
@@ -293,8 +819,16 @@ function indexRootPath(path, options) {
293
819
  return {
294
820
  absolutePath: resolve(path),
295
821
  recursive: true,
296
- include: options.includePaths,
297
- exclude: options.excludePaths,
822
+ globs: options.globs,
823
+ insensitiveGlobs: options.insensitiveGlobs,
824
+ fileTypes: options.fileTypes,
825
+ excludedFileTypes: options.excludedFileTypes,
826
+ hidden: options.hidden,
827
+ noIgnore: options.noIgnore,
828
+ ignoreFiles: options.ignoreFiles,
829
+ maxDepth: options.maxDepth,
830
+ maxFileSizeBytes: options.maxFileSizeBytes,
831
+ follow: options.follow,
298
832
  };
299
833
  }
300
834
  //# sourceMappingURL=commands.js.map