codegate-ai 0.2.3 → 0.3.1

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.d.ts CHANGED
@@ -8,6 +8,7 @@ import { type ScanDiscoveryCandidate, type ScanDiscoveryContext } from "./scan.j
8
8
  import { type ResolvedScanTarget } from "./scan-target.js";
9
9
  import type { CodeGateReport } from "./types/report.js";
10
10
  import { type RemediationRunnerInput, type RemediationRunnerResult } from "./layer4-remediation/remediation-runner.js";
11
+ import { type SkillsWrapperLaunchResult } from "./commands/skills-wrapper.js";
11
12
  import { type DeepAgentOption, type MetaAgentCommandConsentContext, type MetaAgentCommandRunResult, type RemediationConsentContext, type ScanRunnerInput } from "./commands/scan-command.js";
12
13
  export interface RunWarningConsentContext {
13
14
  target: string;
@@ -55,6 +56,11 @@ export interface CliDeps {
55
56
  requestRunWarningConsent?: (context: RunWarningConsentContext) => Promise<boolean> | boolean;
56
57
  requestSkillSelection?: (options: string[]) => Promise<string | null> | string | null;
57
58
  executeDeepResource?: (resource: DeepScanResource) => Promise<ResourceFetchResult>;
59
+ launchSkills?: (args: string[], cwd: string) => SkillsWrapperLaunchResult;
60
+ runSkillsWrapper?: (input: {
61
+ version: string;
62
+ skillsArgs: string[];
63
+ }) => Promise<void>;
58
64
  runWrapper?: (input: {
59
65
  target: string;
60
66
  cwd: string;
package/dist/cli.js CHANGED
@@ -20,6 +20,7 @@ import { executeWrapperRun } from "./wrapper.js";
20
20
  import { runRemediation as runRemediationWorkflow, } from "./layer4-remediation/remediation-runner.js";
21
21
  import { undoLatestSession } from "./commands/undo.js";
22
22
  import { executeScanCommand } from "./commands/scan-command.js";
23
+ import { executeSkillsWrapper, launchSkillsPassthrough, } from "./commands/skills-wrapper.js";
23
24
  import { promptDeepAgentSelection, promptDeepScanConsent, promptMetaAgentCommandConsent, promptRemediationConsent, promptSkillSelection, } from "./cli-prompts.js";
24
25
  import { resetScanState } from "./layer2-static/state/scan-state.js";
25
26
  const require = createRequire(import.meta.url);
@@ -155,6 +156,7 @@ const defaultCliDeps = {
155
156
  }
156
157
  return fetchResourceMetadata(resource.request);
157
158
  },
159
+ launchSkills: (args, cwd) => launchSkillsPassthrough(args, cwd),
158
160
  };
159
161
  function addScanCommand(program, version, deps) {
160
162
  program
@@ -365,6 +367,48 @@ function addRunCommand(program, version, deps) {
365
367
  }
366
368
  });
367
369
  }
370
+ function addSkillsCommand(program, version, deps) {
371
+ program
372
+ .command("skills [skillsArgs...]")
373
+ .description("Wrap npx skills with CodeGate preflight scanning for installs")
374
+ .allowUnknownOption(true)
375
+ .allowExcessArguments(true)
376
+ .addHelpText("after", renderExampleHelp([
377
+ "codegate skills add vercel-labs/skills --skill find-skills",
378
+ "codegate skills add https://github.com/owner/repo --skill security-review",
379
+ "codegate skills find security",
380
+ "codegate skills add owner/repo --skill demo --cg-force",
381
+ ]))
382
+ .action(async (skillsArgs) => {
383
+ try {
384
+ const runSkillsWrapper = deps.runSkillsWrapper ??
385
+ ((input) => executeSkillsWrapper(input, {
386
+ cwd: deps.cwd,
387
+ isTTY: deps.isTTY,
388
+ pathExists: deps.pathExists,
389
+ resolveConfig: deps.resolveConfig,
390
+ runScan: deps.runScan,
391
+ resolveScanTarget: deps.resolveScanTarget,
392
+ requestSkillSelection: deps.requestSkillSelection,
393
+ requestWarningProceed: deps.requestRunWarningConsent,
394
+ launchSkills: deps.launchSkills ?? ((args, cwd) => launchSkillsPassthrough(args, cwd)),
395
+ stdout: deps.stdout,
396
+ stderr: deps.stderr,
397
+ setExitCode: deps.setExitCode,
398
+ renderTui: deps.renderTui,
399
+ }));
400
+ await runSkillsWrapper({
401
+ version,
402
+ skillsArgs: skillsArgs ?? [],
403
+ });
404
+ }
405
+ catch (error) {
406
+ const message = error instanceof Error ? error.message : String(error);
407
+ deps.stderr(`Skills wrapper failed: ${message}`);
408
+ deps.setExitCode(3);
409
+ }
410
+ });
411
+ }
368
412
  function addUndoCommand(program, deps) {
369
413
  program
370
414
  .command("undo [dir]")
@@ -468,9 +512,11 @@ export function createCli(version = packageJson.version ?? "0.0.0-dev", deps = d
468
512
  "codegate scan .",
469
513
  "codegate scan https://github.com/owner/repo",
470
514
  "codegate scan https://github.com/owner/repo/blob/main/skills/security-review/SKILL.md",
515
+ "codegate skills add owner/repo --skill security-review",
471
516
  "codegate run claude",
472
517
  ]));
473
518
  addScanCommand(program, version, deps);
519
+ addSkillsCommand(program, version, deps);
474
520
  addRunCommand(program, version, deps);
475
521
  addUndoCommand(program, deps);
476
522
  addInitCommand(program, deps);
@@ -0,0 +1,63 @@
1
+ import { type CodeGateConfig, type OutputFormat, type ResolveConfigOptions } from "../config.js";
2
+ import { type ResolvedScanTarget } from "../scan-target.js";
3
+ import type { ScanRunnerInput } from "./scan-command.js";
4
+ import type { CodeGateReport } from "../types/report.js";
5
+ export interface SkillsWrapperRuntimeOptions {
6
+ force: boolean;
7
+ noTui: boolean;
8
+ includeUserScope: boolean;
9
+ format?: OutputFormat;
10
+ configPath?: string;
11
+ }
12
+ export interface ParsedSkillsInvocation {
13
+ passthroughArgs: string[];
14
+ wrapper: SkillsWrapperRuntimeOptions;
15
+ subcommand: string | null;
16
+ sourceTarget: string | null;
17
+ preferredSkill: string | null;
18
+ }
19
+ export interface SkillsWrapperLaunchResult {
20
+ status: number | null;
21
+ error?: Error;
22
+ }
23
+ export interface ExecuteSkillsWrapperInput {
24
+ version: string;
25
+ skillsArgs: string[];
26
+ }
27
+ export interface SkillsWarningConsentContext {
28
+ target: string;
29
+ report: CodeGateReport;
30
+ }
31
+ export interface SkillsWrapperDeps {
32
+ cwd: () => string;
33
+ isTTY: () => boolean;
34
+ pathExists?: (path: string) => boolean;
35
+ resolveConfig: (options: ResolveConfigOptions) => CodeGateConfig;
36
+ runScan: (input: ScanRunnerInput) => Promise<CodeGateReport>;
37
+ resolveScanTarget?: (input: {
38
+ rawTarget: string;
39
+ cwd: string;
40
+ preferredSkill?: string;
41
+ interactive?: boolean;
42
+ requestSkillSelection?: (options: string[]) => Promise<string | null> | string | null;
43
+ }) => Promise<ResolvedScanTarget> | ResolvedScanTarget;
44
+ requestSkillSelection?: (options: string[]) => Promise<string | null> | string | null;
45
+ requestWarningProceed?: (context: SkillsWarningConsentContext) => Promise<boolean> | boolean;
46
+ launchSkills: (args: string[], cwd: string) => SkillsWrapperLaunchResult;
47
+ stdout: (message: string) => void;
48
+ stderr: (message: string) => void;
49
+ setExitCode: (code: number) => void;
50
+ renderTui?: (props: {
51
+ view: "dashboard" | "summary";
52
+ report: CodeGateReport;
53
+ notices?: string[];
54
+ }) => void;
55
+ }
56
+ interface SourceDetectionContext {
57
+ cwd: string;
58
+ pathExists: (path: string) => boolean;
59
+ }
60
+ export declare function parseSkillsInvocation(rawArgs: string[], context?: SourceDetectionContext): ParsedSkillsInvocation;
61
+ export declare function launchSkillsPassthrough(args: string[], cwd: string): SkillsWrapperLaunchResult;
62
+ export declare function executeSkillsWrapper(input: ExecuteSkillsWrapperInput, deps: SkillsWrapperDeps): Promise<void>;
63
+ export {};
@@ -0,0 +1,392 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { createInterface } from "node:readline/promises";
4
+ import { resolve } from "node:path";
5
+ import { applyConfigPolicy, OUTPUT_FORMATS, } from "../config.js";
6
+ import { renderByFormat, summarizeRequestedTargetFindings } from "./scan-command/helpers.js";
7
+ import { resolveScanTarget } from "../scan-target.js";
8
+ function parseWrapperOptionValue(args, index, flag) {
9
+ const current = args[index] ?? "";
10
+ const withEquals = `${flag}=`;
11
+ if (current.startsWith(withEquals)) {
12
+ const value = current.slice(withEquals.length).trim();
13
+ if (value.length === 0) {
14
+ throw new Error(`${flag} requires a value`);
15
+ }
16
+ return [value, index];
17
+ }
18
+ const nextValue = args[index + 1];
19
+ if (!nextValue || nextValue.trim().length === 0) {
20
+ throw new Error(`${flag} requires a value`);
21
+ }
22
+ return [nextValue, index + 1];
23
+ }
24
+ function parseOutputFormat(value) {
25
+ const normalized = value.trim().toLowerCase();
26
+ const matched = OUTPUT_FORMATS.find((format) => format === normalized);
27
+ if (!matched) {
28
+ throw new Error(`Unsupported --cg-format value "${value}". Valid values: ${OUTPUT_FORMATS.join(", ")}.`);
29
+ }
30
+ return matched;
31
+ }
32
+ function isLikelyGitSshSource(value) {
33
+ return /^git@[^:]+:.+/iu.test(value) || /^ssh:\/\//iu.test(value);
34
+ }
35
+ function looksLikeSourceToken(value, context) {
36
+ if (value.trim().length === 0 || value.startsWith("-")) {
37
+ return false;
38
+ }
39
+ if (isLikelyHttpUrl(value) || isLikelyGitSshSource(value) || isLikelyGitHubShorthand(value)) {
40
+ return true;
41
+ }
42
+ if (value.startsWith("./") ||
43
+ value.startsWith("../") ||
44
+ value.startsWith("/") ||
45
+ value.startsWith("~/") ||
46
+ value.endsWith(".git")) {
47
+ return true;
48
+ }
49
+ if (context) {
50
+ const localCandidate = resolve(context.cwd, value);
51
+ if (context.pathExists(localCandidate)) {
52
+ return true;
53
+ }
54
+ }
55
+ return false;
56
+ }
57
+ function firstLikelySourceAfterAdd(args, addIndex, context) {
58
+ for (let index = addIndex + 1; index < args.length; index += 1) {
59
+ const token = args[index] ?? "";
60
+ if (token === "--skill") {
61
+ index += 1;
62
+ continue;
63
+ }
64
+ if (token.startsWith("--skill=")) {
65
+ continue;
66
+ }
67
+ if (token === "--") {
68
+ for (let tailIndex = index + 1; tailIndex < args.length; tailIndex += 1) {
69
+ const candidate = args[tailIndex] ?? "";
70
+ if (looksLikeSourceToken(candidate, context)) {
71
+ return candidate;
72
+ }
73
+ }
74
+ return null;
75
+ }
76
+ if (looksLikeSourceToken(token, context)) {
77
+ return token;
78
+ }
79
+ }
80
+ return null;
81
+ }
82
+ function preferredSkillArg(args, addIndex) {
83
+ for (let index = addIndex + 1; index < args.length; index += 1) {
84
+ const token = args[index] ?? "";
85
+ if (token === "--skill") {
86
+ const value = args[index + 1];
87
+ if (value && value.trim().length > 0) {
88
+ return value.trim();
89
+ }
90
+ return null;
91
+ }
92
+ if (token.startsWith("--skill=")) {
93
+ const value = token.slice("--skill=".length).trim();
94
+ return value.length > 0 ? value : null;
95
+ }
96
+ }
97
+ return null;
98
+ }
99
+ function isLikelyHttpUrl(value) {
100
+ return /^https?:\/\//iu.test(value);
101
+ }
102
+ function isLikelyGitHubShorthand(value) {
103
+ return /^[a-z0-9._-]+\/[a-z0-9._-]+(?:\.git)?$/iu.test(value);
104
+ }
105
+ function normalizeSkillsSourceTarget(rawTarget, cwd, pathExists) {
106
+ if (isLikelyHttpUrl(rawTarget)) {
107
+ return rawTarget;
108
+ }
109
+ if (!isLikelyGitHubShorthand(rawTarget)) {
110
+ return rawTarget;
111
+ }
112
+ const localCandidate = resolve(cwd, rawTarget);
113
+ if (pathExists(localCandidate)) {
114
+ return rawTarget;
115
+ }
116
+ return `https://github.com/${rawTarget}`;
117
+ }
118
+ function toSubcommand(args) {
119
+ const index = args.findIndex((value) => !value.startsWith("-"));
120
+ if (index < 0) {
121
+ return [null, -1];
122
+ }
123
+ return [args[index]?.toLowerCase() ?? null, index];
124
+ }
125
+ function isLikelyLeadingGlobalOptionSequence(args, endExclusive) {
126
+ for (let index = 0; index < endExclusive; index += 1) {
127
+ const token = args[index] ?? "";
128
+ if (token.startsWith("-")) {
129
+ continue;
130
+ }
131
+ const previous = index > 0 ? (args[index - 1] ?? "") : "";
132
+ if (previous.startsWith("-")) {
133
+ continue;
134
+ }
135
+ return false;
136
+ }
137
+ return true;
138
+ }
139
+ function findAddSubcommandIndex(args, context) {
140
+ const [subcommand, subcommandIndex] = toSubcommand(args);
141
+ if (subcommand === "add") {
142
+ return subcommandIndex;
143
+ }
144
+ // Only attempt fallback when the first positional token is likely an option value.
145
+ // This avoids misclassifying non-add commands like `skills find add`.
146
+ if (subcommandIndex < 1 ||
147
+ typeof args[subcommandIndex - 1] !== "string" ||
148
+ !String(args[subcommandIndex - 1]).startsWith("-")) {
149
+ return -1;
150
+ }
151
+ // Fallback for inputs where global-option values appear before `add`.
152
+ for (let index = 0; index < args.length; index += 1) {
153
+ const token = args[index]?.toLowerCase();
154
+ if (token !== "add") {
155
+ continue;
156
+ }
157
+ if (isLikelyLeadingGlobalOptionSequence(args, index) &&
158
+ firstLikelySourceAfterAdd(args, index, context)) {
159
+ return index;
160
+ }
161
+ }
162
+ return -1;
163
+ }
164
+ export function parseSkillsInvocation(rawArgs, context) {
165
+ const wrapper = {
166
+ force: false,
167
+ noTui: false,
168
+ includeUserScope: false,
169
+ format: undefined,
170
+ configPath: undefined,
171
+ };
172
+ const passthroughArgs = [];
173
+ for (let index = 0; index < rawArgs.length; index += 1) {
174
+ const token = rawArgs[index] ?? "";
175
+ if (token === "--") {
176
+ passthroughArgs.push("--");
177
+ for (let tailIndex = index + 1; tailIndex < rawArgs.length; tailIndex += 1) {
178
+ passthroughArgs.push(rawArgs[tailIndex] ?? "");
179
+ }
180
+ break;
181
+ }
182
+ if (token === "--cg-force") {
183
+ wrapper.force = true;
184
+ continue;
185
+ }
186
+ if (token === "--cg-no-tui") {
187
+ wrapper.noTui = true;
188
+ continue;
189
+ }
190
+ if (token === "--cg-include-user-scope") {
191
+ wrapper.includeUserScope = true;
192
+ continue;
193
+ }
194
+ if (token === "--cg-format" || token.startsWith("--cg-format=")) {
195
+ const [value, consumedIndex] = parseWrapperOptionValue(rawArgs, index, "--cg-format");
196
+ wrapper.format = parseOutputFormat(value);
197
+ index = consumedIndex;
198
+ continue;
199
+ }
200
+ if (token === "--cg-config" || token.startsWith("--cg-config=")) {
201
+ const [value, consumedIndex] = parseWrapperOptionValue(rawArgs, index, "--cg-config");
202
+ wrapper.configPath = value;
203
+ index = consumedIndex;
204
+ continue;
205
+ }
206
+ if (token.startsWith("--cg-")) {
207
+ throw new Error(`Unknown CodeGate wrapper option: ${token}`);
208
+ }
209
+ passthroughArgs.push(token);
210
+ }
211
+ const addSubcommandIndex = findAddSubcommandIndex(passthroughArgs, context);
212
+ const subcommand = addSubcommandIndex >= 0 ? "add" : toSubcommand(passthroughArgs)[0];
213
+ const sourceTarget = addSubcommandIndex >= 0
214
+ ? firstLikelySourceAfterAdd(passthroughArgs, addSubcommandIndex, context)
215
+ : null;
216
+ const preferredSkill = addSubcommandIndex >= 0 ? preferredSkillArg(passthroughArgs, addSubcommandIndex) : null;
217
+ return {
218
+ passthroughArgs,
219
+ wrapper,
220
+ subcommand,
221
+ sourceTarget,
222
+ preferredSkill,
223
+ };
224
+ }
225
+ async function promptWarningProceed(context) {
226
+ const rl = createInterface({
227
+ input: process.stdin,
228
+ output: process.stdout,
229
+ });
230
+ const prompt = [
231
+ `Warning findings detected for ${context.target}.`,
232
+ `Findings: ${context.report.summary.total}`,
233
+ "Proceed with skills install? [y/N]: ",
234
+ ].join("\n");
235
+ try {
236
+ const answer = await rl.question(prompt);
237
+ return /^y(es)?$/iu.test(answer.trim());
238
+ }
239
+ finally {
240
+ rl.close();
241
+ }
242
+ }
243
+ function finalizeLaunch(result, deps) {
244
+ if (result.error) {
245
+ deps.stderr(`Failed to run npx skills: ${result.error.message}`);
246
+ deps.setExitCode(3);
247
+ return;
248
+ }
249
+ deps.setExitCode(result.status ?? 1);
250
+ }
251
+ function shouldPromptForWarning(report, config, force) {
252
+ return (report.summary.exit_code === 1 &&
253
+ report.findings.length > 0 &&
254
+ force !== true &&
255
+ config.auto_proceed_below_threshold !== true);
256
+ }
257
+ export function launchSkillsPassthrough(args, cwd) {
258
+ const result = spawnSync("npx", ["skills", ...args], {
259
+ cwd,
260
+ stdio: "inherit",
261
+ });
262
+ return {
263
+ status: result.status,
264
+ error: result.error ?? undefined,
265
+ };
266
+ }
267
+ export async function executeSkillsWrapper(input, deps) {
268
+ const cwd = deps.cwd();
269
+ const isTTY = deps.isTTY();
270
+ const pathExists = deps.pathExists ?? ((path) => existsSync(path));
271
+ const sourceDetectionContext = { cwd, pathExists };
272
+ const parsed = parseSkillsInvocation(input.skillsArgs, sourceDetectionContext);
273
+ if (parsed.subcommand !== "add") {
274
+ finalizeLaunch(deps.launchSkills(parsed.passthroughArgs, cwd), deps);
275
+ return;
276
+ }
277
+ const resolvedSourceTarget = parsed.sourceTarget;
278
+ const preferredSkill = parsed.preferredSkill ?? undefined;
279
+ if (!resolvedSourceTarget) {
280
+ deps.stderr("Could not determine the source target for `skills add`. Provide a source URL/path after `add`.");
281
+ deps.setExitCode(3);
282
+ return;
283
+ }
284
+ let resolvedTarget;
285
+ const interactivePromptsEnabled = isTTY && parsed.wrapper.noTui !== true;
286
+ try {
287
+ const sourceTarget = normalizeSkillsSourceTarget(resolvedSourceTarget, cwd, pathExists);
288
+ const resolveTarget = deps.resolveScanTarget ??
289
+ ((resolverInput) => resolveScanTarget(resolverInput));
290
+ resolvedTarget = await resolveTarget({
291
+ rawTarget: sourceTarget,
292
+ cwd,
293
+ preferredSkill,
294
+ interactive: interactivePromptsEnabled,
295
+ requestSkillSelection: interactivePromptsEnabled ? deps.requestSkillSelection : undefined,
296
+ });
297
+ const noTui = parsed.wrapper.noTui === true || !isTTY;
298
+ const cliConfig = {
299
+ format: parsed.wrapper.format,
300
+ configPath: parsed.wrapper.configPath,
301
+ noTui,
302
+ };
303
+ const baseConfig = deps.resolveConfig({
304
+ scanTarget: resolvedTarget.scanTarget,
305
+ cli: cliConfig,
306
+ });
307
+ const config = parsed.wrapper.includeUserScope
308
+ ? { ...baseConfig, scan_user_scope: true }
309
+ : baseConfig;
310
+ let report = await deps.runScan({
311
+ version: input.version,
312
+ scanTarget: resolvedTarget.scanTarget,
313
+ config,
314
+ flags: {
315
+ noTui,
316
+ format: parsed.wrapper.format,
317
+ force: parsed.wrapper.force,
318
+ includeUserScope: parsed.wrapper.includeUserScope,
319
+ skill: preferredSkill,
320
+ },
321
+ discoveryContext: undefined,
322
+ });
323
+ if (resolvedTarget.displayTarget && resolvedTarget.displayTarget !== report.scan_target) {
324
+ report = {
325
+ ...report,
326
+ scan_target: resolvedTarget.displayTarget,
327
+ };
328
+ }
329
+ report = applyConfigPolicy(report, config);
330
+ const shouldUseTui = config.tui.enabled && isTTY && deps.renderTui !== undefined && noTui !== true;
331
+ const targetSummaryNote = config.output_format === "terminal"
332
+ ? summarizeRequestedTargetFindings(report, resolvedTarget.displayTarget)
333
+ : null;
334
+ if (shouldUseTui) {
335
+ deps.renderTui?.({
336
+ view: "dashboard",
337
+ report,
338
+ notices: targetSummaryNote ? [targetSummaryNote] : undefined,
339
+ });
340
+ deps.renderTui?.({ view: "summary", report });
341
+ }
342
+ else {
343
+ if (targetSummaryNote) {
344
+ deps.stdout(targetSummaryNote);
345
+ }
346
+ deps.stdout(renderByFormat(config.output_format, report));
347
+ }
348
+ if (report.summary.exit_code === 2 && parsed.wrapper.force !== true) {
349
+ deps.stderr("Dangerous findings detected. Aborting `skills add` (fail-closed).");
350
+ deps.setExitCode(2);
351
+ return;
352
+ }
353
+ if (shouldPromptForWarning(report, config, parsed.wrapper.force)) {
354
+ if (!interactivePromptsEnabled) {
355
+ deps.stderr("Warning findings detected. Aborting `skills add` in non-interactive mode (fail-closed). Use --cg-force to override.");
356
+ deps.setExitCode(1);
357
+ return;
358
+ }
359
+ const requestProceed = deps.requestWarningProceed ?? promptWarningProceed;
360
+ const approved = await requestProceed({
361
+ target: resolvedSourceTarget,
362
+ report,
363
+ });
364
+ if (!approved) {
365
+ deps.stderr("Install cancelled by user.");
366
+ deps.setExitCode(1);
367
+ return;
368
+ }
369
+ }
370
+ }
371
+ catch (error) {
372
+ const message = error instanceof Error ? error.message : String(error);
373
+ if (parsed.wrapper.force !== true) {
374
+ deps.stderr(`Preflight scan failed (fail-closed): ${message}`);
375
+ deps.setExitCode(3);
376
+ return;
377
+ }
378
+ deps.stderr(`Preflight scan failed, continuing due to --cg-force: ${message}`);
379
+ }
380
+ finally {
381
+ if (resolvedTarget?.cleanup) {
382
+ try {
383
+ await resolvedTarget.cleanup();
384
+ }
385
+ catch (error) {
386
+ const message = error instanceof Error ? error.message : String(error);
387
+ deps.stderr(`Scan target cleanup failed: ${message}`);
388
+ }
389
+ }
390
+ }
391
+ finalizeLaunch(deps.launchSkills(parsed.passthroughArgs, cwd), deps);
392
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codegate-ai",
3
- "version": "0.2.3",
3
+ "version": "0.3.1",
4
4
  "description": "Pre-flight security scanner for AI coding tool configurations.",
5
5
  "license": "MIT",
6
6
  "type": "module",