@angri450/zvec-grep 0.2.2
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.
- package/LICENSE +201 -0
- package/README.md +319 -0
- package/README_CN.md +297 -0
- package/dist/authorization/index.d.ts +7 -0
- package/dist/authorization/index.js +7 -0
- package/dist/authorization/index.js.map +1 -0
- package/dist/authorization/manager.d.ts +8 -0
- package/dist/authorization/manager.js +19 -0
- package/dist/authorization/manager.js.map +1 -0
- package/dist/authorization/operation.d.ts +7 -0
- package/dist/authorization/operation.js +50 -0
- package/dist/authorization/operation.js.map +1 -0
- package/dist/authorization/planner.d.ts +21 -0
- package/dist/authorization/planner.js +97 -0
- package/dist/authorization/planner.js.map +1 -0
- package/dist/authorization/prompt.d.ts +12 -0
- package/dist/authorization/prompt.js +63 -0
- package/dist/authorization/prompt.js.map +1 -0
- package/dist/authorization/store.d.ts +20 -0
- package/dist/authorization/store.js +251 -0
- package/dist/authorization/store.js.map +1 -0
- package/dist/authorization/target.d.ts +15 -0
- package/dist/authorization/target.js +49 -0
- package/dist/authorization/target.js.map +1 -0
- package/dist/authorization/types.d.ts +61 -0
- package/dist/authorization/types.js +2 -0
- package/dist/authorization/types.js.map +1 -0
- package/dist/cli/args.d.ts +6 -0
- package/dist/cli/args.js +1255 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/auth.d.ts +21 -0
- package/dist/cli/auth.js +209 -0
- package/dist/cli/auth.js.map +1 -0
- package/dist/cli/commands.d.ts +4 -0
- package/dist/cli/commands.js +801 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/errors.d.ts +9 -0
- package/dist/cli/errors.js +131 -0
- package/dist/cli/errors.js.map +1 -0
- package/dist/cli/format/context.d.ts +9 -0
- package/dist/cli/format/context.js +786 -0
- package/dist/cli/format/context.js.map +1 -0
- package/dist/cli/format/debug.d.ts +4 -0
- package/dist/cli/format/debug.js +60 -0
- package/dist/cli/format/debug.js.map +1 -0
- package/dist/cli/format/highlight.d.ts +3 -0
- package/dist/cli/format/highlight.js +106 -0
- package/dist/cli/format/highlight.js.map +1 -0
- package/dist/cli/format/progress.d.ts +13 -0
- package/dist/cli/format/progress.js +290 -0
- package/dist/cli/format/progress.js.map +1 -0
- package/dist/cli/format/range.d.ts +3 -0
- package/dist/cli/format/range.js +24 -0
- package/dist/cli/format/range.js.map +1 -0
- package/dist/cli/format/status.d.ts +83 -0
- package/dist/cli/format/status.js +530 -0
- package/dist/cli/format/status.js.map +1 -0
- package/dist/cli/help.d.ts +1 -0
- package/dist/cli/help.js +546 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +40 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/install.d.ts +4 -0
- package/dist/cli/install.js +1735 -0
- package/dist/cli/install.js.map +1 -0
- package/dist/cli/managed-rg.d.ts +10 -0
- package/dist/cli/managed-rg.js +337 -0
- package/dist/cli/managed-rg.js.map +1 -0
- package/dist/cli/server-search.d.ts +3 -0
- package/dist/cli/server-search.js +12 -0
- package/dist/cli/server-search.js.map +1 -0
- package/dist/cli/types.d.ts +87 -0
- package/dist/cli/types.js +10 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/cli/version.d.ts +1 -0
- package/dist/cli/version.js +13 -0
- package/dist/cli/version.js.map +1 -0
- package/dist/client/daemon-client.d.ts +31 -0
- package/dist/client/daemon-client.js +203 -0
- package/dist/client/daemon-client.js.map +1 -0
- package/dist/client/mode-router.d.ts +9 -0
- package/dist/client/mode-router.js +28 -0
- package/dist/client/mode-router.js.map +1 -0
- package/dist/client/search-policy.d.ts +11 -0
- package/dist/client/search-policy.js +15 -0
- package/dist/client/search-policy.js.map +1 -0
- package/dist/daemon/backend.d.ts +78 -0
- package/dist/daemon/backend.js +1118 -0
- package/dist/daemon/backend.js.map +1 -0
- package/dist/daemon/change-set.d.ts +30 -0
- package/dist/daemon/change-set.js +163 -0
- package/dist/daemon/change-set.js.map +1 -0
- package/dist/daemon/config.d.ts +27 -0
- package/dist/daemon/config.js +107 -0
- package/dist/daemon/config.js.map +1 -0
- package/dist/daemon/errors.d.ts +5 -0
- package/dist/daemon/errors.js +11 -0
- package/dist/daemon/errors.js.map +1 -0
- package/dist/daemon/http-server.d.ts +30 -0
- package/dist/daemon/http-server.js +307 -0
- package/dist/daemon/http-server.js.map +1 -0
- package/dist/daemon/index-coordinator.d.ts +22 -0
- package/dist/daemon/index-coordinator.js +55 -0
- package/dist/daemon/index-coordinator.js.map +1 -0
- package/dist/daemon/job-scheduler.d.ts +85 -0
- package/dist/daemon/job-scheduler.js +492 -0
- package/dist/daemon/job-scheduler.js.map +1 -0
- package/dist/daemon/logger.d.ts +10 -0
- package/dist/daemon/logger.js +101 -0
- package/dist/daemon/logger.js.map +1 -0
- package/dist/daemon/model-pool.d.ts +48 -0
- package/dist/daemon/model-pool.js +190 -0
- package/dist/daemon/model-pool.js.map +1 -0
- package/dist/daemon/root-lease.d.ts +14 -0
- package/dist/daemon/root-lease.js +197 -0
- package/dist/daemon/root-lease.js.map +1 -0
- package/dist/daemon/root-runtime.d.ts +80 -0
- package/dist/daemon/root-runtime.js +340 -0
- package/dist/daemon/root-runtime.js.map +1 -0
- package/dist/daemon/runtime-manager.d.ts +47 -0
- package/dist/daemon/runtime-manager.js +252 -0
- package/dist/daemon/runtime-manager.js.map +1 -0
- package/dist/daemon/runtime.d.ts +12 -0
- package/dist/daemon/runtime.js +105 -0
- package/dist/daemon/runtime.js.map +1 -0
- package/dist/daemon/server-controller.d.ts +38 -0
- package/dist/daemon/server-controller.js +327 -0
- package/dist/daemon/server-controller.js.map +1 -0
- package/dist/daemon/watch-manager.d.ts +62 -0
- package/dist/daemon/watch-manager.js +415 -0
- package/dist/daemon/watch-manager.js.map +1 -0
- package/dist/daemon/workspace-read-session-cache.d.ts +31 -0
- package/dist/daemon/workspace-read-session-cache.js +115 -0
- package/dist/daemon/workspace-read-session-cache.js.map +1 -0
- package/dist/engine/code-formats.d.ts +3 -0
- package/dist/engine/code-formats.js +14 -0
- package/dist/engine/code-formats.js.map +1 -0
- package/dist/engine/config.d.ts +57 -0
- package/dist/engine/config.js +351 -0
- package/dist/engine/config.js.map +1 -0
- package/dist/engine/errors.d.ts +20 -0
- package/dist/engine/errors.js +51 -0
- package/dist/engine/errors.js.map +1 -0
- package/dist/engine/extraction/code/adapter.d.ts +19 -0
- package/dist/engine/extraction/code/adapter.js +24 -0
- package/dist/engine/extraction/code/adapter.js.map +1 -0
- package/dist/engine/extraction/code/extractor.d.ts +13 -0
- package/dist/engine/extraction/code/extractor.js +696 -0
- package/dist/engine/extraction/code/extractor.js.map +1 -0
- package/dist/engine/extraction/code/families/c-family.d.ts +2 -0
- package/dist/engine/extraction/code/families/c-family.js +122 -0
- package/dist/engine/extraction/code/families/c-family.js.map +1 -0
- package/dist/engine/extraction/code/families/js-ts.d.ts +12 -0
- package/dist/engine/extraction/code/families/js-ts.js +118 -0
- package/dist/engine/extraction/code/families/js-ts.js.map +1 -0
- package/dist/engine/extraction/code/families/metadata.d.ts +8 -0
- package/dist/engine/extraction/code/families/metadata.js +86 -0
- package/dist/engine/extraction/code/families/metadata.js.map +1 -0
- package/dist/engine/extraction/code/families/name-field.d.ts +3 -0
- package/dist/engine/extraction/code/families/name-field.js +22 -0
- package/dist/engine/extraction/code/families/name-field.js.map +1 -0
- package/dist/engine/extraction/code/languages/c.d.ts +1 -0
- package/dist/engine/extraction/code/languages/c.js +12 -0
- package/dist/engine/extraction/code/languages/c.js.map +1 -0
- package/dist/engine/extraction/code/languages/cpp.d.ts +1 -0
- package/dist/engine/extraction/code/languages/cpp.js +18 -0
- package/dist/engine/extraction/code/languages/cpp.js.map +1 -0
- package/dist/engine/extraction/code/languages/go.d.ts +2 -0
- package/dist/engine/extraction/code/languages/go.js +70 -0
- package/dist/engine/extraction/code/languages/go.js.map +1 -0
- package/dist/engine/extraction/code/languages/java.d.ts +1 -0
- package/dist/engine/extraction/code/languages/java.js +17 -0
- package/dist/engine/extraction/code/languages/java.js.map +1 -0
- package/dist/engine/extraction/code/languages/javascript.d.ts +2 -0
- package/dist/engine/extraction/code/languages/javascript.js +23 -0
- package/dist/engine/extraction/code/languages/javascript.js.map +1 -0
- package/dist/engine/extraction/code/languages/python.d.ts +2 -0
- package/dist/engine/extraction/code/languages/python.js +52 -0
- package/dist/engine/extraction/code/languages/python.js.map +1 -0
- package/dist/engine/extraction/code/languages/rust.d.ts +2 -0
- package/dist/engine/extraction/code/languages/rust.js +25 -0
- package/dist/engine/extraction/code/languages/rust.js.map +1 -0
- package/dist/engine/extraction/code/languages/typescript.d.ts +2 -0
- package/dist/engine/extraction/code/languages/typescript.js +37 -0
- package/dist/engine/extraction/code/languages/typescript.js.map +1 -0
- package/dist/engine/extraction/code/tree-sitter/grammar.d.ts +4 -0
- package/dist/engine/extraction/code/tree-sitter/grammar.js +49 -0
- package/dist/engine/extraction/code/tree-sitter/grammar.js.map +1 -0
- package/dist/engine/extraction/code/tree-sitter/nodes.d.ts +3 -0
- package/dist/engine/extraction/code/tree-sitter/nodes.js +27 -0
- package/dist/engine/extraction/code/tree-sitter/nodes.js.map +1 -0
- package/dist/engine/extraction/code/tree-sitter/parser.d.ts +2 -0
- package/dist/engine/extraction/code/tree-sitter/parser.js +31 -0
- package/dist/engine/extraction/code/tree-sitter/parser.js.map +1 -0
- package/dist/engine/extraction/ids.d.ts +1 -0
- package/dist/engine/extraction/ids.js +5 -0
- package/dist/engine/extraction/ids.js.map +1 -0
- package/dist/engine/extraction/image/extractor.d.ts +5 -0
- package/dist/engine/extraction/image/extractor.js +35 -0
- package/dist/engine/extraction/image/extractor.js.map +1 -0
- package/dist/engine/extraction/index.d.ts +4 -0
- package/dist/engine/extraction/index.js +3 -0
- package/dist/engine/extraction/index.js.map +1 -0
- package/dist/engine/extraction/markdown/extractor.d.ts +7 -0
- package/dist/engine/extraction/markdown/extractor.js +362 -0
- package/dist/engine/extraction/markdown/extractor.js.map +1 -0
- package/dist/engine/extraction/runtime.d.ts +9 -0
- package/dist/engine/extraction/runtime.js +37 -0
- package/dist/engine/extraction/runtime.js.map +1 -0
- package/dist/engine/extraction/source.d.ts +18 -0
- package/dist/engine/extraction/source.js +22 -0
- package/dist/engine/extraction/source.js.map +1 -0
- package/dist/engine/extraction/text/extractor.d.ts +7 -0
- package/dist/engine/extraction/text/extractor.js +168 -0
- package/dist/engine/extraction/text/extractor.js.map +1 -0
- package/dist/engine/extraction/types.d.ts +4 -0
- package/dist/engine/extraction/types.js +2 -0
- package/dist/engine/extraction/types.js.map +1 -0
- package/dist/engine/extraction/vector-content.d.ts +5 -0
- package/dist/engine/extraction/vector-content.js +83 -0
- package/dist/engine/extraction/vector-content.js.map +1 -0
- package/dist/engine/file-size-policy.d.ts +6 -0
- package/dist/engine/file-size-policy.js +20 -0
- package/dist/engine/file-size-policy.js.map +1 -0
- package/dist/engine/file-type.d.ts +15 -0
- package/dist/engine/file-type.js +152 -0
- package/dist/engine/file-type.js.map +1 -0
- package/dist/engine/index-status.d.ts +9 -0
- package/dist/engine/index-status.js +38 -0
- package/dist/engine/index-status.js.map +1 -0
- package/dist/engine/manifest.d.ts +13 -0
- package/dist/engine/manifest.js +124 -0
- package/dist/engine/manifest.js.map +1 -0
- package/dist/engine/models/artifact-cache-lock.d.ts +16 -0
- package/dist/engine/models/artifact-cache-lock.js +228 -0
- package/dist/engine/models/artifact-cache-lock.js.map +1 -0
- package/dist/engine/models/artifact-downloader.d.ts +77 -0
- package/dist/engine/models/artifact-downloader.js +766 -0
- package/dist/engine/models/artifact-downloader.js.map +1 -0
- package/dist/engine/models/backends/llama-cpp.d.ts +100 -0
- package/dist/engine/models/backends/llama-cpp.js +567 -0
- package/dist/engine/models/backends/llama-cpp.js.map +1 -0
- package/dist/engine/models/backends/model2vec-runtime.d.ts +63 -0
- package/dist/engine/models/backends/model2vec-runtime.js +112 -0
- package/dist/engine/models/backends/model2vec-runtime.js.map +1 -0
- package/dist/engine/models/backends/model2vec-tokenizer.d.ts +2 -0
- package/dist/engine/models/backends/model2vec-tokenizer.js +42 -0
- package/dist/engine/models/backends/model2vec-tokenizer.js.map +1 -0
- package/dist/engine/models/backends/model2vec-worker-pool.d.ts +25 -0
- package/dist/engine/models/backends/model2vec-worker-pool.js +264 -0
- package/dist/engine/models/backends/model2vec-worker-pool.js.map +1 -0
- package/dist/engine/models/backends/model2vec-worker.d.ts +1 -0
- package/dist/engine/models/backends/model2vec-worker.js +41 -0
- package/dist/engine/models/backends/model2vec-worker.js.map +1 -0
- package/dist/engine/models/backends/model2vec.d.ts +38 -0
- package/dist/engine/models/backends/model2vec.js +344 -0
- package/dist/engine/models/backends/model2vec.js.map +1 -0
- package/dist/engine/models/backends/qwen.d.ts +44 -0
- package/dist/engine/models/backends/qwen.js +411 -0
- package/dist/engine/models/backends/qwen.js.map +1 -0
- package/dist/engine/models/backends/transformers-js.d.ts +90 -0
- package/dist/engine/models/backends/transformers-js.js +411 -0
- package/dist/engine/models/backends/transformers-js.js.map +1 -0
- package/dist/engine/models/catalog.d.ts +490 -0
- package/dist/engine/models/catalog.js +522 -0
- package/dist/engine/models/catalog.js.map +1 -0
- package/dist/engine/models/download-progress.d.ts +16 -0
- package/dist/engine/models/download-progress.js +47 -0
- package/dist/engine/models/download-progress.js.map +1 -0
- package/dist/engine/models/embeddings.d.ts +73 -0
- package/dist/engine/models/embeddings.js +125 -0
- package/dist/engine/models/embeddings.js.map +1 -0
- package/dist/engine/models/factory.d.ts +2 -0
- package/dist/engine/models/factory.js +51 -0
- package/dist/engine/models/factory.js.map +1 -0
- package/dist/engine/models/index.d.ts +5 -0
- package/dist/engine/models/index.js +6 -0
- package/dist/engine/models/index.js.map +1 -0
- package/dist/engine/models/local-embedding-parallelism.d.ts +15 -0
- package/dist/engine/models/local-embedding-parallelism.js +63 -0
- package/dist/engine/models/local-embedding-parallelism.js.map +1 -0
- package/dist/engine/models/local-embedding-queue.d.ts +12 -0
- package/dist/engine/models/local-embedding-queue.js +65 -0
- package/dist/engine/models/local-embedding-queue.js.map +1 -0
- package/dist/engine/models/ranking.d.ts +19 -0
- package/dist/engine/models/ranking.js +2 -0
- package/dist/engine/models/ranking.js.map +1 -0
- package/dist/engine/models/resolution.d.ts +8 -0
- package/dist/engine/models/resolution.js +22 -0
- package/dist/engine/models/resolution.js.map +1 -0
- package/dist/engine/pipeline/indexing/index.d.ts +14 -0
- package/dist/engine/pipeline/indexing/index.js +1219 -0
- package/dist/engine/pipeline/indexing/index.js.map +1 -0
- package/dist/engine/pipeline/indexing/input-budget.d.ts +2 -0
- package/dist/engine/pipeline/indexing/input-budget.js +51 -0
- package/dist/engine/pipeline/indexing/input-budget.js.map +1 -0
- package/dist/engine/pipeline/indexing/root-paths.d.ts +7 -0
- package/dist/engine/pipeline/indexing/root-paths.js +148 -0
- package/dist/engine/pipeline/indexing/root-paths.js.map +1 -0
- package/dist/engine/pipeline/indexing/scanner/index.d.ts +14 -0
- package/dist/engine/pipeline/indexing/scanner/index.js +776 -0
- package/dist/engine/pipeline/indexing/scanner/index.js.map +1 -0
- package/dist/engine/pipeline/search/index.d.ts +12 -0
- package/dist/engine/pipeline/search/index.js +811 -0
- package/dist/engine/pipeline/search/index.js.map +1 -0
- package/dist/engine/service/index.d.ts +3 -0
- package/dist/engine/service/index.js +2 -0
- package/dist/engine/service/index.js.map +1 -0
- package/dist/engine/service/lexical.d.ts +28 -0
- package/dist/engine/service/lexical.js +446 -0
- package/dist/engine/service/lexical.js.map +1 -0
- package/dist/engine/service/root.d.ts +15 -0
- package/dist/engine/service/root.js +57 -0
- package/dist/engine/service/root.js.map +1 -0
- package/dist/engine/service/structure-enrichment.d.ts +8 -0
- package/dist/engine/service/structure-enrichment.js +178 -0
- package/dist/engine/service/structure-enrichment.js.map +1 -0
- package/dist/engine/service/types.d.ts +212 -0
- package/dist/engine/service/types.js +2 -0
- package/dist/engine/service/types.js.map +1 -0
- package/dist/engine/service/workspace-index.d.ts +26 -0
- package/dist/engine/service/workspace-index.js +157 -0
- package/dist/engine/service/workspace-index.js.map +1 -0
- package/dist/engine/service/zvec-grep.d.ts +15 -0
- package/dist/engine/service/zvec-grep.js +1520 -0
- package/dist/engine/service/zvec-grep.js.map +1 -0
- package/dist/engine/storage/index.d.ts +53 -0
- package/dist/engine/storage/index.js +3 -0
- package/dist/engine/storage/index.js.map +1 -0
- package/dist/engine/storage/layout.d.ts +10 -0
- package/dist/engine/storage/layout.js +29 -0
- package/dist/engine/storage/layout.js.map +1 -0
- package/dist/engine/storage/zvec.d.ts +4 -0
- package/dist/engine/storage/zvec.js +976 -0
- package/dist/engine/storage/zvec.js.map +1 -0
- package/dist/engine/types.d.ts +303 -0
- package/dist/engine/types.js +14 -0
- package/dist/engine/types.js.map +1 -0
- package/dist/engine/utils/atomic-file.d.ts +3 -0
- package/dist/engine/utils/atomic-file.js +39 -0
- package/dist/engine/utils/atomic-file.js.map +1 -0
- package/dist/engine/utils/daemon-lease.d.ts +17 -0
- package/dist/engine/utils/daemon-lease.js +175 -0
- package/dist/engine/utils/daemon-lease.js.map +1 -0
- package/dist/engine/utils/file-selection.d.ts +10 -0
- package/dist/engine/utils/file-selection.js +157 -0
- package/dist/engine/utils/file-selection.js.map +1 -0
- package/dist/engine/utils/glob.d.ts +9 -0
- package/dist/engine/utils/glob.js +208 -0
- package/dist/engine/utils/glob.js.map +1 -0
- package/dist/engine/utils/hash.d.ts +2 -0
- package/dist/engine/utils/hash.js +8 -0
- package/dist/engine/utils/hash.js.map +1 -0
- package/dist/engine/utils/json.d.ts +8 -0
- package/dist/engine/utils/json.js +83 -0
- package/dist/engine/utils/json.js.map +1 -0
- package/dist/engine/utils/lock.d.ts +19 -0
- package/dist/engine/utils/lock.js +254 -0
- package/dist/engine/utils/lock.js.map +1 -0
- package/dist/engine/utils/path.d.ts +4 -0
- package/dist/engine/utils/path.js +18 -0
- package/dist/engine/utils/path.js.map +1 -0
- package/dist/engine/utils/timing.d.ts +19 -0
- package/dist/engine/utils/timing.js +87 -0
- package/dist/engine/utils/timing.js.map +1 -0
- package/dist/index-progress.d.ts +10 -0
- package/dist/index-progress.js +159 -0
- package/dist/index-progress.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/http-transport.d.ts +27 -0
- package/dist/mcp/http-transport.js +182 -0
- package/dist/mcp/http-transport.js.map +1 -0
- package/dist/mcp/input-normalization.d.ts +38 -0
- package/dist/mcp/input-normalization.js +104 -0
- package/dist/mcp/input-normalization.js.map +1 -0
- package/dist/mcp/progress-heartbeat.d.ts +17 -0
- package/dist/mcp/progress-heartbeat.js +46 -0
- package/dist/mcp/progress-heartbeat.js.map +1 -0
- package/dist/mcp/request-metadata.d.ts +2 -0
- package/dist/mcp/request-metadata.js +11 -0
- package/dist/mcp/request-metadata.js.map +1 -0
- package/dist/mcp/request-state.d.ts +40 -0
- package/dist/mcp/request-state.js +210 -0
- package/dist/mcp/request-state.js.map +1 -0
- package/dist/mcp/result-format.d.ts +13 -0
- package/dist/mcp/result-format.js +75 -0
- package/dist/mcp/result-format.js.map +1 -0
- package/dist/mcp/schemas.d.ts +668 -0
- package/dist/mcp/schemas.js +512 -0
- package/dist/mcp/schemas.js.map +1 -0
- package/dist/mcp/stdio-bridge.d.ts +15 -0
- package/dist/mcp/stdio-bridge.js +190 -0
- package/dist/mcp/stdio-bridge.js.map +1 -0
- package/dist/mcp/tools.d.ts +153 -0
- package/dist/mcp/tools.js +488 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/toolset.d.ts +5 -0
- package/dist/mcp/toolset.js +12 -0
- package/dist/mcp/toolset.js.map +1 -0
- package/dist/observability/trace-context.d.ts +11 -0
- package/dist/observability/trace-context.js +138 -0
- package/dist/observability/trace-context.js.map +1 -0
- package/dist/prompts/zvec-grep-guidance.d.ts +2 -0
- package/dist/prompts/zvec-grep-guidance.js +9 -0
- package/dist/prompts/zvec-grep-guidance.js.map +1 -0
- package/docs/01-agents.md +217 -0
- package/docs/02-cli.md +259 -0
- package/docs/03-mcp.md +185 -0
- package/docs/04-pipeline.md +234 -0
- package/docs/05-architecture.md +86 -0
- package/docs/06-server.md +279 -0
- package/docs/07-embedding.md +227 -0
- package/docs/08-roadmap.md +85 -0
- package/docs/README.md +53 -0
- package/package.json +87 -0
|
@@ -0,0 +1,1735 @@
|
|
|
1
|
+
import { applyEdits, createScanner, findNodeAtLocation, modify, parse as parseJsonWithComments, parseTree, } from "jsonc-parser";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { constants as fileSystemConstants } from "node:fs";
|
|
4
|
+
import { access, chmod, lstat, mkdir, open, readFile, readlink, rename, stat, unlink, } from "node:fs/promises";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { delimiter, dirname, resolve } from "node:path";
|
|
7
|
+
import { createInterface } from "node:readline/promises";
|
|
8
|
+
import { parseEnv } from "node:util";
|
|
9
|
+
import { REMOTE_EMBEDDING_ELICITATION_UNSUPPORTED_MESSAGE } from "../authorization/prompt.js";
|
|
10
|
+
import { resolveServerUrl } from "../client/mode-router.js";
|
|
11
|
+
import { formatPromptRules, ZVEC_GREP_WORKSPACE_EVIDENCE_RULES, } from "../prompts/zvec-grep-guidance.js";
|
|
12
|
+
const AGENT_INSTALLERS = [
|
|
13
|
+
{
|
|
14
|
+
id: "claude",
|
|
15
|
+
aliases: ["cc", "claude-code"],
|
|
16
|
+
label: "Claude Code",
|
|
17
|
+
executables: ["claude"],
|
|
18
|
+
install: installClaudeIntegration,
|
|
19
|
+
uninstall: uninstallClaudeIntegration,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "codex",
|
|
23
|
+
label: "Codex",
|
|
24
|
+
executables: ["codex"],
|
|
25
|
+
install: installCodexIntegration,
|
|
26
|
+
uninstall: uninstallCodexIntegration,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "opencode",
|
|
30
|
+
label: "OpenCode",
|
|
31
|
+
executables: ["opencode"],
|
|
32
|
+
install: installOpenCodeIntegration,
|
|
33
|
+
uninstall: uninstallOpenCodeIntegration,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "cursor",
|
|
37
|
+
label: "Cursor",
|
|
38
|
+
executables: ["cursor"],
|
|
39
|
+
install: installCursorIntegration,
|
|
40
|
+
uninstall: uninstallCursorIntegration,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "qwen",
|
|
44
|
+
aliases: ["qwen-code", "qwencode"],
|
|
45
|
+
label: "Qwen Code",
|
|
46
|
+
executables: ["qwen"],
|
|
47
|
+
install: installQwenIntegration,
|
|
48
|
+
uninstall: uninstallQwenIntegration,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "qoder",
|
|
52
|
+
label: "Qoder",
|
|
53
|
+
executables: ["qoder", "qodercli", "qoder-ide"],
|
|
54
|
+
detect: qoderIdeIsAvailable,
|
|
55
|
+
install: installQoderIntegration,
|
|
56
|
+
uninstall: uninstallQoderIntegration,
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
const ZVEC_GREP_CONFIG_START = "# ZVEC_GREP_START";
|
|
60
|
+
const ZVEC_GREP_CONFIG_END = "# ZVEC_GREP_END";
|
|
61
|
+
const ZVEC_GREP_AGENTS_START = "<!-- ZVEC_GREP_START -->";
|
|
62
|
+
const ZVEC_GREP_AGENTS_END = "<!-- ZVEC_GREP_END -->";
|
|
63
|
+
const CLAUDE_MCP_PERMISSION = "mcp__zvec_grep__*";
|
|
64
|
+
const NAMESPACED_SEARCH_TOOL = "mcp__zvec_grep__zvec_grep_search";
|
|
65
|
+
const NAMESPACED_RG_TOOL = "mcp__zvec_grep__zvec_grep_rg";
|
|
66
|
+
const QODER_MCP_PERMISSION_RULES = [
|
|
67
|
+
{
|
|
68
|
+
permission: NAMESPACED_SEARCH_TOOL,
|
|
69
|
+
tool: "zvec_grep_search",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
permission: NAMESPACED_RG_TOOL,
|
|
73
|
+
tool: "zvec_grep_rg",
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
const QODER_MCP_PERMISSIONS = QODER_MCP_PERMISSION_RULES.map(({ permission }) => permission);
|
|
77
|
+
const QODER_CLI_MCP_DESCRIPTION = "Managed by zg --install";
|
|
78
|
+
const QODER_PERMISSION_OWNERSHIP_PREFIX = `${QODER_CLI_MCP_DESCRIPTION}; managed permissions=`;
|
|
79
|
+
const QODER_IDE_MCP_DESCRIPTION = "Managed by zg --install";
|
|
80
|
+
const LEGACY_QODER_MCP_DESCRIPTION = "Managed by zg install";
|
|
81
|
+
const LEGACY_QODER_PERMISSION_OWNERSHIP_PREFIX = `${LEGACY_QODER_MCP_DESCRIPTION}; managed permissions=`;
|
|
82
|
+
const DEFAULT_MCP_TOOL_TIMEOUT_SECONDS = 600;
|
|
83
|
+
export async function runInstall(parsed) {
|
|
84
|
+
printInstallHeader();
|
|
85
|
+
const installers = await resolveInstallers(parsed, "install");
|
|
86
|
+
if (installers.length === 0) {
|
|
87
|
+
console.log("\nNo agent integrations selected.");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const transport = await resolveInstallTransport(parsed);
|
|
91
|
+
console.log("\nInstalling integrations\n");
|
|
92
|
+
for (const installer of installers) {
|
|
93
|
+
const result = await installer.install({
|
|
94
|
+
force: parsed.options.force === true,
|
|
95
|
+
transport,
|
|
96
|
+
mcpToolset: parsed.options.mcpToolset,
|
|
97
|
+
mcpToolTimeoutSeconds: parsed.options.installMcpToolTimeoutSeconds ??
|
|
98
|
+
DEFAULT_MCP_TOOL_TIMEOUT_SECONDS,
|
|
99
|
+
mcpTokenEnv: parsed.options.installMcpTokenEnv,
|
|
100
|
+
});
|
|
101
|
+
console.log(` ${installSuccessMark()} ${installer.label}`);
|
|
102
|
+
console.log(" MCP configured");
|
|
103
|
+
if (result.configPath) {
|
|
104
|
+
console.log(` Config ${result.configPath}`);
|
|
105
|
+
}
|
|
106
|
+
if (result.configNote) {
|
|
107
|
+
console.log(` Note ${result.configNote}`);
|
|
108
|
+
}
|
|
109
|
+
console.log("");
|
|
110
|
+
}
|
|
111
|
+
const server = await ensureInstalledServer(parsed.options.mcpToolset);
|
|
112
|
+
if (server.ready) {
|
|
113
|
+
console.log(` ${installSuccessMark()} Server`);
|
|
114
|
+
console.log(` ready at ${server.serverUrl ?? resolveServerUrl()}`);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
console.log(` ${installMutedMark()} Server`);
|
|
118
|
+
console.log(" not started; run `zg --server on`");
|
|
119
|
+
}
|
|
120
|
+
if (transport === "stdio") {
|
|
121
|
+
console.log(` ${installSuccessMark()} Connection`);
|
|
122
|
+
console.log(" stdio; reconnects start the server automatically");
|
|
123
|
+
}
|
|
124
|
+
console.log("\nzvec-grep is ready\n");
|
|
125
|
+
console.log(` Agents ${installers.map((installer) => installer.label).join(", ")}`);
|
|
126
|
+
console.log(" Remote data Authorization requested on first remote use");
|
|
127
|
+
console.log("\nRestart the selected agents or start a new session to load the integration.");
|
|
128
|
+
}
|
|
129
|
+
export async function runUninstall(parsed) {
|
|
130
|
+
printInstallHeader();
|
|
131
|
+
const installers = await resolveInstallers(parsed, "uninstall");
|
|
132
|
+
if (installers.length === 0) {
|
|
133
|
+
console.log("\nNo agent integrations selected.");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
console.log("\nRemoving integrations\n");
|
|
137
|
+
for (const installer of installers) {
|
|
138
|
+
await installer.uninstall();
|
|
139
|
+
console.log(` ${installSuccessMark()} ${installer.label}`);
|
|
140
|
+
console.log(" integration removed");
|
|
141
|
+
}
|
|
142
|
+
console.log("\nRestart the selected agents or start a new session to apply the change.");
|
|
143
|
+
}
|
|
144
|
+
async function installCodexIntegration(options) {
|
|
145
|
+
const codexHome = resolveCodexHome();
|
|
146
|
+
const configPath = resolve(codexHome, "config.toml");
|
|
147
|
+
const agentsPath = resolve(codexHome, "AGENTS.md");
|
|
148
|
+
await writeMarkedFile({
|
|
149
|
+
path: configPath,
|
|
150
|
+
startMarker: ZVEC_GREP_CONFIG_START,
|
|
151
|
+
endMarker: ZVEC_GREP_CONFIG_END,
|
|
152
|
+
block: codexConfigBlock(options),
|
|
153
|
+
force: options.force,
|
|
154
|
+
hasConflict: hasCodexMcpServerConfig,
|
|
155
|
+
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.`,
|
|
156
|
+
removeConflict: removeCodexMcpServerConfig,
|
|
157
|
+
});
|
|
158
|
+
await writeMarkedFile({
|
|
159
|
+
path: agentsPath,
|
|
160
|
+
startMarker: ZVEC_GREP_AGENTS_START,
|
|
161
|
+
endMarker: ZVEC_GREP_AGENTS_END,
|
|
162
|
+
block: agentGuidanceBlock(),
|
|
163
|
+
force: true,
|
|
164
|
+
});
|
|
165
|
+
return { files: [configPath, agentsPath] };
|
|
166
|
+
}
|
|
167
|
+
async function installClaudeIntegration(options) {
|
|
168
|
+
const configDirectory = resolveClaudeConfigDirectory();
|
|
169
|
+
const mcpConfigPath = resolveClaudeMcpConfigPath();
|
|
170
|
+
const settingsPath = resolve(configDirectory, "settings.json");
|
|
171
|
+
const guidancePath = resolve(configDirectory, "CLAUDE.md");
|
|
172
|
+
await updateClaudeMcpConfig({
|
|
173
|
+
path: mcpConfigPath,
|
|
174
|
+
force: options.force,
|
|
175
|
+
transport: options.transport,
|
|
176
|
+
mcpToolset: options.mcpToolset,
|
|
177
|
+
tokenEnv: options.mcpTokenEnv,
|
|
178
|
+
});
|
|
179
|
+
await updateClaudePermissionSettings(settingsPath, true);
|
|
180
|
+
await writeMarkedFile({
|
|
181
|
+
path: guidancePath,
|
|
182
|
+
startMarker: ZVEC_GREP_AGENTS_START,
|
|
183
|
+
endMarker: ZVEC_GREP_AGENTS_END,
|
|
184
|
+
block: agentGuidanceBlock(),
|
|
185
|
+
force: true,
|
|
186
|
+
});
|
|
187
|
+
return { files: [mcpConfigPath, settingsPath, guidancePath] };
|
|
188
|
+
}
|
|
189
|
+
async function uninstallClaudeIntegration() {
|
|
190
|
+
const configDirectory = resolveClaudeConfigDirectory();
|
|
191
|
+
const mcpConfigPath = resolveClaudeMcpConfigPath();
|
|
192
|
+
const settingsPath = resolve(configDirectory, "settings.json");
|
|
193
|
+
const guidancePath = resolve(configDirectory, "CLAUDE.md");
|
|
194
|
+
await removeClaudeMcpConfig(mcpConfigPath);
|
|
195
|
+
await updateClaudePermissionSettings(settingsPath, false);
|
|
196
|
+
await removeMarkedFile({
|
|
197
|
+
path: guidancePath,
|
|
198
|
+
startMarker: ZVEC_GREP_AGENTS_START,
|
|
199
|
+
endMarker: ZVEC_GREP_AGENTS_END,
|
|
200
|
+
});
|
|
201
|
+
return { files: [mcpConfigPath, settingsPath, guidancePath] };
|
|
202
|
+
}
|
|
203
|
+
async function uninstallCodexIntegration() {
|
|
204
|
+
const codexHome = resolveCodexHome();
|
|
205
|
+
const configPath = resolve(codexHome, "config.toml");
|
|
206
|
+
const agentsPath = resolve(codexHome, "AGENTS.md");
|
|
207
|
+
await removeMarkedFile({
|
|
208
|
+
path: configPath,
|
|
209
|
+
startMarker: ZVEC_GREP_CONFIG_START,
|
|
210
|
+
endMarker: ZVEC_GREP_CONFIG_END,
|
|
211
|
+
});
|
|
212
|
+
await removeMarkedFile({
|
|
213
|
+
path: agentsPath,
|
|
214
|
+
startMarker: ZVEC_GREP_AGENTS_START,
|
|
215
|
+
endMarker: ZVEC_GREP_AGENTS_END,
|
|
216
|
+
});
|
|
217
|
+
return { files: [configPath, agentsPath] };
|
|
218
|
+
}
|
|
219
|
+
async function installOpenCodeIntegration(options) {
|
|
220
|
+
const resolvedConfig = await resolveOpenCodeConfigPath();
|
|
221
|
+
const configPath = resolvedConfig.path;
|
|
222
|
+
const guidancePath = resolve(dirname(configPath), "AGENTS.md");
|
|
223
|
+
await updateJsoncMcpSettings({
|
|
224
|
+
path: configPath,
|
|
225
|
+
containerKey: "mcp",
|
|
226
|
+
// OpenCode accepts JSONC trailing commas, so its installer must accept them too.
|
|
227
|
+
allowTrailingComma: true,
|
|
228
|
+
server: options.transport === "stdio"
|
|
229
|
+
? {
|
|
230
|
+
type: "local",
|
|
231
|
+
command: stdioCommand(options.mcpToolset),
|
|
232
|
+
enabled: true,
|
|
233
|
+
timeout: options.mcpToolTimeoutSeconds * 1_000,
|
|
234
|
+
}
|
|
235
|
+
: {
|
|
236
|
+
type: "remote",
|
|
237
|
+
url: resolveServerUrl(),
|
|
238
|
+
enabled: true,
|
|
239
|
+
timeout: options.mcpToolTimeoutSeconds * 1_000,
|
|
240
|
+
oauth: false,
|
|
241
|
+
...(options.mcpTokenEnv
|
|
242
|
+
? {
|
|
243
|
+
headers: {
|
|
244
|
+
Authorization: `Bearer {env:${options.mcpTokenEnv}}`,
|
|
245
|
+
},
|
|
246
|
+
}
|
|
247
|
+
: {}),
|
|
248
|
+
},
|
|
249
|
+
force: options.force,
|
|
250
|
+
label: "OpenCode",
|
|
251
|
+
isManaged: isManagedJsonMcpServer,
|
|
252
|
+
});
|
|
253
|
+
await writeMarkedFile({
|
|
254
|
+
path: guidancePath,
|
|
255
|
+
startMarker: ZVEC_GREP_AGENTS_START,
|
|
256
|
+
endMarker: ZVEC_GREP_AGENTS_END,
|
|
257
|
+
block: agentGuidanceBlock({
|
|
258
|
+
search: "zvec_grep_zvec_grep_search",
|
|
259
|
+
rg: "zvec_grep_zvec_grep_rg",
|
|
260
|
+
}),
|
|
261
|
+
force: true,
|
|
262
|
+
});
|
|
263
|
+
return {
|
|
264
|
+
files: [configPath, guidancePath],
|
|
265
|
+
configPath,
|
|
266
|
+
configNote: resolvedConfig.note,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
async function uninstallOpenCodeIntegration() {
|
|
270
|
+
const resolvedConfig = await resolveOpenCodeConfigPath();
|
|
271
|
+
const configPath = resolvedConfig.path;
|
|
272
|
+
const guidancePath = resolve(dirname(configPath), "AGENTS.md");
|
|
273
|
+
// OpenCode deep-merges both global files, so legacy managed entries in the
|
|
274
|
+
// non-selected file must also be removed. An explicit OPENCODE_CONFIG remains scoped.
|
|
275
|
+
for (const path of resolvedConfig.managedCleanupPaths ?? [configPath]) {
|
|
276
|
+
await removeJsoncMcpSettings(path, "OpenCode", isManagedJsonMcpServer, "mcp",
|
|
277
|
+
// Keep uninstall compatible with every configuration accepted by OpenCode.
|
|
278
|
+
true);
|
|
279
|
+
}
|
|
280
|
+
await removeMarkedFile({
|
|
281
|
+
path: guidancePath,
|
|
282
|
+
startMarker: ZVEC_GREP_AGENTS_START,
|
|
283
|
+
endMarker: ZVEC_GREP_AGENTS_END,
|
|
284
|
+
});
|
|
285
|
+
return { files: [configPath, guidancePath] };
|
|
286
|
+
}
|
|
287
|
+
async function installCursorIntegration(options) {
|
|
288
|
+
const configPath = resolveCursorConfigPath();
|
|
289
|
+
await installJsonMcpServer({
|
|
290
|
+
path: configPath,
|
|
291
|
+
containerKey: "mcpServers",
|
|
292
|
+
server: options.transport === "stdio"
|
|
293
|
+
? { command: "zg", args: stdioArgs(options.mcpToolset) }
|
|
294
|
+
: {
|
|
295
|
+
url: resolveServerUrl(),
|
|
296
|
+
...(options.mcpTokenEnv
|
|
297
|
+
? {
|
|
298
|
+
headers: {
|
|
299
|
+
Authorization: `Bearer \${${options.mcpTokenEnv}}`,
|
|
300
|
+
},
|
|
301
|
+
}
|
|
302
|
+
: {}),
|
|
303
|
+
},
|
|
304
|
+
force: options.force,
|
|
305
|
+
label: "Cursor",
|
|
306
|
+
});
|
|
307
|
+
return { files: [configPath] };
|
|
308
|
+
}
|
|
309
|
+
async function uninstallCursorIntegration() {
|
|
310
|
+
const configPath = resolveCursorConfigPath();
|
|
311
|
+
await uninstallJsonMcpServer(configPath, "mcpServers");
|
|
312
|
+
return { files: [configPath] };
|
|
313
|
+
}
|
|
314
|
+
async function installQwenIntegration(options) {
|
|
315
|
+
const qwenHome = await resolveQwenHome();
|
|
316
|
+
const settingsPath = resolve(qwenHome, "settings.json");
|
|
317
|
+
const guidancePath = resolve(qwenHome, "QWEN.md");
|
|
318
|
+
const warnings = await updateQwenSettings({
|
|
319
|
+
...options,
|
|
320
|
+
path: settingsPath,
|
|
321
|
+
});
|
|
322
|
+
await writeMarkedFile({
|
|
323
|
+
path: guidancePath,
|
|
324
|
+
startMarker: ZVEC_GREP_AGENTS_START,
|
|
325
|
+
endMarker: ZVEC_GREP_AGENTS_END,
|
|
326
|
+
block: agentGuidanceBlock({
|
|
327
|
+
search: NAMESPACED_SEARCH_TOOL,
|
|
328
|
+
rg: NAMESPACED_RG_TOOL,
|
|
329
|
+
}),
|
|
330
|
+
force: true,
|
|
331
|
+
});
|
|
332
|
+
for (const warning of warnings) {
|
|
333
|
+
console.warn(` warning ${warning}`);
|
|
334
|
+
}
|
|
335
|
+
return { files: [settingsPath, guidancePath] };
|
|
336
|
+
}
|
|
337
|
+
async function uninstallQwenIntegration() {
|
|
338
|
+
const qwenHome = await resolveQwenHome();
|
|
339
|
+
const settingsPath = resolve(qwenHome, "settings.json");
|
|
340
|
+
const guidancePath = resolve(qwenHome, "QWEN.md");
|
|
341
|
+
await removeQwenSettings(settingsPath);
|
|
342
|
+
await removeMarkedFile({
|
|
343
|
+
path: guidancePath,
|
|
344
|
+
startMarker: ZVEC_GREP_AGENTS_START,
|
|
345
|
+
endMarker: ZVEC_GREP_AGENTS_END,
|
|
346
|
+
});
|
|
347
|
+
return { files: [settingsPath, guidancePath] };
|
|
348
|
+
}
|
|
349
|
+
async function installQoderIntegration(options) {
|
|
350
|
+
const qoderHome = resolveQoderHome();
|
|
351
|
+
const settingsPath = resolve(qoderHome, "settings.json");
|
|
352
|
+
const ideMcpPath = resolveQoderIdeMcpPath();
|
|
353
|
+
const guidancePath = resolve(qoderHome, "AGENTS.md");
|
|
354
|
+
await assertQoderMcpSettingsReplaceable(settingsPath, "Qoder CLI", options.force, isManagedJsonMcpServer);
|
|
355
|
+
await assertQoderPermissionSettingsValid(settingsPath);
|
|
356
|
+
await assertQoderMcpSettingsReplaceable(ideMcpPath, "Qoder IDE", options.force, isManagedQoderIdeMcpServer);
|
|
357
|
+
const warnings = await updateQoderSettings({
|
|
358
|
+
...options,
|
|
359
|
+
path: settingsPath,
|
|
360
|
+
});
|
|
361
|
+
await updateQoderIdeSettings({ ...options, path: ideMcpPath });
|
|
362
|
+
await writeMarkedFile({
|
|
363
|
+
path: guidancePath,
|
|
364
|
+
startMarker: ZVEC_GREP_AGENTS_START,
|
|
365
|
+
endMarker: ZVEC_GREP_AGENTS_END,
|
|
366
|
+
block: agentGuidanceBlock({
|
|
367
|
+
search: NAMESPACED_SEARCH_TOOL,
|
|
368
|
+
rg: NAMESPACED_RG_TOOL,
|
|
369
|
+
qoderAuthorizationRecovery: true,
|
|
370
|
+
}),
|
|
371
|
+
force: true,
|
|
372
|
+
});
|
|
373
|
+
for (const warning of warnings) {
|
|
374
|
+
console.warn(` warning ${warning}`);
|
|
375
|
+
}
|
|
376
|
+
return { files: [settingsPath, ideMcpPath, guidancePath] };
|
|
377
|
+
}
|
|
378
|
+
async function uninstallQoderIntegration() {
|
|
379
|
+
const qoderHome = resolveQoderHome();
|
|
380
|
+
const settingsPath = resolve(qoderHome, "settings.json");
|
|
381
|
+
const ideMcpPaths = qoderIdeMcpPathsForUninstall();
|
|
382
|
+
const guidancePath = resolve(qoderHome, "AGENTS.md");
|
|
383
|
+
await removeQoderSettings(settingsPath);
|
|
384
|
+
for (const path of ideMcpPaths) {
|
|
385
|
+
await removeQoderIdeSettings(path);
|
|
386
|
+
}
|
|
387
|
+
await removeMarkedFile({
|
|
388
|
+
path: guidancePath,
|
|
389
|
+
startMarker: ZVEC_GREP_AGENTS_START,
|
|
390
|
+
endMarker: ZVEC_GREP_AGENTS_END,
|
|
391
|
+
});
|
|
392
|
+
return { files: [settingsPath, ...ideMcpPaths, guidancePath] };
|
|
393
|
+
}
|
|
394
|
+
async function resolveInstallers(parsed, action) {
|
|
395
|
+
const detected = await detectAgentInstallers();
|
|
396
|
+
const targetTokens = [
|
|
397
|
+
...(parsed.options.installTargets ?? []),
|
|
398
|
+
...parsed.positionals,
|
|
399
|
+
];
|
|
400
|
+
if (targetTokens.length > 0) {
|
|
401
|
+
return installersFromTokens(targetTokens, detected);
|
|
402
|
+
}
|
|
403
|
+
if (parsed.options.yes === true ||
|
|
404
|
+
!process.stdin.isTTY ||
|
|
405
|
+
!process.stdout.isTTY) {
|
|
406
|
+
return installersFromTokens(["auto"], detected);
|
|
407
|
+
}
|
|
408
|
+
return promptInstallers(action, detected);
|
|
409
|
+
}
|
|
410
|
+
async function resolveInstallTransport(parsed) {
|
|
411
|
+
if (parsed.options.installMcpTransport) {
|
|
412
|
+
return parsed.options.installMcpTransport;
|
|
413
|
+
}
|
|
414
|
+
if (parsed.options.yes === true ||
|
|
415
|
+
!process.stdin.isTTY ||
|
|
416
|
+
!process.stdout.isTTY) {
|
|
417
|
+
return "stdio";
|
|
418
|
+
}
|
|
419
|
+
const readline = createInterface({
|
|
420
|
+
input: process.stdin,
|
|
421
|
+
output: process.stdout,
|
|
422
|
+
});
|
|
423
|
+
try {
|
|
424
|
+
const answer = await readline.question("MCP transport [stdio] (stdio/http): ");
|
|
425
|
+
const value = answer.trim().toLowerCase();
|
|
426
|
+
if (!value || value === "stdio")
|
|
427
|
+
return "stdio";
|
|
428
|
+
if (value === "http")
|
|
429
|
+
return "http";
|
|
430
|
+
throw new Error("MCP transport must be stdio or http");
|
|
431
|
+
}
|
|
432
|
+
finally {
|
|
433
|
+
readline.close();
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
async function promptInstallers(action, detected) {
|
|
437
|
+
console.log(action === "install"
|
|
438
|
+
? "\nChoose agent integrations\n"
|
|
439
|
+
: "\nChoose integrations to remove\n");
|
|
440
|
+
const selected = await promptInstallerSelection(detected);
|
|
441
|
+
return AGENT_INSTALLERS.filter((installer) => selected.has(installer.id));
|
|
442
|
+
}
|
|
443
|
+
function installersFromTokens(tokens, detected = new Set()) {
|
|
444
|
+
const normalized = tokens.flatMap(splitTargetTokens);
|
|
445
|
+
if (normalized.length === 0) {
|
|
446
|
+
return installersFromTokens(["auto"], detected);
|
|
447
|
+
}
|
|
448
|
+
const selected = new Map();
|
|
449
|
+
for (const token of normalized) {
|
|
450
|
+
const lower = token.toLowerCase();
|
|
451
|
+
if (lower === "none") {
|
|
452
|
+
return [];
|
|
453
|
+
}
|
|
454
|
+
if (lower === "auto") {
|
|
455
|
+
for (const installer of AGENT_INSTALLERS) {
|
|
456
|
+
if (detected.has(installer.id)) {
|
|
457
|
+
selected.set(installer.id, installer);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
if (lower === "all") {
|
|
463
|
+
for (const installer of AGENT_INSTALLERS) {
|
|
464
|
+
selected.set(installer.id, installer);
|
|
465
|
+
}
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const numbered = /^\d+$/.test(lower) ? Number(lower) : Number.NaN;
|
|
469
|
+
if (Number.isInteger(numbered) &&
|
|
470
|
+
numbered >= 1 &&
|
|
471
|
+
numbered <= AGENT_INSTALLERS.length) {
|
|
472
|
+
const installer = AGENT_INSTALLERS[numbered - 1];
|
|
473
|
+
selected.set(installer.id, installer);
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
const installer = AGENT_INSTALLERS.find((candidate) => candidate.id === lower ||
|
|
477
|
+
candidate.aliases?.includes(lower) ||
|
|
478
|
+
candidate.label.toLowerCase() === lower);
|
|
479
|
+
if (!installer) {
|
|
480
|
+
throw new Error(`Unknown install target: ${token}`);
|
|
481
|
+
}
|
|
482
|
+
selected.set(installer.id, installer);
|
|
483
|
+
}
|
|
484
|
+
return [...selected.values()];
|
|
485
|
+
}
|
|
486
|
+
async function detectAgentInstallers() {
|
|
487
|
+
const detected = new Set();
|
|
488
|
+
const checks = await Promise.all(AGENT_INSTALLERS.map(async (installer) => {
|
|
489
|
+
const executableDetected = (await Promise.all(installer.executables.map((executable) => executableIsAvailable(executable)))).some(Boolean);
|
|
490
|
+
return {
|
|
491
|
+
installer,
|
|
492
|
+
available: executableDetected || (await installer.detect?.()) === true,
|
|
493
|
+
};
|
|
494
|
+
}));
|
|
495
|
+
for (const check of checks) {
|
|
496
|
+
if (check.available) {
|
|
497
|
+
detected.add(check.installer.id);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return detected;
|
|
501
|
+
}
|
|
502
|
+
async function executableIsAvailable(executable) {
|
|
503
|
+
const path = process.env.PATH ?? "";
|
|
504
|
+
const extensions = process.platform === "win32"
|
|
505
|
+
? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")
|
|
506
|
+
: [""];
|
|
507
|
+
for (const pathEntry of path.split(delimiter)) {
|
|
508
|
+
const directory = pathEntry || process.cwd();
|
|
509
|
+
for (const extension of extensions) {
|
|
510
|
+
try {
|
|
511
|
+
await access(resolve(directory, `${executable}${extension.toLowerCase()}`), fileSystemConstants.X_OK);
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
514
|
+
catch {
|
|
515
|
+
// Continue searching PATH.
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
async function promptInstallerSelection(detected) {
|
|
522
|
+
if (typeof process.stdin.setRawMode !== "function") {
|
|
523
|
+
return promptInstallerLineSelection(detected);
|
|
524
|
+
}
|
|
525
|
+
let activeIndex = Math.max(0, AGENT_INSTALLERS.findIndex((installer) => detected.has(installer.id)));
|
|
526
|
+
let renderedLineCount = 0;
|
|
527
|
+
const render = () => {
|
|
528
|
+
const lines = installerSelectionLines(activeIndex, detected);
|
|
529
|
+
if (renderedLineCount > 0) {
|
|
530
|
+
process.stdout.write(`\u001b[${renderedLineCount}A`);
|
|
531
|
+
}
|
|
532
|
+
for (const line of lines) {
|
|
533
|
+
process.stdout.write(`\u001b[2K${line}\n`);
|
|
534
|
+
}
|
|
535
|
+
renderedLineCount = lines.length;
|
|
536
|
+
};
|
|
537
|
+
render();
|
|
538
|
+
return new Promise((resolveSelection) => {
|
|
539
|
+
const wasRaw = process.stdin.isRaw === true;
|
|
540
|
+
const finish = (result) => {
|
|
541
|
+
process.stdin.removeListener("data", onData);
|
|
542
|
+
process.stdin.setRawMode(wasRaw);
|
|
543
|
+
if (!wasRaw)
|
|
544
|
+
process.stdin.pause();
|
|
545
|
+
process.stdout.write("\n");
|
|
546
|
+
resolveSelection(result);
|
|
547
|
+
};
|
|
548
|
+
const onData = (chunk) => {
|
|
549
|
+
const key = chunk.toString();
|
|
550
|
+
if (key === "\u0003" || key === "\u001b") {
|
|
551
|
+
finish(new Set());
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
if (key === "\u001b[A") {
|
|
555
|
+
activeIndex =
|
|
556
|
+
(activeIndex - 1 + AGENT_INSTALLERS.length) % AGENT_INSTALLERS.length;
|
|
557
|
+
render();
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
if (key === "\u001b[B") {
|
|
561
|
+
activeIndex = (activeIndex + 1) % AGENT_INSTALLERS.length;
|
|
562
|
+
render();
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (key.includes("\r") || key.includes("\n")) {
|
|
566
|
+
finish(new Set([AGENT_INSTALLERS[activeIndex].id]));
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
process.stdin.setRawMode(true);
|
|
570
|
+
process.stdin.resume();
|
|
571
|
+
process.stdin.on("data", onData);
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
export function installerSelectionLines(activeIndex, detected) {
|
|
575
|
+
const labelWidth = Math.max(...AGENT_INSTALLERS.map((installer) => installer.label.length));
|
|
576
|
+
return [
|
|
577
|
+
...AGENT_INSTALLERS.map((installer, index) => {
|
|
578
|
+
const marker = index === activeIndex ? installSuccess("●") : installDim("○");
|
|
579
|
+
const label = installer.label.padEnd(labelWidth);
|
|
580
|
+
const status = detected.has(installer.id)
|
|
581
|
+
? installDim("detected")
|
|
582
|
+
: installDim("not found");
|
|
583
|
+
return ` ${marker} ${label} ${status}`;
|
|
584
|
+
}),
|
|
585
|
+
"",
|
|
586
|
+
installDim(" Use ↑↓ to move · Enter to select"),
|
|
587
|
+
];
|
|
588
|
+
}
|
|
589
|
+
async function promptInstallerLineSelection(detected) {
|
|
590
|
+
AGENT_INSTALLERS.forEach((installer, index) => {
|
|
591
|
+
console.log(` ${index + 1}. ${installer.label} (${detected.has(installer.id) ? "detected" : "not found"})`);
|
|
592
|
+
});
|
|
593
|
+
const defaults = [...detected].join(",") || "none";
|
|
594
|
+
const readline = createInterface({
|
|
595
|
+
input: process.stdin,
|
|
596
|
+
output: process.stdout,
|
|
597
|
+
});
|
|
598
|
+
try {
|
|
599
|
+
const answer = await readline.question(`Agents [${defaults}]: `);
|
|
600
|
+
const value = answer.trim() || defaults;
|
|
601
|
+
return new Set(installersFromTokens(splitTargetTokens(value), detected).map((installer) => installer.id));
|
|
602
|
+
}
|
|
603
|
+
finally {
|
|
604
|
+
readline.close();
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function printInstallHeader() {
|
|
608
|
+
console.log(installAccent("zvec-grep setup"));
|
|
609
|
+
console.log(installDim("─".repeat(40)));
|
|
610
|
+
}
|
|
611
|
+
function installSuccessMark() {
|
|
612
|
+
return installSuccess("✓");
|
|
613
|
+
}
|
|
614
|
+
function installMutedMark() {
|
|
615
|
+
return installDim("○");
|
|
616
|
+
}
|
|
617
|
+
function installSuccess(value) {
|
|
618
|
+
return installStyle("32", value);
|
|
619
|
+
}
|
|
620
|
+
function installAccent(value) {
|
|
621
|
+
return installStyle("36", value);
|
|
622
|
+
}
|
|
623
|
+
function installDim(value) {
|
|
624
|
+
return installStyle("2", value);
|
|
625
|
+
}
|
|
626
|
+
function installStyle(code, value) {
|
|
627
|
+
return process.stdout.isTTY && process.env.NO_COLOR === undefined
|
|
628
|
+
? `\u001b[${code}m${value}\u001b[0m`
|
|
629
|
+
: value;
|
|
630
|
+
}
|
|
631
|
+
async function ensureInstalledServer(mcpToolset) {
|
|
632
|
+
if (process.env.ZVEC_GREP_INSTALL_SKIP_SERVER === "1") {
|
|
633
|
+
return { running: false, ready: false };
|
|
634
|
+
}
|
|
635
|
+
const { startServer } = await import("../daemon/server-controller.js");
|
|
636
|
+
return startServer({ cliPath: process.argv[1], mcpToolset });
|
|
637
|
+
}
|
|
638
|
+
function splitTargetTokens(value) {
|
|
639
|
+
return value
|
|
640
|
+
.split(/[,\s]+/)
|
|
641
|
+
.map((target) => target.trim())
|
|
642
|
+
.filter((target) => target.length > 0);
|
|
643
|
+
}
|
|
644
|
+
function resolveCodexHome() {
|
|
645
|
+
return resolve(process.env.CODEX_HOME ?? resolve(homedir(), ".codex"));
|
|
646
|
+
}
|
|
647
|
+
function resolveClaudeConfigDirectory() {
|
|
648
|
+
return resolve(process.env.CLAUDE_CONFIG_DIR ?? resolve(homedir(), ".claude"));
|
|
649
|
+
}
|
|
650
|
+
function resolveClaudeMcpConfigPath() {
|
|
651
|
+
return process.env.CLAUDE_CONFIG_DIR
|
|
652
|
+
? resolve(process.env.CLAUDE_CONFIG_DIR, ".claude.json")
|
|
653
|
+
: resolve(homedir(), ".claude.json");
|
|
654
|
+
}
|
|
655
|
+
async function resolveOpenCodeConfigPath() {
|
|
656
|
+
const configured = process.env.OPENCODE_CONFIG?.trim();
|
|
657
|
+
if (configured)
|
|
658
|
+
return { path: resolve(configured) };
|
|
659
|
+
const configDirectory = resolve(process.env.XDG_CONFIG_HOME?.trim() || resolve(homedir(), ".config"), "opencode");
|
|
660
|
+
const jsoncPath = resolve(configDirectory, "opencode.jsonc");
|
|
661
|
+
const jsonPath = resolve(configDirectory, "opencode.json");
|
|
662
|
+
const [jsoncExists, jsonExists] = await Promise.all([
|
|
663
|
+
pathExists(jsoncPath),
|
|
664
|
+
pathExists(jsonPath),
|
|
665
|
+
]);
|
|
666
|
+
if (jsoncExists) {
|
|
667
|
+
return {
|
|
668
|
+
path: jsoncPath,
|
|
669
|
+
managedCleanupPaths: [jsoncPath, jsonPath],
|
|
670
|
+
note: jsonExists
|
|
671
|
+
? "both opencode.jsonc and opencode.json exist; selected opencode.jsonc"
|
|
672
|
+
: undefined,
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
return {
|
|
676
|
+
path: jsonPath,
|
|
677
|
+
managedCleanupPaths: [jsoncPath, jsonPath],
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
function resolveCursorConfigPath() {
|
|
681
|
+
return resolve(process.env.CURSOR_CONFIG_DIR ?? resolve(homedir(), ".cursor"), "mcp.json");
|
|
682
|
+
}
|
|
683
|
+
function resolveQoderHome() {
|
|
684
|
+
return resolve(process.env.QODER_CONFIG_DIR || resolve(homedir(), ".qoder"));
|
|
685
|
+
}
|
|
686
|
+
function resolveQoderIdeMcpPath() {
|
|
687
|
+
const configured = process.env.QODER_IDE_MCP_PATH?.trim();
|
|
688
|
+
if (configured)
|
|
689
|
+
return resolve(configured);
|
|
690
|
+
return resolve(homedir(), ".qoder", "mcp.json");
|
|
691
|
+
}
|
|
692
|
+
function qoderIdeMcpPathsForUninstall() {
|
|
693
|
+
return [resolveQoderIdeMcpPath()];
|
|
694
|
+
}
|
|
695
|
+
async function qoderIdeIsAvailable() {
|
|
696
|
+
const configured = process.env.QODER_IDE_EXECUTABLE?.trim();
|
|
697
|
+
const candidates = configured
|
|
698
|
+
? [resolve(configured)]
|
|
699
|
+
: qoderIdeExecutableCandidates();
|
|
700
|
+
for (const candidate of candidates) {
|
|
701
|
+
try {
|
|
702
|
+
await access(candidate, fileSystemConstants.X_OK);
|
|
703
|
+
return true;
|
|
704
|
+
}
|
|
705
|
+
catch {
|
|
706
|
+
// Try the next platform-specific installation path.
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
function qoderIdeExecutableCandidates() {
|
|
712
|
+
if (process.platform === "darwin") {
|
|
713
|
+
return [
|
|
714
|
+
resolve(homedir(), "Applications", "Qoder IDE.app", "Contents", "MacOS", "Qoder"),
|
|
715
|
+
resolve("/Applications", "Qoder IDE.app", "Contents", "MacOS", "Qoder"),
|
|
716
|
+
resolve(homedir(), "Applications", "Qoder.app", "Contents", "MacOS", "Qoder"),
|
|
717
|
+
resolve("/Applications", "Qoder.app", "Contents", "MacOS", "Qoder"),
|
|
718
|
+
];
|
|
719
|
+
}
|
|
720
|
+
if (process.platform === "win32") {
|
|
721
|
+
const localPrograms = resolve(process.env.LOCALAPPDATA || resolve(homedir(), "AppData", "Local"), "Programs");
|
|
722
|
+
return [
|
|
723
|
+
resolve(localPrograms, "Qoder IDE", "Qoder IDE.exe"),
|
|
724
|
+
resolve(localPrograms, "Qoder", "Qoder.exe"),
|
|
725
|
+
...(process.env.ProgramFiles
|
|
726
|
+
? [
|
|
727
|
+
resolve(process.env.ProgramFiles, "Qoder IDE", "Qoder IDE.exe"),
|
|
728
|
+
resolve(process.env.ProgramFiles, "Qoder", "Qoder.exe"),
|
|
729
|
+
]
|
|
730
|
+
: []),
|
|
731
|
+
];
|
|
732
|
+
}
|
|
733
|
+
return [
|
|
734
|
+
"/usr/share/qoder-ide/qoder-ide",
|
|
735
|
+
"/usr/share/qoder-ide/bin/qoder-ide",
|
|
736
|
+
"/usr/bin/qoder-ide",
|
|
737
|
+
"/usr/share/qoder/bin/qoder",
|
|
738
|
+
];
|
|
739
|
+
}
|
|
740
|
+
async function resolveQwenHome() {
|
|
741
|
+
const defaultQwenHome = resolve(homedir(), ".qwen");
|
|
742
|
+
const configured = process.env.QWEN_HOME;
|
|
743
|
+
if (configured) {
|
|
744
|
+
return resolveQwenHomeValue(configured);
|
|
745
|
+
}
|
|
746
|
+
// Qwen Code treats an explicitly exported empty QWEN_HOME as the default
|
|
747
|
+
// directory and does not replace it from a user .env file.
|
|
748
|
+
if (Object.hasOwn(process.env, "QWEN_HOME")) {
|
|
749
|
+
return defaultQwenHome;
|
|
750
|
+
}
|
|
751
|
+
const discovered = (await readQwenHomeFromEnv(resolve(defaultQwenHome, ".env"))) ??
|
|
752
|
+
(await readQwenHomeFromEnv(resolve(homedir(), ".env")));
|
|
753
|
+
return discovered ? resolveQwenHomeValue(discovered) : defaultQwenHome;
|
|
754
|
+
}
|
|
755
|
+
async function readQwenHomeFromEnv(path) {
|
|
756
|
+
const source = await readTextFileIfExists(path);
|
|
757
|
+
if (!source)
|
|
758
|
+
return undefined;
|
|
759
|
+
try {
|
|
760
|
+
return parseEnv(source).QWEN_HOME || undefined;
|
|
761
|
+
}
|
|
762
|
+
catch {
|
|
763
|
+
// Match Qwen Code's quiet dotenv bootstrap behavior.
|
|
764
|
+
return undefined;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
function resolveQwenHomeValue(value) {
|
|
768
|
+
if (value === "~")
|
|
769
|
+
return homedir();
|
|
770
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
771
|
+
return resolve(homedir(), ...value
|
|
772
|
+
.slice(2)
|
|
773
|
+
.split(/[/\\]+/)
|
|
774
|
+
.filter(Boolean));
|
|
775
|
+
}
|
|
776
|
+
return resolve(value);
|
|
777
|
+
}
|
|
778
|
+
async function installJsonMcpServer(options) {
|
|
779
|
+
const config = await readJsonObject(options.path);
|
|
780
|
+
const existingContainer = config[options.containerKey];
|
|
781
|
+
if (existingContainer !== undefined && !isJsonObject(existingContainer)) {
|
|
782
|
+
throw new Error(`Expected ${options.containerKey} in ${options.path} to be a JSON object`);
|
|
783
|
+
}
|
|
784
|
+
const container = { ...(existingContainer ?? {}) };
|
|
785
|
+
const existingServer = container.zvec_grep;
|
|
786
|
+
if (existingServer !== undefined &&
|
|
787
|
+
!isManagedJsonMcpServer(existingServer) &&
|
|
788
|
+
!options.force) {
|
|
789
|
+
throw new Error(`Existing unmanaged zvec_grep MCP server found in ${options.path}. Re-run with --force to replace it for ${options.label}.`);
|
|
790
|
+
}
|
|
791
|
+
container.zvec_grep = options.server;
|
|
792
|
+
config[options.containerKey] = container;
|
|
793
|
+
await writeTextFileAtomic(options.path, `${JSON.stringify(config, null, 2)}\n`);
|
|
794
|
+
}
|
|
795
|
+
async function uninstallJsonMcpServer(path, containerKey) {
|
|
796
|
+
const existing = await readTextFileIfExists(path);
|
|
797
|
+
if (!existing)
|
|
798
|
+
return;
|
|
799
|
+
const config = parseJsonObject(path, existing);
|
|
800
|
+
const container = config[containerKey];
|
|
801
|
+
if (!isJsonObject(container))
|
|
802
|
+
return;
|
|
803
|
+
if (!isManagedJsonMcpServer(container.zvec_grep))
|
|
804
|
+
return;
|
|
805
|
+
const nextContainer = { ...container };
|
|
806
|
+
delete nextContainer.zvec_grep;
|
|
807
|
+
if (Object.keys(nextContainer).length === 0) {
|
|
808
|
+
delete config[containerKey];
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
config[containerKey] = nextContainer;
|
|
812
|
+
}
|
|
813
|
+
await writeTextFileAtomic(path, `${JSON.stringify(config, null, 2)}\n`);
|
|
814
|
+
}
|
|
815
|
+
async function updateClaudeMcpConfig(options) {
|
|
816
|
+
const root = await readJsonObject(options.path);
|
|
817
|
+
const currentServers = root.mcpServers;
|
|
818
|
+
if (currentServers !== undefined &&
|
|
819
|
+
!isJsonObject(currentServers) &&
|
|
820
|
+
!options.force) {
|
|
821
|
+
throw new Error(`Invalid mcpServers configuration in ${options.path}. Re-run with --force to replace it.`);
|
|
822
|
+
}
|
|
823
|
+
const mcpServers = isJsonObject(currentServers) ? currentServers : {};
|
|
824
|
+
const current = mcpServers.zvec_grep;
|
|
825
|
+
if (current !== undefined &&
|
|
826
|
+
!isManagedJsonMcpServer(current) &&
|
|
827
|
+
!options.force) {
|
|
828
|
+
throw new Error(`Existing Claude Code MCP server "zvec_grep" found in ${options.path}. Re-run with --force to replace it.`);
|
|
829
|
+
}
|
|
830
|
+
const existingServer = isJsonObject(current) ? current : {};
|
|
831
|
+
const { type: _type, url: _url, command: _command, args: _args, headers: _headers, ...retained } = existingServer;
|
|
832
|
+
mcpServers.zvec_grep =
|
|
833
|
+
options.transport === "stdio"
|
|
834
|
+
? {
|
|
835
|
+
...retained,
|
|
836
|
+
type: "stdio",
|
|
837
|
+
command: "zg",
|
|
838
|
+
args: stdioArgs(options.mcpToolset),
|
|
839
|
+
}
|
|
840
|
+
: {
|
|
841
|
+
...retained,
|
|
842
|
+
type: "http",
|
|
843
|
+
url: resolveServerUrl(),
|
|
844
|
+
...(options.tokenEnv
|
|
845
|
+
? {
|
|
846
|
+
headers: {
|
|
847
|
+
Authorization: `Bearer \${${options.tokenEnv}}`,
|
|
848
|
+
},
|
|
849
|
+
}
|
|
850
|
+
: {}),
|
|
851
|
+
};
|
|
852
|
+
root.mcpServers = mcpServers;
|
|
853
|
+
await writeJsonObject(options.path, root);
|
|
854
|
+
}
|
|
855
|
+
async function removeClaudeMcpConfig(path) {
|
|
856
|
+
const source = await readTextFileIfExists(path);
|
|
857
|
+
if (!source.trim())
|
|
858
|
+
return;
|
|
859
|
+
const root = parseJsonObject(path, source);
|
|
860
|
+
if (!isJsonObject(root.mcpServers))
|
|
861
|
+
return;
|
|
862
|
+
const current = root.mcpServers.zvec_grep;
|
|
863
|
+
if (!isManagedJsonMcpServer(current))
|
|
864
|
+
return;
|
|
865
|
+
delete root.mcpServers.zvec_grep;
|
|
866
|
+
if (Object.keys(root.mcpServers).length === 0) {
|
|
867
|
+
delete root.mcpServers;
|
|
868
|
+
}
|
|
869
|
+
await writeJsonObject(path, root);
|
|
870
|
+
}
|
|
871
|
+
async function updateClaudePermissionSettings(path, grant) {
|
|
872
|
+
const source = await readTextFileIfExists(path);
|
|
873
|
+
if (!source.trim() && !grant)
|
|
874
|
+
return;
|
|
875
|
+
const root = source.trim() ? parseJsonObject(path, source) : {};
|
|
876
|
+
const currentPermissions = root.permissions;
|
|
877
|
+
if (currentPermissions !== undefined && !isJsonObject(currentPermissions)) {
|
|
878
|
+
throw new Error(`Invalid permissions configuration in ${path}.`);
|
|
879
|
+
}
|
|
880
|
+
const permissions = isJsonObject(currentPermissions)
|
|
881
|
+
? currentPermissions
|
|
882
|
+
: {};
|
|
883
|
+
const currentAllow = permissions.allow;
|
|
884
|
+
if (currentAllow !== undefined && !Array.isArray(currentAllow)) {
|
|
885
|
+
throw new Error(`Invalid permissions.allow configuration in ${path}.`);
|
|
886
|
+
}
|
|
887
|
+
if (Array.isArray(currentAllow) &&
|
|
888
|
+
currentAllow.some((item) => typeof item !== "string")) {
|
|
889
|
+
throw new Error(`Invalid permissions.allow rule in ${path}.`);
|
|
890
|
+
}
|
|
891
|
+
const allow = Array.isArray(currentAllow)
|
|
892
|
+
? currentAllow.slice()
|
|
893
|
+
: [];
|
|
894
|
+
if (grant) {
|
|
895
|
+
if (!allow.includes(CLAUDE_MCP_PERMISSION)) {
|
|
896
|
+
allow.push(CLAUDE_MCP_PERMISSION);
|
|
897
|
+
}
|
|
898
|
+
permissions.allow = allow;
|
|
899
|
+
root.permissions = permissions;
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
const retained = allow.filter((item) => item !== CLAUDE_MCP_PERMISSION);
|
|
903
|
+
if (retained.length > 0)
|
|
904
|
+
permissions.allow = retained;
|
|
905
|
+
else
|
|
906
|
+
delete permissions.allow;
|
|
907
|
+
if (Object.keys(permissions).length > 0)
|
|
908
|
+
root.permissions = permissions;
|
|
909
|
+
else
|
|
910
|
+
delete root.permissions;
|
|
911
|
+
}
|
|
912
|
+
await writeJsonObject(path, root);
|
|
913
|
+
}
|
|
914
|
+
async function updateQwenSettings(options) {
|
|
915
|
+
const root = await updateJsoncMcpSettings({
|
|
916
|
+
path: options.path,
|
|
917
|
+
force: options.force,
|
|
918
|
+
label: "Qwen Code",
|
|
919
|
+
server: qwenMcpServer(options),
|
|
920
|
+
isManaged: isManagedQwenMcpServer,
|
|
921
|
+
});
|
|
922
|
+
return contextFileWarnings(root, "QWEN.md");
|
|
923
|
+
}
|
|
924
|
+
async function removeQwenSettings(path) {
|
|
925
|
+
await removeJsoncMcpSettings(path, "Qwen Code", isManagedQwenMcpServer);
|
|
926
|
+
}
|
|
927
|
+
async function updateQoderSettings(options) {
|
|
928
|
+
const existing = await readTextFileIfExists(options.path);
|
|
929
|
+
let source = existing.trim() ? existing : "{}\n";
|
|
930
|
+
const root = parseJsoncSettings(options.path, source, "Qoder");
|
|
931
|
+
validateMcpSettingsContainer(options.path, root);
|
|
932
|
+
const mcpServers = isJsonObject(root.mcpServers) ? root.mcpServers : {};
|
|
933
|
+
const current = mcpServers.zvec_grep;
|
|
934
|
+
const currentIsManaged = isManagedJsonMcpServer(current);
|
|
935
|
+
if (current !== undefined && !currentIsManaged && !options.force) {
|
|
936
|
+
throw new Error(`Existing unmanaged zvec_grep MCP server found in ${options.path}. Re-run with --force to replace it for Qoder.`);
|
|
937
|
+
}
|
|
938
|
+
const allow = qoderPermissionAllowRules(options.path, root);
|
|
939
|
+
const ownedPermissions = new Set([
|
|
940
|
+
...(currentIsManaged ? qoderOwnedPermissionRules(current) : []),
|
|
941
|
+
...QODER_MCP_PERMISSIONS.filter((permission) => !allow.includes(permission)),
|
|
942
|
+
]);
|
|
943
|
+
const alwaysAllow = currentIsManaged ? qoderAlwaysAllowTools(current) : [];
|
|
944
|
+
for (const { tool } of QODER_MCP_PERMISSION_RULES) {
|
|
945
|
+
if (!alwaysAllow.includes(tool)) {
|
|
946
|
+
alwaysAllow.push(tool);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
source = editJsonWithComments(source, ["mcpServers", "zvec_grep"], qoderMcpServer(options, alwaysAllow, [...ownedPermissions]));
|
|
950
|
+
source = addQoderPermissionRules(source, options.path, root);
|
|
951
|
+
await writeTextFileAtomic(options.path, ensureTrailingNewline(source));
|
|
952
|
+
return contextFileWarnings(root, "AGENTS.md");
|
|
953
|
+
}
|
|
954
|
+
async function removeQoderSettings(path) {
|
|
955
|
+
const existing = await readTextFileIfExists(path);
|
|
956
|
+
if (!existing.trim())
|
|
957
|
+
return;
|
|
958
|
+
let source = existing;
|
|
959
|
+
const root = parseJsoncSettings(path, source, "Qoder");
|
|
960
|
+
validateMcpSettingsContainer(path, root);
|
|
961
|
+
qoderPermissionAllowRules(path, root);
|
|
962
|
+
const mcpServers = isJsonObject(root.mcpServers) ? root.mcpServers : {};
|
|
963
|
+
const current = mcpServers.zvec_grep;
|
|
964
|
+
if (!isManagedJsonMcpServer(current))
|
|
965
|
+
return;
|
|
966
|
+
const ownedPermissions = qoderOwnedPermissionRules(current);
|
|
967
|
+
source = hasJsoncComments(source)
|
|
968
|
+
? removeJsoncPropertyPreservingComments(source, ["mcpServers", "zvec_grep"])
|
|
969
|
+
: editJsonWithComments(source, Object.keys(mcpServers).length === 1
|
|
970
|
+
? ["mcpServers"]
|
|
971
|
+
: ["mcpServers", "zvec_grep"], undefined);
|
|
972
|
+
source = removeQoderPermissionRules(source, path, root, ownedPermissions);
|
|
973
|
+
if (source !== existing) {
|
|
974
|
+
await writeTextFileAtomic(path, ensureTrailingNewline(source));
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
async function assertQoderPermissionSettingsValid(path) {
|
|
978
|
+
const source = await readTextFileIfExists(path);
|
|
979
|
+
if (!source.trim())
|
|
980
|
+
return;
|
|
981
|
+
const root = parseJsoncSettings(path, source, "Qoder");
|
|
982
|
+
qoderPermissionAllowRules(path, root);
|
|
983
|
+
}
|
|
984
|
+
function addQoderPermissionRules(source, path, root) {
|
|
985
|
+
const allow = qoderPermissionAllowRules(path, root);
|
|
986
|
+
const missing = QODER_MCP_PERMISSIONS.filter((permission) => !allow.includes(permission));
|
|
987
|
+
if (missing.length === 0)
|
|
988
|
+
return source;
|
|
989
|
+
const hasAllowArray = isJsonObject(root.permissions) && Array.isArray(root.permissions.allow);
|
|
990
|
+
if (!hasAllowArray) {
|
|
991
|
+
return editJsonWithComments(source, ["permissions", "allow"], [...allow, ...missing]);
|
|
992
|
+
}
|
|
993
|
+
return insertJsoncArrayValuesAtStart(source, ["permissions", "allow"], missing);
|
|
994
|
+
}
|
|
995
|
+
function removeQoderPermissionRules(source, path, root, ownedPermissions) {
|
|
996
|
+
const allow = qoderPermissionAllowRules(path, root);
|
|
997
|
+
let removedCount = 0;
|
|
998
|
+
for (const permission of ownedPermissions) {
|
|
999
|
+
const removal = removeFirstJsoncArrayStringValue(source, ["permissions", "allow"], permission);
|
|
1000
|
+
source = removal.source;
|
|
1001
|
+
if (removal.removed)
|
|
1002
|
+
removedCount += 1;
|
|
1003
|
+
}
|
|
1004
|
+
if (removedCount === 0)
|
|
1005
|
+
return source;
|
|
1006
|
+
if (removedCount < allow.length)
|
|
1007
|
+
return source;
|
|
1008
|
+
const permissions = root.permissions;
|
|
1009
|
+
const hasOtherPermissionSettings = Object.keys(permissions).some((key) => key !== "allow");
|
|
1010
|
+
if (hasJsoncComments(source)) {
|
|
1011
|
+
return source;
|
|
1012
|
+
}
|
|
1013
|
+
if (hasOtherPermissionSettings) {
|
|
1014
|
+
return editJsonWithComments(source, ["permissions", "allow"], undefined);
|
|
1015
|
+
}
|
|
1016
|
+
return editJsonWithComments(source, ["permissions"], undefined);
|
|
1017
|
+
}
|
|
1018
|
+
function qoderOwnedPermissionRules(server) {
|
|
1019
|
+
if (!isJsonObject(server) || typeof server.description !== "string") {
|
|
1020
|
+
return [];
|
|
1021
|
+
}
|
|
1022
|
+
const description = server.description;
|
|
1023
|
+
const prefix = [
|
|
1024
|
+
QODER_PERMISSION_OWNERSHIP_PREFIX,
|
|
1025
|
+
LEGACY_QODER_PERMISSION_OWNERSHIP_PREFIX,
|
|
1026
|
+
].find((candidate) => description.startsWith(candidate));
|
|
1027
|
+
if (!prefix)
|
|
1028
|
+
return [];
|
|
1029
|
+
const alwaysAllow = description
|
|
1030
|
+
.slice(prefix.length)
|
|
1031
|
+
.split(",")
|
|
1032
|
+
.filter(Boolean);
|
|
1033
|
+
return QODER_MCP_PERMISSION_RULES.filter(({ tool }) => alwaysAllow.includes(tool)).map(({ permission }) => permission);
|
|
1034
|
+
}
|
|
1035
|
+
function qoderMcpDescription(ownedPermissions) {
|
|
1036
|
+
const tools = QODER_MCP_PERMISSION_RULES.filter(({ permission }) => ownedPermissions.includes(permission)).map(({ tool }) => tool);
|
|
1037
|
+
return tools.length > 0
|
|
1038
|
+
? `${QODER_PERMISSION_OWNERSHIP_PREFIX}${tools.join(",")}`
|
|
1039
|
+
: QODER_CLI_MCP_DESCRIPTION;
|
|
1040
|
+
}
|
|
1041
|
+
function qoderAlwaysAllowTools(server) {
|
|
1042
|
+
if (!isJsonObject(server) || !Array.isArray(server.alwaysAllow))
|
|
1043
|
+
return [];
|
|
1044
|
+
return server.alwaysAllow.filter((tool) => typeof tool === "string");
|
|
1045
|
+
}
|
|
1046
|
+
function insertJsoncArrayValuesAtStart(source, path, values) {
|
|
1047
|
+
if (values.length === 0)
|
|
1048
|
+
return source;
|
|
1049
|
+
const root = parseTree(source);
|
|
1050
|
+
const arrayNode = root ? findNodeAtLocation(root, [...path]) : undefined;
|
|
1051
|
+
if (arrayNode?.type !== "array")
|
|
1052
|
+
return source;
|
|
1053
|
+
const children = arrayNode.children ?? [];
|
|
1054
|
+
const insertionOffset = arrayNode.offset + 1;
|
|
1055
|
+
const closingBracketOffset = arrayNode.offset + arrayNode.length - 1;
|
|
1056
|
+
const multiline = source
|
|
1057
|
+
.slice(arrayNode.offset, closingBracketOffset + 1)
|
|
1058
|
+
.includes("\n");
|
|
1059
|
+
const serialized = values.map((value) => JSON.stringify(value));
|
|
1060
|
+
let insertion;
|
|
1061
|
+
if (multiline) {
|
|
1062
|
+
const formatting = jsonFormattingOptions(source);
|
|
1063
|
+
const lineStart = source.lastIndexOf("\n", arrayNode.offset - 1) + 1;
|
|
1064
|
+
const propertyIndent = source.slice(lineStart, arrayNode.offset).match(/^[\t ]*/)?.[0] ?? "";
|
|
1065
|
+
const indentUnit = formatting.insertSpaces
|
|
1066
|
+
? " ".repeat(formatting.tabSize ?? 2)
|
|
1067
|
+
: "\t";
|
|
1068
|
+
const itemIndent = `${propertyIndent}${indentUnit}`;
|
|
1069
|
+
const lines = serialized.map((value, index) => `${itemIndent}${value}${index < serialized.length - 1 || children.length > 0 ? "," : ""}`);
|
|
1070
|
+
insertion = `${formatting.eol}${lines.join(formatting.eol)}`;
|
|
1071
|
+
}
|
|
1072
|
+
else {
|
|
1073
|
+
insertion = `${serialized.join(", ")}${children.length > 0 ? ", " : ""}`;
|
|
1074
|
+
}
|
|
1075
|
+
return (source.slice(0, insertionOffset) + insertion + source.slice(insertionOffset));
|
|
1076
|
+
}
|
|
1077
|
+
function removeFirstJsoncArrayStringValue(source, path, value) {
|
|
1078
|
+
const root = parseTree(source);
|
|
1079
|
+
const arrayNode = root ? findNodeAtLocation(root, [...path]) : undefined;
|
|
1080
|
+
if (arrayNode?.type !== "array" || !arrayNode.children) {
|
|
1081
|
+
return { source, removed: false };
|
|
1082
|
+
}
|
|
1083
|
+
const index = arrayNode.children.findIndex((child) => child.type === "string" && child.value === value);
|
|
1084
|
+
if (index < 0)
|
|
1085
|
+
return { source, removed: false };
|
|
1086
|
+
const target = arrayNode.children[index];
|
|
1087
|
+
const previous = arrayNode.children[index - 1];
|
|
1088
|
+
const next = arrayNode.children[index + 1];
|
|
1089
|
+
const ranges = [nodeRange(target)];
|
|
1090
|
+
const commaOffset = next
|
|
1091
|
+
? findComma(source, target.offset + target.length, next.offset)
|
|
1092
|
+
: previous
|
|
1093
|
+
? findComma(source, previous.offset + previous.length, target.offset)
|
|
1094
|
+
: undefined;
|
|
1095
|
+
if (commaOffset !== undefined) {
|
|
1096
|
+
ranges.push({ offset: commaOffset, length: 1 });
|
|
1097
|
+
}
|
|
1098
|
+
return {
|
|
1099
|
+
source: ranges
|
|
1100
|
+
.sort((left, right) => right.offset - left.offset)
|
|
1101
|
+
.reduce((current, range) => current.slice(0, range.offset) +
|
|
1102
|
+
current.slice(range.offset + range.length), source),
|
|
1103
|
+
removed: true,
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
function qoderPermissionAllowRules(path, root) {
|
|
1107
|
+
const currentPermissions = root.permissions;
|
|
1108
|
+
if (currentPermissions !== undefined && !isJsonObject(currentPermissions)) {
|
|
1109
|
+
throw new Error(`Invalid permissions configuration in ${path}.`);
|
|
1110
|
+
}
|
|
1111
|
+
const permissions = isJsonObject(currentPermissions)
|
|
1112
|
+
? currentPermissions
|
|
1113
|
+
: {};
|
|
1114
|
+
const currentAllow = permissions.allow;
|
|
1115
|
+
if (currentAllow !== undefined && !Array.isArray(currentAllow)) {
|
|
1116
|
+
throw new Error(`Invalid permissions.allow configuration in ${path}.`);
|
|
1117
|
+
}
|
|
1118
|
+
if (Array.isArray(currentAllow) &&
|
|
1119
|
+
currentAllow.some((permission) => typeof permission !== "string")) {
|
|
1120
|
+
throw new Error(`Invalid permissions.allow rule in ${path}.`);
|
|
1121
|
+
}
|
|
1122
|
+
return Array.isArray(currentAllow) ? currentAllow : [];
|
|
1123
|
+
}
|
|
1124
|
+
async function updateQoderIdeSettings(options) {
|
|
1125
|
+
await updateJsoncMcpSettings({
|
|
1126
|
+
path: options.path,
|
|
1127
|
+
force: options.force,
|
|
1128
|
+
label: "Qoder IDE",
|
|
1129
|
+
server: qoderIdeMcpServer(options),
|
|
1130
|
+
isManaged: isManagedQoderIdeMcpServer,
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
async function removeQoderIdeSettings(path) {
|
|
1134
|
+
await removeJsoncMcpSettings(path, "Qoder IDE", isManagedQoderIdeMcpServer);
|
|
1135
|
+
}
|
|
1136
|
+
function qwenMcpServer(options) {
|
|
1137
|
+
const timeout = options.mcpToolTimeoutSeconds * 1_000;
|
|
1138
|
+
// Qwen otherwise defers MCP schemas behind tool_search, so the managed
|
|
1139
|
+
// search tool would not be directly callable on the first turn.
|
|
1140
|
+
if (options.transport === "stdio") {
|
|
1141
|
+
return {
|
|
1142
|
+
command: "zg",
|
|
1143
|
+
args: stdioArgs(options.mcpToolset),
|
|
1144
|
+
timeout,
|
|
1145
|
+
alwaysLoadTools: true,
|
|
1146
|
+
trust: true,
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
return {
|
|
1150
|
+
httpUrl: resolveServerUrl(),
|
|
1151
|
+
timeout,
|
|
1152
|
+
alwaysLoadTools: true,
|
|
1153
|
+
trust: true,
|
|
1154
|
+
...(options.mcpTokenEnv
|
|
1155
|
+
? {
|
|
1156
|
+
headers: {
|
|
1157
|
+
Authorization: `Bearer \${${options.mcpTokenEnv}}`,
|
|
1158
|
+
},
|
|
1159
|
+
}
|
|
1160
|
+
: {}),
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
function qoderMcpServer(options, alwaysAllow = [], ownedPermissions = []) {
|
|
1164
|
+
const timeout = options.mcpToolTimeoutSeconds * 1_000;
|
|
1165
|
+
const permissionPolicy = alwaysAllow.length > 0 ? { alwaysAllow: [...alwaysAllow] } : {};
|
|
1166
|
+
if (options.transport === "stdio") {
|
|
1167
|
+
return {
|
|
1168
|
+
command: "zg",
|
|
1169
|
+
args: stdioArgs(options.mcpToolset),
|
|
1170
|
+
timeout,
|
|
1171
|
+
trust: true,
|
|
1172
|
+
description: qoderMcpDescription(ownedPermissions),
|
|
1173
|
+
...permissionPolicy,
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
return {
|
|
1177
|
+
type: "http",
|
|
1178
|
+
url: resolveServerUrl(),
|
|
1179
|
+
timeout,
|
|
1180
|
+
trust: true,
|
|
1181
|
+
description: qoderMcpDescription(ownedPermissions),
|
|
1182
|
+
...permissionPolicy,
|
|
1183
|
+
...(options.mcpTokenEnv
|
|
1184
|
+
? {
|
|
1185
|
+
headers: {
|
|
1186
|
+
Authorization: `Bearer \${${options.mcpTokenEnv}}`,
|
|
1187
|
+
},
|
|
1188
|
+
}
|
|
1189
|
+
: {}),
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
function qoderIdeMcpServer(options) {
|
|
1193
|
+
const timeout = options.mcpToolTimeoutSeconds * 1_000;
|
|
1194
|
+
if (options.transport === "stdio") {
|
|
1195
|
+
const launch = stableQoderIdeStdioLaunch(options.mcpToolset);
|
|
1196
|
+
return {
|
|
1197
|
+
command: launch.command,
|
|
1198
|
+
args: launch.args,
|
|
1199
|
+
timeout,
|
|
1200
|
+
description: QODER_IDE_MCP_DESCRIPTION,
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
return {
|
|
1204
|
+
type: "sse",
|
|
1205
|
+
url: resolveServerUrl(),
|
|
1206
|
+
timeout,
|
|
1207
|
+
description: QODER_IDE_MCP_DESCRIPTION,
|
|
1208
|
+
...(options.mcpTokenEnv
|
|
1209
|
+
? {
|
|
1210
|
+
headers: {
|
|
1211
|
+
Authorization: `Bearer \${${options.mcpTokenEnv}}`,
|
|
1212
|
+
},
|
|
1213
|
+
}
|
|
1214
|
+
: {}),
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
async function assertQoderMcpSettingsReplaceable(path, label, force, isManaged) {
|
|
1218
|
+
const existing = await readTextFileIfExists(path);
|
|
1219
|
+
const source = existing.trim() ? existing : "{}\n";
|
|
1220
|
+
const root = parseJsoncSettings(path, source, label);
|
|
1221
|
+
validateMcpSettingsContainer(path, root);
|
|
1222
|
+
const mcpServers = isJsonObject(root.mcpServers) ? root.mcpServers : {};
|
|
1223
|
+
if (mcpServers.zvec_grep !== undefined &&
|
|
1224
|
+
!isManaged(mcpServers.zvec_grep) &&
|
|
1225
|
+
!force) {
|
|
1226
|
+
throw new Error(`Existing unmanaged zvec_grep MCP server found in ${path}. Re-run with --force to replace it for ${label}.`);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
async function updateJsoncMcpSettings(options) {
|
|
1230
|
+
const containerKey = options.containerKey ?? "mcpServers";
|
|
1231
|
+
const existing = await readTextFileIfExists(options.path);
|
|
1232
|
+
let source = existing.trim() ? existing : "{}\n";
|
|
1233
|
+
const root = parseJsoncSettings(options.path, source, options.label, {
|
|
1234
|
+
allowTrailingComma: options.allowTrailingComma,
|
|
1235
|
+
});
|
|
1236
|
+
validateJsoncMcpContainer(options.path, root, containerKey);
|
|
1237
|
+
const currentContainer = root[containerKey];
|
|
1238
|
+
const container = isJsonObject(currentContainer) ? currentContainer : {};
|
|
1239
|
+
const current = container.zvec_grep;
|
|
1240
|
+
if (current !== undefined && !options.isManaged(current) && !options.force) {
|
|
1241
|
+
throw new Error(`Existing unmanaged zvec_grep MCP server found in ${options.path}. Re-run with --force to replace it for ${options.label}.`);
|
|
1242
|
+
}
|
|
1243
|
+
source = editJsonWithComments(source, [containerKey, "zvec_grep"], options.server);
|
|
1244
|
+
await writeTextFileAtomic(options.path, ensureTrailingNewline(source));
|
|
1245
|
+
return root;
|
|
1246
|
+
}
|
|
1247
|
+
async function removeJsoncMcpSettings(path, label, isManaged, containerKey = "mcpServers", allowTrailingComma = false) {
|
|
1248
|
+
const existing = await readTextFileIfExists(path);
|
|
1249
|
+
if (!existing.trim())
|
|
1250
|
+
return;
|
|
1251
|
+
let source = existing;
|
|
1252
|
+
const root = parseJsoncSettings(path, source, label, {
|
|
1253
|
+
allowTrailingComma,
|
|
1254
|
+
});
|
|
1255
|
+
validateJsoncMcpContainer(path, root, containerKey);
|
|
1256
|
+
const currentContainer = root[containerKey];
|
|
1257
|
+
const container = isJsonObject(currentContainer) ? currentContainer : {};
|
|
1258
|
+
if (isManaged(container.zvec_grep)) {
|
|
1259
|
+
source = hasJsoncComments(source)
|
|
1260
|
+
? removeJsoncPropertyPreservingComments(source, [
|
|
1261
|
+
containerKey,
|
|
1262
|
+
"zvec_grep",
|
|
1263
|
+
])
|
|
1264
|
+
: editJsonWithComments(source, Object.keys(container).length === 1
|
|
1265
|
+
? [containerKey]
|
|
1266
|
+
: [containerKey, "zvec_grep"], undefined);
|
|
1267
|
+
}
|
|
1268
|
+
if (source !== existing) {
|
|
1269
|
+
await writeTextFileAtomic(path, ensureTrailingNewline(source));
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
function validateJsoncMcpContainer(path, root, containerKey) {
|
|
1273
|
+
if (root[containerKey] !== undefined && !isJsonObject(root[containerKey])) {
|
|
1274
|
+
throw new Error(`Invalid ${containerKey} configuration in ${path}.`);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
function parseJsoncSettings(path, source, label, options = {}) {
|
|
1278
|
+
const errors = [];
|
|
1279
|
+
const parsed = parseJsonWithComments(source, errors, {
|
|
1280
|
+
allowTrailingComma: options.allowTrailingComma ?? false,
|
|
1281
|
+
disallowComments: false,
|
|
1282
|
+
});
|
|
1283
|
+
if (errors.length > 0 || !isJsonObject(parsed)) {
|
|
1284
|
+
throw new Error(`Invalid ${label} configuration in ${path}.`);
|
|
1285
|
+
}
|
|
1286
|
+
return parsed;
|
|
1287
|
+
}
|
|
1288
|
+
function validateMcpSettingsContainer(path, root) {
|
|
1289
|
+
if (root.mcpServers !== undefined && !isJsonObject(root.mcpServers)) {
|
|
1290
|
+
throw new Error(`Invalid mcpServers configuration in ${path}.`);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
function editJsonWithComments(source, path, value) {
|
|
1294
|
+
return applyEdits(source, modify(source, [...path], value, {
|
|
1295
|
+
formattingOptions: jsonFormattingOptions(source),
|
|
1296
|
+
}));
|
|
1297
|
+
}
|
|
1298
|
+
function hasJsoncComments(source) {
|
|
1299
|
+
const scanner = createScanner(source, false);
|
|
1300
|
+
for (let token = scanner.scan(); token !== 17 /* SyntaxKind.EOF */; token = scanner.scan()) {
|
|
1301
|
+
if (token === 12 /* SyntaxKind.LineCommentTrivia */ ||
|
|
1302
|
+
token === 13 /* SyntaxKind.BlockCommentTrivia */) {
|
|
1303
|
+
return true;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
return false;
|
|
1307
|
+
}
|
|
1308
|
+
function removeJsoncPropertyPreservingComments(source, path) {
|
|
1309
|
+
const root = parseTree(source);
|
|
1310
|
+
if (!root)
|
|
1311
|
+
return source;
|
|
1312
|
+
const valueNode = findNodeAtLocation(root, [...path]);
|
|
1313
|
+
const propertyNode = valueNode?.parent;
|
|
1314
|
+
const objectNode = propertyNode?.parent;
|
|
1315
|
+
if (propertyNode?.type !== "property" ||
|
|
1316
|
+
objectNode?.type !== "object" ||
|
|
1317
|
+
!objectNode.children) {
|
|
1318
|
+
return source;
|
|
1319
|
+
}
|
|
1320
|
+
const propertyIndex = objectNode.children.indexOf(propertyNode);
|
|
1321
|
+
if (propertyIndex < 0)
|
|
1322
|
+
return source;
|
|
1323
|
+
const ranges = [nodeRange(propertyNode)];
|
|
1324
|
+
const previousProperty = objectNode.children[propertyIndex - 1];
|
|
1325
|
+
const nextProperty = objectNode.children[propertyIndex + 1];
|
|
1326
|
+
const separatorOffset = nextProperty
|
|
1327
|
+
? findComma(source, propertyNode.offset + propertyNode.length, nextProperty.offset)
|
|
1328
|
+
: previousProperty
|
|
1329
|
+
? findComma(source, previousProperty.offset + previousProperty.length, propertyNode.offset)
|
|
1330
|
+
: undefined;
|
|
1331
|
+
if (separatorOffset !== undefined) {
|
|
1332
|
+
ranges.push({ offset: separatorOffset, length: 1 });
|
|
1333
|
+
}
|
|
1334
|
+
return ranges
|
|
1335
|
+
.sort((left, right) => right.offset - left.offset)
|
|
1336
|
+
.reduce((current, range) => current.slice(0, range.offset) +
|
|
1337
|
+
current.slice(range.offset + range.length), source);
|
|
1338
|
+
}
|
|
1339
|
+
function nodeRange(node) {
|
|
1340
|
+
return { offset: node.offset, length: node.length };
|
|
1341
|
+
}
|
|
1342
|
+
function findComma(source, startOffset, endOffset) {
|
|
1343
|
+
const scanner = createScanner(source, false);
|
|
1344
|
+
scanner.setPosition(startOffset);
|
|
1345
|
+
for (let token = scanner.scan(); token !== 17 /* SyntaxKind.EOF */; token = scanner.scan()) {
|
|
1346
|
+
const tokenOffset = scanner.getTokenOffset();
|
|
1347
|
+
if (tokenOffset >= endOffset)
|
|
1348
|
+
return undefined;
|
|
1349
|
+
if (token === 5 /* SyntaxKind.CommaToken */)
|
|
1350
|
+
return tokenOffset;
|
|
1351
|
+
}
|
|
1352
|
+
return undefined;
|
|
1353
|
+
}
|
|
1354
|
+
function jsonFormattingOptions(source) {
|
|
1355
|
+
const eol = source.includes("\r\n") ? "\r\n" : "\n";
|
|
1356
|
+
const indentation = source.match(/^[ \t]+(?=")/m)?.[0];
|
|
1357
|
+
if (indentation?.startsWith("\t")) {
|
|
1358
|
+
return { insertSpaces: false, tabSize: 1, eol };
|
|
1359
|
+
}
|
|
1360
|
+
return {
|
|
1361
|
+
insertSpaces: true,
|
|
1362
|
+
tabSize: indentation?.length ?? 2,
|
|
1363
|
+
eol,
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
function contextFileWarnings(root, fileName) {
|
|
1367
|
+
if (!isJsonObject(root.context))
|
|
1368
|
+
return [];
|
|
1369
|
+
const configured = root.context.fileName;
|
|
1370
|
+
const fileNames = typeof configured === "string"
|
|
1371
|
+
? [configured]
|
|
1372
|
+
: Array.isArray(configured) &&
|
|
1373
|
+
configured.every((value) => typeof value === "string")
|
|
1374
|
+
? configured
|
|
1375
|
+
: undefined;
|
|
1376
|
+
if (!fileNames || fileNames.includes(fileName))
|
|
1377
|
+
return [];
|
|
1378
|
+
return [
|
|
1379
|
+
`context.fileName does not include ${fileName}; the installed zvec-grep guidance may not be loaded.`,
|
|
1380
|
+
];
|
|
1381
|
+
}
|
|
1382
|
+
function isManagedQwenMcpServer(value) {
|
|
1383
|
+
return (isJsonObject(value) &&
|
|
1384
|
+
(value.httpUrl === resolveServerUrl() ||
|
|
1385
|
+
(value.command === "zg" && isStdioArgs(value.args))));
|
|
1386
|
+
}
|
|
1387
|
+
function ensureTrailingNewline(source) {
|
|
1388
|
+
return source.endsWith("\n") ? source : `${source}\n`;
|
|
1389
|
+
}
|
|
1390
|
+
async function readJsonObject(path) {
|
|
1391
|
+
const source = await readTextFileIfExists(path);
|
|
1392
|
+
return source.trim() ? parseJsonObject(path, source) : {};
|
|
1393
|
+
}
|
|
1394
|
+
function parseJsonObject(path, source) {
|
|
1395
|
+
let parsed;
|
|
1396
|
+
try {
|
|
1397
|
+
parsed = JSON.parse(source);
|
|
1398
|
+
}
|
|
1399
|
+
catch (error) {
|
|
1400
|
+
throw new Error(`Invalid JSON in ${path}.`, { cause: error });
|
|
1401
|
+
}
|
|
1402
|
+
if (!isJsonObject(parsed)) {
|
|
1403
|
+
throw new Error(`Expected a JSON object in ${path}.`);
|
|
1404
|
+
}
|
|
1405
|
+
return parsed;
|
|
1406
|
+
}
|
|
1407
|
+
function isJsonObject(value) {
|
|
1408
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1409
|
+
}
|
|
1410
|
+
function isManagedJsonMcpServer(value) {
|
|
1411
|
+
if (!isJsonObject(value))
|
|
1412
|
+
return false;
|
|
1413
|
+
if (value.url === resolveServerUrl())
|
|
1414
|
+
return true;
|
|
1415
|
+
if (value.command === "zg" && isStdioArgs(value.args))
|
|
1416
|
+
return true;
|
|
1417
|
+
return (value.type === "local" &&
|
|
1418
|
+
Array.isArray(value.command) &&
|
|
1419
|
+
(value.command.length === 3 || value.command.length === 5) &&
|
|
1420
|
+
value.command[0] === "zg" &&
|
|
1421
|
+
isServerActionArg(value.command[1]) &&
|
|
1422
|
+
value.command[2] === "--stdio" &&
|
|
1423
|
+
(value.command.length === 3 ||
|
|
1424
|
+
(value.command[3] === "--mcp-toolset" &&
|
|
1425
|
+
(value.command[4] === "agent" || value.command[4] === "full"))));
|
|
1426
|
+
}
|
|
1427
|
+
function isManagedQoderIdeMcpServer(value) {
|
|
1428
|
+
return (isJsonObject(value) &&
|
|
1429
|
+
(value.description === QODER_IDE_MCP_DESCRIPTION ||
|
|
1430
|
+
value.description === LEGACY_QODER_MCP_DESCRIPTION) &&
|
|
1431
|
+
((typeof value.command === "string" && isStableStdioArgs(value.args)) ||
|
|
1432
|
+
(value.type === "sse" && typeof value.url === "string")));
|
|
1433
|
+
}
|
|
1434
|
+
function isStdioArgs(value) {
|
|
1435
|
+
return (Array.isArray(value) &&
|
|
1436
|
+
(value.length === 2 || value.length === 4) &&
|
|
1437
|
+
isServerActionArg(value[0]) &&
|
|
1438
|
+
value[1] === "--stdio" &&
|
|
1439
|
+
(value.length === 2 ||
|
|
1440
|
+
(value[2] === "--mcp-toolset" &&
|
|
1441
|
+
(value[3] === "agent" || value[3] === "full"))));
|
|
1442
|
+
}
|
|
1443
|
+
function isStableStdioArgs(value) {
|
|
1444
|
+
return (Array.isArray(value) &&
|
|
1445
|
+
(value.length === 3 || value.length === 5) &&
|
|
1446
|
+
typeof value[0] === "string" &&
|
|
1447
|
+
isServerActionArg(value[1]) &&
|
|
1448
|
+
value[2] === "--stdio" &&
|
|
1449
|
+
(value.length === 3 ||
|
|
1450
|
+
(value[3] === "--mcp-toolset" &&
|
|
1451
|
+
(value[4] === "agent" || value[4] === "full"))));
|
|
1452
|
+
}
|
|
1453
|
+
function isServerActionArg(value) {
|
|
1454
|
+
return value === "--server" || value === "server";
|
|
1455
|
+
}
|
|
1456
|
+
function stdioArgs(mcpToolset) {
|
|
1457
|
+
return [
|
|
1458
|
+
"--server",
|
|
1459
|
+
"--stdio",
|
|
1460
|
+
...(mcpToolset ? ["--mcp-toolset", mcpToolset] : []),
|
|
1461
|
+
];
|
|
1462
|
+
}
|
|
1463
|
+
function stdioCommand(mcpToolset) {
|
|
1464
|
+
return ["zg", ...stdioArgs(mcpToolset)];
|
|
1465
|
+
}
|
|
1466
|
+
function stableQoderIdeStdioLaunch(mcpToolset) {
|
|
1467
|
+
const cliPath = process.argv[1];
|
|
1468
|
+
if (!cliPath) {
|
|
1469
|
+
return { command: "zg", args: stdioArgs(mcpToolset) };
|
|
1470
|
+
}
|
|
1471
|
+
return {
|
|
1472
|
+
command: process.execPath,
|
|
1473
|
+
args: [resolve(cliPath), ...stdioArgs(mcpToolset)],
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
function tomlStringArray(values) {
|
|
1477
|
+
return `[${values.map((value) => JSON.stringify(value)).join(", ")}]`;
|
|
1478
|
+
}
|
|
1479
|
+
async function writeJsonObject(path, value) {
|
|
1480
|
+
await writeTextFileAtomic(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
1481
|
+
}
|
|
1482
|
+
async function writeMarkedFile(options) {
|
|
1483
|
+
let existing = await readTextFileIfExists(options.path);
|
|
1484
|
+
const next = replaceMarkedBlock(existing, options.startMarker, options.endMarker, options.block);
|
|
1485
|
+
if (next === null) {
|
|
1486
|
+
existing = removeOrphanedMarkers(existing, options.startMarker, options.endMarker);
|
|
1487
|
+
}
|
|
1488
|
+
if (next === null && options.hasConflict?.(existing)) {
|
|
1489
|
+
if (!options.force) {
|
|
1490
|
+
throw new Error(options.conflictMessage ??
|
|
1491
|
+
`Existing unmanaged configuration found in ${options.path}`);
|
|
1492
|
+
}
|
|
1493
|
+
existing = options.removeConflict
|
|
1494
|
+
? options.removeConflict(existing)
|
|
1495
|
+
: existing;
|
|
1496
|
+
}
|
|
1497
|
+
await writeTextFileAtomic(options.path, next ?? appendMarkedBlock(existing, options.block));
|
|
1498
|
+
}
|
|
1499
|
+
async function removeMarkedFile(options) {
|
|
1500
|
+
const existing = await readTextFileIfExists(options.path);
|
|
1501
|
+
if (!existing) {
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
const next = replaceMarkedBlock(existing, options.startMarker, options.endMarker, "") ??
|
|
1505
|
+
removeOrphanedMarkers(existing, options.startMarker, options.endMarker);
|
|
1506
|
+
if (next === existing) {
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
await writeTextFileAtomic(options.path, next);
|
|
1510
|
+
}
|
|
1511
|
+
async function writeTextFileAtomic(path, content) {
|
|
1512
|
+
const targetPath = await resolveAtomicWriteTarget(path);
|
|
1513
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
1514
|
+
const mode = await fileModeIfExists(targetPath);
|
|
1515
|
+
const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
1516
|
+
let temporaryFile;
|
|
1517
|
+
try {
|
|
1518
|
+
temporaryFile = await open(temporaryPath, "wx", mode);
|
|
1519
|
+
await temporaryFile.writeFile(content, "utf8");
|
|
1520
|
+
await temporaryFile.sync();
|
|
1521
|
+
await temporaryFile.close();
|
|
1522
|
+
temporaryFile = undefined;
|
|
1523
|
+
if (mode !== undefined) {
|
|
1524
|
+
await chmod(temporaryPath, mode);
|
|
1525
|
+
}
|
|
1526
|
+
await rename(temporaryPath, targetPath);
|
|
1527
|
+
}
|
|
1528
|
+
catch (error) {
|
|
1529
|
+
await temporaryFile?.close().catch(() => undefined);
|
|
1530
|
+
await unlink(temporaryPath).catch(() => undefined);
|
|
1531
|
+
throw error;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
async function resolveAtomicWriteTarget(path, visited = new Set()) {
|
|
1535
|
+
const absolutePath = resolve(path);
|
|
1536
|
+
if (visited.has(absolutePath)) {
|
|
1537
|
+
throw new Error(`Cannot atomically write through circular symbolic link: ${path}`);
|
|
1538
|
+
}
|
|
1539
|
+
try {
|
|
1540
|
+
const file = await lstat(absolutePath);
|
|
1541
|
+
if (!file.isSymbolicLink()) {
|
|
1542
|
+
return absolutePath;
|
|
1543
|
+
}
|
|
1544
|
+
visited.add(absolutePath);
|
|
1545
|
+
const link = await readlink(absolutePath);
|
|
1546
|
+
return resolveAtomicWriteTarget(resolve(dirname(absolutePath), link), visited);
|
|
1547
|
+
}
|
|
1548
|
+
catch (error) {
|
|
1549
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
1550
|
+
return absolutePath;
|
|
1551
|
+
}
|
|
1552
|
+
throw error;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
async function fileModeIfExists(path) {
|
|
1556
|
+
try {
|
|
1557
|
+
return (await stat(path)).mode & 0o777;
|
|
1558
|
+
}
|
|
1559
|
+
catch (error) {
|
|
1560
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
1561
|
+
return undefined;
|
|
1562
|
+
}
|
|
1563
|
+
throw error;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
async function pathExists(path) {
|
|
1567
|
+
try {
|
|
1568
|
+
await access(path, fileSystemConstants.F_OK);
|
|
1569
|
+
return true;
|
|
1570
|
+
}
|
|
1571
|
+
catch {
|
|
1572
|
+
return false;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
async function readTextFileIfExists(path) {
|
|
1576
|
+
try {
|
|
1577
|
+
return await readFile(path, "utf8");
|
|
1578
|
+
}
|
|
1579
|
+
catch (error) {
|
|
1580
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
1581
|
+
return "";
|
|
1582
|
+
}
|
|
1583
|
+
throw error;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
function isNodeError(error) {
|
|
1587
|
+
return typeof error === "object" && error !== null && "code" in error;
|
|
1588
|
+
}
|
|
1589
|
+
function replaceMarkedBlock(existing, startMarker, endMarker, block) {
|
|
1590
|
+
const lines = existing.split(/\r?\n/);
|
|
1591
|
+
const markerLines = new Set();
|
|
1592
|
+
const ranges = [];
|
|
1593
|
+
let pendingStart;
|
|
1594
|
+
for (const [index, line] of lines.entries()) {
|
|
1595
|
+
const trimmed = line.trim();
|
|
1596
|
+
if (trimmed === startMarker) {
|
|
1597
|
+
markerLines.add(index);
|
|
1598
|
+
pendingStart = index;
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
if (trimmed === endMarker) {
|
|
1602
|
+
markerLines.add(index);
|
|
1603
|
+
if (pendingStart !== undefined) {
|
|
1604
|
+
ranges.push({ start: pendingStart, end: index });
|
|
1605
|
+
pendingStart = undefined;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
const first = ranges[0];
|
|
1610
|
+
if (!first) {
|
|
1611
|
+
return null;
|
|
1612
|
+
}
|
|
1613
|
+
const before = retainedMarkedLines(lines, 0, first.start, markerLines, [])
|
|
1614
|
+
.join("\n")
|
|
1615
|
+
.trimEnd();
|
|
1616
|
+
const after = retainedMarkedLines(lines, first.end + 1, lines.length, markerLines, ranges.slice(1))
|
|
1617
|
+
.join("\n")
|
|
1618
|
+
.trim();
|
|
1619
|
+
return ([before, block.trim(), after.trim()]
|
|
1620
|
+
.filter((part) => part.length > 0)
|
|
1621
|
+
.join("\n\n") + "\n");
|
|
1622
|
+
}
|
|
1623
|
+
function retainedMarkedLines(lines, start, end, markerLines, removedRanges) {
|
|
1624
|
+
const retained = [];
|
|
1625
|
+
for (let index = start; index < end; index += 1) {
|
|
1626
|
+
if (markerLines.has(index)) {
|
|
1627
|
+
continue;
|
|
1628
|
+
}
|
|
1629
|
+
if (removedRanges.some((range) => index >= range.start && index <= range.end)) {
|
|
1630
|
+
continue;
|
|
1631
|
+
}
|
|
1632
|
+
retained.push(lines[index]);
|
|
1633
|
+
}
|
|
1634
|
+
return retained;
|
|
1635
|
+
}
|
|
1636
|
+
function removeOrphanedMarkers(existing, startMarker, endMarker) {
|
|
1637
|
+
return (existing
|
|
1638
|
+
.split(/\r?\n/)
|
|
1639
|
+
.filter((line) => {
|
|
1640
|
+
const trimmed = line.trim();
|
|
1641
|
+
return trimmed !== startMarker && trimmed !== endMarker;
|
|
1642
|
+
})
|
|
1643
|
+
.join("\n")
|
|
1644
|
+
.trimEnd() + "\n");
|
|
1645
|
+
}
|
|
1646
|
+
function appendMarkedBlock(existing, block) {
|
|
1647
|
+
const prefix = existing.trimEnd();
|
|
1648
|
+
return `${prefix.length > 0 ? `${prefix}\n\n` : ""}${block.trim()}\n`;
|
|
1649
|
+
}
|
|
1650
|
+
function hasCodexMcpServerConfig(existing) {
|
|
1651
|
+
return existing.split(/\r?\n/).some((line) => {
|
|
1652
|
+
const tableName = tomlTableName(line);
|
|
1653
|
+
return tableName !== undefined && isCodexMcpServerTableName(tableName);
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
function removeCodexMcpServerConfig(existing) {
|
|
1657
|
+
const lines = existing.split(/\r?\n/);
|
|
1658
|
+
const kept = [];
|
|
1659
|
+
let skipping = false;
|
|
1660
|
+
for (const line of lines) {
|
|
1661
|
+
const tableName = tomlTableName(line);
|
|
1662
|
+
if (tableName !== undefined) {
|
|
1663
|
+
skipping = isCodexMcpServerTableName(tableName);
|
|
1664
|
+
}
|
|
1665
|
+
if (!skipping) {
|
|
1666
|
+
kept.push(line);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
return kept.join("\n").trimEnd() + "\n";
|
|
1670
|
+
}
|
|
1671
|
+
function tomlTableName(line) {
|
|
1672
|
+
return line.match(/^\s*\[([^\]]+)\]\s*(?:#.*)?$/)?.[1]?.trim();
|
|
1673
|
+
}
|
|
1674
|
+
function isCodexMcpServerTableName(tableName) {
|
|
1675
|
+
return /^(?:mcp_servers|"mcp_servers"|'mcp_servers')\s*\.\s*(?:zvec_grep|"zvec_grep"|'zvec_grep')(?:\s*\.|$)/.test(tableName);
|
|
1676
|
+
}
|
|
1677
|
+
function codexConfigBlock(options) {
|
|
1678
|
+
return `${ZVEC_GREP_CONFIG_START}
|
|
1679
|
+
[mcp_servers.zvec_grep]
|
|
1680
|
+
${options.transport === "stdio"
|
|
1681
|
+
? `command = "zg"
|
|
1682
|
+
args = ${tomlStringArray(stdioArgs(options.mcpToolset))}`
|
|
1683
|
+
: `url = "${resolveServerUrl()}"`}
|
|
1684
|
+
${options.mcpTokenEnv
|
|
1685
|
+
? `bearer_token_env_var = "${options.mcpTokenEnv}"
|
|
1686
|
+
`
|
|
1687
|
+
: ""}tool_timeout_sec = ${options.mcpToolTimeoutSeconds}
|
|
1688
|
+
default_tools_approval_mode = "approve"
|
|
1689
|
+
${ZVEC_GREP_CONFIG_END}`;
|
|
1690
|
+
}
|
|
1691
|
+
function agentGuidanceBlock(toolNames) {
|
|
1692
|
+
const searchTool = toolNames?.search ?? "zvec_grep_search";
|
|
1693
|
+
const rgTool = toolNames?.rg ?? "zvec_grep_rg";
|
|
1694
|
+
const exactLookupRoute = `\`${rgTool}\` when it is listed by the current host; otherwise native Grep or \`rg\``;
|
|
1695
|
+
const qoderAuthorizationRecovery = toolNames?.qoderAuthorizationRecovery
|
|
1696
|
+
? `
|
|
1697
|
+
|
|
1698
|
+
${formatPromptRules("### Qoder Remote Embedding authorization recovery", [
|
|
1699
|
+
`When \`${searchTool}\` needs \`remote_embedding_authorization\` and the current Qoder host returns \`code = 51500 message = method not found: No request handler configured\` without showing an authorization form, returns \`${REMOTE_EMBEDDING_ELICITATION_UNSUPPORTED_MESSAGE}\`, or reports that authorization was declined or cancelled without showing the user an authorization form, treat it as a Qoder client interaction limitation: the host lacks the server-to-client \`elicitation/create\` request handler. The outer MCP \`tools/call\` has already reached the registered MCP server and tool, so do not diagnose this error as a disconnected or missing MCP server or as a missing tool. Apply this recovery only to the Remote Embedding authorization path, not to arbitrary 51500 failures. Do not immediately fall back to broad file reads, do not treat it as a missing API credential, and do not grant access silently. If the user actually declined a displayed authorization form, respect that decision and do not ask again.`,
|
|
1700
|
+
"Use the current Qoder host's built-in user-question tool (`ask_user_question` in Qoder IDE or `AskUserQuestion` in Qoder CLI/SDK) to offer exactly these choices: allow Remote Embedding for this workspace, use local FTS only, or cancel. Explain that workspace approval may send query text and selected workspace content to the configured provider and endpoint and may incur provider charges.",
|
|
1701
|
+
'Only after the user explicitly chooses workspace approval, run `zg --auth grant "<absolute-root>" --capability embedding --scope workspace`, substituting the same absolute root used by the failed search, and then retry the original search call once. Do not use `--allow-remote`; it applies only to one CLI command and does not authorize the MCP retry.',
|
|
1702
|
+
`If the user chooses local FTS, retry \`${searchTool}\` once with the original search text in \`fts\`, omit \`query\`, \`queries\`, and \`vector\`, set \`autoUpdate\` to \`false\` and \`freshness\` to \`eventual\`, and preserve \`root\`, filters, and limits. This route is lexical-only, does not refresh the remote-embedding index, and sends no query text or workspace content to a remote Embedding provider.`,
|
|
1703
|
+
"If the user cancels, the grant command fails, or interactive user input is unavailable, stop and report that no remote data was sent. Provider credentials and Remote Embedding data authorization are separate; never request or modify an API key merely to resolve this interaction error.",
|
|
1704
|
+
])}`
|
|
1705
|
+
: "";
|
|
1706
|
+
return `${ZVEC_GREP_AGENTS_START}
|
|
1707
|
+
## zvec-grep
|
|
1708
|
+
|
|
1709
|
+
Choose the evidence source before the retrieval mode.
|
|
1710
|
+
|
|
1711
|
+
${formatPromptRules("### Workspace evidence", ZVEC_GREP_WORKSPACE_EVIDENCE_RULES)}
|
|
1712
|
+
|
|
1713
|
+
${formatPromptRules("### Retrieval routing", [
|
|
1714
|
+
`When an exact word, phrase, name, date, identifier, filename, path, configuration key, error message, source fragment, literal, or regex is known and locating its occurrences is sufficient, use ${exactLookupRoute}.`,
|
|
1715
|
+
`Use \`${searchTool}\` when wording or location is unknown, or when the answer requires semantic, conceptual, fuzzy, or paraphrase discovery; relationships, chronology, causality, architecture, or data or control flow; or comparison or synthesis across files, sections, or documents.`,
|
|
1716
|
+
`For a mixed task with exact anchors that still requires relationships or cross-file synthesis, call \`${searchTool}\` with the concept and anchors, then use ${exactLookupRoute} for focused follow-up.`,
|
|
1717
|
+
`When no sufficient exact anchor is available and the user asks whether conceptually related material exists locally, make at most one focused \`${searchTool}\` probe using the question plus distinctive names, dates, or terms. This probe does not apply to exact quotations, configuration keys, filenames, regexes, or exhaustive occurrence requests. Continue only when results are relevant; otherwise stop and report that the indexed workspace did not establish the answer.`,
|
|
1718
|
+
"Before broad file reads or delegating workspace discovery, use the appropriate search route. Do not delegate solely to locate material, and stop when the evidence is sufficient.",
|
|
1719
|
+
])}
|
|
1720
|
+
|
|
1721
|
+
${formatPromptRules("### Search evidence", [
|
|
1722
|
+
"Search results include bounded source snippets. Treat a sufficient snippet as already-read evidence, and read a cited file only when a required detail falls outside the snippet.",
|
|
1723
|
+
])}
|
|
1724
|
+
|
|
1725
|
+
${formatPromptRules("### Freshness and index lifecycle", [
|
|
1726
|
+
"Pass a daemon-visible absolute `root` on every zvec-grep workspace call.",
|
|
1727
|
+
"Read `freshness` and `background_refresh` from search results without a status preflight.",
|
|
1728
|
+
"When results are `served_from_current_index`, use them when sufficient instead of waiting for the background refresh.",
|
|
1729
|
+
`If the index is missing but exact or regex lookup can answer the task, use ${exactLookupRoute}.`,
|
|
1730
|
+
"Creating, rebuilding, or dropping a persistent index requires an explicit user request or authorization; never do so silently.",
|
|
1731
|
+
])}
|
|
1732
|
+
${qoderAuthorizationRecovery}
|
|
1733
|
+
${ZVEC_GREP_AGENTS_END}`;
|
|
1734
|
+
}
|
|
1735
|
+
//# sourceMappingURL=install.js.map
|