sapper-ai 0.5.0 → 0.6.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.
Files changed (43) hide show
  1. package/dist/auth.d.ts +11 -0
  2. package/dist/auth.d.ts.map +1 -0
  3. package/dist/auth.js +102 -0
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +316 -32
  6. package/dist/harden.d.ts +28 -0
  7. package/dist/harden.d.ts.map +1 -0
  8. package/dist/harden.js +309 -0
  9. package/dist/mcp/jsonc.d.ts +3 -0
  10. package/dist/mcp/jsonc.d.ts.map +1 -0
  11. package/dist/mcp/jsonc.js +119 -0
  12. package/dist/mcp/wrapConfig.d.ts +22 -0
  13. package/dist/mcp/wrapConfig.d.ts.map +1 -0
  14. package/dist/mcp/wrapConfig.js +192 -0
  15. package/dist/policyYaml.d.ts +3 -0
  16. package/dist/policyYaml.d.ts.map +1 -0
  17. package/dist/policyYaml.js +27 -0
  18. package/dist/postinstall.d.ts.map +1 -1
  19. package/dist/postinstall.js +11 -2
  20. package/dist/quarantine.d.ts +13 -0
  21. package/dist/quarantine.d.ts.map +1 -0
  22. package/dist/quarantine.js +22 -0
  23. package/dist/report.d.ts.map +1 -1
  24. package/dist/report.js +1061 -59
  25. package/dist/scan.d.ts +15 -0
  26. package/dist/scan.d.ts.map +1 -1
  27. package/dist/scan.js +179 -178
  28. package/dist/utils/env.d.ts +3 -0
  29. package/dist/utils/env.d.ts.map +1 -0
  30. package/dist/utils/env.js +25 -0
  31. package/dist/utils/format.d.ts +22 -0
  32. package/dist/utils/format.d.ts.map +1 -0
  33. package/dist/utils/format.js +97 -0
  34. package/dist/utils/fs.d.ts +7 -0
  35. package/dist/utils/fs.d.ts.map +1 -0
  36. package/dist/utils/fs.js +47 -0
  37. package/dist/utils/repoRoot.d.ts +2 -0
  38. package/dist/utils/repoRoot.d.ts.map +1 -0
  39. package/dist/utils/repoRoot.js +20 -0
  40. package/dist/utils/semver.d.ts +2 -0
  41. package/dist/utils/semver.d.ts.map +1 -0
  42. package/dist/utils/semver.js +7 -0
  43. package/package.json +5 -7
package/dist/auth.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export declare function getAuthPath(): string;
2
+ export declare function loadOpenAiApiKey(options?: {
3
+ env?: NodeJS.ProcessEnv;
4
+ authPath?: string;
5
+ }): Promise<string | null>;
6
+ export declare function maskApiKey(apiKey: string): string;
7
+ export declare function promptAndSaveOpenAiApiKey(options?: {
8
+ authPath?: string;
9
+ mask?: string;
10
+ }): Promise<string | null>;
11
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAkBA,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,wBAAsB,gBAAgB,CAAC,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkB3H;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAIjD;AAED,wBAAsB,yBAAyB,CAAC,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA2B1H"}
package/dist/auth.js ADDED
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.getAuthPath = getAuthPath;
40
+ exports.loadOpenAiApiKey = loadOpenAiApiKey;
41
+ exports.maskApiKey = maskApiKey;
42
+ exports.promptAndSaveOpenAiApiKey = promptAndSaveOpenAiApiKey;
43
+ const node_os_1 = require("node:os");
44
+ const node_path_1 = require("node:path");
45
+ const password_1 = __importDefault(require("@inquirer/password"));
46
+ const fs_1 = require("./utils/fs");
47
+ function isNonEmptyString(value) {
48
+ return typeof value === 'string' && value.trim().length > 0;
49
+ }
50
+ function getAuthPath() {
51
+ return (0, node_path_1.join)((0, node_os_1.homedir)(), '.sapperai', 'auth.json');
52
+ }
53
+ async function loadOpenAiApiKey(options = {}) {
54
+ const env = options.env ?? process.env;
55
+ const fromEnv = env.OPENAI_API_KEY;
56
+ if (isNonEmptyString(fromEnv)) {
57
+ return fromEnv.trim();
58
+ }
59
+ const authPath = options.authPath ?? getAuthPath();
60
+ const raw = await (0, fs_1.readFileIfExists)(authPath);
61
+ if (raw === null)
62
+ return null;
63
+ try {
64
+ const parsed = JSON.parse(raw);
65
+ const key = parsed.openai?.apiKey;
66
+ return isNonEmptyString(key) ? key.trim() : null;
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ }
72
+ function maskApiKey(apiKey) {
73
+ const trimmed = apiKey.trim();
74
+ if (trimmed.length <= 3)
75
+ return '***';
76
+ return `${trimmed.slice(0, 3)}${'█'.repeat(Math.min(24, Math.max(6, trimmed.length - 3)))}`;
77
+ }
78
+ async function promptAndSaveOpenAiApiKey(options = {}) {
79
+ const key = await (0, password_1.default)({
80
+ message: 'Enter your API key:',
81
+ mask: options.mask ?? '█',
82
+ });
83
+ if (!isNonEmptyString(key))
84
+ return null;
85
+ const authPath = options.authPath ?? getAuthPath();
86
+ const payload = {
87
+ openai: {
88
+ apiKey: key.trim(),
89
+ savedAt: new Date().toISOString(),
90
+ },
91
+ };
92
+ await (0, fs_1.atomicWriteFile)(authPath, `${JSON.stringify(payload, null, 2)}\n`, { mode: 0o600 });
93
+ // Best-effort: ensure perms even if the file already existed with broader mode.
94
+ // On Windows this is typically a no-op; prefer env vars there.
95
+ try {
96
+ const { chmod } = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
97
+ await chmod(authPath, 0o600);
98
+ }
99
+ catch {
100
+ }
101
+ return payload.openai.apiKey;
102
+ }
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAaA,wBAAsB,MAAM,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,MAAM,CAAC,CAiCpF"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAkBA,wBAAsB,MAAM,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,MAAM,CAAC,CAuFpF"}
package/dist/cli.js CHANGED
@@ -45,7 +45,12 @@ const node_path_1 = require("node:path");
45
45
  const readline = __importStar(require("node:readline"));
46
46
  const select_1 = __importDefault(require("@inquirer/select"));
47
47
  const presets_1 = require("./presets");
48
+ const policyYaml_1 = require("./policyYaml");
49
+ const harden_1 = require("./harden");
50
+ const quarantine_1 = require("./quarantine");
51
+ const wrapConfig_1 = require("./mcp/wrapConfig");
48
52
  const scan_1 = require("./scan");
53
+ const env_1 = require("./utils/env");
49
54
  async function runCli(argv = process.argv.slice(2)) {
50
55
  if (argv[0] === '--help' || argv[0] === '-h') {
51
56
  printUsage();
@@ -62,7 +67,49 @@ async function runCli(argv = process.argv.slice(2)) {
62
67
  printUsage();
63
68
  return 1;
64
69
  }
65
- return (0, scan_1.runScan)(scanOptions);
70
+ const scanExitCode = await (0, scan_1.runScan)(scanOptions);
71
+ const shouldOfferHarden = parsed.noPrompt !== true &&
72
+ process.stdout.isTTY === true &&
73
+ process.stdin.isTTY === true &&
74
+ (0, env_1.isCiEnv)(process.env) !== true &&
75
+ (parsed.harden === true || (await (0, harden_1.getHardenPlanSummary)({ includeSystem: true })).actions.length > 0);
76
+ if (shouldOfferHarden) {
77
+ const hardenExitCode = await (0, harden_1.runHarden)({
78
+ apply: true,
79
+ includeSystem: true,
80
+ });
81
+ if (scanExitCode === 0 && hardenExitCode !== 0) {
82
+ return hardenExitCode;
83
+ }
84
+ }
85
+ return scanExitCode;
86
+ }
87
+ if (argv[0] === 'harden') {
88
+ const parsed = parseHardenArgs(argv.slice(1));
89
+ if (!parsed) {
90
+ printUsage();
91
+ return 1;
92
+ }
93
+ return (0, harden_1.runHarden)(parsed);
94
+ }
95
+ if (argv[0] === 'mcp') {
96
+ const parsed = parseMcpArgs(argv.slice(1));
97
+ if (!parsed) {
98
+ printUsage();
99
+ return 1;
100
+ }
101
+ return runMcpCommand(parsed);
102
+ }
103
+ if (argv[0] === 'quarantine') {
104
+ const parsed = parseQuarantineArgs(argv.slice(1));
105
+ if (!parsed) {
106
+ printUsage();
107
+ return 1;
108
+ }
109
+ if (parsed.command === 'quarantine_list') {
110
+ return (0, quarantine_1.runQuarantineList)({ quarantineDir: parsed.quarantineDir });
111
+ }
112
+ return (0, quarantine_1.runQuarantineRestore)({ id: parsed.id, quarantineDir: parsed.quarantineDir, force: parsed.force });
66
113
  }
67
114
  if (argv[0] === 'dashboard') {
68
115
  return runDashboard();
@@ -84,10 +131,21 @@ Usage:
84
131
  sapper-ai scan --deep Current directory + subdirectories
85
132
  sapper-ai scan --system AI system paths (~/.claude, ~/.cursor, ...)
86
133
  sapper-ai scan ./path Scan a specific file/directory
134
+ sapper-ai scan --policy ./sapperai.config.yaml Use explicit policy path (fatal if invalid)
87
135
  sapper-ai scan --fix Quarantine blocked files
88
- sapper-ai scan --ai Deep scan with AI analysis (requires OPENAI_API_KEY)
136
+ sapper-ai scan --ai Deep scan with AI analysis (OpenAI; prompts for key in a TTY)
137
+ sapper-ai scan --no-color Disable ANSI colors
138
+ sapper-ai scan --no-prompt Disable all prompts (CI-safe)
139
+ sapper-ai scan --harden After scan, offer to apply recommended hardening
89
140
  sapper-ai scan --no-open Skip opening report in browser
90
141
  sapper-ai scan --no-save Skip saving scan results to ~/.sapperai/scans/
142
+ sapper-ai harden Plan recommended setup changes (no writes)
143
+ sapper-ai harden --apply Apply recommended project changes
144
+ sapper-ai harden --include-system Include system changes (home directory)
145
+ sapper-ai mcp wrap-config Wrap MCP servers to run behind sapperai-proxy (defaults to Claude Code config)
146
+ sapper-ai mcp unwrap-config Undo MCP wrapping
147
+ sapper-ai quarantine list List quarantined files
148
+ sapper-ai quarantine restore <id> [--force] Restore quarantined file by id
91
149
  sapper-ai init Interactive setup wizard
92
150
  sapper-ai dashboard Launch web dashboard
93
151
  sapper-ai --help Show this help
@@ -97,17 +155,31 @@ Learn more: https://github.com/sapper-ai/sapperai
97
155
  }
98
156
  function parseScanArgs(argv) {
99
157
  const targets = [];
158
+ let policyPath;
100
159
  let fix = false;
101
160
  let deep = false;
102
161
  let system = false;
103
162
  let ai = false;
104
163
  let noSave = false;
105
164
  let noOpen = false;
106
- for (const arg of argv) {
165
+ let noColor = false;
166
+ let noPrompt = false;
167
+ let harden = false;
168
+ for (let index = 0; index < argv.length; index += 1) {
169
+ const arg = argv[index];
170
+ const nextArg = argv[index + 1];
107
171
  if (arg === '--fix') {
108
172
  fix = true;
109
173
  continue;
110
174
  }
175
+ if (arg === '--policy') {
176
+ if (!nextArg || nextArg.startsWith('-')) {
177
+ return null;
178
+ }
179
+ policyPath = nextArg;
180
+ index += 1;
181
+ continue;
182
+ }
111
183
  if (arg === '--deep') {
112
184
  deep = true;
113
185
  continue;
@@ -120,6 +192,18 @@ function parseScanArgs(argv) {
120
192
  ai = true;
121
193
  continue;
122
194
  }
195
+ if (arg === '--no-color') {
196
+ noColor = true;
197
+ continue;
198
+ }
199
+ if (arg === '--no-prompt') {
200
+ noPrompt = true;
201
+ continue;
202
+ }
203
+ if (arg === '--harden') {
204
+ harden = true;
205
+ continue;
206
+ }
123
207
  if (arg === '--no-open') {
124
208
  noOpen = true;
125
209
  continue;
@@ -133,7 +217,227 @@ function parseScanArgs(argv) {
133
217
  }
134
218
  targets.push(arg);
135
219
  }
136
- return { targets, fix, deep, system, ai, noSave, noOpen };
220
+ return { targets, policyPath, fix, deep, system, ai, noSave, noOpen, noColor, noPrompt, harden };
221
+ }
222
+ function parseHardenArgs(argv) {
223
+ let apply = false;
224
+ let includeSystem = false;
225
+ let yes = false;
226
+ let noColor = false;
227
+ let noPrompt = false;
228
+ let force = false;
229
+ let workflowVersion;
230
+ let mcpVersion;
231
+ for (let index = 0; index < argv.length; index += 1) {
232
+ const arg = argv[index];
233
+ const nextArg = argv[index + 1];
234
+ if (arg === '--dry-run') {
235
+ apply = false;
236
+ continue;
237
+ }
238
+ if (arg === '--apply') {
239
+ apply = true;
240
+ continue;
241
+ }
242
+ if (arg === '--include-system') {
243
+ includeSystem = true;
244
+ continue;
245
+ }
246
+ if (arg === '--yes') {
247
+ yes = true;
248
+ continue;
249
+ }
250
+ if (arg === '--no-color') {
251
+ noColor = true;
252
+ continue;
253
+ }
254
+ if (arg === '--no-prompt') {
255
+ noPrompt = true;
256
+ continue;
257
+ }
258
+ if (arg === '--force') {
259
+ force = true;
260
+ continue;
261
+ }
262
+ if (arg === '--workflow-version') {
263
+ if (!nextArg || nextArg.startsWith('-'))
264
+ return null;
265
+ workflowVersion = nextArg;
266
+ index += 1;
267
+ continue;
268
+ }
269
+ if (arg === '--mcp-version') {
270
+ if (!nextArg || nextArg.startsWith('-'))
271
+ return null;
272
+ mcpVersion = nextArg;
273
+ index += 1;
274
+ continue;
275
+ }
276
+ return null;
277
+ }
278
+ return {
279
+ apply,
280
+ includeSystem,
281
+ yes,
282
+ noColor,
283
+ noPrompt,
284
+ force,
285
+ workflowVersion,
286
+ mcpVersion,
287
+ };
288
+ }
289
+ function parseMcpArgs(argv) {
290
+ const subcommand = argv[0];
291
+ const rest = argv.slice(1);
292
+ if (!subcommand)
293
+ return null;
294
+ const defaultConfigPath = (0, node_path_1.join)((0, node_os_1.homedir)(), '.config', 'claude-code', 'config.json');
295
+ let configPath = defaultConfigPath;
296
+ let format = 'jsonc';
297
+ let dryRun = false;
298
+ let mcpVersion;
299
+ for (let index = 0; index < rest.length; index += 1) {
300
+ const arg = rest[index];
301
+ const nextArg = rest[index + 1];
302
+ if (arg === '--config') {
303
+ if (!nextArg || nextArg.startsWith('-'))
304
+ return null;
305
+ configPath = nextArg;
306
+ format = 'json';
307
+ index += 1;
308
+ continue;
309
+ }
310
+ if (arg === '--jsonc') {
311
+ format = 'jsonc';
312
+ continue;
313
+ }
314
+ if (arg === '--dry-run') {
315
+ dryRun = true;
316
+ continue;
317
+ }
318
+ if (arg === '--mcp-version') {
319
+ if (!nextArg || nextArg.startsWith('-'))
320
+ return null;
321
+ mcpVersion = nextArg;
322
+ index += 1;
323
+ continue;
324
+ }
325
+ return null;
326
+ }
327
+ if (subcommand === 'wrap-config') {
328
+ return { command: 'mcp_wrap_config', configPath, format, dryRun, mcpVersion };
329
+ }
330
+ if (subcommand === 'unwrap-config') {
331
+ return { command: 'mcp_unwrap_config', configPath, format, dryRun };
332
+ }
333
+ return null;
334
+ }
335
+ async function runMcpCommand(args) {
336
+ if (args.command === 'mcp_unwrap_config') {
337
+ const result = await (0, wrapConfig_1.unwrapMcpConfigFile)({
338
+ filePath: args.configPath,
339
+ format: args.format,
340
+ dryRun: args.dryRun,
341
+ });
342
+ if (!result.changed) {
343
+ console.log('No changes needed.');
344
+ return 0;
345
+ }
346
+ if (result.restoredFromBackupPath) {
347
+ if (args.dryRun) {
348
+ console.log(`Config parse failed; would restore from backup: ${result.restoredFromBackupPath}`);
349
+ return 0;
350
+ }
351
+ console.log(`Config parse failed; restored from backup: ${result.restoredFromBackupPath}`);
352
+ if (result.backupPath)
353
+ console.log(`Backup: ${result.backupPath}`);
354
+ return 0;
355
+ }
356
+ if (args.dryRun) {
357
+ console.log(`Would unwrap ${result.changedServers.length} server(s): ${result.changedServers.join(', ')}`);
358
+ return 0;
359
+ }
360
+ console.log(`Unwrapped ${result.changedServers.length} server(s): ${result.changedServers.join(', ')}`);
361
+ if (result.backupPath)
362
+ console.log(`Backup: ${result.backupPath}`);
363
+ return 0;
364
+ }
365
+ if (!(0, wrapConfig_1.checkNpxAvailable)()) {
366
+ console.error("npx is not available on PATH. Install Node.js/npm and retry.");
367
+ return 1;
368
+ }
369
+ const envVersion = process.env.SAPPERAI_MCP_VERSION?.trim();
370
+ const installedVersion = (0, wrapConfig_1.resolveInstalledPackageVersion)('@sapper-ai/mcp');
371
+ const mcpVersion = (args.mcpVersion ?? envVersion ?? installedVersion ?? '').trim();
372
+ if (!mcpVersion) {
373
+ console.error("Missing MCP version. Provide '--mcp-version <semver>' or install @sapper-ai/mcp.");
374
+ return 1;
375
+ }
376
+ const result = await (0, wrapConfig_1.wrapMcpConfigFile)({
377
+ filePath: args.configPath,
378
+ mcpVersion,
379
+ format: args.format,
380
+ dryRun: args.dryRun,
381
+ });
382
+ if (!result.changed) {
383
+ console.log('No changes needed.');
384
+ return 0;
385
+ }
386
+ if (args.dryRun) {
387
+ console.log(`Would wrap ${result.changedServers.length} server(s): ${result.changedServers.join(', ')}`);
388
+ return 0;
389
+ }
390
+ console.log(`Wrapped ${result.changedServers.length} server(s): ${result.changedServers.join(', ')}`);
391
+ if (result.backupPath)
392
+ console.log(`Backup: ${result.backupPath}`);
393
+ return 0;
394
+ }
395
+ function parseQuarantineArgs(argv) {
396
+ const subcommand = argv[0];
397
+ const rest = argv.slice(1);
398
+ if (!subcommand)
399
+ return null;
400
+ let quarantineDir;
401
+ let force = false;
402
+ if (subcommand === 'list') {
403
+ for (let index = 0; index < rest.length; index += 1) {
404
+ const arg = rest[index];
405
+ const nextArg = rest[index + 1];
406
+ if (arg === '--quarantine-dir') {
407
+ if (!nextArg || nextArg.startsWith('-'))
408
+ return null;
409
+ quarantineDir = nextArg;
410
+ index += 1;
411
+ continue;
412
+ }
413
+ return null;
414
+ }
415
+ return { command: 'quarantine_list', quarantineDir };
416
+ }
417
+ if (subcommand === 'restore') {
418
+ const id = rest[0];
419
+ if (!id)
420
+ return null;
421
+ const tail = rest.slice(1);
422
+ for (let index = 0; index < tail.length; index += 1) {
423
+ const arg = tail[index];
424
+ const nextArg = tail[index + 1];
425
+ if (arg === '--force') {
426
+ force = true;
427
+ continue;
428
+ }
429
+ if (arg === '--quarantine-dir') {
430
+ if (!nextArg || nextArg.startsWith('-'))
431
+ return null;
432
+ quarantineDir = nextArg;
433
+ index += 1;
434
+ continue;
435
+ }
436
+ return null;
437
+ }
438
+ return { command: 'quarantine_restore', id, quarantineDir, force };
439
+ }
440
+ return null;
137
441
  }
138
442
  function displayPath(path) {
139
443
  const home = (0, node_os_1.homedir)();
@@ -162,7 +466,7 @@ async function promptScanDepth() {
162
466
  choices: [
163
467
  { name: 'Quick scan (rules only) Fast regex pattern matching', value: false },
164
468
  {
165
- name: 'Deep scan (rules + AI) AI-powered analysis (requires OPENAI_API_KEY)',
469
+ name: 'Deep scan (rules + AI) AI-powered analysis (OpenAI)',
166
470
  value: true,
167
471
  },
168
472
  ],
@@ -176,6 +480,9 @@ async function resolveScanOptions(args) {
176
480
  fix: args.fix,
177
481
  noSave: args.noSave,
178
482
  noOpen: args.noOpen,
483
+ noColor: args.noColor,
484
+ noPrompt: args.noPrompt,
485
+ policyPath: args.policyPath,
179
486
  };
180
487
  if (args.system) {
181
488
  if (args.targets.length > 0) {
@@ -204,8 +511,8 @@ async function resolveScanOptions(args) {
204
511
  if (args.deep) {
205
512
  return { ...common, targets: [cwd], deep: true, ai: args.ai, scopeLabel: 'Current + subdirectories' };
206
513
  }
207
- if (process.stdout.isTTY !== true) {
208
- return { ...common, targets: [cwd], deep: true, ai: false, scopeLabel: 'Current + subdirectories' };
514
+ if (args.noPrompt === true || process.stdout.isTTY !== true) {
515
+ return { ...common, targets: [cwd], deep: true, ai: args.ai, scopeLabel: 'Current + subdirectories' };
209
516
  }
210
517
  const scope = await promptScanScope(cwd);
211
518
  const ai = args.ai ? true : await promptScanDepth();
@@ -285,9 +592,9 @@ async function runInitWizard() {
285
592
  '# Generated by: sapper-ai init',
286
593
  '# Docs: https://github.com/sapper-ai/sapperai',
287
594
  '',
288
- ...buildPolicyYaml(selectedPreset, auditLogPath),
289
595
  ];
290
- (0, node_fs_1.writeFileSync)(outputPath, `${lines.join('\n')}\n`, 'utf8');
596
+ const body = (0, policyYaml_1.renderPolicyYaml)(selectedPreset, auditLogPath);
597
+ (0, node_fs_1.writeFileSync)(outputPath, `${lines.join('\n')}\n${body}`, 'utf8');
291
598
  console.log(`\n Created ${outputPath}\n`);
292
599
  console.log(' Quick start:\n');
293
600
  console.log(" import { createGuard } from 'sapper-ai'");
@@ -296,29 +603,6 @@ async function runInitWizard() {
296
603
  console.log();
297
604
  rl.close();
298
605
  }
299
- function buildPolicyYaml(preset, auditLogPath) {
300
- const p = presets_1.presets[preset].policy;
301
- const lines = [];
302
- lines.push(`mode: ${p.mode}`);
303
- lines.push(`defaultAction: ${p.defaultAction}`);
304
- lines.push(`failOpen: ${p.failOpen}`);
305
- lines.push('');
306
- lines.push('detectors:');
307
- const detectors = p.detectors ?? ['rules'];
308
- for (const d of detectors) {
309
- lines.push(` - ${d}`);
310
- }
311
- lines.push('');
312
- lines.push('thresholds:');
313
- const thresholds = p.thresholds ?? {};
314
- lines.push(` riskThreshold: ${thresholds.riskThreshold ?? 0.7}`);
315
- lines.push(` blockMinConfidence: ${thresholds.blockMinConfidence ?? 0.5}`);
316
- if (auditLogPath) {
317
- lines.push('');
318
- lines.push(`auditLogPath: ${auditLogPath}`);
319
- }
320
- return lines;
321
- }
322
606
  function isDirectExecution(argv) {
323
607
  const entry = argv[1];
324
608
  if (!entry) {
@@ -0,0 +1,28 @@
1
+ type HardenScope = 'project' | 'system';
2
+ export interface HardenPlanSummary {
3
+ repoRoot: string;
4
+ notes: string[];
5
+ actions: Array<{
6
+ id: string;
7
+ scope: HardenScope;
8
+ title: string;
9
+ paths: string[];
10
+ }>;
11
+ }
12
+ export interface HardenOptions {
13
+ cwd?: string;
14
+ env?: NodeJS.ProcessEnv;
15
+ includeSystem?: boolean;
16
+ dryRun?: boolean;
17
+ apply?: boolean;
18
+ yes?: boolean;
19
+ noPrompt?: boolean;
20
+ force?: boolean;
21
+ workflowVersion?: string;
22
+ mcpVersion?: string;
23
+ write?: (text: string) => void;
24
+ }
25
+ export declare function getHardenPlanSummary(options?: HardenOptions): Promise<HardenPlanSummary>;
26
+ export declare function runHarden(options?: HardenOptions): Promise<number>;
27
+ export {};
28
+ //# sourceMappingURL=harden.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harden.d.ts","sourceRoot":"","sources":["../src/harden.ts"],"names":[],"mappings":"AAeA,KAAK,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAA;AAUvC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,EAAE,KAAK,CAAC;QACb,EAAE,EAAE,MAAM,CAAA;QACV,KAAK,EAAE,WAAW,CAAA;QAClB,KAAK,EAAE,MAAM,CAAA;QACb,KAAK,EAAE,MAAM,EAAE,CAAA;KAChB,CAAC,CAAA;CACH;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAC/B;AAoLD,wBAAsB,oBAAoB,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAOlG;AAED,wBAAsB,SAAS,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgG5E"}