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.
- package/dist/auth.d.ts +11 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +102 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +316 -32
- package/dist/harden.d.ts +28 -0
- package/dist/harden.d.ts.map +1 -0
- package/dist/harden.js +309 -0
- package/dist/mcp/jsonc.d.ts +3 -0
- package/dist/mcp/jsonc.d.ts.map +1 -0
- package/dist/mcp/jsonc.js +119 -0
- package/dist/mcp/wrapConfig.d.ts +22 -0
- package/dist/mcp/wrapConfig.d.ts.map +1 -0
- package/dist/mcp/wrapConfig.js +192 -0
- package/dist/policyYaml.d.ts +3 -0
- package/dist/policyYaml.d.ts.map +1 -0
- package/dist/policyYaml.js +27 -0
- package/dist/postinstall.d.ts.map +1 -1
- package/dist/postinstall.js +11 -2
- package/dist/quarantine.d.ts +13 -0
- package/dist/quarantine.d.ts.map +1 -0
- package/dist/quarantine.js +22 -0
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +1061 -59
- package/dist/scan.d.ts +15 -0
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +179 -178
- package/dist/utils/env.d.ts +3 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +25 -0
- package/dist/utils/format.d.ts +22 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +97 -0
- package/dist/utils/fs.d.ts +7 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +47 -0
- package/dist/utils/repoRoot.d.ts +2 -0
- package/dist/utils/repoRoot.d.ts.map +1 -0
- package/dist/utils/repoRoot.js +20 -0
- package/dist/utils/semver.d.ts +2 -0
- package/dist/utils/semver.d.ts.map +1 -0
- package/dist/utils/semver.js +7 -0
- 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":";
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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:
|
|
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,
|
|
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) {
|
package/dist/harden.d.ts
ADDED
|
@@ -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"}
|