@shomra/agent 0.3.17 → 0.3.18
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/NOTICE +1 -1
- package/README.md +57 -57
- package/package.json +3 -9
- package/shomra.mjs +9 -7168
- package/src/agents/hook-command.mjs +19 -0
- package/src/agents/hook-files.mjs +41 -0
- package/src/agents/installers.mjs +203 -0
- package/src/artifacts/matchers.mjs +59 -0
- package/src/artifacts/report.mjs +50 -0
- package/src/cli/flags.mjs +68 -0
- package/src/cli/help-sections.mjs +309 -0
- package/src/cli/help.mjs +27 -0
- package/src/cli/main.mjs +55 -0
- package/src/cli/registry.mjs +80 -0
- package/src/cli/suggestions.mjs +33 -0
- package/src/commands/add.mjs +149 -0
- package/src/commands/agent-identity.mjs +46 -0
- package/src/commands/check.mjs +194 -0
- package/src/commands/corpus.mjs +126 -0
- package/src/commands/design.mjs +168 -0
- package/src/commands/doctor.mjs +209 -0
- package/src/commands/fix.mjs +115 -0
- package/src/commands/gate.mjs +154 -0
- package/src/commands/git-hooks.mjs +163 -0
- package/src/commands/init.mjs +36 -0
- package/src/commands/install-hook.mjs +51 -0
- package/src/commands/llm-proxy.mjs +153 -0
- package/src/commands/mcp-add.mjs +185 -0
- package/src/commands/mcp.mjs +143 -0
- package/src/commands/memory-scan.mjs +181 -0
- package/src/commands/model-scan.mjs +99 -0
- package/src/commands/models.mjs +145 -0
- package/src/commands/new.mjs +64 -0
- package/src/commands/plan.mjs +87 -0
- package/src/commands/pr.mjs +249 -0
- package/src/commands/protect.mjs +38 -0
- package/src/commands/provenance.mjs +91 -0
- package/src/commands/redteam.mjs +166 -0
- package/src/commands/rules.mjs +220 -0
- package/src/commands/run.mjs +128 -0
- package/src/commands/scan-zip.mjs +118 -0
- package/src/commands/scan.mjs +102 -0
- package/src/commands/secrets.mjs +99 -0
- package/src/commands/status.mjs +50 -0
- package/src/commands/why.mjs +88 -0
- package/src/core/api-client.mjs +66 -0
- package/src/core/api-key.mjs +6 -0
- package/src/core/circuit-breaker.mjs +42 -0
- package/src/core/config.mjs +37 -0
- package/src/core/exit-codes.mjs +9 -0
- package/src/core/json-file.mjs +13 -0
- package/src/core/numbers.mjs +4 -0
- package/src/core/package-root.mjs +10 -0
- package/src/core/terminal.mjs +16 -0
- package/src/core/version.mjs +14 -0
- package/src/core/wire-limits.mjs +53 -0
- package/src/corpus/screening.mjs +127 -0
- package/{ai-usage.mjs → src/detect/ai-usage.mjs} +0 -27
- package/src/detect/code-sast.mjs +2 -0
- package/{design.mjs → src/detect/design.mjs} +17 -106
- package/src/detect/guard-signals.mjs +18 -0
- package/{model-refs.mjs → src/detect/model-refs.mjs} +18 -77
- package/src/detect/sast/chains.mjs +30 -0
- package/src/detect/sast/path-expressions.mjs +76 -0
- package/src/detect/sast/rules-chains.mjs +33 -0
- package/src/detect/sast/rules-config.mjs +51 -0
- package/src/detect/sast/rules-javascript.mjs +109 -0
- package/src/detect/sast/rules-python.mjs +292 -0
- package/src/detect/sast/scanner.mjs +104 -0
- package/src/detect/sast/source-lines.mjs +115 -0
- package/src/detect/sast/taint.mjs +71 -0
- package/src/detect/signals/artifacts.mjs +113 -0
- package/src/detect/signals/autonomy.mjs +55 -0
- package/src/detect/signals/config-markers.mjs +28 -0
- package/src/detect/signals/credential-harvest.mjs +64 -0
- package/src/detect/signals/durable-claims.mjs +73 -0
- package/src/detect/signals/egress.mjs +56 -0
- package/src/detect/signals/execution-hijack.mjs +128 -0
- package/src/detect/signals/gate.mjs +91 -0
- package/src/detect/signals/injection.mjs +55 -0
- package/src/detect/signals/lines.mjs +42 -0
- package/src/detect/signals/masking.mjs +99 -0
- package/src/detect/signals/memory.mjs +357 -0
- package/src/detect/signals/packages.mjs +45 -0
- package/src/detect/signals/propagation.mjs +86 -0
- package/src/detect/signals/prose-context.mjs +82 -0
- package/src/detect/signals/scan.mjs +91 -0
- package/src/detect/signals/secrets.mjs +85 -0
- package/src/detect/signals/sensitive.mjs +9 -0
- package/src/detect/signals/severity.mjs +10 -0
- package/src/detect/signals/shell.mjs +96 -0
- package/src/detect/signals/staged-fetch.mjs +66 -0
- package/src/detect/signals/text-match.mjs +35 -0
- package/src/gate/batch.mjs +157 -0
- package/src/gate/environment.mjs +122 -0
- package/src/gate/repo-policy.mjs +65 -0
- package/src/gate/result.mjs +53 -0
- package/src/gate/sarif.mjs +33 -0
- package/src/gate/sast.mjs +64 -0
- package/src/gate/suppressions.mjs +0 -0
- package/src/guard/classify.mjs +50 -0
- package/src/guard/emit.mjs +51 -0
- package/src/guard/ignore.mjs +24 -0
- package/src/guard/ledger.mjs +112 -0
- package/src/guard/model-load.mjs +50 -0
- package/src/guard/normalize.mjs +77 -0
- package/src/guard/options.mjs +10 -0
- package/src/guard/prompt-guard.mjs +184 -0
- package/src/guard/report.mjs +35 -0
- package/src/guard/result-guard.mjs +140 -0
- package/src/guard/tool-guard.mjs +166 -0
- package/src/inventory/agent-artifacts.mjs +5 -0
- package/src/inventory/agent-posture.mjs +249 -0
- package/src/inventory/artifacts/classify.mjs +27 -0
- package/src/inventory/artifacts/discover.mjs +187 -0
- package/src/inventory/artifacts/file-read.mjs +42 -0
- package/src/inventory/artifacts/hooks.mjs +14 -0
- package/src/inventory/artifacts/limits.mjs +37 -0
- package/src/inventory/artifacts/marketplaces.mjs +45 -0
- package/src/inventory/artifacts/roots.mjs +20 -0
- package/src/inventory/artifacts/walk.mjs +36 -0
- package/src/inventory/discovery/ai-dependencies.mjs +161 -0
- package/src/inventory/discovery/ai-tools.mjs +23 -0
- package/src/inventory/discovery/all.mjs +40 -0
- package/src/inventory/discovery/coding-agents.mjs +77 -0
- package/src/inventory/discovery/fs-read.mjs +36 -0
- package/src/inventory/discovery/local-runtimes.mjs +53 -0
- package/src/inventory/discovery/mcp-clients.mjs +67 -0
- package/src/inventory/discovery/mcp-servers.mjs +78 -0
- package/src/inventory/discovery/model-keys.mjs +97 -0
- package/src/inventory/discovery/platform.mjs +16 -0
- package/src/inventory/discovery/rules-files.mjs +25 -0
- package/src/inventory/discovery/vector-stores.mjs +176 -0
- package/src/inventory/discovery/workspace.mjs +124 -0
- package/src/inventory/discovery.mjs +10 -0
- package/src/mcp/child-process.mjs +50 -0
- package/src/mcp/config-wrapping.mjs +75 -0
- package/src/mcp/connect-gate.mjs +45 -0
- package/src/mcp/hosts.mjs +16 -0
- package/src/mcp/jsonrpc.mjs +48 -0
- package/src/mcp/lookup.mjs +50 -0
- package/src/mcp/screening.mjs +103 -0
- package/src/mcp/server-tools.mjs +97 -0
- package/src/mcp/server.mjs +102 -0
- package/src/mcp/shim.mjs +205 -0
- package/src/models/lookup.mjs +79 -0
- package/src/models/references.mjs +103 -0
- package/src/rules/context.mjs +98 -0
- package/src/rules/generate.mjs +103 -0
- package/src/rules/sections.mjs +145 -0
- package/src/scaffold/agent-project.mjs +185 -0
- package/src/scaffold/artifact-templates.mjs +35 -0
- package/code-sast.mjs +0 -1063
- package/discovery.mjs +0 -977
- package/guard-ledger.mjs +0 -239
- package/guard-signals.mjs +0 -2055
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { stripJsonComments } from './file-read.mjs';
|
|
2
|
+
|
|
3
|
+
export function canonicalHooks(text) {
|
|
4
|
+
let doc;
|
|
5
|
+
try {
|
|
6
|
+
doc = JSON.parse(stripJsonComments(text));
|
|
7
|
+
} catch {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
if (!doc || typeof doc !== 'object') return null;
|
|
11
|
+
const hooks = doc.hooks;
|
|
12
|
+
if (!hooks || typeof hooks !== 'object' || !Object.keys(hooks).length) return null;
|
|
13
|
+
return JSON.stringify({ hooks }, null, 2);
|
|
14
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const HOME = os.homedir();
|
|
5
|
+
|
|
6
|
+
export const ARTIFACT_KINDS = ['skill', 'command', 'subagent', 'hook'];
|
|
7
|
+
|
|
8
|
+
export const MAX_DEPTH = 6;
|
|
9
|
+
|
|
10
|
+
export const MAX_DIRS = 4_000;
|
|
11
|
+
|
|
12
|
+
export const MAX_ARTIFACTS = 400;
|
|
13
|
+
|
|
14
|
+
export const MAX_FILE_BYTES = 64_000;
|
|
15
|
+
|
|
16
|
+
export const MAX_BUNDLED = 20;
|
|
17
|
+
|
|
18
|
+
export const MAX_TOTAL_BYTES = 4_000_000;
|
|
19
|
+
|
|
20
|
+
export const IGNORE_DIRS = new Set([
|
|
21
|
+
'node_modules', '.git', 'dist', 'build', 'out', 'coverage', '__pycache__',
|
|
22
|
+
'.venv', 'venv', 'site-packages', '.next', '.turbo', 'target',
|
|
23
|
+
|
|
24
|
+
'projects', 'todos', 'statsig', 'shell-snapshots', 'history', 'logs', 'cache',
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
export const TEXT_EXTS = new Set([
|
|
28
|
+
'md', 'mdc', 'markdown', 'txt', 'toml', 'json', 'jsonc', 'yaml', 'yml',
|
|
29
|
+
'sh', 'bash', 'zsh', 'ps1', 'bat', 'cmd', 'py', 'js', 'mjs', 'cjs', 'ts',
|
|
30
|
+
'rb', 'pl', 'lua', 'sql', 'env', 'cfg', 'ini', 'conf',
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
export const extOf = (p) => {
|
|
34
|
+
const b = path.basename(p);
|
|
35
|
+
const i = b.lastIndexOf('.');
|
|
36
|
+
return i > 0 ? b.slice(i + 1).toLowerCase() : '';
|
|
37
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readJsonAt } from './file-read.mjs';
|
|
3
|
+
|
|
4
|
+
export const SETTINGS_BASENAMES = new Set([
|
|
5
|
+
'settings.json', 'settings.local.json', 'hooks.json', 'config.toml',
|
|
6
|
+
]);
|
|
7
|
+
|
|
8
|
+
export const PLUGIN_PATH_RE = /(^|\/)plugins\/marketplaces\/([^/]+)\//;
|
|
9
|
+
|
|
10
|
+
export const CATALOGUE_DIR_RE = /(^|\/)(\.tmp|tmp|temp|vendor_imports|bundled-marketplaces|backups?)\//i;
|
|
11
|
+
|
|
12
|
+
export function installedMarketplaces(root) {
|
|
13
|
+
|
|
14
|
+
const out = new Set();
|
|
15
|
+
|
|
16
|
+
const manifest = readJsonAt(path.join(root, 'plugins', 'installed_plugins.json'));
|
|
17
|
+
if (manifest === undefined) return null;
|
|
18
|
+
if (manifest !== null) {
|
|
19
|
+
const plugins = manifest?.plugins;
|
|
20
|
+
if (plugins && typeof plugins === 'object') {
|
|
21
|
+
for (const [key, value] of Object.entries(plugins)) {
|
|
22
|
+
addPluginKey(out, key);
|
|
23
|
+
const named = value && typeof value === 'object' ? (value.marketplace ?? value.source) : null;
|
|
24
|
+
if (typeof named === 'string') out.add(named);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for (const f of ['settings.json', 'settings.local.json']) {
|
|
30
|
+
const doc = readJsonAt(path.join(root, f));
|
|
31
|
+
if (doc === undefined) return null;
|
|
32
|
+
if (doc === null) continue;
|
|
33
|
+
const enabled = doc?.enabledPlugins;
|
|
34
|
+
if (enabled && typeof enabled === 'object') {
|
|
35
|
+
for (const key of Object.keys(enabled)) addPluginKey(out, key);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function addPluginKey(set, key) {
|
|
43
|
+
const at = String(key).indexOf('@');
|
|
44
|
+
set.add(at > -1 ? String(key).slice(at + 1) : String(key));
|
|
45
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { HOME } from './limits.mjs';
|
|
3
|
+
|
|
4
|
+
export function artifactRoots(cwd = process.cwd()) {
|
|
5
|
+
return [
|
|
6
|
+
{ vendor: 'claude-code', scope: 'user', dir: path.join(HOME, '.claude') },
|
|
7
|
+
{ vendor: 'claude-code', scope: 'project', dir: path.join(cwd, '.claude') },
|
|
8
|
+
{ vendor: 'cursor', scope: 'user', dir: path.join(HOME, '.cursor') },
|
|
9
|
+
{ vendor: 'cursor', scope: 'project', dir: path.join(cwd, '.cursor') },
|
|
10
|
+
{ vendor: 'codex', scope: 'user', dir: path.join(HOME, '.codex') },
|
|
11
|
+
{ vendor: 'codex', scope: 'project', dir: path.join(cwd, '.codex') },
|
|
12
|
+
{ vendor: 'gemini', scope: 'user', dir: path.join(HOME, '.gemini') },
|
|
13
|
+
{ vendor: 'gemini', scope: 'project', dir: path.join(cwd, '.gemini') },
|
|
14
|
+
{ vendor: 'windsurf', scope: 'project', dir: path.join(cwd, '.windsurf') },
|
|
15
|
+
{ vendor: 'opencode', scope: 'user', dir: path.join(HOME, '.opencode') },
|
|
16
|
+
{ vendor: 'opencode', scope: 'project', dir: path.join(cwd, '.opencode') },
|
|
17
|
+
|
|
18
|
+
{ vendor: 'copilot', scope: 'project', dir: path.join(cwd, '.github') },
|
|
19
|
+
];
|
|
20
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { IGNORE_DIRS, MAX_DEPTH } from './limits.mjs';
|
|
4
|
+
|
|
5
|
+
export function walkRoot(dir, budget) {
|
|
6
|
+
const out = [];
|
|
7
|
+
const seen = new Set();
|
|
8
|
+
const queue = [{ d: dir, depth: 0 }];
|
|
9
|
+
while (queue.length && budget.dirs > 0) {
|
|
10
|
+
const { d, depth } = queue.shift();
|
|
11
|
+
let real;
|
|
12
|
+
try {
|
|
13
|
+
real = fs.realpathSync(d);
|
|
14
|
+
} catch {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (seen.has(real)) continue;
|
|
18
|
+
seen.add(real);
|
|
19
|
+
budget.dirs--;
|
|
20
|
+
let entries;
|
|
21
|
+
try {
|
|
22
|
+
entries = fs.readdirSync(d, { withFileTypes: true });
|
|
23
|
+
} catch {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
for (const e of entries) {
|
|
27
|
+
const full = path.join(d, e.name);
|
|
28
|
+
if (e.isDirectory()) {
|
|
29
|
+
if (depth < MAX_DEPTH && !IGNORE_DIRS.has(e.name)) queue.push({ d: full, depth: depth + 1 });
|
|
30
|
+
} else if (e.isFile()) {
|
|
31
|
+
out.push(full);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { AI_USAGE_CATEGORY_LABEL, isAiUsageScannable, rollupAiUsage, scanAiUsage } from '../../detect/ai-usage.mjs';
|
|
3
|
+
import { readJson, readText } from './fs-read.mjs';
|
|
4
|
+
import { walkWorkspace } from './workspace.mjs';
|
|
5
|
+
|
|
6
|
+
const NPM_AI = new Set([
|
|
7
|
+
'openai', 'ai', 'langchain', 'llamaindex', 'ollama', 'replicate', 'cohere-ai',
|
|
8
|
+
'groq-sdk', 'together-ai', 'openrouter', 'mistralai', 'chromadb',
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
const NPM_AI_PREFIX = ['@anthropic-ai/', '@google/generative-ai', '@google/genai', '@ai-sdk/', '@langchain/', '@llamaindex/', '@mistralai/', '@huggingface/', '@pinecone-database/', '@qdrant/'];
|
|
12
|
+
|
|
13
|
+
const PY_AI = [
|
|
14
|
+
'openai', 'anthropic', 'google-generativeai', 'google-genai', 'langchain',
|
|
15
|
+
'langchain-openai', 'langchain-anthropic', 'langchain-community', 'llama-index',
|
|
16
|
+
'llama_index', 'transformers', 'sentence-transformers', 'mistralai', 'cohere',
|
|
17
|
+
'groq', 'huggingface-hub', 'huggingface_hub', 'ollama', 'litellm', 'guidance',
|
|
18
|
+
'vllm', 'crewai', 'autogen', 'pyautogen', 'haystack-ai', 'instructor', 'dspy',
|
|
19
|
+
'dspy-ai', 'semantic-kernel', 'replicate', 'together', 'chromadb', 'qdrant-client',
|
|
20
|
+
'pinecone-client', 'pinecone', 'faiss-cpu', 'faiss-gpu', 'tiktoken',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const VECTOR_LIBS = {
|
|
24
|
+
chromadb: { engine: 'chroma', hosted: false },
|
|
25
|
+
'faiss-cpu': { engine: 'faiss', hosted: false },
|
|
26
|
+
'faiss-gpu': { engine: 'faiss', hosted: false },
|
|
27
|
+
lancedb: { engine: 'lancedb', hosted: false },
|
|
28
|
+
pgvector: { engine: 'pgvector', hosted: false },
|
|
29
|
+
'qdrant-client': { engine: 'qdrant', hosted: true },
|
|
30
|
+
'pinecone-client': { engine: 'pinecone', hosted: true },
|
|
31
|
+
pinecone: { engine: 'pinecone', hosted: true },
|
|
32
|
+
'weaviate-client': { engine: 'weaviate', hosted: true },
|
|
33
|
+
'weaviate-ts-client': { engine: 'weaviate', hosted: true },
|
|
34
|
+
pymilvus: { engine: 'milvus', hosted: true },
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function vectorLibInfo(pkg) {
|
|
38
|
+
if (VECTOR_LIBS[pkg]) return VECTOR_LIBS[pkg];
|
|
39
|
+
if (pkg.startsWith('@pinecone-database/')) return { engine: 'pinecone', hosted: true };
|
|
40
|
+
if (pkg.startsWith('@qdrant/')) return { engine: 'qdrant', hosted: true };
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const isVectorLib = (pkg) => !!vectorLibInfo(pkg);
|
|
45
|
+
|
|
46
|
+
export const PY_VECTOR = [
|
|
47
|
+
'chromadb', 'faiss-cpu', 'faiss-gpu', 'lancedb', 'pgvector', 'qdrant-client',
|
|
48
|
+
'pinecone-client', 'pinecone', 'weaviate-client', 'pymilvus',
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
export const VECTOR_ENV = {
|
|
52
|
+
PINECONE_API_KEY: { engine: 'pinecone', kind: 'key' },
|
|
53
|
+
PINECONE_ENVIRONMENT: { engine: 'pinecone', kind: 'endpoint' },
|
|
54
|
+
PINECONE_HOST: { engine: 'pinecone', kind: 'endpoint' },
|
|
55
|
+
PINECONE_INDEX: { engine: 'pinecone', kind: 'endpoint' },
|
|
56
|
+
PINECONE_INDEX_NAME: { engine: 'pinecone', kind: 'endpoint' },
|
|
57
|
+
WEAVIATE_URL: { engine: 'weaviate', kind: 'endpoint' },
|
|
58
|
+
WEAVIATE_HOST: { engine: 'weaviate', kind: 'endpoint' },
|
|
59
|
+
WEAVIATE_API_KEY: { engine: 'weaviate', kind: 'key' },
|
|
60
|
+
QDRANT_URL: { engine: 'qdrant', kind: 'endpoint' },
|
|
61
|
+
QDRANT_HOST: { engine: 'qdrant', kind: 'endpoint' },
|
|
62
|
+
QDRANT_API_KEY: { engine: 'qdrant', kind: 'key' },
|
|
63
|
+
MILVUS_URI: { engine: 'milvus', kind: 'endpoint' },
|
|
64
|
+
MILVUS_HOST: { engine: 'milvus', kind: 'endpoint' },
|
|
65
|
+
ZILLIZ_CLOUD_URI: { engine: 'milvus', kind: 'endpoint' },
|
|
66
|
+
CHROMA_SERVER_HOST: { engine: 'chroma', kind: 'endpoint' },
|
|
67
|
+
CHROMA_HOST: { engine: 'chroma', kind: 'endpoint' },
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
function npmAiDeps(pkg) {
|
|
71
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}), ...(pkg.peerDependencies || {}), ...(pkg.optionalDependencies || {}) };
|
|
72
|
+
const hits = [];
|
|
73
|
+
for (const name of Object.keys(deps)) {
|
|
74
|
+
if (NPM_AI.has(name) || NPM_AI_PREFIX.some((p) => name.startsWith(p))) hits.push(name);
|
|
75
|
+
}
|
|
76
|
+
return hits;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function pyAiDeps(text) {
|
|
80
|
+
const hits = [];
|
|
81
|
+
for (const pkg of PY_AI) {
|
|
82
|
+
const re = new RegExp(`(^|[^a-z0-9_.-])${pkg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^a-z0-9_.-]|$)`, 'im');
|
|
83
|
+
if (re.test(text)) hits.push(pkg);
|
|
84
|
+
}
|
|
85
|
+
return hits;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function discoverAiDependencies(roots = [process.cwd()], files = null) {
|
|
89
|
+
const walk = files || walkWorkspace(roots);
|
|
90
|
+
const byPkg = new Map();
|
|
91
|
+
const add = (eco, pkg, manifest) => {
|
|
92
|
+
const key = `${eco}:${pkg}`;
|
|
93
|
+
if (!byPkg.has(key)) byPkg.set(key, { pkg, eco, manifests: new Set() });
|
|
94
|
+
byPkg.get(key).manifests.add(manifest);
|
|
95
|
+
};
|
|
96
|
+
for (const { file } of walk.manifests) {
|
|
97
|
+
const base = path.basename(file);
|
|
98
|
+
if (base === 'package.json') {
|
|
99
|
+
const json = readJson(file);
|
|
100
|
+
if (!json) continue;
|
|
101
|
+
|
|
102
|
+
for (const pkg of npmAiDeps(json)) if (!isVectorLib(pkg)) add('npm', pkg, file);
|
|
103
|
+
} else {
|
|
104
|
+
const text = readText(file, 100_000);
|
|
105
|
+
if (text == null) continue;
|
|
106
|
+
for (const pkg of pyAiDeps(text)) if (!isVectorLib(pkg)) add('pip', pkg, file);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const assets = [];
|
|
110
|
+
for (const { pkg, eco, manifests } of byPkg.values()) {
|
|
111
|
+
const list = [...manifests];
|
|
112
|
+
assets.push({
|
|
113
|
+
type: 'AI_TOOL',
|
|
114
|
+
name: `${pkg} (${eco})`,
|
|
115
|
+
identifier: `dep:${eco}:${pkg}`,
|
|
116
|
+
vendor: 'ai-sdk',
|
|
117
|
+
metadata: {
|
|
118
|
+
category: 'dependency',
|
|
119
|
+
ecosystem: eco,
|
|
120
|
+
package: pkg,
|
|
121
|
+
usedInProjects: list.length,
|
|
122
|
+
manifests: list.slice(0, 10),
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return assets;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function discoverAiUsageInCode(roots = [process.cwd()], files = null) {
|
|
130
|
+
const walk = files || walkWorkspace(roots);
|
|
131
|
+
const usages = [];
|
|
132
|
+
for (const { file } of walk.source || []) {
|
|
133
|
+
if (!isAiUsageScannable(path.basename(file))) continue;
|
|
134
|
+
const text = readText(file, 300_000);
|
|
135
|
+
if (text == null) continue;
|
|
136
|
+
for (const u of scanAiUsage(text, file)) usages.push(u);
|
|
137
|
+
}
|
|
138
|
+
const assets = [];
|
|
139
|
+
for (const row of rollupAiUsage(usages)) {
|
|
140
|
+
const site = row.firstSite;
|
|
141
|
+
assets.push({
|
|
142
|
+
type: 'AI_TOOL',
|
|
143
|
+
name: `${row.label} (in code)`,
|
|
144
|
+
identifier: `ai-usage:${row.provider}`,
|
|
145
|
+
vendor: 'ai-sdk',
|
|
146
|
+
metadata: {
|
|
147
|
+
category: 'code-usage',
|
|
148
|
+
provider: row.provider,
|
|
149
|
+
aiCategory: row.category,
|
|
150
|
+
aiCategoryLabel: AI_USAGE_CATEGORY_LABEL[row.category],
|
|
151
|
+
fileCount: row.files.length,
|
|
152
|
+
files: row.files.slice(0, 10),
|
|
153
|
+
models: row.models.slice(0, 10),
|
|
154
|
+
callSites: row.sightings,
|
|
155
|
+
hasCallSite: row.hasCallSite,
|
|
156
|
+
firstSite: site ? { file: site.file, line: site.line } : null,
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return assets;
|
|
161
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { firstExisting } from './fs-read.mjs';
|
|
3
|
+
import { discoverLocalRuntimes } from './local-runtimes.mjs';
|
|
4
|
+
import { APPDATA, HOME, LOCALAPPDATA, vscodeUserDir } from './platform.mjs';
|
|
5
|
+
|
|
6
|
+
export function discoverAiTools() {
|
|
7
|
+
const checks = [
|
|
8
|
+
{ vendor: 'cursor', name: 'Cursor', probe: [path.join(HOME, '.cursor')] },
|
|
9
|
+
{ vendor: 'claude', name: 'Claude Desktop', probe: [path.join(APPDATA, 'Claude'), path.join(HOME, 'Library', 'Application Support', 'Claude'), path.join(HOME, '.config', 'Claude')] },
|
|
10
|
+
{ vendor: 'windsurf', name: 'Windsurf', probe: [path.join(HOME, '.codeium', 'windsurf')] },
|
|
11
|
+
{ vendor: 'continue', name: 'Continue', probe: [path.join(HOME, '.continue')] },
|
|
12
|
+
{ vendor: 'zed', name: 'Zed', probe: [path.join(HOME, '.config', 'zed'), path.join(HOME, 'Library', 'Application Support', 'Zed')] },
|
|
13
|
+
{ vendor: 'cody', name: 'Sourcegraph Cody', probe: [path.join(vscodeUserDir(), 'globalStorage', 'sourcegraph.cody-ai')] },
|
|
14
|
+
{ vendor: 'copilot', name: 'GitHub Copilot (VS Code)', probe: [path.join(vscodeUserDir(), 'globalStorage', 'github.copilot'), path.join(vscodeUserDir(), 'globalStorage', 'github.copilot-chat')] },
|
|
15
|
+
{ vendor: 'tabnine', name: 'Tabnine', probe: [path.join(HOME, '.tabnine'), path.join(LOCALAPPDATA, 'TabNine')] },
|
|
16
|
+
];
|
|
17
|
+
const assets = [];
|
|
18
|
+
for (const c of checks) {
|
|
19
|
+
const at = firstExisting(c.probe);
|
|
20
|
+
if (at) assets.push({ type: 'AI_TOOL', name: c.name, identifier: at, vendor: c.vendor, metadata: { category: 'assistant', detectedAt: at } });
|
|
21
|
+
}
|
|
22
|
+
return [...assets, ...discoverLocalRuntimes()];
|
|
23
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { clampAsset } from '../../core/wire-limits.mjs';
|
|
2
|
+
import { discoverAiDependencies, discoverAiUsageInCode } from './ai-dependencies.mjs';
|
|
3
|
+
import { discoverAiTools } from './ai-tools.mjs';
|
|
4
|
+
import { discoverCodingAgents } from './coding-agents.mjs';
|
|
5
|
+
import { discoverMcpClients } from './mcp-clients.mjs';
|
|
6
|
+
import { discoverMcpServers } from './mcp-servers.mjs';
|
|
7
|
+
import { discoverDotenvKeys, discoverModelKeys } from './model-keys.mjs';
|
|
8
|
+
import { discoverRulesFiles } from './rules-files.mjs';
|
|
9
|
+
import { discoverVectorStores } from './vector-stores.mjs';
|
|
10
|
+
import { resolveRoots, walkWorkspace } from './workspace.mjs';
|
|
11
|
+
|
|
12
|
+
export function discoverAll(roots = [process.cwd()], opts = {}) {
|
|
13
|
+
const { autoExpand = true } = opts;
|
|
14
|
+
const scanRoots = resolveRoots(roots, autoExpand);
|
|
15
|
+
const files = walkWorkspace(scanRoots);
|
|
16
|
+
const all = [
|
|
17
|
+
...discoverMcpServers(scanRoots, files),
|
|
18
|
+
...discoverRulesFiles(scanRoots, files),
|
|
19
|
+
...discoverAiDependencies(scanRoots, files),
|
|
20
|
+
...discoverAiUsageInCode(scanRoots, files),
|
|
21
|
+
...discoverMcpClients(scanRoots, files),
|
|
22
|
+
...discoverVectorStores(scanRoots, files),
|
|
23
|
+
...discoverDotenvKeys(scanRoots, files),
|
|
24
|
+
...discoverAiTools(),
|
|
25
|
+
...discoverCodingAgents(scanRoots),
|
|
26
|
+
...discoverModelKeys(),
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const clamped = all.map(clampAsset);
|
|
30
|
+
|
|
31
|
+
const seen = new Set();
|
|
32
|
+
const out = [];
|
|
33
|
+
for (const a of clamped) {
|
|
34
|
+
const key = `${a.type}::${a.identifier || a.name}`;
|
|
35
|
+
if (seen.has(key)) continue;
|
|
36
|
+
seen.add(key);
|
|
37
|
+
out.push(a);
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { canonicalGrant, readVendorPosture } from '../agent-posture.mjs';
|
|
3
|
+
import { exists, readText } from './fs-read.mjs';
|
|
4
|
+
import { HOME, PLAT, vscodeUserDir } from './platform.mjs';
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
export function listProcesses() {
|
|
8
|
+
try {
|
|
9
|
+
if (PLAT === 'win32') {
|
|
10
|
+
const out = execFileSync('tasklist', ['/fo', 'csv', '/nh'], { timeout: 4000, encoding: 'utf8', windowsHide: true, maxBuffer: 16 * 1024 * 1024 });
|
|
11
|
+
return out.split(/\r?\n/).map((l) => (l.match(/^"([^"]+)"/)?.[1] || '').toLowerCase()).filter(Boolean);
|
|
12
|
+
}
|
|
13
|
+
const out = execFileSync('ps', ['-eo', 'comm='], { timeout: 4000, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
|
|
14
|
+
return out.split(/\r?\n/).map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
15
|
+
} catch {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function discoverCodingAgents(roots = [process.cwd()]) {
|
|
21
|
+
const cwd = process.cwd();
|
|
22
|
+
const agents = [
|
|
23
|
+
{ vendor: 'claude-code', name: 'Claude Code', probes: [path.join(HOME, '.claude.json'), path.join(HOME, '.claude')], hookFiles: [path.join(HOME, '.claude', 'settings.json'), path.join(cwd, '.claude', 'settings.json')] },
|
|
24
|
+
{ vendor: 'cursor', name: 'Cursor', probes: [path.join(HOME, '.cursor')], hookFiles: [path.join(HOME, '.cursor', 'hooks.json'), path.join(cwd, '.cursor', 'hooks.json')] },
|
|
25
|
+
{ vendor: 'windsurf', name: 'Windsurf', probes: [path.join(HOME, '.codeium', 'windsurf')], hookFiles: [path.join(HOME, '.codeium', 'windsurf', 'hooks.json'), path.join(cwd, '.windsurf', 'hooks.json')] },
|
|
26
|
+
{ vendor: 'gemini', name: 'Gemini CLI', probes: [path.join(HOME, '.gemini')], hookFiles: [path.join(HOME, '.gemini', 'settings.json'), path.join(cwd, '.gemini', 'settings.json')] },
|
|
27
|
+
{ vendor: 'codex', name: 'OpenAI Codex CLI', probes: [path.join(HOME, '.codex')], hookFiles: [path.join(HOME, '.codex', 'hooks.json'), path.join(cwd, '.codex', 'hooks.json')] },
|
|
28
|
+
{ vendor: 'copilot', name: 'GitHub Copilot CLI', probes: [path.join(HOME, '.copilot')], hookFiles: [path.join(HOME, '.copilot', 'hooks', 'shomra.json'), path.join(cwd, '.github', 'hooks', 'shomra.json')] },
|
|
29
|
+
{ vendor: 'cline', name: 'Cline', probes: [path.join(vscodeUserDir(), 'globalStorage', 'saoudrizwan.claude-dev')], hookFiles: [path.join(HOME, '.cline', 'hooks.json'), path.join(cwd, '.cline', 'hooks.json')] },
|
|
30
|
+
{ vendor: 'roo', name: 'Roo Code', probes: [path.join(vscodeUserDir(), 'globalStorage', 'rooveterinaryinc.roo-cline')], hookFiles: [path.join(cwd, '.roo', 'hooks.json')] },
|
|
31
|
+
{ vendor: 'aider', name: 'Aider', probes: [path.join(HOME, '.aider.conf.yml'), path.join(cwd, '.aider.conf.yml'), path.join(HOME, '.aider')], hookFiles: [path.join(HOME, '.aider.conf.yml'), path.join(cwd, '.aider.conf.yml')] },
|
|
32
|
+
];
|
|
33
|
+
const assets = [];
|
|
34
|
+
for (const a of agents) {
|
|
35
|
+
const installedAt = a.probes.find((p) => exists(p));
|
|
36
|
+
if (!installedAt) continue;
|
|
37
|
+
const guardFile = a.hookFiles.find((f) => {
|
|
38
|
+
const t = readText(f, 20_000);
|
|
39
|
+
return t != null && /shomra/i.test(t);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const posture = readVendorPosture(a.vendor, cwd);
|
|
43
|
+
const grant = canonicalGrant(posture);
|
|
44
|
+
assets.push({
|
|
45
|
+
type: 'AI_AGENT',
|
|
46
|
+
name: a.name,
|
|
47
|
+
identifier: `agent:${a.vendor}`,
|
|
48
|
+
vendor: a.vendor,
|
|
49
|
+
...(grant ? { content: grant } : {}),
|
|
50
|
+
metadata: {
|
|
51
|
+
detectedAt: installedAt,
|
|
52
|
+
guarded: !!guardFile,
|
|
53
|
+
guardFile: guardFile || null,
|
|
54
|
+
posture: posture
|
|
55
|
+
? {
|
|
56
|
+
tier: posture.tier,
|
|
57
|
+
readable: posture.readable,
|
|
58
|
+
claim: posture.claim,
|
|
59
|
+
mode: posture.mode,
|
|
60
|
+
allowCount: posture.allow.length,
|
|
61
|
+
denyCount: posture.deny.length,
|
|
62
|
+
askCount: posture.ask.length,
|
|
63
|
+
allow: posture.allow.slice(0, 25),
|
|
64
|
+
enableAllProjectMcpServers: posture.enableAllProjectMcpServers,
|
|
65
|
+
switches: posture.switches,
|
|
66
|
+
mcpServerCount: posture.mcpServers.length,
|
|
67
|
+
autoApprovedMcp: posture.autoApprovedMcp,
|
|
68
|
+
unreadableCount: posture.unreadableCount,
|
|
69
|
+
|
|
70
|
+
sources: posture.sources.map((s) => ({ path: s.path, scope: s.scope, state: s.state, reason: s.reason })),
|
|
71
|
+
}
|
|
72
|
+
: null,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return assets;
|
|
77
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
|
|
3
|
+
export function readJson(file) {
|
|
4
|
+
try {
|
|
5
|
+
return JSON.parse(stripJsonComments(fs.readFileSync(file, 'utf8')));
|
|
6
|
+
} catch {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function stripJsonComments(s) {
|
|
12
|
+
return String(s)
|
|
13
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
14
|
+
.replace(/(^|[^:])\/\/.*$/gm, '$1');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function readText(file, cap = 200_000) {
|
|
18
|
+
try {
|
|
19
|
+
const b = fs.readFileSync(file, 'utf8');
|
|
20
|
+
return b.length > cap ? b.slice(0, cap) : b;
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function exists(p) {
|
|
27
|
+
try {
|
|
28
|
+
return fs.existsSync(p);
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function firstExisting(paths) {
|
|
35
|
+
return paths.find((p) => p && exists(p)) || null;
|
|
36
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { listProcesses } from './coding-agents.mjs';
|
|
4
|
+
import { firstExisting } from './fs-read.mjs';
|
|
5
|
+
import { APPDATA, HOME, LOCALAPPDATA } from './platform.mjs';
|
|
6
|
+
|
|
7
|
+
export function discoverLocalRuntimes() {
|
|
8
|
+
const runtimes = [
|
|
9
|
+
{ vendor: 'ollama', name: 'Ollama', dirs: [path.join(HOME, '.ollama'), path.join(LOCALAPPDATA, 'Ollama')], modelsDir: path.join(HOME, '.ollama', 'models', 'manifests'), proc: ['ollama'] },
|
|
10
|
+
{ vendor: 'lmstudio', name: 'LM Studio', dirs: [path.join(HOME, '.lmstudio'), path.join(HOME, '.cache', 'lm-studio'), path.join(LOCALAPPDATA, 'LM Studio')], proc: ['lm studio', 'lmstudio', 'lms'] },
|
|
11
|
+
{ vendor: 'jan', name: 'Jan', dirs: [path.join(HOME, 'jan'), path.join(HOME, '.jan'), path.join(APPDATA, 'Jan')], proc: ['jan'] },
|
|
12
|
+
{ vendor: 'gpt4all', name: 'GPT4All', dirs: [path.join(HOME, '.cache', 'gpt4all'), path.join(HOME, 'Library', 'Application Support', 'nomic.ai', 'GPT4All'), path.join(LOCALAPPDATA, 'nomic.ai', 'GPT4All')], proc: ['gpt4all'] },
|
|
13
|
+
{ vendor: 'huggingface', name: 'Hugging Face cache', dirs: [path.join(HOME, '.cache', 'huggingface'), path.join(process.env.HF_HOME || '', 'hub')], proc: [] },
|
|
14
|
+
{ vendor: 'localai', name: 'LocalAI', dirs: [path.join(HOME, '.localai')], proc: ['local-ai', 'localai'] },
|
|
15
|
+
{ vendor: 'textgen', name: 'Text Generation WebUI', dirs: [], proc: ['text-generation', 'oobabooga'] },
|
|
16
|
+
{ vendor: 'vllm', name: 'vLLM', dirs: [], proc: ['vllm'] },
|
|
17
|
+
];
|
|
18
|
+
const procs = listProcesses();
|
|
19
|
+
const assets = [];
|
|
20
|
+
for (const r of runtimes) {
|
|
21
|
+
const at = firstExisting(r.dirs);
|
|
22
|
+
const running = r.proc.some((tok) => procs.some((p) => p.includes(tok)));
|
|
23
|
+
if (!at && !running) continue;
|
|
24
|
+
const meta = { category: 'local-runtime', detectedAt: at || null, running };
|
|
25
|
+
if (r.vendor === 'ollama' && r.modelsDir) meta.models = ollamaModels(r.modelsDir);
|
|
26
|
+
assets.push({ type: 'AI_TOOL', name: r.name, identifier: at || `proc:${r.vendor}`, vendor: r.vendor, metadata: meta });
|
|
27
|
+
}
|
|
28
|
+
return assets;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function ollamaModels(manifestsDir) {
|
|
32
|
+
const out = [];
|
|
33
|
+
const walk = (dir, depth) => {
|
|
34
|
+
if (depth > 5 || out.length > 100) return;
|
|
35
|
+
let entries;
|
|
36
|
+
try {
|
|
37
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
38
|
+
} catch {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
for (const e of entries) {
|
|
42
|
+
const full = path.join(dir, e.name);
|
|
43
|
+
if (e.isDirectory()) walk(full, depth + 1);
|
|
44
|
+
else if (e.isFile()) {
|
|
45
|
+
|
|
46
|
+
const rel = path.relative(manifestsDir, full).split(path.sep);
|
|
47
|
+
if (rel.length >= 2) out.push(`${rel.slice(1, -1).join('/')}:${rel[rel.length - 1]}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
walk(manifestsDir, 0);
|
|
52
|
+
return out.slice(0, 100);
|
|
53
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readJson, readText } from './fs-read.mjs';
|
|
3
|
+
import { walkWorkspace } from './workspace.mjs';
|
|
4
|
+
|
|
5
|
+
const NPM_MCP_CLIENT = new Set(['mcp-use', 'mcp-client']);
|
|
6
|
+
|
|
7
|
+
const NPM_MCP_CLIENT_PREFIX = ['@modelcontextprotocol/', '@mastra/mcp', '@langchain/mcp'];
|
|
8
|
+
|
|
9
|
+
const PY_MCP_CLIENT = ['mcp', 'fastmcp', 'mcp-use', 'mcpadapt', 'langchain-mcp-adapters'];
|
|
10
|
+
|
|
11
|
+
function npmMcpClientDeps(pkg) {
|
|
12
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}), ...(pkg.peerDependencies || {}), ...(pkg.optionalDependencies || {}) };
|
|
13
|
+
const hits = [];
|
|
14
|
+
for (const name of Object.keys(deps)) {
|
|
15
|
+
if (NPM_MCP_CLIENT.has(name) || NPM_MCP_CLIENT_PREFIX.some((p) => name.startsWith(p))) hits.push(name);
|
|
16
|
+
}
|
|
17
|
+
return hits;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function pyMcpClientDeps(text) {
|
|
21
|
+
const hits = [];
|
|
22
|
+
for (const pkg of PY_MCP_CLIENT) {
|
|
23
|
+
const re = new RegExp(`(^|[^a-z0-9_.-])${pkg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^a-z0-9_.-]|$)`, 'im');
|
|
24
|
+
if (re.test(text)) hits.push(pkg);
|
|
25
|
+
}
|
|
26
|
+
return hits;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function discoverMcpClients(roots = [process.cwd()], files = null) {
|
|
30
|
+
const walk = files || walkWorkspace(roots);
|
|
31
|
+
const byPkg = new Map();
|
|
32
|
+
const add = (eco, pkg, manifest) => {
|
|
33
|
+
const key = `${eco}:${pkg}`;
|
|
34
|
+
if (!byPkg.has(key)) byPkg.set(key, { pkg, eco, manifests: new Set() });
|
|
35
|
+
byPkg.get(key).manifests.add(manifest);
|
|
36
|
+
};
|
|
37
|
+
for (const { file } of walk.manifests) {
|
|
38
|
+
const base = path.basename(file);
|
|
39
|
+
if (base === 'package.json') {
|
|
40
|
+
const json = readJson(file);
|
|
41
|
+
if (!json) continue;
|
|
42
|
+
for (const pkg of npmMcpClientDeps(json)) add('npm', pkg, file);
|
|
43
|
+
} else {
|
|
44
|
+
const text = readText(file, 100_000);
|
|
45
|
+
if (text == null) continue;
|
|
46
|
+
for (const pkg of pyMcpClientDeps(text)) add('pip', pkg, file);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const assets = [];
|
|
50
|
+
for (const { pkg, eco, manifests } of byPkg.values()) {
|
|
51
|
+
const list = [...manifests];
|
|
52
|
+
assets.push({
|
|
53
|
+
type: 'AI_TOOL',
|
|
54
|
+
name: `${pkg} (${eco})`,
|
|
55
|
+
identifier: `mcp-client:${eco}:${pkg}`,
|
|
56
|
+
vendor: 'mcp-client',
|
|
57
|
+
metadata: {
|
|
58
|
+
category: 'mcp-client',
|
|
59
|
+
ecosystem: eco,
|
|
60
|
+
package: pkg,
|
|
61
|
+
usedInProjects: list.length,
|
|
62
|
+
manifests: list.slice(0, 10),
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return assets;
|
|
67
|
+
}
|