@cdxgen/cdxgen 13.4.0 → 13.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/bin/cdxgen-secure.js +15 -0
- package/bin/cdxgen.js +29 -5
- package/bin/evinse.js +8 -0
- package/bin/hbom.js +15 -7
- package/bin/repl.js +29 -0
- package/bin/validate.js +6 -0
- package/data/cbomosdb-queries.json +13 -1
- package/data/component-tags.json +8 -0
- package/data/license-aliases.json +24 -2
- package/data/queries-darwin.json +25 -1
- package/data/queries-win.json +19 -1
- package/data/queries.json +41 -0
- package/data/remediations.json +407 -1
- package/data/rules/ai-bom.yaml +1 -1
- package/data/rules/brotli-decoder.yaml +107 -0
- package/data/rules/cbom-security.yaml +235 -4
- package/data/rules/go-binaries.yaml +46 -0
- package/data/rules/golem-go.yaml +80 -0
- package/data/rules/hbom-compliance.yaml +40 -22
- package/data/rules/hbom-performance.yaml +7 -1
- package/data/rules/host-topology.yaml +1 -1
- package/data/rules/obom-runtime.yaml +31 -2
- package/data/rules/php-runtime.yaml +143 -0
- package/data/rules/vscode-extensions.yaml +75 -0
- package/data/spdx-license-list.json +121 -7
- package/data/spdx-licenses.json +69 -0
- package/data/spdx.schema.json +845 -2
- package/lib/audit/index.js +14 -0
- package/lib/cli/bomAssembly.js +120 -6
- package/lib/cli/cliOptions.js +110 -3
- package/lib/cli/index.js +182 -6
- package/lib/cli/jsBom.js +153 -64
- package/lib/cli/jvmBom.js +363 -66
- package/lib/cli/managedBom.js +853 -87
- package/lib/cli/nativeBom.js +267 -33
- package/lib/core/boundedFile.js +344 -0
- package/lib/core/brotliProbe.js +182 -0
- package/lib/core/env.js +38 -8
- package/lib/core/fs.js +40 -19
- package/lib/core/httpClient.js +172 -46
- package/lib/core/lockPaths.js +38 -0
- package/lib/core/paths.js +24 -1
- package/lib/core/processTree.js +5 -0
- package/lib/core/projectConfig.js +1 -0
- package/lib/core/projectTooling.js +97 -0
- package/lib/core/propertySanitizer.js +72 -0
- package/lib/core/toolRegistry.js +170 -0
- package/lib/ecosystems/cppEvidence.js +263 -106
- package/lib/ecosystems/cppIncludeResolver.js +16 -5
- package/lib/ecosystems/ecosystems.js +25 -10
- package/lib/ecosystems/gemHostProbe.js +301 -0
- package/lib/ecosystems/golem.js +550 -6
- package/lib/ecosystems/gradleutils.js +858 -244
- package/lib/ecosystems/parsers-bazel.js +107 -6
- package/lib/ecosystems/parsers-dotnet.js +117 -18
- package/lib/ecosystems/parsers-go.js +1090 -367
- package/lib/ecosystems/parsers-js.js +505 -22
- package/lib/ecosystems/parsers-jvm.js +259 -17
- package/lib/ecosystems/parsers-misc.js +438 -64
- package/lib/ecosystems/parsers-python.js +100 -12
- package/lib/ecosystems/pythonutils.js +128 -2
- package/lib/ecosystems/rubyutils.js +544 -34
- package/lib/ecosystems/rusi.js +10 -0
- package/lib/ecosystems/scalacliutils.js +4 -1
- package/lib/ecosystems/scalasem.js +798 -151
- package/lib/ecosystems/vendoredCode.js +237 -3
- package/lib/ecosystems/venvProbe.js +360 -0
- package/lib/evinser/evinser.js +472 -61
- package/lib/evinser/swiftsem.js +68 -5
- package/lib/helpers/agenticToolutils.js +413 -93
- package/lib/helpers/vsixutils.js +1574 -315
- package/lib/inventory/agentSkillSpec.js +17 -0
- package/lib/inventory/apkInstalledDb.js +243 -0
- package/lib/inventory/atomUtils.js +110 -0
- package/lib/inventory/auditCategories.js +3 -3
- package/lib/inventory/brotliDecoder.js +142 -0
- package/lib/inventory/buildToolRateLimit.js +1 -1
- package/lib/inventory/cacheDir.js +52 -9
- package/lib/inventory/cbomutils.js +56 -7
- package/lib/inventory/certificates.js +2371 -0
- package/lib/inventory/communityAiConfigParser.js +4 -9
- package/lib/inventory/deps.js +41 -4
- package/lib/inventory/display.js +200 -3
- package/lib/inventory/dosai.js +639 -63
- package/lib/inventory/dotnetRuntime.js +217 -0
- package/lib/inventory/dynamic.js +45 -3
- package/lib/inventory/envcontext.js +475 -18
- package/lib/inventory/evidenceUtils.js +447 -23
- package/lib/inventory/fetchBatch.js +1 -1
- package/lib/inventory/formulationParsers.js +32 -3
- package/lib/inventory/goBuildInfo.js +140 -0
- package/lib/inventory/gtfobins.js +13 -0
- package/lib/inventory/hbom.js +67 -7
- package/lib/inventory/hbomAnalysis.js +16 -0
- package/lib/inventory/hostRuntimeProbe.js +686 -0
- package/lib/inventory/hostTopology.js +21 -0
- package/lib/inventory/jvmLocalRepos.js +9 -2
- package/lib/inventory/jvmToolEnv.js +29 -0
- package/lib/inventory/licenseEnhancer.js +12 -0
- package/lib/inventory/mavenCentralMirror.js +534 -0
- package/lib/inventory/nodeBuildInfo.js +218 -0
- package/lib/inventory/nodeHostProbe.js +202 -0
- package/lib/inventory/osPackagePurl.js +244 -0
- package/lib/inventory/osPackageResolver.js +271 -87
- package/lib/inventory/osinfo.js +79 -31
- package/lib/inventory/osqueryTransform.js +114 -5
- package/lib/inventory/phpBuildInfo.js +438 -0
- package/lib/inventory/phpHostProbe.js +185 -0
- package/lib/inventory/rootfsCerts.js +426 -0
- package/lib/inventory/rubyBuildInfo.js +972 -0
- package/lib/inventory/rubyHostProbe.js +218 -0
- package/lib/inventory/scalaCoords.js +4 -3
- package/lib/inventory/scalaDedupe.js +440 -0
- package/lib/inventory/source.js +256 -5
- package/lib/inventory/spdx.js +144 -0
- package/lib/inventory/traceRunner.js +5 -1
- package/lib/inventory/tracedCrypto.js +129 -0
- package/lib/managers/binary.js +866 -186
- package/lib/managers/docker.js +245 -12
- package/lib/parsers/email.js +277 -0
- package/lib/parsers/largeJson.js +62 -13
- package/lib/parsers/vscodeExtensionId.js +183 -0
- package/lib/server/server.js +198 -2
- package/lib/stages/postgen/annotator.js +17 -1
- package/lib/stages/postgen/introspection/reflect.js +4 -1
- package/lib/stages/postgen/postgen.js +5 -5
- package/lib/stages/postgen/ruleEngine.js +71 -6
- package/lib/stages/postgen/specVersionCompat.js +30 -0
- package/lib/stages/pregen/envAudit.js +1 -0
- package/lib/stages/pregen/pregen.js +231 -62
- package/lib/validator/bomValidator.js +32 -6
- package/lib/validator/generated/runtime.mjs +13 -0
- package/lib/validator/generated/validate-1.6.mjs +27 -8
- package/lib/validator/generated/validate-1.7.mjs +32 -13
- package/lib/validator/generated/validate-2.0.mjs +31 -12
- package/lib/validator/index.js +1 -1
- package/package.json +19 -18
- package/types/bin/cdxgen-secure.d.ts +3 -0
- package/types/bin/cdxgen-secure.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +22 -3
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +43 -0
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +1 -17
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +20 -0
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/boundedFile.d.ts +70 -0
- package/types/lib/core/boundedFile.d.ts.map +1 -0
- package/types/lib/core/brotliProbe.d.ts +37 -0
- package/types/lib/core/brotliProbe.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +22 -2
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/httpClient.d.ts +24 -0
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/lockPaths.d.ts +19 -0
- package/types/lib/core/lockPaths.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +2 -0
- package/types/lib/core/processTree.d.ts.map +1 -1
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/projectTooling.d.ts +41 -0
- package/types/lib/core/projectTooling.d.ts.map +1 -0
- package/types/lib/core/propertySanitizer.d.ts +26 -0
- package/types/lib/core/propertySanitizer.d.ts.map +1 -1
- package/types/lib/core/toolRegistry.d.ts +36 -0
- package/types/lib/core/toolRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +40 -13
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +2 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gemHostProbe.d.ts +28 -0
- package/types/lib/ecosystems/gemHostProbe.d.ts.map +1 -0
- package/types/lib/ecosystems/golem.d.ts +81 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +57 -21
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +40 -4
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +85 -13
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +17 -20
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +28 -2
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +37 -58
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +14 -3
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +19 -1
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +144 -12
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +26 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -1
- package/types/lib/ecosystems/venvProbe.d.ts +40 -0
- package/types/lib/ecosystems/venvProbe.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +88 -8
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +10 -0
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/inventory/agentSkillSpec.d.ts +13 -0
- package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
- package/types/lib/inventory/apkInstalledDb.d.ts +42 -0
- package/types/lib/inventory/apkInstalledDb.d.ts.map +1 -0
- package/types/lib/inventory/atomUtils.d.ts +33 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/brotliDecoder.d.ts +58 -0
- package/types/lib/inventory/brotliDecoder.d.ts.map +1 -0
- package/types/lib/inventory/cacheDir.d.ts +12 -4
- package/types/lib/inventory/cacheDir.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts +17 -0
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/certificates.d.ts +205 -0
- package/types/lib/inventory/certificates.d.ts.map +1 -0
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/deps.d.ts +2 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts +18 -0
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +162 -7
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dotnetRuntime.d.ts +44 -0
- package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -0
- package/types/lib/inventory/dynamic.d.ts +6 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts +92 -8
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +29 -10
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/goBuildInfo.d.ts +10 -0
- package/types/lib/inventory/goBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/gtfobins.d.ts.map +1 -1
- package/types/lib/inventory/hbom.d.ts.map +1 -1
- package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
- package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
- package/types/lib/inventory/hostRuntimeProbe.d.ts +44 -0
- package/types/lib/inventory/hostRuntimeProbe.d.ts.map +1 -0
- package/types/lib/inventory/hostTopology.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +12 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -1
- package/types/lib/inventory/mavenCentralMirror.d.ts +170 -0
- package/types/lib/inventory/mavenCentralMirror.d.ts.map +1 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts +95 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/nodeHostProbe.d.ts +15 -0
- package/types/lib/inventory/nodeHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/osPackagePurl.d.ts +99 -0
- package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +44 -2
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
- package/types/lib/inventory/osinfo.d.ts +31 -6
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/osqueryTransform.d.ts +51 -0
- package/types/lib/inventory/osqueryTransform.d.ts.map +1 -1
- package/types/lib/inventory/phpBuildInfo.d.ts +142 -0
- package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/phpHostProbe.d.ts +35 -0
- package/types/lib/inventory/phpHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/rootfsCerts.d.ts +27 -0
- package/types/lib/inventory/rootfsCerts.d.ts.map +1 -0
- package/types/lib/inventory/rubyBuildInfo.d.ts +271 -0
- package/types/lib/inventory/rubyBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/rubyHostProbe.d.ts +47 -0
- package/types/lib/inventory/rubyHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +38 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +15 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +4 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +22 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +10 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +24 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/email.d.ts +19 -0
- package/types/lib/parsers/email.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +17 -3
- package/types/lib/parsers/largeJson.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +8 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +7 -4
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +26 -9
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { readdirSync,
|
|
1
|
+
import { readdirSync, realpathSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
4
4
|
|
|
5
5
|
import { build } from "@cdxgen/cdx-purl";
|
|
6
6
|
import { parse as loadYaml } from "yaml";
|
|
7
7
|
|
|
8
8
|
import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
|
|
9
|
+
import { readUserFileText } from "../core/boundedFile.js";
|
|
9
10
|
import { safeExistsSync } from "../core/fs.js";
|
|
10
|
-
import { isMac, isWin } from "../core/paths.js";
|
|
11
|
+
import { isMac, isWin, redactHomePath } from "../core/paths.js";
|
|
11
12
|
import { parseToml } from "../core/toml.js";
|
|
13
|
+
import { findIdeProductInfo, IDE_PRODUCTS } from "./vsixutils.js";
|
|
14
|
+
|
|
15
|
+
export { redactHomePath };
|
|
12
16
|
|
|
13
17
|
/**
|
|
14
18
|
* The purl type used for installed agentic CLI tools. These tools ship as
|
|
@@ -45,29 +49,6 @@ function buildGenericPurl(name, version) {
|
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
/**
|
|
49
|
-
* Rewrite an absolute path under the user's home directory to a `~`-relative
|
|
50
|
-
* form so the emitted BOM does not carry the local username.
|
|
51
|
-
*
|
|
52
|
-
* @param {string} filePath Absolute path
|
|
53
|
-
* @returns {string} Home-relative path when applicable, else the input
|
|
54
|
-
*/
|
|
55
|
-
export function redactHomePath(filePath) {
|
|
56
|
-
const home = homedir();
|
|
57
|
-
const value = String(filePath || "");
|
|
58
|
-
if (!home || !value.startsWith(home)) {
|
|
59
|
-
return value;
|
|
60
|
-
}
|
|
61
|
-
// Require a path boundary so a sibling home such as /Users/alice-backup is
|
|
62
|
-
// not rewritten as if it sat under /Users/alice.
|
|
63
|
-
const remainder = value.slice(home.length);
|
|
64
|
-
if (remainder && !/^[/\\]/u.test(remainder)) {
|
|
65
|
-
return value;
|
|
66
|
-
}
|
|
67
|
-
const rest = remainder.replace(/^[/\\]/u, "");
|
|
68
|
-
return rest ? `~/${rest.replaceAll("\\", "/")}` : "~";
|
|
69
|
-
}
|
|
70
|
-
|
|
71
52
|
// Directory or file names inside a tool home that must never be read for their
|
|
72
53
|
// values. They hold credentials, transcripts, or telemetry. The scanner may
|
|
73
54
|
// count entries under some of these, but it does not open their contents.
|
|
@@ -95,23 +76,39 @@ const SENSITIVE_ENTRY_NAMES = new Set([
|
|
|
95
76
|
* extension locations). Only the current platform's directories plus
|
|
96
77
|
* always-present dot-directories are included.
|
|
97
78
|
*
|
|
98
|
-
*
|
|
79
|
+
* An entry may also name `mcpConfigs`, the files and keys where the tool
|
|
80
|
+
* lists its MCP servers, and `ide`, the IDE_PRODUCTS id of the editor the tool
|
|
81
|
+
* ships in, whose installed version and extensions folder it then reports.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} [home] Home directory to resolve the folders against
|
|
84
|
+
* @returns {Array<{tool: string, name: string, dirs: string[], supportDirs?: string[], mcpConfigs?: Array<{file: string, key: string}>, ide?: string}>}
|
|
99
85
|
* One entry per known agentic CLI tool.
|
|
100
86
|
*/
|
|
101
|
-
export function getAgenticToolDirs() {
|
|
102
|
-
|
|
87
|
+
export function getAgenticToolDirs(home = homedir()) {
|
|
88
|
+
// These variables describe the current user, so another home, such as a
|
|
89
|
+
// fixture or another user's home, uses the defaults.
|
|
90
|
+
const ownHome = home === homedir();
|
|
103
91
|
const appData =
|
|
104
|
-
readEnvironmentVariable("APPDATA") ||
|
|
92
|
+
(ownHome && readEnvironmentVariable("APPDATA")) ||
|
|
93
|
+
join(home, "AppData", "Roaming");
|
|
105
94
|
const xdgConfigHome =
|
|
106
|
-
readEnvironmentVariable("XDG_CONFIG_HOME") ||
|
|
95
|
+
(ownHome && readEnvironmentVariable("XDG_CONFIG_HOME")) ||
|
|
96
|
+
join(home, ".config");
|
|
107
97
|
const xdgDataHome =
|
|
108
|
-
readEnvironmentVariable("XDG_DATA_HOME") ||
|
|
98
|
+
(ownHome && readEnvironmentVariable("XDG_DATA_HOME")) ||
|
|
99
|
+
join(home, ".local", "share");
|
|
109
100
|
|
|
110
101
|
return [
|
|
111
102
|
{
|
|
112
103
|
tool: "kiro-cli",
|
|
113
104
|
name: "Kiro CLI",
|
|
114
|
-
|
|
105
|
+
// The Kiro editor also creates ~/.kiro, so the CLI is recognised by the
|
|
106
|
+
// files only the CLI writes there.
|
|
107
|
+
dirs: [
|
|
108
|
+
join(home, ".kiro", "settings", "cli.json"),
|
|
109
|
+
join(home, ".kiro", "sessions", "cli"),
|
|
110
|
+
],
|
|
111
|
+
homeDir: join(home, ".kiro"),
|
|
115
112
|
supportDirs: isWin
|
|
116
113
|
? [join(appData, "kiro-cli")]
|
|
117
114
|
: isMac
|
|
@@ -136,6 +133,8 @@ export function getAgenticToolDirs() {
|
|
|
136
133
|
tool: "claude-code",
|
|
137
134
|
name: "Claude Code",
|
|
138
135
|
dirs: [join(home, ".claude")],
|
|
136
|
+
// User-scope MCP servers live in ~/.claude.json, beside the folder.
|
|
137
|
+
mcpConfigs: [{ file: join(home, ".claude.json"), key: "mcpServers" }],
|
|
139
138
|
},
|
|
140
139
|
{
|
|
141
140
|
tool: "codex",
|
|
@@ -161,11 +160,111 @@ export function getAgenticToolDirs() {
|
|
|
161
160
|
tool: "cursor",
|
|
162
161
|
name: "Cursor",
|
|
163
162
|
dirs: [join(home, ".cursor")],
|
|
163
|
+
mcpConfigs: [{ file: "mcp.json", key: "mcpServers" }],
|
|
164
|
+
ide: "cursor",
|
|
164
165
|
},
|
|
165
166
|
{
|
|
166
167
|
tool: "windsurf",
|
|
167
168
|
name: "Windsurf",
|
|
168
169
|
dirs: [join(home, ".codeium", "windsurf")],
|
|
170
|
+
mcpConfigs: [{ file: "mcp_config.json", key: "mcpServers" }],
|
|
171
|
+
ide: "windsurf",
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
tool: "devin-desktop",
|
|
175
|
+
name: "Devin Desktop",
|
|
176
|
+
dirs: [join(home, ".devin")],
|
|
177
|
+
mcpConfigs: [
|
|
178
|
+
{
|
|
179
|
+
file: join(xdgConfigHome, "devin", "config.json"),
|
|
180
|
+
key: "mcpServers",
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
ide: "devin-desktop",
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
tool: "kiro",
|
|
187
|
+
name: "Kiro",
|
|
188
|
+
// Kiro and Kiro CLI share ~/.kiro; the IDE is told apart by its
|
|
189
|
+
// extensions folder.
|
|
190
|
+
dirs: [join(home, ".kiro", "extensions")],
|
|
191
|
+
homeDir: join(home, ".kiro"),
|
|
192
|
+
mcpConfigs: [{ file: join("settings", "mcp.json"), key: "mcpServers" }],
|
|
193
|
+
ide: "kiro",
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
tool: "trae",
|
|
197
|
+
name: "Trae",
|
|
198
|
+
dirs: [join(home, ".trae")],
|
|
199
|
+
ide: "trae",
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
tool: "antigravity",
|
|
203
|
+
name: "Antigravity",
|
|
204
|
+
dirs: [join(home, ".antigravity")],
|
|
205
|
+
ide: "antigravity",
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
tool: "antigravity-ide",
|
|
209
|
+
name: "Antigravity IDE",
|
|
210
|
+
dirs: [join(home, ".antigravity-ide")],
|
|
211
|
+
ide: "antigravity-ide",
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
tool: "qoder",
|
|
215
|
+
name: "Qoder",
|
|
216
|
+
dirs: [join(home, ".qoder")],
|
|
217
|
+
ide: "qoder",
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
tool: "void",
|
|
221
|
+
name: "Void",
|
|
222
|
+
dirs: [join(home, ".void-editor")],
|
|
223
|
+
ide: "void",
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
tool: "qwen-code",
|
|
227
|
+
name: "Qwen Code",
|
|
228
|
+
dirs: [join(home, ".qwen")],
|
|
229
|
+
mcpConfigs: [{ file: "settings.json", key: "mcpServers" }],
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
tool: "factory-droid",
|
|
233
|
+
name: "Factory Droid",
|
|
234
|
+
dirs: [join(home, ".factory")],
|
|
235
|
+
mcpConfigs: [{ file: "mcp.json", key: "mcpServers" }],
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
tool: "auggie",
|
|
239
|
+
name: "Augment Auggie CLI",
|
|
240
|
+
dirs: [join(home, ".augment")],
|
|
241
|
+
mcpConfigs: [{ file: "settings.json", key: "mcpServers" }],
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
tool: "continue",
|
|
245
|
+
name: "Continue",
|
|
246
|
+
dirs: [join(home, ".continue")],
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
tool: "cline-cli",
|
|
250
|
+
name: "Cline CLI",
|
|
251
|
+
dirs: [join(home, ".cline")],
|
|
252
|
+
mcpConfigs: [
|
|
253
|
+
{
|
|
254
|
+
file: join("data", "settings", "cline_mcp_settings.json"),
|
|
255
|
+
key: "mcpServers",
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
tool: "claude-desktop",
|
|
261
|
+
name: "Claude Desktop",
|
|
262
|
+
dirs: isWin
|
|
263
|
+
? [join(appData, "Claude")]
|
|
264
|
+
: isMac
|
|
265
|
+
? [join(home, "Library", "Application Support", "Claude")]
|
|
266
|
+
: [join(xdgConfigHome, "Claude")],
|
|
267
|
+
mcpConfigs: [{ file: "claude_desktop_config.json", key: "mcpServers" }],
|
|
169
268
|
},
|
|
170
269
|
{
|
|
171
270
|
tool: "goose",
|
|
@@ -184,11 +283,16 @@ export function getAgenticToolDirs() {
|
|
|
184
283
|
join(home, ".config", "amp"),
|
|
185
284
|
...(isWin ? [join(appData, "amp")] : []),
|
|
186
285
|
],
|
|
286
|
+
mcpConfigs: [
|
|
287
|
+
{ file: "settings.json", key: "amp.mcpServers" },
|
|
288
|
+
{ file: "settings.jsonc", key: "amp.mcpServers" },
|
|
289
|
+
],
|
|
187
290
|
},
|
|
188
291
|
{
|
|
189
292
|
tool: "copilot-cli",
|
|
190
293
|
name: "GitHub Copilot CLI",
|
|
191
294
|
dirs: [join(home, ".copilot-cli"), join(home, ".copilot")],
|
|
295
|
+
mcpConfigs: [{ file: "mcp-config.json", key: "mcpServers" }],
|
|
192
296
|
},
|
|
193
297
|
{
|
|
194
298
|
tool: "crush",
|
|
@@ -206,27 +310,52 @@ export function getAgenticToolDirs() {
|
|
|
206
310
|
* Discover which agentic CLI tools are installed for the current user by
|
|
207
311
|
* checking their known home directories.
|
|
208
312
|
*
|
|
209
|
-
* @
|
|
313
|
+
* @param {string} [home] Home directory to resolve the folders against
|
|
314
|
+
* @returns {Array<{tool: string, name: string, dir: string, supportDir?: string, mcpConfigs?: Object[], ide?: string}>}
|
|
210
315
|
* One entry per discovered tool, using the first existing directory.
|
|
211
316
|
*/
|
|
212
|
-
export function discoverAgenticTools() {
|
|
317
|
+
export function discoverAgenticTools(home = homedir()) {
|
|
213
318
|
const found = [];
|
|
214
|
-
for (const entry of getAgenticToolDirs()) {
|
|
215
|
-
const
|
|
216
|
-
if (!
|
|
319
|
+
for (const entry of getAgenticToolDirs(home)) {
|
|
320
|
+
const marker = entry.dirs.find((candidate) => safeExistsSync(candidate));
|
|
321
|
+
if (!marker) {
|
|
217
322
|
continue;
|
|
218
323
|
}
|
|
219
324
|
const supportDir = (entry.supportDirs || []).find((candidate) =>
|
|
220
325
|
safeExistsSync(candidate),
|
|
221
326
|
);
|
|
222
|
-
found.push({
|
|
327
|
+
found.push({
|
|
328
|
+
tool: entry.tool,
|
|
329
|
+
name: entry.name,
|
|
330
|
+
dir: entry.homeDir || marker,
|
|
331
|
+
supportDir,
|
|
332
|
+
mcpConfigs: entry.mcpConfigs,
|
|
333
|
+
ide: entry.ide,
|
|
334
|
+
});
|
|
223
335
|
}
|
|
224
336
|
return found;
|
|
225
337
|
}
|
|
226
338
|
|
|
339
|
+
// Largest tool configuration file read. ~/.claude.json keeps per-project state
|
|
340
|
+
// and grows with use, so the cap is generous; it only has to stop a FIFO, a
|
|
341
|
+
// device or a runaway file from stalling the scan or exhausting memory.
|
|
342
|
+
const MAX_CONFIG_FILE_BYTES = 64 * 1024 * 1024;
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Read a tool configuration file only when it is a regular file of bounded
|
|
346
|
+
* size. A FIFO or a device reads as undefined, and so does a symlink when the
|
|
347
|
+
* scan runs as root.
|
|
348
|
+
*
|
|
349
|
+
* @param {string} filePath Absolute path
|
|
350
|
+
* @returns {string|undefined} File text, or undefined
|
|
351
|
+
*/
|
|
352
|
+
function readConfigText(filePath) {
|
|
353
|
+
return readUserFileText(filePath, MAX_CONFIG_FILE_BYTES);
|
|
354
|
+
}
|
|
355
|
+
|
|
227
356
|
function parseJsonFile(filePath) {
|
|
228
357
|
try {
|
|
229
|
-
return parseJsonLikeText(
|
|
358
|
+
return parseJsonLikeText(readConfigText(filePath));
|
|
230
359
|
} catch {
|
|
231
360
|
return undefined;
|
|
232
361
|
}
|
|
@@ -335,7 +464,52 @@ function countDirEntries(dirPath, filterFn) {
|
|
|
335
464
|
* @param {string} [supportDir] Optional platform support directory
|
|
336
465
|
* @returns {string|undefined} Version string when found
|
|
337
466
|
*/
|
|
467
|
+
/**
|
|
468
|
+
* Read a semver-like version from the start of a release folder name such as
|
|
469
|
+
* `2.1.278` or `0.137.0-aarch64-apple-darwin`.
|
|
470
|
+
*
|
|
471
|
+
* @param {string} name Folder name
|
|
472
|
+
* @returns {string|undefined} The version, or undefined
|
|
473
|
+
*/
|
|
474
|
+
function versionFromReleaseName(name) {
|
|
475
|
+
const match = /^v?(\d+\.\d+\.\d+)/u.exec(`${name || ""}`);
|
|
476
|
+
return match ? match[1] : undefined;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Follow a launcher symlink that points into a per-version folder, as the
|
|
481
|
+
* native Claude Code and Codex installers do, and read the version from the
|
|
482
|
+
* folder name.
|
|
483
|
+
*
|
|
484
|
+
* @param {string} linkPath Launcher or `current` symlink
|
|
485
|
+
* @returns {string|undefined} The version, or undefined
|
|
486
|
+
*/
|
|
487
|
+
function versionFromReleaseLink(linkPath) {
|
|
488
|
+
try {
|
|
489
|
+
return versionFromReleaseName(basename(realpathSync(linkPath)));
|
|
490
|
+
} catch {
|
|
491
|
+
return undefined;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
338
495
|
function readToolVersion(tool, dir, supportDir) {
|
|
496
|
+
if (tool === "claude-code") {
|
|
497
|
+
const home = dirname(dir);
|
|
498
|
+
const version =
|
|
499
|
+
versionFromReleaseLink(join(home, ".local", "bin", "claude")) ||
|
|
500
|
+
versionFromReleaseLink(join(home, ".claude", "local", "claude"));
|
|
501
|
+
if (version) {
|
|
502
|
+
return version;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (tool === "codex") {
|
|
506
|
+
const version = versionFromReleaseLink(
|
|
507
|
+
join(dir, "packages", "standalone", "current"),
|
|
508
|
+
);
|
|
509
|
+
if (version) {
|
|
510
|
+
return version;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
339
513
|
const candidates = [];
|
|
340
514
|
if (tool === "opencode") {
|
|
341
515
|
// ~/.opencode/package.json pins the installed opencode plugin runtime,
|
|
@@ -370,7 +544,7 @@ function readToolVersion(tool, dir, supportDir) {
|
|
|
370
544
|
}
|
|
371
545
|
} else {
|
|
372
546
|
try {
|
|
373
|
-
const raw =
|
|
547
|
+
const raw = (readConfigText(candidate) || "").trim();
|
|
374
548
|
if (/^v?\d+\.\d+/.test(raw)) {
|
|
375
549
|
return raw.replace(/^v/, "");
|
|
376
550
|
}
|
|
@@ -476,7 +650,7 @@ function countCodexSignals(dir) {
|
|
|
476
650
|
return signals;
|
|
477
651
|
}
|
|
478
652
|
try {
|
|
479
|
-
const config = parseToml(
|
|
653
|
+
const config = parseToml(readConfigText(configPath) || "");
|
|
480
654
|
if (config?.mcp_servers && typeof config.mcp_servers === "object") {
|
|
481
655
|
signals.mcpServerCount = Object.keys(config.mcp_servers).length;
|
|
482
656
|
}
|
|
@@ -488,24 +662,35 @@ function countCodexSignals(dir) {
|
|
|
488
662
|
|
|
489
663
|
function countCursorSignals(dir) {
|
|
490
664
|
const signals = {};
|
|
491
|
-
const mcpConfig = parseJsonFile(join(dir, "mcp.json"));
|
|
492
|
-
if (mcpConfig?.mcpServers && typeof mcpConfig.mcpServers === "object") {
|
|
493
|
-
signals.mcpServerCount = Object.keys(mcpConfig.mcpServers).length;
|
|
494
|
-
}
|
|
495
665
|
const agentsDir = join(dir, "agents");
|
|
496
666
|
if (safeExistsSync(agentsDir)) {
|
|
497
667
|
signals.agentCount = countDirEntries(agentsDir, (name) =>
|
|
498
668
|
name.endsWith(".md"),
|
|
499
669
|
);
|
|
500
670
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
671
|
+
const rulesDir = join(dir, "rules");
|
|
672
|
+
if (safeExistsSync(rulesDir)) {
|
|
673
|
+
signals.ruleCount = countDirEntries(rulesDir, (name) =>
|
|
674
|
+
/\.(md|mdc)$/u.test(name),
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
for (const [skillsDir, key] of [
|
|
678
|
+
[join(dir, "skills"), "skillCount"],
|
|
679
|
+
[join(dir, "skills-cursor"), "bundledSkillCount"],
|
|
680
|
+
]) {
|
|
681
|
+
if (safeExistsSync(skillsDir)) {
|
|
682
|
+
signals[key] = countDirEntries(skillsDir);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
// The Cursor CLI keeps its tool permissions in cli-config.json. Only the
|
|
686
|
+
// number of rules is recorded, not the commands or paths they name.
|
|
687
|
+
const cliConfig = parseJsonFile(join(dir, "cli-config.json"));
|
|
688
|
+
const permissions = cliConfig?.permissions;
|
|
689
|
+
if (Array.isArray(permissions?.allow)) {
|
|
690
|
+
signals.cliAllowRuleCount = permissions.allow.length;
|
|
691
|
+
}
|
|
692
|
+
if (Array.isArray(permissions?.deny)) {
|
|
693
|
+
signals.cliDenyRuleCount = permissions.deny.length;
|
|
509
694
|
}
|
|
510
695
|
return signals;
|
|
511
696
|
}
|
|
@@ -514,7 +699,7 @@ function countGooseSignals(dir) {
|
|
|
514
699
|
const signals = {};
|
|
515
700
|
// Goose keeps its resolved extension set in config.yaml.
|
|
516
701
|
try {
|
|
517
|
-
const config = loadYaml(
|
|
702
|
+
const config = loadYaml(readConfigText(join(dir, "config.yaml")) || "");
|
|
518
703
|
if (Array.isArray(config?.extensions)) {
|
|
519
704
|
signals.extensionCount = config.extensions.length;
|
|
520
705
|
}
|
|
@@ -546,6 +731,81 @@ function countGeminiSignals(dir) {
|
|
|
546
731
|
return signals;
|
|
547
732
|
}
|
|
548
733
|
|
|
734
|
+
function countClaudeDesktopSignals(dir) {
|
|
735
|
+
const signals = {};
|
|
736
|
+
// Desktop extensions (.mcpb/.dxt) each bundle an MCP server.
|
|
737
|
+
const extensionsDir = join(dir, "Claude Extensions");
|
|
738
|
+
if (safeExistsSync(extensionsDir)) {
|
|
739
|
+
signals.extensionCount = countDirEntries(extensionsDir);
|
|
740
|
+
}
|
|
741
|
+
return signals;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Count the MCP servers a tool lists across its configuration files. A server
|
|
746
|
+
* named in two files is counted once.
|
|
747
|
+
*
|
|
748
|
+
* @param {string} dir Tool home directory, which relative files resolve against
|
|
749
|
+
* @param {Array<{file: string, key: string}>} [mcpConfigs] Files and the key
|
|
750
|
+
* holding the server map. A key is looked up as written first, so a dotted
|
|
751
|
+
* key such as `amp.mcpServers` matches a literal property.
|
|
752
|
+
* @returns {number|undefined} Number of servers, or undefined when no file
|
|
753
|
+
* defines any
|
|
754
|
+
*/
|
|
755
|
+
export function countConfiguredMcpServers(dir, mcpConfigs) {
|
|
756
|
+
const names = new Set();
|
|
757
|
+
let sawConfig = false;
|
|
758
|
+
for (const { file, key } of mcpConfigs || []) {
|
|
759
|
+
const config = parseJsonFile(isAbsolute(file) ? file : join(dir, file));
|
|
760
|
+
if (!config || typeof config !== "object") {
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
let servers = config[key];
|
|
764
|
+
if (servers === undefined && key.includes(".")) {
|
|
765
|
+
servers = key.split(".").reduce((value, part) => value?.[part], config);
|
|
766
|
+
}
|
|
767
|
+
if (servers && typeof servers === "object" && !Array.isArray(servers)) {
|
|
768
|
+
sawConfig = true;
|
|
769
|
+
for (const name of Object.keys(servers)) {
|
|
770
|
+
names.add(name);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return sawConfig ? names.size : undefined;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Describe the editor an agentic tool ships in: its installed version and
|
|
779
|
+
* how many extensions are in its extensions folder.
|
|
780
|
+
*
|
|
781
|
+
* @param {string} ideId Entry id from IDE_PRODUCTS
|
|
782
|
+
* @param {string} home Home directory
|
|
783
|
+
* @returns {{version?: string, installDir?: string, extensionCount?: number}}
|
|
784
|
+
* What could be read
|
|
785
|
+
*/
|
|
786
|
+
function describeAgenticIde(ideId, home) {
|
|
787
|
+
const product = IDE_PRODUCTS.find((p) => p.id === ideId);
|
|
788
|
+
if (!product) {
|
|
789
|
+
return {};
|
|
790
|
+
}
|
|
791
|
+
const info = findIdeProductInfo({ id: ideId, kind: "desktop" }, home);
|
|
792
|
+
const result = {};
|
|
793
|
+
if (info?.version) {
|
|
794
|
+
result.version = info.version;
|
|
795
|
+
}
|
|
796
|
+
if (info?.appDir) {
|
|
797
|
+
result.installDir = info.appDir;
|
|
798
|
+
}
|
|
799
|
+
const extensionsDir = join(home, product.dataFolderName, "extensions");
|
|
800
|
+
if (safeExistsSync(extensionsDir)) {
|
|
801
|
+
result.extensionCount = countDirEntries(
|
|
802
|
+
extensionsDir,
|
|
803
|
+
(name) => !name.startsWith(".") && name !== "extensions.json",
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
return result;
|
|
807
|
+
}
|
|
808
|
+
|
|
549
809
|
/**
|
|
550
810
|
* Compute safe, redacted signal counts for a discovered tool. Only structural
|
|
551
811
|
* counts and small enumerated values are returned; no credential, transcript,
|
|
@@ -554,9 +814,34 @@ function countGeminiSignals(dir) {
|
|
|
554
814
|
* @param {string} tool Tool key
|
|
555
815
|
* @param {string} dir Discovered tool home directory
|
|
556
816
|
* @param {string} [supportDir] Optional platform support directory
|
|
817
|
+
* @param {Array<{file: string, key: string}>} [mcpConfigs] Files that list
|
|
818
|
+
* the tool's MCP servers, used when the tool has no reader of its own
|
|
819
|
+
* @param {string} [home] Home directory the tool was found in
|
|
557
820
|
* @returns {Object} Map of signal name to count/boolean/small-string value
|
|
558
821
|
*/
|
|
559
|
-
export function collectToolSignals(
|
|
822
|
+
export function collectToolSignals(
|
|
823
|
+
tool,
|
|
824
|
+
dir,
|
|
825
|
+
supportDir,
|
|
826
|
+
mcpConfigs,
|
|
827
|
+
home = homedir(),
|
|
828
|
+
) {
|
|
829
|
+
const signals = toolSpecificSignals(tool, dir, supportDir);
|
|
830
|
+
if (signals.mcpServerCount === undefined) {
|
|
831
|
+
const mcpServerCount = countConfiguredMcpServers(
|
|
832
|
+
dir,
|
|
833
|
+
mcpConfigs ||
|
|
834
|
+
getAgenticToolDirs(home).find((entry) => entry.tool === tool)
|
|
835
|
+
?.mcpConfigs || [{ file: ".mcp.json", key: "mcpServers" }],
|
|
836
|
+
);
|
|
837
|
+
if (mcpServerCount !== undefined) {
|
|
838
|
+
signals.mcpServerCount = mcpServerCount;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return signals;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function toolSpecificSignals(tool, dir, supportDir) {
|
|
560
845
|
switch (tool) {
|
|
561
846
|
case "kiro-cli":
|
|
562
847
|
return countKiroSignals(dir, supportDir);
|
|
@@ -572,48 +857,60 @@ export function collectToolSignals(tool, dir, supportDir) {
|
|
|
572
857
|
return countGeminiSignals(dir);
|
|
573
858
|
case "cursor":
|
|
574
859
|
return countCursorSignals(dir);
|
|
575
|
-
case "
|
|
576
|
-
return
|
|
860
|
+
case "claude-desktop":
|
|
861
|
+
return countClaudeDesktopSignals(dir);
|
|
577
862
|
case "goose":
|
|
578
863
|
return countGooseSignals(dir);
|
|
579
864
|
case "crush":
|
|
580
865
|
return countCrushSignals(dir);
|
|
581
|
-
default:
|
|
582
|
-
|
|
583
|
-
const signals = {};
|
|
584
|
-
const mcpConfig = parseJsonFile(join(dir, ".mcp.json"));
|
|
585
|
-
if (mcpConfig?.mcpServers) {
|
|
586
|
-
signals.mcpServerCount = Object.keys(mcpConfig.mcpServers).length;
|
|
587
|
-
}
|
|
588
|
-
return signals;
|
|
589
|
-
}
|
|
866
|
+
default:
|
|
867
|
+
return {};
|
|
590
868
|
}
|
|
591
869
|
}
|
|
592
870
|
|
|
593
871
|
/**
|
|
594
872
|
* Build a CycloneDX application component for one discovered agentic tool.
|
|
595
873
|
*
|
|
596
|
-
* @param {{tool: string, name: string, dir: string, supportDir?: string}} entry
|
|
874
|
+
* @param {{tool: string, name: string, dir: string, supportDir?: string, mcpConfigs?: Object[], ide?: string}} entry
|
|
597
875
|
* Discovered tool entry
|
|
876
|
+
* @param {string} [home] Home directory, used to find the tool's editor
|
|
598
877
|
* @returns {Object} CycloneDX component
|
|
599
878
|
*/
|
|
600
|
-
export function componentForAgenticTool(entry) {
|
|
601
|
-
const { tool, name, dir, supportDir } = entry;
|
|
602
|
-
const
|
|
879
|
+
export function componentForAgenticTool(entry, home = homedir()) {
|
|
880
|
+
const { tool, name, dir, supportDir, mcpConfigs, ide } = entry;
|
|
881
|
+
const ideInfo = ide ? describeAgenticIde(ide, home) : {};
|
|
882
|
+
const version = readToolVersion(tool, dir, supportDir) || ideInfo.version;
|
|
603
883
|
const purl = buildGenericPurl(tool, version);
|
|
604
884
|
const properties = [
|
|
605
|
-
{ name: "internal:SrcFile", value: redactHomePath(dir) },
|
|
885
|
+
{ name: "internal:SrcFile", value: redactHomePath(dir, home) },
|
|
606
886
|
{ name: "cdx:agentic:tool", value: tool },
|
|
607
|
-
{ name: "cdx:agentic:home", value: redactHomePath(dir) },
|
|
887
|
+
{ name: "cdx:agentic:home", value: redactHomePath(dir, home) },
|
|
608
888
|
{ name: "cdx:agentic:inventorySource", value: "host-scan" },
|
|
609
889
|
];
|
|
610
890
|
if (supportDir) {
|
|
611
891
|
properties.push({
|
|
612
892
|
name: "cdx:agentic:supportDir",
|
|
613
|
-
value: redactHomePath(supportDir),
|
|
893
|
+
value: redactHomePath(supportDir, home),
|
|
614
894
|
});
|
|
615
895
|
}
|
|
616
|
-
|
|
896
|
+
// ~/.cursor or ~/.codeium/windsurf can exist with only a CLI or a plugin
|
|
897
|
+
// installed, so the editor is named only when it was found.
|
|
898
|
+
const ideFound =
|
|
899
|
+
Boolean(ide) &&
|
|
900
|
+
(ideInfo.installDir !== undefined || ideInfo.extensionCount !== undefined);
|
|
901
|
+
if (ideFound) {
|
|
902
|
+
properties.push({ name: "cdx:agentic:ide", value: ide });
|
|
903
|
+
}
|
|
904
|
+
if (ideInfo.installDir) {
|
|
905
|
+
properties.push({
|
|
906
|
+
name: "cdx:agentic:ideInstallDir",
|
|
907
|
+
value: redactHomePath(ideInfo.installDir, home),
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
const signals = collectToolSignals(tool, dir, supportDir, mcpConfigs, home);
|
|
911
|
+
if (ideInfo.extensionCount !== undefined) {
|
|
912
|
+
signals.extensionCount = ideInfo.extensionCount;
|
|
913
|
+
}
|
|
617
914
|
for (const [key, value] of Object.entries(signals)) {
|
|
618
915
|
if (value === undefined || value === null || value === "") {
|
|
619
916
|
continue;
|
|
@@ -639,7 +936,12 @@ export function componentForAgenticTool(entry) {
|
|
|
639
936
|
if (purl) {
|
|
640
937
|
component.purl = purl;
|
|
641
938
|
}
|
|
642
|
-
component.description =
|
|
939
|
+
component.description = ideFound
|
|
940
|
+
? `${name} AI editor discovered on the host`
|
|
941
|
+
: `${name} agentic CLI tool discovered on the host`;
|
|
942
|
+
if (ideFound) {
|
|
943
|
+
component.tags.push("ai-ide");
|
|
944
|
+
}
|
|
643
945
|
return component;
|
|
644
946
|
}
|
|
645
947
|
|
|
@@ -656,9 +958,10 @@ function childBomRef(parentRef, kind, id) {
|
|
|
656
958
|
*
|
|
657
959
|
* @param {string} dir Kiro home directory
|
|
658
960
|
* @param {string} parentRef Parent tool bom-ref
|
|
961
|
+
* @param {string} [home] Home directory the tool was found in, for redaction
|
|
659
962
|
* @returns {Object[]} Child components
|
|
660
963
|
*/
|
|
661
|
-
function parseKiroAgents(dir, parentRef) {
|
|
964
|
+
function parseKiroAgents(dir, parentRef, home = homedir()) {
|
|
662
965
|
const agentsDir = join(dir, "agents");
|
|
663
966
|
if (!safeExistsSync(agentsDir)) {
|
|
664
967
|
return [];
|
|
@@ -681,7 +984,7 @@ function parseKiroAgents(dir, parentRef) {
|
|
|
681
984
|
const properties = [
|
|
682
985
|
{
|
|
683
986
|
name: "internal:SrcFile",
|
|
684
|
-
value: redactHomePath(join(agentsDir, file)),
|
|
987
|
+
value: redactHomePath(join(agentsDir, file), home),
|
|
685
988
|
},
|
|
686
989
|
{ name: "cdx:agentic:tool", value: "kiro-cli" },
|
|
687
990
|
{ name: "cdx:agentic:kind", value: "agent-definition" },
|
|
@@ -725,9 +1028,10 @@ function parseKiroAgents(dir, parentRef) {
|
|
|
725
1028
|
*
|
|
726
1029
|
* @param {string} dir zcode home directory
|
|
727
1030
|
* @param {string} parentRef Parent tool bom-ref
|
|
1031
|
+
* @param {string} [home] Home directory the tool was found in, for redaction
|
|
728
1032
|
* @returns {Object[]} Child components
|
|
729
1033
|
*/
|
|
730
|
-
function parseZcodePlugins(dir, parentRef) {
|
|
1034
|
+
function parseZcodePlugins(dir, parentRef, home = homedir()) {
|
|
731
1035
|
const cacheDir = join(dir, "cli", "plugins", "cache");
|
|
732
1036
|
if (!safeExistsSync(cacheDir)) {
|
|
733
1037
|
return [];
|
|
@@ -763,7 +1067,7 @@ function parseZcodePlugins(dir, parentRef) {
|
|
|
763
1067
|
typeof plugin.version === "string" ? plugin.version : undefined,
|
|
764
1068
|
);
|
|
765
1069
|
const properties = [
|
|
766
|
-
{ name: "internal:SrcFile", value: redactHomePath(full) },
|
|
1070
|
+
{ name: "internal:SrcFile", value: redactHomePath(full, home) },
|
|
767
1071
|
{ name: "cdx:agentic:tool", value: "zcode" },
|
|
768
1072
|
{ name: "cdx:agentic:kind", value: "plugin" },
|
|
769
1073
|
];
|
|
@@ -813,6 +1117,7 @@ function parseZcodePlugins(dir, parentRef) {
|
|
|
813
1117
|
*
|
|
814
1118
|
* @param {string} dir opencode home directory
|
|
815
1119
|
* @param {string} parentRef Parent tool bom-ref
|
|
1120
|
+
* @param {string} [home] Home directory the tool was found in, for redaction
|
|
816
1121
|
* @returns {Object[]} Child components
|
|
817
1122
|
*/
|
|
818
1123
|
/**
|
|
@@ -823,9 +1128,10 @@ function parseZcodePlugins(dir, parentRef) {
|
|
|
823
1128
|
*
|
|
824
1129
|
* @param {string} dir opencode home directory
|
|
825
1130
|
* @param {string} parentRef Parent tool bom-ref
|
|
1131
|
+
* @param {string} [home] Home directory the tool was found in, for redaction
|
|
826
1132
|
* @returns {Object[]} Child provider services
|
|
827
1133
|
*/
|
|
828
|
-
function parseOpencodeProviders(dir, parentRef) {
|
|
1134
|
+
function parseOpencodeProviders(dir, parentRef, home = homedir()) {
|
|
829
1135
|
const jsonPath = join(dir, "opencode.json");
|
|
830
1136
|
const jsoncPath = join(dir, "opencode.jsonc");
|
|
831
1137
|
const usedPath = safeExistsSync(jsonPath) ? jsonPath : jsoncPath;
|
|
@@ -840,7 +1146,7 @@ function parseOpencodeProviders(dir, parentRef) {
|
|
|
840
1146
|
group: providerName,
|
|
841
1147
|
name: providerName,
|
|
842
1148
|
properties: [
|
|
843
|
-
{ name: "internal:SrcFile", value: redactHomePath(usedPath) },
|
|
1149
|
+
{ name: "internal:SrcFile", value: redactHomePath(usedPath, home) },
|
|
844
1150
|
{ name: "cdx:agentic:tool", value: "opencode" },
|
|
845
1151
|
{ name: "cdx:agentic:kind", value: "provider" },
|
|
846
1152
|
{ name: "cdx:ai:kind", value: "inference-service" },
|
|
@@ -857,18 +1163,25 @@ function parseOpencodeProviders(dir, parentRef) {
|
|
|
857
1163
|
* @param {string} tool Tool key
|
|
858
1164
|
* @param {string} dir Tool home directory
|
|
859
1165
|
* @param {string} parentRef Parent tool bom-ref
|
|
1166
|
+
* @param {string} [home] Home directory the tool was found in, for redaction
|
|
860
1167
|
* @returns {{components: Object[], services: Object[]}} Child inventory
|
|
861
1168
|
*/
|
|
862
|
-
function collectToolChildren(tool, dir, parentRef) {
|
|
1169
|
+
function collectToolChildren(tool, dir, parentRef, home = homedir()) {
|
|
863
1170
|
switch (tool) {
|
|
864
1171
|
case "kiro-cli":
|
|
865
|
-
return {
|
|
1172
|
+
return {
|
|
1173
|
+
components: parseKiroAgents(dir, parentRef, home),
|
|
1174
|
+
services: [],
|
|
1175
|
+
};
|
|
866
1176
|
case "zcode":
|
|
867
|
-
return {
|
|
1177
|
+
return {
|
|
1178
|
+
components: parseZcodePlugins(dir, parentRef, home),
|
|
1179
|
+
services: [],
|
|
1180
|
+
};
|
|
868
1181
|
case "opencode":
|
|
869
1182
|
return {
|
|
870
1183
|
components: [],
|
|
871
|
-
services: parseOpencodeProviders(dir, parentRef),
|
|
1184
|
+
services: parseOpencodeProviders(dir, parentRef, home),
|
|
872
1185
|
};
|
|
873
1186
|
default:
|
|
874
1187
|
return { components: [], services: [] };
|
|
@@ -882,22 +1195,29 @@ function collectToolChildren(tool, dir, parentRef) {
|
|
|
882
1195
|
*
|
|
883
1196
|
* This host scan is unconditional: it does not consult scan filters such as
|
|
884
1197
|
* `--exclude` or profile/technique options, because it inventories fixed tool
|
|
885
|
-
* home directories rather than a project tree.
|
|
886
|
-
* accepted for signature parity and is intentionally not used to alter results.
|
|
1198
|
+
* home directories rather than a project tree.
|
|
887
1199
|
*
|
|
888
|
-
* @param {Object} [options={}] Collection options
|
|
1200
|
+
* @param {Object} [options={}] Collection options
|
|
1201
|
+
* @param {string} [options.home] Home directory to scan, defaulting to the
|
|
1202
|
+
* current user's
|
|
889
1203
|
* @returns {{components: Object[], services: Object[], dependencies: Object[]}} Discovered inventory
|
|
890
1204
|
*/
|
|
891
|
-
export function collectAgenticTools(
|
|
1205
|
+
export function collectAgenticTools(options = {}) {
|
|
892
1206
|
const components = [];
|
|
893
1207
|
const services = [];
|
|
894
1208
|
const dependencies = [];
|
|
895
|
-
|
|
1209
|
+
const home = options.home || homedir();
|
|
1210
|
+
for (const entry of discoverAgenticTools(home)) {
|
|
896
1211
|
try {
|
|
897
|
-
const toolComponent = componentForAgenticTool(entry);
|
|
1212
|
+
const toolComponent = componentForAgenticTool(entry, home);
|
|
898
1213
|
components.push(toolComponent);
|
|
899
1214
|
const parentRef = toolComponent["bom-ref"];
|
|
900
|
-
const children = collectToolChildren(
|
|
1215
|
+
const children = collectToolChildren(
|
|
1216
|
+
entry.tool,
|
|
1217
|
+
entry.dir,
|
|
1218
|
+
parentRef,
|
|
1219
|
+
home,
|
|
1220
|
+
);
|
|
901
1221
|
const childRefs = [];
|
|
902
1222
|
if (children.components.length) {
|
|
903
1223
|
components.push(...children.components);
|