@shomra/agent 0.3.16 → 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.
Files changed (156) hide show
  1. package/NOTICE +1 -1
  2. package/README.md +57 -57
  3. package/package.json +3 -9
  4. package/shomra.mjs +9 -7168
  5. package/src/agents/hook-command.mjs +19 -0
  6. package/src/agents/hook-files.mjs +41 -0
  7. package/src/agents/installers.mjs +203 -0
  8. package/src/artifacts/matchers.mjs +59 -0
  9. package/src/artifacts/report.mjs +50 -0
  10. package/src/cli/flags.mjs +68 -0
  11. package/src/cli/help-sections.mjs +309 -0
  12. package/src/cli/help.mjs +27 -0
  13. package/src/cli/main.mjs +55 -0
  14. package/src/cli/registry.mjs +80 -0
  15. package/src/cli/suggestions.mjs +33 -0
  16. package/src/commands/add.mjs +149 -0
  17. package/src/commands/agent-identity.mjs +46 -0
  18. package/src/commands/check.mjs +194 -0
  19. package/src/commands/corpus.mjs +126 -0
  20. package/src/commands/design.mjs +168 -0
  21. package/src/commands/doctor.mjs +209 -0
  22. package/src/commands/fix.mjs +115 -0
  23. package/src/commands/gate.mjs +154 -0
  24. package/src/commands/git-hooks.mjs +163 -0
  25. package/src/commands/init.mjs +36 -0
  26. package/src/commands/install-hook.mjs +51 -0
  27. package/src/commands/llm-proxy.mjs +153 -0
  28. package/src/commands/mcp-add.mjs +185 -0
  29. package/src/commands/mcp.mjs +143 -0
  30. package/src/commands/memory-scan.mjs +181 -0
  31. package/src/commands/model-scan.mjs +99 -0
  32. package/src/commands/models.mjs +145 -0
  33. package/src/commands/new.mjs +64 -0
  34. package/src/commands/plan.mjs +87 -0
  35. package/src/commands/pr.mjs +249 -0
  36. package/src/commands/protect.mjs +38 -0
  37. package/src/commands/provenance.mjs +91 -0
  38. package/src/commands/redteam.mjs +166 -0
  39. package/src/commands/rules.mjs +220 -0
  40. package/src/commands/run.mjs +128 -0
  41. package/src/commands/scan-zip.mjs +118 -0
  42. package/src/commands/scan.mjs +102 -0
  43. package/src/commands/secrets.mjs +99 -0
  44. package/src/commands/status.mjs +50 -0
  45. package/src/commands/why.mjs +88 -0
  46. package/src/core/api-client.mjs +66 -0
  47. package/src/core/api-key.mjs +6 -0
  48. package/src/core/circuit-breaker.mjs +42 -0
  49. package/src/core/config.mjs +37 -0
  50. package/src/core/exit-codes.mjs +9 -0
  51. package/src/core/json-file.mjs +13 -0
  52. package/src/core/numbers.mjs +4 -0
  53. package/src/core/package-root.mjs +10 -0
  54. package/src/core/terminal.mjs +16 -0
  55. package/src/core/version.mjs +14 -0
  56. package/src/core/wire-limits.mjs +53 -0
  57. package/src/corpus/screening.mjs +127 -0
  58. package/{ai-usage.mjs → src/detect/ai-usage.mjs} +0 -27
  59. package/src/detect/code-sast.mjs +2 -0
  60. package/{design.mjs → src/detect/design.mjs} +18 -107
  61. package/src/detect/guard-signals.mjs +18 -0
  62. package/{model-refs.mjs → src/detect/model-refs.mjs} +18 -77
  63. package/src/detect/sast/chains.mjs +30 -0
  64. package/src/detect/sast/path-expressions.mjs +76 -0
  65. package/src/detect/sast/rules-chains.mjs +33 -0
  66. package/src/detect/sast/rules-config.mjs +51 -0
  67. package/src/detect/sast/rules-javascript.mjs +109 -0
  68. package/src/detect/sast/rules-python.mjs +292 -0
  69. package/src/detect/sast/scanner.mjs +104 -0
  70. package/src/detect/sast/source-lines.mjs +115 -0
  71. package/src/detect/sast/taint.mjs +71 -0
  72. package/src/detect/signals/artifacts.mjs +113 -0
  73. package/src/detect/signals/autonomy.mjs +55 -0
  74. package/src/detect/signals/config-markers.mjs +28 -0
  75. package/src/detect/signals/credential-harvest.mjs +64 -0
  76. package/src/detect/signals/durable-claims.mjs +73 -0
  77. package/src/detect/signals/egress.mjs +56 -0
  78. package/src/detect/signals/execution-hijack.mjs +128 -0
  79. package/src/detect/signals/gate.mjs +91 -0
  80. package/src/detect/signals/injection.mjs +55 -0
  81. package/src/detect/signals/lines.mjs +42 -0
  82. package/src/detect/signals/masking.mjs +99 -0
  83. package/src/detect/signals/memory.mjs +357 -0
  84. package/src/detect/signals/packages.mjs +45 -0
  85. package/src/detect/signals/propagation.mjs +86 -0
  86. package/src/detect/signals/prose-context.mjs +82 -0
  87. package/src/detect/signals/scan.mjs +91 -0
  88. package/src/detect/signals/secrets.mjs +85 -0
  89. package/src/detect/signals/sensitive.mjs +9 -0
  90. package/src/detect/signals/severity.mjs +10 -0
  91. package/src/detect/signals/shell.mjs +96 -0
  92. package/src/detect/signals/staged-fetch.mjs +66 -0
  93. package/src/detect/signals/text-match.mjs +35 -0
  94. package/src/gate/batch.mjs +157 -0
  95. package/src/gate/environment.mjs +122 -0
  96. package/src/gate/repo-policy.mjs +65 -0
  97. package/src/gate/result.mjs +53 -0
  98. package/src/gate/sarif.mjs +33 -0
  99. package/src/gate/sast.mjs +64 -0
  100. package/src/gate/suppressions.mjs +0 -0
  101. package/src/guard/classify.mjs +50 -0
  102. package/src/guard/emit.mjs +51 -0
  103. package/src/guard/ignore.mjs +24 -0
  104. package/src/guard/ledger.mjs +112 -0
  105. package/src/guard/model-load.mjs +50 -0
  106. package/src/guard/normalize.mjs +77 -0
  107. package/src/guard/options.mjs +10 -0
  108. package/src/guard/prompt-guard.mjs +184 -0
  109. package/src/guard/report.mjs +35 -0
  110. package/src/guard/result-guard.mjs +140 -0
  111. package/src/guard/tool-guard.mjs +166 -0
  112. package/src/inventory/agent-artifacts.mjs +5 -0
  113. package/src/inventory/agent-posture.mjs +249 -0
  114. package/src/inventory/artifacts/classify.mjs +27 -0
  115. package/src/inventory/artifacts/discover.mjs +187 -0
  116. package/src/inventory/artifacts/file-read.mjs +42 -0
  117. package/src/inventory/artifacts/hooks.mjs +14 -0
  118. package/src/inventory/artifacts/limits.mjs +37 -0
  119. package/src/inventory/artifacts/marketplaces.mjs +45 -0
  120. package/src/inventory/artifacts/roots.mjs +20 -0
  121. package/src/inventory/artifacts/walk.mjs +36 -0
  122. package/src/inventory/discovery/ai-dependencies.mjs +161 -0
  123. package/src/inventory/discovery/ai-tools.mjs +23 -0
  124. package/src/inventory/discovery/all.mjs +40 -0
  125. package/src/inventory/discovery/coding-agents.mjs +77 -0
  126. package/src/inventory/discovery/fs-read.mjs +36 -0
  127. package/src/inventory/discovery/local-runtimes.mjs +53 -0
  128. package/src/inventory/discovery/mcp-clients.mjs +67 -0
  129. package/src/inventory/discovery/mcp-servers.mjs +78 -0
  130. package/src/inventory/discovery/model-keys.mjs +97 -0
  131. package/src/inventory/discovery/platform.mjs +16 -0
  132. package/src/inventory/discovery/rules-files.mjs +25 -0
  133. package/src/inventory/discovery/vector-stores.mjs +176 -0
  134. package/src/inventory/discovery/workspace.mjs +124 -0
  135. package/src/inventory/discovery.mjs +10 -0
  136. package/src/mcp/child-process.mjs +50 -0
  137. package/src/mcp/config-wrapping.mjs +75 -0
  138. package/src/mcp/connect-gate.mjs +45 -0
  139. package/src/mcp/hosts.mjs +16 -0
  140. package/src/mcp/jsonrpc.mjs +48 -0
  141. package/src/mcp/lookup.mjs +50 -0
  142. package/src/mcp/screening.mjs +103 -0
  143. package/src/mcp/server-tools.mjs +97 -0
  144. package/src/mcp/server.mjs +102 -0
  145. package/src/mcp/shim.mjs +205 -0
  146. package/src/models/lookup.mjs +79 -0
  147. package/src/models/references.mjs +103 -0
  148. package/src/rules/context.mjs +98 -0
  149. package/src/rules/generate.mjs +103 -0
  150. package/src/rules/sections.mjs +145 -0
  151. package/src/scaffold/agent-project.mjs +185 -0
  152. package/src/scaffold/artifact-templates.mjs +35 -0
  153. package/code-sast.mjs +0 -1063
  154. package/discovery.mjs +0 -977
  155. package/guard-ledger.mjs +0 -239
  156. package/guard-signals.mjs +0 -1268
@@ -0,0 +1,19 @@
1
+ import { CLI_ENTRY_PATH } from '../core/package-root.mjs';
2
+
3
+ export const LLM_PROXY_BASE = process.env.SHOMRA_LLM_PROXY_BASE || 'http://127.0.0.1:4141/openai/v1';
4
+
5
+ export const SELF_PATH = CLI_ENTRY_PATH;
6
+
7
+ function quoteArg(argument) {
8
+ return /\s/.test(argument) ? `"${argument}"` : argument;
9
+ }
10
+
11
+ export function hookCommand(args) {
12
+ return `${quoteArg(process.execPath)} ${quoteArg(SELF_PATH)} ${args}`;
13
+ }
14
+
15
+ export function shomraHookRe(verb) {
16
+ return new RegExp(`shomra(\\.mjs"?)?\\s+${verb}`, 'i');
17
+ }
18
+
19
+ export const SHOMRA_ANY_HOOK_RE = /shomra(\.mjs"?)?\s+(tool-guard|result-guard|prompt-guard|plan-guard)/i;
@@ -0,0 +1,41 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { SHOMRA_ANY_HOOK_RE, shomraHookRe } from './hook-command.mjs';
5
+
6
+ function agentHookFiles(agent) {
7
+ const home = os.homedir();
8
+ const cwd = process.cwd();
9
+ switch (agent) {
10
+ case 'claude': return [path.join(home, '.claude', 'settings.json'), path.join(cwd, '.claude', 'settings.json')];
11
+ case 'codex': return [path.join(home, '.codex', 'hooks.json'), path.join(cwd, '.codex', 'hooks.json')];
12
+ case 'gemini': return [path.join(home, '.gemini', 'settings.json'), path.join(cwd, '.gemini', 'settings.json')];
13
+ case 'cursor': return [path.join(home, '.cursor', 'hooks.json'), path.join(cwd, '.cursor', 'hooks.json')];
14
+ case 'windsurf': return [path.join(home, '.codeium', 'windsurf', 'hooks.json'), path.join(cwd, '.windsurf', 'hooks.json')];
15
+ case 'copilot': return [path.join(home, '.copilot', 'hooks', 'shomra.json'), path.join(cwd, '.github', 'hooks', 'shomra.json')];
16
+ case 'cline': return [path.join(home, '.cline', 'hooks.json'), path.join(cwd, '.cline', 'hooks.json')];
17
+ case 'aider': return [path.join(home, '.aider.conf.yml'), path.join(cwd, '.aider.conf.yml')];
18
+ default: return [];
19
+ }
20
+ }
21
+
22
+ export function agentHookInstalled(agent) {
23
+ return agentHookFiles(agent).filter((f) => {
24
+ try {
25
+ const text = fs.readFileSync(f, 'utf8');
26
+ if (agent === 'aider') return /shomra llm guard/i.test(text);
27
+ return SHOMRA_ANY_HOOK_RE.test(text);
28
+ } catch {
29
+ return false;
30
+ }
31
+ });
32
+ }
33
+
34
+ export function hasGroupedHook(list, verb) {
35
+ const re = shomraHookRe(verb);
36
+ return Array.isArray(list) && list.some((g) => Array.isArray(g.hooks) && g.hooks.some((h) => re.test(String(h.command || ''))));
37
+ }
38
+
39
+ export function hasFlatHook(list) {
40
+ return Array.isArray(list) && list.some((h) => SHOMRA_ANY_HOOK_RE.test(String(h.command || '')));
41
+ }
@@ -0,0 +1,203 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { readJsonFile } from '../core/json-file.mjs';
5
+ import { LLM_PROXY_BASE, hookCommand } from './hook-command.mjs';
6
+ import { hasFlatHook, hasGroupedHook } from './hook-files.mjs';
7
+
8
+ export const AGENT_LABELS = {
9
+ claude: 'Claude Code',
10
+ cursor: 'Cursor',
11
+ windsurf: 'Windsurf',
12
+ gemini: 'Gemini CLI',
13
+ codex: 'OpenAI Codex CLI',
14
+ copilot: 'GitHub Copilot CLI',
15
+ cline: 'Cline',
16
+ aider: 'Aider',
17
+ };
18
+
19
+ export const AGENT_KEYS = Object.keys(AGENT_LABELS);
20
+
21
+ export const AGENT_INSTALLERS = {
22
+ claude(global) {
23
+ const dir = global ? path.join(os.homedir(), '.claude') : path.join(process.cwd(), '.claude');
24
+ const file = path.join(dir, 'settings.json');
25
+ const settings = readJsonFile(file);
26
+ settings.hooks = settings.hooks || {};
27
+ const pre = (settings.hooks.PreToolUse = settings.hooks.PreToolUse || []);
28
+ const post = (settings.hooks.PostToolUse = settings.hooks.PostToolUse || []);
29
+ let changed = false;
30
+ if (!hasGroupedHook(pre, 'tool-guard')) {
31
+ pre.push({ matcher: 'Bash|Write|Edit|MultiEdit|NotebookEdit|mcp__.*', hooks: [{ type: 'command', command: hookCommand('tool-guard --agent claude') }] });
32
+ changed = true;
33
+ }
34
+ if (!hasGroupedHook(post, 'result-guard')) {
35
+ post.push({ matcher: 'WebFetch|WebSearch|Read|NotebookRead|mcp__.*', hooks: [{ type: 'command', command: hookCommand('result-guard --agent claude') }] });
36
+ changed = true;
37
+ }
38
+
39
+ const prompt = (settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit || []);
40
+ if (!hasGroupedHook(prompt, 'prompt-guard')) {
41
+ prompt.push({ hooks: [{ type: 'command', command: hookCommand('prompt-guard --agent claude') }] });
42
+ changed = true;
43
+ }
44
+
45
+ if (!hasGroupedHook(pre, 'plan-guard')) {
46
+ pre.push({ matcher: 'ExitPlanMode', hooks: [{ type: 'command', command: hookCommand('plan-guard --agent claude') }] });
47
+ changed = true;
48
+ }
49
+ if (changed) {
50
+ fs.mkdirSync(dir, { recursive: true });
51
+ fs.writeFileSync(file, JSON.stringify(settings, null, 2));
52
+ }
53
+ return { file, changed };
54
+ },
55
+
56
+ codex(global) {
57
+ const dir = global ? path.join(os.homedir(), '.codex') : path.join(process.cwd(), '.codex');
58
+ const file = path.join(dir, 'hooks.json');
59
+ const settings = readJsonFile(file);
60
+ const pre = (settings.PreToolUse = settings.PreToolUse || []);
61
+ const post = (settings.PostToolUse = settings.PostToolUse || []);
62
+ let changed = false;
63
+ if (!hasGroupedHook(pre, 'tool-guard')) {
64
+ pre.push({ matcher: 'Bash|Write|Edit|mcp__.*', hooks: [{ type: 'command', command: hookCommand('tool-guard --agent codex') }] });
65
+ changed = true;
66
+ }
67
+ if (!hasGroupedHook(post, 'result-guard')) {
68
+ post.push({ matcher: 'WebFetch|WebSearch|Read|mcp__.*', hooks: [{ type: 'command', command: hookCommand('result-guard --agent codex') }] });
69
+ changed = true;
70
+ }
71
+ if (changed) {
72
+ fs.mkdirSync(dir, { recursive: true });
73
+ fs.writeFileSync(file, JSON.stringify(settings, null, 2));
74
+ }
75
+ return { file, changed };
76
+ },
77
+
78
+ gemini(global) {
79
+ const dir = global ? path.join(os.homedir(), '.gemini') : path.join(process.cwd(), '.gemini');
80
+ const file = path.join(dir, 'settings.json');
81
+ const settings = readJsonFile(file);
82
+ settings.hooks = settings.hooks || {};
83
+ const before = (settings.hooks.BeforeTool = settings.hooks.BeforeTool || []);
84
+ const after = (settings.hooks.AfterTool = settings.hooks.AfterTool || []);
85
+ let changed = false;
86
+ if (!hasGroupedHook(before, 'tool-guard')) {
87
+ before.push({ matcher: '.*', hooks: [{ type: 'command', command: hookCommand('tool-guard --agent gemini') }] });
88
+ changed = true;
89
+ }
90
+ if (!hasGroupedHook(after, 'result-guard')) {
91
+ after.push({ matcher: '.*', hooks: [{ type: 'command', command: hookCommand('result-guard --agent gemini') }] });
92
+ changed = true;
93
+ }
94
+ if (changed) {
95
+ fs.mkdirSync(dir, { recursive: true });
96
+ fs.writeFileSync(file, JSON.stringify(settings, null, 2));
97
+ }
98
+ return { file, changed };
99
+ },
100
+
101
+ cursor(global) {
102
+ const dir = global ? path.join(os.homedir(), '.cursor') : path.join(process.cwd(), '.cursor');
103
+ const file = path.join(dir, 'hooks.json');
104
+ const cfg = readJsonFile(file);
105
+ if (cfg.version === undefined) cfg.version = 1;
106
+ cfg.hooks = cfg.hooks || {};
107
+ let changed = false;
108
+ const wire = (event, command) => {
109
+ const list = (cfg.hooks[event] = cfg.hooks[event] || []);
110
+ if (!hasFlatHook(list)) {
111
+ list.push({ command });
112
+ changed = true;
113
+ }
114
+ };
115
+ wire('beforeShellExecution', hookCommand('tool-guard --agent cursor'));
116
+ wire('beforeMCPExecution', hookCommand('tool-guard --agent cursor'));
117
+ wire('afterFileEdit', hookCommand('result-guard --agent cursor'));
118
+ wire('afterMCPExecution', hookCommand('result-guard --agent cursor'));
119
+
120
+ wire('beforeSubmitPrompt', hookCommand('prompt-guard --agent cursor'));
121
+ if (changed) {
122
+ fs.mkdirSync(dir, { recursive: true });
123
+ fs.writeFileSync(file, JSON.stringify(cfg, null, 2));
124
+ }
125
+ return { file, changed };
126
+ },
127
+
128
+ windsurf(global) {
129
+ const dir = global ? path.join(os.homedir(), '.codeium', 'windsurf') : path.join(process.cwd(), '.windsurf');
130
+ const file = path.join(dir, 'hooks.json');
131
+ const cfg = readJsonFile(file);
132
+ cfg.hooks = cfg.hooks || {};
133
+ let changed = false;
134
+ const wire = (event, command) => {
135
+ const list = (cfg.hooks[event] = cfg.hooks[event] || []);
136
+ if (!hasFlatHook(list)) {
137
+ list.push({ command });
138
+ changed = true;
139
+ }
140
+ };
141
+ wire('pre_run_command', hookCommand('tool-guard --agent windsurf'));
142
+ wire('pre_write_code', hookCommand('tool-guard --agent windsurf'));
143
+ wire('pre_mcp_tool_use', hookCommand('tool-guard --agent windsurf'));
144
+ wire('post_mcp_tool_use', hookCommand('result-guard --agent windsurf'));
145
+ if (changed) {
146
+ fs.mkdirSync(dir, { recursive: true });
147
+ fs.writeFileSync(file, JSON.stringify(cfg, null, 2));
148
+ }
149
+ return { file, changed };
150
+ },
151
+
152
+ copilot(global) {
153
+ const dir = global ? path.join(os.homedir(), '.copilot', 'hooks') : path.join(process.cwd(), '.github', 'hooks');
154
+ const file = path.join(dir, 'shomra.json');
155
+ if (fs.existsSync(file)) return { file, changed: false };
156
+ const cfg = {
157
+ preToolUse: [{ command: hookCommand('tool-guard --agent copilot') }],
158
+ postToolUse: [{ command: hookCommand('result-guard --agent copilot') }],
159
+ };
160
+ fs.mkdirSync(dir, { recursive: true });
161
+ fs.writeFileSync(file, JSON.stringify(cfg, null, 2));
162
+ return { file, changed: true };
163
+ },
164
+
165
+ cline(global) {
166
+ const dir = global ? path.join(os.homedir(), '.cline') : path.join(process.cwd(), '.cline');
167
+ const file = path.join(dir, 'hooks.json');
168
+ const settings = readJsonFile(file);
169
+ settings.hooks = settings.hooks || {};
170
+ const pre = (settings.hooks.PreToolUse = settings.hooks.PreToolUse || []);
171
+ const post = (settings.hooks.PostToolUse = settings.hooks.PostToolUse || []);
172
+ let changed = false;
173
+ if (!hasGroupedHook(pre, 'tool-guard')) {
174
+ pre.push({ matcher: 'execute_command|write_to_file|replace_in_file|new_rule|use_mcp_tool', hooks: [{ type: 'command', command: hookCommand('tool-guard --agent cline') }] });
175
+ changed = true;
176
+ }
177
+ if (!hasGroupedHook(post, 'result-guard')) {
178
+ post.push({ matcher: 'read_file|web_fetch|use_mcp_tool', hooks: [{ type: 'command', command: hookCommand('result-guard --agent cline') }] });
179
+ changed = true;
180
+ }
181
+ if (changed) {
182
+ fs.mkdirSync(dir, { recursive: true });
183
+ fs.writeFileSync(file, JSON.stringify(settings, null, 2));
184
+ }
185
+ return { file, changed };
186
+ },
187
+
188
+ aider(global) {
189
+ const file = global ? path.join(os.homedir(), '.aider.conf.yml') : path.join(process.cwd(), '.aider.conf.yml');
190
+ let text = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
191
+ if (/#\s*shomra llm guard/i.test(text) || text.includes(LLM_PROXY_BASE)) {
192
+ return { file, changed: false };
193
+ }
194
+ const block =
195
+ `\n# --- shomra llm guard ---\n` +
196
+ `# Routes Aider's model traffic through the Shomra LLM Guard proxy so every\n` +
197
+ `# prompt/response is policy-screened. Requires: shomra llm-proxy (running).\n` +
198
+ `openai-api-base: ${LLM_PROXY_BASE}\n` +
199
+ `# --- end shomra ---\n`;
200
+ fs.writeFileSync(file, (text.endsWith('\n') || !text ? text : text + '\n') + block);
201
+ return { file, changed: true };
202
+ },
203
+ };
@@ -0,0 +1,59 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ export const ARTIFACT_MATCHERS = [
5
+ { kind: 'mcp', re: /(^|\/)\.?mcp\.json$/i },
6
+ { kind: 'mcp', re: /(^|\/)\.(vscode|cursor)\/mcp\.json$/i },
7
+ { kind: 'skill', re: /(^|\/)SKILL\.md$/i },
8
+ { kind: 'command', re: /(^|\/)\.claude\/commands\/[^/]+\.md$/i },
9
+ { kind: 'subagent', re: /(^|\/)\.claude\/agents\/[^/]+\.md$/i },
10
+ { kind: 'hook', re: /(^|\/)\.claude\/settings(\.local)?\.json$/i },
11
+
12
+ { kind: 'hook', re: /(^|\/)\.cursor\/hooks\.json$/i },
13
+ { kind: 'hook', re: /(^|\/)(\.windsurf|\.codeium\/windsurf)\/hooks\.json$/i },
14
+ { kind: 'hook', re: /(^|\/)\.gemini\/settings\.json$/i },
15
+ { kind: 'hook', re: /(^|\/)\.cline\/hooks\.json$/i },
16
+ { kind: 'hook', re: /(^|\/)\.codex\/hooks\.json$/i },
17
+ { kind: 'hook', re: /(^|\/)(\.github|\.copilot)\/hooks\/[^/]+\.json$/i },
18
+ { kind: 'hook', re: /(^|\/)\.aider\.conf\.yml$/i },
19
+ { kind: 'agent-card', re: /(^|\/)\.well-known\/agent(-card)?\.json$/i },
20
+ { kind: 'agent-card', re: /(^|\/)agent[-_]card\.json$/i },
21
+ { kind: 'rules', re: /(^|\/)(CLAUDE|AGENTS|GEMINI|CONVENTIONS)\.md$/i },
22
+ { kind: 'rules', re: /(^|\/)\.(cursorrules|windsurfrules|clinerules|aiderrules|continuerules|goosehints)$/i },
23
+ { kind: 'rules', re: /(^|\/)\.github\/copilot-instructions\.md$/i },
24
+ { kind: 'rules', re: /(^|\/)\.cursor\/rules\/[^/]+\.mdc$/i },
25
+
26
+ { kind: 'env', re: /(^|\/)\.env(\.(local|development|dev|production|prod|staging|stage|test))?$/i },
27
+ { kind: 'memory', re: /(^|\/)MEMORY\.md$/i },
28
+ { kind: 'memory', re: /(^|\/)(mem0|letta_memory|memgpt_memory)\.json$/i },
29
+ { kind: 'memory', re: /(^|\/)(\.mem0|\.letta|\.memgpt|memory)\/[^/]+\.(md|json)$/i },
30
+ ];
31
+
32
+ export const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', 'out', 'vendor', '.venv', '__pycache__']);
33
+
34
+ export const MAX_ARTIFACT_BYTES = 1_000_000;
35
+
36
+ export function walkArtifacts(root) {
37
+ const found = [];
38
+ const stack = [root];
39
+ while (stack.length) {
40
+ const dir = stack.pop();
41
+ let entries;
42
+ try {
43
+ entries = fs.readdirSync(dir, { withFileTypes: true });
44
+ } catch {
45
+ continue;
46
+ }
47
+ for (const ent of entries) {
48
+ const full = path.join(dir, ent.name);
49
+ if (ent.isDirectory()) {
50
+ if (!SKIP_DIRS.has(ent.name)) stack.push(full);
51
+ continue;
52
+ }
53
+ const rel = path.relative(root, full).split(path.sep).join('/');
54
+ const match = ARTIFACT_MATCHERS.find((m) => m.re.test(rel));
55
+ if (match) found.push({ full, rel, kind: match.kind });
56
+ }
57
+ }
58
+ return found;
59
+ }
@@ -0,0 +1,50 @@
1
+ import path from 'node:path';
2
+ import { bold, cyan, dim, gray, yellow } from '../core/terminal.mjs';
3
+ import { rollupArtifacts } from '../inventory/agent-artifacts.mjs';
4
+
5
+ export function printAssets(assets) {
6
+ const byType = {};
7
+ for (const a of assets) byType[a.type] = (byType[a.type] ?? 0) + 1;
8
+ console.log(bold('\n Discovered AI assets'));
9
+ console.log(
10
+ ' ' +
11
+ Object.entries(byType)
12
+ .map(([t, n]) => `${cyan(n)} ${dim(t.replace('_', ' ').toLowerCase())}`)
13
+ .join(dim(' · ')),
14
+ );
15
+ for (const a of assets) {
16
+ console.log(` ${gray('•')} ${bold(a.name)} ${dim(a.type)} ${a.vendor ? gray('(' + a.vendor + ')') : ''}`);
17
+ if (a.identifier && a.identifier !== a.name) console.log(` ${dim(a.identifier)}`);
18
+ }
19
+ }
20
+
21
+ export function printArtifacts(artifacts, capped = [], available = []) {
22
+ if (!artifacts.length && !capped.length && !available.length) return;
23
+ const by = rollupArtifacts(artifacts);
24
+ console.log(bold('\n Installed agent artifacts'));
25
+ console.log(
26
+ ' ' +
27
+ (Object.keys(by).length
28
+ ? Object.entries(by)
29
+ .map(([k, n]) => `${cyan(n)} ${dim(k + (n === 1 ? '' : 's'))}`)
30
+ .join(dim(' · '))
31
+ : dim('none found')),
32
+ );
33
+ for (const a of artifacts) {
34
+ const scope = a.scope === 'user' ? yellow('user') : dim('project');
35
+ console.log(` ${gray('•')} ${bold(a.name)} ${dim(a.kind)} ${scope} ${a.vendor ? gray('(' + a.vendor + ')') : ''}`);
36
+ console.log(` ${dim(a.path)}`);
37
+ if (a.files?.length) console.log(` ${dim(a.files.length + ' bundled file(s)')}`);
38
+ }
39
+ if (available.length) {
40
+ const total = available.reduce((n, a) => n + a.count, 0);
41
+
42
+ console.log(
43
+ dim(`\n ${total} more available in ${available.length} checked-out catalogue(s), none enabled:`),
44
+ );
45
+ for (const a of available) console.log(` ${dim(a.marketplace)} ${gray(a.count)}`);
46
+ }
47
+ if (capped.length) {
48
+ console.log(dim(`\n Sweep capped (${capped.length}) - this list is a floor, not a total.`));
49
+ }
50
+ }
@@ -0,0 +1,68 @@
1
+ const BOOLEAN_FLAGS = new Set([
2
+ 'strict', 'json', 'sarif', 'fix', 'staged', 'changed', 'all', 'history', 'force',
3
+ 'apply', 'dry-run', 'global', 'local', 'trailer', 'evolve', 'report', 'init',
4
+ 'no-suppress', 'no-baseline', 'no-policy', 'no-index', 'adaptive',
5
+ 'fail-on-regression', 'fail-on-blocked', 'write', 'yes', 'stdin', 'quiet', 'help',
6
+ 'check', 'checklist', 'pre-receive', 'uninstall', 'save', 'list',
7
+ ]);
8
+
9
+ const VALUE_FLAGS = new Set([
10
+ 'key', 'url', 'path', 'kind', 'name', 'project', 'agent', 'agent-id', 'min',
11
+ 'scenarios', 'objectives', 'turns', 'target', 'run', 'port', 'config', 'env',
12
+ 'command', 'base', 'repo', 'pr', 'token', 'sha', 'session', 'since', 'depth',
13
+ 'scope', 'writer', 'type', 'slug', 'framework', 'chunk-size', 'manifest',
14
+
15
+ 'fail-on',
16
+
17
+ 'subject', 'title', 'note', 'actor',
18
+
19
+ 'input',
20
+ ]);
21
+
22
+ export const KNOWN_FLAGS = new Set([...BOOLEAN_FLAGS, ...VALUE_FLAGS]);
23
+
24
+ const REPEATABLE_FLAGS = new Set(['input']);
25
+
26
+ function setFlag(flags, name, value) {
27
+ if (!REPEATABLE_FLAGS.has(name)) {
28
+ flags[name] = value;
29
+ return;
30
+ }
31
+ const prev = flags[name];
32
+ flags[name] = prev === undefined ? value : [].concat(prev, value);
33
+ }
34
+
35
+ export function parseFlags(argv) {
36
+ const flags = {};
37
+ const positional = [];
38
+ const unknown = [];
39
+ for (let i = 0; i < argv.length; i++) {
40
+ const a = argv[i];
41
+ if (a.startsWith('--')) {
42
+ const body = a.slice(2);
43
+
44
+ const eq = body.indexOf('=');
45
+ if (eq !== -1) {
46
+ const name = body.slice(0, eq);
47
+ if (!KNOWN_FLAGS.has(name)) unknown.push(name);
48
+ setFlag(flags, name, body.slice(eq + 1));
49
+ continue;
50
+ }
51
+ if (!KNOWN_FLAGS.has(body)) {
52
+ unknown.push(body);
53
+ flags[body] = true;
54
+ continue;
55
+ }
56
+ if (BOOLEAN_FLAGS.has(body)) {
57
+ flags[body] = true;
58
+ continue;
59
+ }
60
+ const next = argv[i + 1];
61
+ if (next !== undefined && !next.startsWith('--')) {
62
+ setFlag(flags, body, next);
63
+ i++;
64
+ } else flags[body] = true;
65
+ } else positional.push(a);
66
+ }
67
+ return { flags, positional, unknown };
68
+ }