codegate-ai 0.12.4 → 0.14.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/dist/cli.js
CHANGED
|
@@ -17,6 +17,7 @@ import { renderTuiApp } from "./tui/app.js";
|
|
|
17
17
|
import { executeWrapperRun } from "./wrapper.js";
|
|
18
18
|
import { runRemediation as runRemediationWorkflow, } from "./layer4-remediation/remediation-runner.js";
|
|
19
19
|
import { undoLatestSession } from "./commands/undo.js";
|
|
20
|
+
import { runInventory } from "./commands/inventory-command.js";
|
|
20
21
|
import { executeScanCommand } from "./commands/scan-command.js";
|
|
21
22
|
import { executeScanContentCommand, SCAN_CONTENT_TYPES, } from "./commands/scan-content-command.js";
|
|
22
23
|
import { executeSkillsWrapper, launchSkillsPassthrough, } from "./commands/skills-wrapper.js";
|
|
@@ -619,6 +620,70 @@ function addInitCommand(program, deps) {
|
|
|
619
620
|
}
|
|
620
621
|
});
|
|
621
622
|
}
|
|
623
|
+
const INVENTORY_SCOPES = ["user", "project", "all"];
|
|
624
|
+
const INVENTORY_KINDS = ["skills", "configs", "all"];
|
|
625
|
+
function addInventoryCommand(program, deps) {
|
|
626
|
+
program
|
|
627
|
+
.command("inventory")
|
|
628
|
+
.description("List the AI-tool config + skill artifacts the knowledge base knows about, resolved against this machine.")
|
|
629
|
+
.addOption(new Option("--scope <scope>", "scope filter")
|
|
630
|
+
.choices(INVENTORY_SCOPES)
|
|
631
|
+
.default("all"))
|
|
632
|
+
.addOption(new Option("--kind <kind>", "artifact kind filter")
|
|
633
|
+
.choices(INVENTORY_KINDS)
|
|
634
|
+
.default("all"))
|
|
635
|
+
.option("--only-existing", "return only items that currently exist on disk")
|
|
636
|
+
.option("--workspace <path>", "additional project-scope root (repeatable); defaults to cwd when omitted", collectRepeatable, [])
|
|
637
|
+
.addOption(new Option("--format <format>", "output format").choices(["text", "json"]).default("text"))
|
|
638
|
+
.addHelpText("after", renderExampleHelp([
|
|
639
|
+
"codegate inventory",
|
|
640
|
+
"codegate inventory --format json --kind skills --only-existing",
|
|
641
|
+
"codegate inventory --scope user --format json",
|
|
642
|
+
"codegate inventory --workspace . --workspace /path/to/other/repo",
|
|
643
|
+
]))
|
|
644
|
+
.action((options) => {
|
|
645
|
+
try {
|
|
646
|
+
const home = deps.homeDir?.() ?? homedir();
|
|
647
|
+
const explicitWorkspaces = options.workspace ?? [];
|
|
648
|
+
const workspaces = explicitWorkspaces.length > 0
|
|
649
|
+
? explicitWorkspaces.map((w) => resolve(deps.cwd(), w))
|
|
650
|
+
: [deps.cwd()];
|
|
651
|
+
const summary = runInventory({
|
|
652
|
+
scope: options.scope ?? "all",
|
|
653
|
+
kind: options.kind ?? "all",
|
|
654
|
+
onlyExisting: options.onlyExisting === true,
|
|
655
|
+
workspaces,
|
|
656
|
+
homeDir: home,
|
|
657
|
+
});
|
|
658
|
+
if (options.format === "json") {
|
|
659
|
+
deps.stdout(JSON.stringify(summary, null, 2));
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
renderInventoryText(summary, deps.stdout);
|
|
663
|
+
}
|
|
664
|
+
deps.setExitCode(0);
|
|
665
|
+
}
|
|
666
|
+
catch (error) {
|
|
667
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
668
|
+
deps.stderr(`Inventory failed: ${message}`);
|
|
669
|
+
deps.setExitCode(3);
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
function collectRepeatable(value, previous) {
|
|
674
|
+
return [...previous, value];
|
|
675
|
+
}
|
|
676
|
+
function renderInventoryText(summary, stdout) {
|
|
677
|
+
stdout(`Knowledge base v${summary.kb_version}`);
|
|
678
|
+
stdout(`Tools: ${summary.tools.map((t) => t.name).join(", ")}`);
|
|
679
|
+
stdout(`Items: ${summary.items.length}`);
|
|
680
|
+
stdout("");
|
|
681
|
+
for (const item of summary.items) {
|
|
682
|
+
const mark = item.exists ? "✓" : "·";
|
|
683
|
+
const tag = item.kind === "skill" ? `${item.kind}:${item.type ?? "?"}` : item.kind;
|
|
684
|
+
stdout(` ${mark} [${item.tool}] ${tag} (${item.scope}) ${item.path}`);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
622
687
|
function addUpdateCommands(program, deps) {
|
|
623
688
|
const guidance = [
|
|
624
689
|
"Updates are bundled with CodeGate releases in v1/v2.",
|
|
@@ -680,6 +745,7 @@ export function createCli(version = packageJson.version ?? "0.0.0-dev", deps = d
|
|
|
680
745
|
addRunCommand(program, version, deps);
|
|
681
746
|
addUndoCommand(program, deps);
|
|
682
747
|
addInitCommand(program, deps);
|
|
748
|
+
addInventoryCommand(program, deps);
|
|
683
749
|
addUpdateCommands(program, deps);
|
|
684
750
|
return program;
|
|
685
751
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** One resolved artifact the scanner knows about. */
|
|
2
|
+
export interface InventoryItem {
|
|
3
|
+
tool: string;
|
|
4
|
+
kind: "config" | "skill";
|
|
5
|
+
/** Only set for skill entries; mirrors KB `skill_paths[].type`. */
|
|
6
|
+
type?: string;
|
|
7
|
+
scope: "user" | "project";
|
|
8
|
+
/** Pattern as declared in the KB (relative, may contain wildcards). */
|
|
9
|
+
pattern: string;
|
|
10
|
+
/** Absolute resolved filesystem path (concrete, not the pattern). */
|
|
11
|
+
path: string;
|
|
12
|
+
/** True if the filesystem shows the path exists. */
|
|
13
|
+
exists: boolean;
|
|
14
|
+
risk_surface: string[];
|
|
15
|
+
/** Only populated for config entries that declare them. */
|
|
16
|
+
fields_of_interest?: Record<string, string>;
|
|
17
|
+
/** Resolution root used (e.g., the home dir or a workspace root). */
|
|
18
|
+
resolved_against: string;
|
|
19
|
+
}
|
|
20
|
+
export interface InventorySummary {
|
|
21
|
+
kb_version: string;
|
|
22
|
+
/** Known tools (from KB file names) with their version ranges. */
|
|
23
|
+
tools: Array<{
|
|
24
|
+
name: string;
|
|
25
|
+
version_range: string;
|
|
26
|
+
}>;
|
|
27
|
+
items: InventoryItem[];
|
|
28
|
+
}
|
|
29
|
+
export interface InventoryOptions {
|
|
30
|
+
scope: "user" | "project" | "all";
|
|
31
|
+
kind: "skills" | "configs" | "all";
|
|
32
|
+
onlyExisting: boolean;
|
|
33
|
+
/** Roots for project-scope resolution. Empty if project scope is skipped. */
|
|
34
|
+
workspaces: string[];
|
|
35
|
+
homeDir: string;
|
|
36
|
+
/** Optional injection for tests. */
|
|
37
|
+
kbBaseDir?: string;
|
|
38
|
+
}
|
|
39
|
+
export declare function runInventory(options: InventoryOptions): InventorySummary;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { loadKnowledgeBase, } from "../layer1-discovery/knowledge-base.js";
|
|
4
|
+
const MAX_WILDCARD_DEPTH = 8;
|
|
5
|
+
const MAX_WILDCARD_MATCHES = 2000;
|
|
6
|
+
export function runInventory(options) {
|
|
7
|
+
const kb = loadKnowledgeBase(options.kbBaseDir);
|
|
8
|
+
const includeConfigs = options.kind === "all" || options.kind === "configs";
|
|
9
|
+
const includeSkills = options.kind === "all" || options.kind === "skills";
|
|
10
|
+
const rawItems = [];
|
|
11
|
+
for (const entry of kb.entries) {
|
|
12
|
+
if (includeConfigs) {
|
|
13
|
+
for (const cp of entry.config_paths) {
|
|
14
|
+
rawItems.push(...resolveConfigEntry(entry.tool, cp, options));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (includeSkills) {
|
|
18
|
+
for (const sp of entry.skill_paths ?? []) {
|
|
19
|
+
rawItems.push(...resolveSkillEntry(entry.tool, sp, options));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const items = options.onlyExisting ? rawItems.filter((item) => item.exists) : rawItems;
|
|
24
|
+
// Stable ordering: by tool, then kind, then scope, then path.
|
|
25
|
+
items.sort((a, b) => {
|
|
26
|
+
if (a.tool !== b.tool)
|
|
27
|
+
return a.tool.localeCompare(b.tool);
|
|
28
|
+
if (a.kind !== b.kind)
|
|
29
|
+
return a.kind.localeCompare(b.kind);
|
|
30
|
+
if (a.scope !== b.scope)
|
|
31
|
+
return a.scope.localeCompare(b.scope);
|
|
32
|
+
return a.path.localeCompare(b.path);
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
kb_version: kb.schemaVersion,
|
|
36
|
+
tools: kb.entries
|
|
37
|
+
.map((entry) => ({
|
|
38
|
+
name: entry.tool,
|
|
39
|
+
version_range: entry.version_range,
|
|
40
|
+
}))
|
|
41
|
+
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
42
|
+
items,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function resolveConfigEntry(tool, cp, options) {
|
|
46
|
+
if (!scopeIncluded(cp.scope, options.scope))
|
|
47
|
+
return [];
|
|
48
|
+
const roots = rootsFor(cp.scope, options);
|
|
49
|
+
const items = [];
|
|
50
|
+
for (const root of roots) {
|
|
51
|
+
items.push(...resolvePattern({
|
|
52
|
+
tool,
|
|
53
|
+
kind: "config",
|
|
54
|
+
scope: cp.scope,
|
|
55
|
+
pattern: cp.path,
|
|
56
|
+
root,
|
|
57
|
+
riskSurface: cp.risk_surface,
|
|
58
|
+
fieldsOfInterest: cp.fields_of_interest,
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
return items;
|
|
62
|
+
}
|
|
63
|
+
function resolveSkillEntry(tool, sp, options) {
|
|
64
|
+
if (!scopeIncluded(sp.scope, options.scope))
|
|
65
|
+
return [];
|
|
66
|
+
const roots = rootsFor(sp.scope, options);
|
|
67
|
+
const items = [];
|
|
68
|
+
for (const root of roots) {
|
|
69
|
+
items.push(...resolvePattern({
|
|
70
|
+
tool,
|
|
71
|
+
kind: "skill",
|
|
72
|
+
type: sp.type,
|
|
73
|
+
scope: sp.scope,
|
|
74
|
+
pattern: sp.path,
|
|
75
|
+
root,
|
|
76
|
+
riskSurface: sp.risk_surface,
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
return items;
|
|
80
|
+
}
|
|
81
|
+
function scopeIncluded(entryScope, optionScope) {
|
|
82
|
+
if (optionScope === "all")
|
|
83
|
+
return true;
|
|
84
|
+
return entryScope === optionScope;
|
|
85
|
+
}
|
|
86
|
+
function rootsFor(entryScope, options) {
|
|
87
|
+
if (entryScope === "user")
|
|
88
|
+
return [options.homeDir];
|
|
89
|
+
if (options.workspaces.length === 0)
|
|
90
|
+
return [];
|
|
91
|
+
return options.workspaces;
|
|
92
|
+
}
|
|
93
|
+
function resolvePattern(input) {
|
|
94
|
+
const normalized = normalizePattern(input.pattern);
|
|
95
|
+
const hasWildcard = /[*?]/.test(normalized);
|
|
96
|
+
if (!hasWildcard) {
|
|
97
|
+
const absolute = resolve(input.root, normalized);
|
|
98
|
+
return [makeItem(input, absolute, existsSync(absolute))];
|
|
99
|
+
}
|
|
100
|
+
const matches = expandWildcard(input.root, normalized);
|
|
101
|
+
return matches.map((absolute) => makeItem(input, absolute, true));
|
|
102
|
+
}
|
|
103
|
+
function makeItem(input, absolute, exists) {
|
|
104
|
+
return {
|
|
105
|
+
tool: input.tool,
|
|
106
|
+
kind: input.kind,
|
|
107
|
+
type: input.type,
|
|
108
|
+
scope: input.scope,
|
|
109
|
+
pattern: input.pattern,
|
|
110
|
+
path: absolute,
|
|
111
|
+
exists,
|
|
112
|
+
risk_surface: input.riskSurface,
|
|
113
|
+
fields_of_interest: input.fieldsOfInterest,
|
|
114
|
+
resolved_against: input.root,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function normalizePattern(pattern) {
|
|
118
|
+
return pattern.replace(/^~\//, "").replace(/^\/+/, "");
|
|
119
|
+
}
|
|
120
|
+
function escapeRegex(value) {
|
|
121
|
+
return value.replace(/[|\\{}()[\]^$+?.*]/g, "\\$&");
|
|
122
|
+
}
|
|
123
|
+
function wildcardToRegex(pattern) {
|
|
124
|
+
let escaped = escapeRegex(pattern);
|
|
125
|
+
escaped = escaped.replace(/\\\*\\\*\//g, "(?:[^/]+/)*");
|
|
126
|
+
escaped = escaped.replace(/\\\*\\\*/g, ".*");
|
|
127
|
+
escaped = escaped.replace(/\\\*/g, "[^/]*");
|
|
128
|
+
escaped = escaped.replace(/\\\?/g, "[^/]");
|
|
129
|
+
return new RegExp(`^${escaped}$`);
|
|
130
|
+
}
|
|
131
|
+
function fixedPrefix(pattern) {
|
|
132
|
+
const firstStar = pattern.indexOf("*");
|
|
133
|
+
const firstQuestion = pattern.indexOf("?");
|
|
134
|
+
const firstWildcard = firstStar === -1
|
|
135
|
+
? firstQuestion
|
|
136
|
+
: firstQuestion === -1
|
|
137
|
+
? firstStar
|
|
138
|
+
: Math.min(firstStar, firstQuestion);
|
|
139
|
+
if (firstWildcard === -1)
|
|
140
|
+
return pattern;
|
|
141
|
+
const prefix = pattern.slice(0, firstWildcard);
|
|
142
|
+
const lastSlash = prefix.lastIndexOf("/");
|
|
143
|
+
return lastSlash === -1 ? "" : prefix.slice(0, lastSlash);
|
|
144
|
+
}
|
|
145
|
+
function expandWildcard(root, pattern) {
|
|
146
|
+
const matchRegex = wildcardToRegex(pattern);
|
|
147
|
+
const prefix = fixedPrefix(pattern);
|
|
148
|
+
const baseDir = prefix ? resolve(root, prefix) : resolve(root);
|
|
149
|
+
if (!existsSync(baseDir))
|
|
150
|
+
return [];
|
|
151
|
+
try {
|
|
152
|
+
if (!statSync(baseDir).isDirectory())
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
const matches = [];
|
|
159
|
+
const queue = [{ dir: baseDir, depth: 0 }];
|
|
160
|
+
while (queue.length > 0 && matches.length < MAX_WILDCARD_MATCHES) {
|
|
161
|
+
const current = queue.pop();
|
|
162
|
+
if (!current)
|
|
163
|
+
break;
|
|
164
|
+
let entries;
|
|
165
|
+
try {
|
|
166
|
+
entries = readdirSync(current.dir, { withFileTypes: true });
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
for (const entry of entries) {
|
|
172
|
+
if (matches.length >= MAX_WILDCARD_MATCHES)
|
|
173
|
+
break;
|
|
174
|
+
const absolute = join(current.dir, entry.name);
|
|
175
|
+
if (entry.isSymbolicLink())
|
|
176
|
+
continue;
|
|
177
|
+
if (entry.isDirectory()) {
|
|
178
|
+
if (current.depth < MAX_WILDCARD_DEPTH) {
|
|
179
|
+
queue.push({ dir: absolute, depth: current.depth + 1 });
|
|
180
|
+
}
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (!entry.isFile())
|
|
184
|
+
continue;
|
|
185
|
+
const rel = relative(root, absolute).split(sep).join("/");
|
|
186
|
+
if (rel.startsWith(".."))
|
|
187
|
+
continue;
|
|
188
|
+
if (!matchRegex.test(rel))
|
|
189
|
+
continue;
|
|
190
|
+
matches.push(absolute);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return matches;
|
|
194
|
+
}
|
|
@@ -133,6 +133,18 @@
|
|
|
133
133
|
"scope": "user",
|
|
134
134
|
"type": "custom_command",
|
|
135
135
|
"risk_surface": ["prompt_injection"]
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"path": ".claude/skills/*/SKILL.md",
|
|
139
|
+
"scope": "project",
|
|
140
|
+
"type": "anthropic_skill",
|
|
141
|
+
"risk_surface": ["prompt_injection", "unicode_backdoor", "command_exec", "mcp_config"]
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"path": ".claude/skills/*/SKILL.md",
|
|
145
|
+
"scope": "user",
|
|
146
|
+
"type": "anthropic_skill",
|
|
147
|
+
"risk_surface": ["prompt_injection", "unicode_backdoor", "command_exec", "mcp_config"]
|
|
136
148
|
}
|
|
137
149
|
],
|
|
138
150
|
"extension_mechanisms": [
|