@westbayberry/dg 1.0.53 → 1.0.56

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 (64) hide show
  1. package/README.md +5 -1
  2. package/dist/index.mjs +249 -114
  3. package/dist/packages/cli/src/alt-screen.js +36 -0
  4. package/dist/packages/cli/src/api.js +322 -0
  5. package/dist/packages/cli/src/auth.js +218 -0
  6. package/dist/packages/cli/src/bin.js +386 -0
  7. package/dist/packages/cli/src/config.js +228 -0
  8. package/dist/packages/cli/src/discover.js +126 -0
  9. package/dist/packages/cli/src/first-run.js +135 -0
  10. package/dist/packages/cli/src/hook.js +360 -0
  11. package/dist/packages/cli/src/lockfile.js +303 -0
  12. package/dist/packages/cli/src/npm-wrapper.js +218 -0
  13. package/dist/packages/cli/src/pip-wrapper.js +273 -0
  14. package/dist/packages/cli/src/sanitize.js +38 -0
  15. package/dist/packages/cli/src/scan-core.js +144 -0
  16. package/dist/packages/cli/src/setup-status.js +46 -0
  17. package/dist/packages/cli/src/static-output.js +625 -0
  18. package/dist/packages/cli/src/telemetry.js +141 -0
  19. package/dist/packages/cli/src/ui/App.js +137 -0
  20. package/dist/packages/cli/src/ui/InitApp.js +391 -0
  21. package/dist/packages/cli/src/ui/LoginApp.js +51 -0
  22. package/dist/packages/cli/src/ui/NpmWrapperApp.js +73 -0
  23. package/dist/packages/cli/src/ui/PipWrapperApp.js +72 -0
  24. package/dist/packages/cli/src/ui/components/ConfirmPrompt.js +24 -0
  25. package/dist/packages/cli/src/ui/components/DemoScanAnimation.js +26 -0
  26. package/dist/packages/cli/src/ui/components/DurationLine.js +7 -0
  27. package/dist/packages/cli/src/ui/components/ErrorView.js +30 -0
  28. package/dist/packages/cli/src/ui/components/FileSavePrompt.js +210 -0
  29. package/dist/packages/cli/src/ui/components/InteractiveResultsView.js +557 -0
  30. package/dist/packages/cli/src/ui/components/Mascot.js +33 -0
  31. package/dist/packages/cli/src/ui/components/ProgressBar.js +51 -0
  32. package/dist/packages/cli/src/ui/components/ProgressDots.js +35 -0
  33. package/dist/packages/cli/src/ui/components/ProjectSelector.js +60 -0
  34. package/dist/packages/cli/src/ui/components/ResultsView.js +105 -0
  35. package/dist/packages/cli/src/ui/components/ScanResultCard.js +54 -0
  36. package/dist/packages/cli/src/ui/components/ScoreHeader.js +142 -0
  37. package/dist/packages/cli/src/ui/components/SetupBanner.js +17 -0
  38. package/dist/packages/cli/src/ui/components/Spinner.js +11 -0
  39. package/dist/packages/cli/src/ui/hooks/useExpandAnimation.js +44 -0
  40. package/dist/packages/cli/src/ui/hooks/useInit.js +341 -0
  41. package/dist/packages/cli/src/ui/hooks/useLogin.js +121 -0
  42. package/dist/packages/cli/src/ui/hooks/useNpmWrapper.js +192 -0
  43. package/dist/packages/cli/src/ui/hooks/usePipWrapper.js +195 -0
  44. package/dist/packages/cli/src/ui/hooks/useScan.js +202 -0
  45. package/dist/packages/cli/src/ui/hooks/useTerminalSize.js +29 -0
  46. package/dist/packages/cli/src/update-check.js +152 -0
  47. package/dist/packages/cli/src/wizard-demo-data.js +63 -0
  48. package/dist/src/ecosystem.js +2 -0
  49. package/dist/src/lockfile/diff.js +38 -0
  50. package/dist/src/lockfile/parse_package_json.js +41 -0
  51. package/dist/src/lockfile/parse_package_lock.js +55 -0
  52. package/dist/src/lockfile/parse_pipfile_lock.js +69 -0
  53. package/dist/src/lockfile/parse_pnpm_lock.js +62 -0
  54. package/dist/src/lockfile/parse_poetry_lock.js +71 -0
  55. package/dist/src/lockfile/parse_requirements.js +83 -0
  56. package/dist/src/lockfile/parse_yarn_lock.js +66 -0
  57. package/dist/src/logger.js +21 -0
  58. package/dist/src/npm/h2pool.js +161 -0
  59. package/dist/src/npm/registry.js +299 -0
  60. package/dist/src/npm/tarball.js +274 -0
  61. package/dist/src/pypi/registry.js +299 -0
  62. package/dist/src/pypi/tarball.js +361 -0
  63. package/dist/src/types.js +2 -0
  64. package/package.json +6 -3
@@ -0,0 +1,625 @@
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.renderResultStatic = renderResultStatic;
40
+ exports.runStatic = runStatic;
41
+ exports.runStaticNpm = runStaticNpm;
42
+ exports.runStaticPip = runStaticPip;
43
+ exports.runStaticLogin = runStaticLogin;
44
+ const chalk_1 = __importDefault(require("chalk"));
45
+ const node_fs_1 = require("node:fs");
46
+ const node_path_1 = require("node:path");
47
+ const api_1 = require("./api");
48
+ const auth_1 = require("./auth");
49
+ const lockfile_1 = require("./lockfile");
50
+ const npm_wrapper_1 = require("./npm-wrapper");
51
+ const pip_wrapper_1 = require("./pip-wrapper");
52
+ // ── JSON output handling ────────────────────────────────────
53
+ function writeJsonFile(filepath, json) {
54
+ const resolved = (0, node_path_1.resolve)(filepath);
55
+ try {
56
+ (0, node_fs_1.writeFileSync)(resolved, json + "\n");
57
+ process.stderr.write(chalk_1.default.green(` \u2713 Saved to ${resolved}\n\n`));
58
+ }
59
+ catch (err) {
60
+ const msg = err instanceof Error ? err.message : String(err);
61
+ process.stderr.write(chalk_1.default.red(` Error saving file: ${msg}\n\n`));
62
+ }
63
+ }
64
+ async function handleJsonOutput(result, config) {
65
+ if (!config.json)
66
+ return false;
67
+ const json = JSON.stringify(result, null, 2);
68
+ // Explicit --output: save directly (works in any context)
69
+ if (config.outputFile) {
70
+ writeJsonFile(config.outputFile, json);
71
+ return true;
72
+ }
73
+ // Non-TTY (piped): raw JSON to stdout
74
+ if (!process.stdout.isTTY) {
75
+ process.stdout.write(json + "\n");
76
+ return true;
77
+ }
78
+ // TTY interactive: launch file save TUI
79
+ const localDate = new Date().toLocaleDateString("sv-SE");
80
+ const defaultName = `dg-scan-${localDate}`;
81
+ const { render } = await Promise.resolve().then(() => __importStar(require("ink")));
82
+ const React = await Promise.resolve().then(() => __importStar(require("react")));
83
+ const { FileSavePrompt } = await Promise.resolve().then(() => __importStar(require("./ui/components/FileSavePrompt")));
84
+ await new Promise((resolve) => {
85
+ const inkInstance = render(React.createElement(FileSavePrompt, {
86
+ defaultName,
87
+ directory: process.cwd(),
88
+ score: result.score,
89
+ action: result.action,
90
+ packageCount: result.packages.length,
91
+ json,
92
+ onSaved: (filepath) => {
93
+ inkInstance.unmount();
94
+ process.stderr.write(chalk_1.default.green(` \u2713 Saved to ${filepath}\n\n`));
95
+ resolve();
96
+ },
97
+ onCancel: () => {
98
+ inkInstance.unmount();
99
+ process.stderr.write(chalk_1.default.dim(" Cancelled.\n\n"));
100
+ resolve();
101
+ },
102
+ }));
103
+ });
104
+ return true;
105
+ }
106
+ // ── Helpers ─────────────────────────────────────────────────
107
+ function printTrialBanner(result) {
108
+ if (result.trialScansRemaining === undefined)
109
+ return;
110
+ process.stderr.write(chalk_1.default.dim(` Free tier \u00b7 Run \`dg login\` for higher scan limits.\n`));
111
+ }
112
+ function handleTrialExhausted(error, jsonMode = false) {
113
+ if (error instanceof api_1.TrialExhaustedError) {
114
+ // Check if user has stored credentials — if so, their key is likely invalid
115
+ let hasKey = false;
116
+ try {
117
+ hasKey = !!(0, auth_1.getStoredApiKey)();
118
+ }
119
+ catch { /* ignore — best-effort */ }
120
+ const message = hasKey
121
+ ? "Your API key may be invalid or expired. Run `dg logout` then `dg login` to re-authenticate."
122
+ : "Monthly scan limit reached. Run `dg login` to create a free account for higher limits.";
123
+ if (jsonMode) {
124
+ process.stdout.write(JSON.stringify({
125
+ error: true,
126
+ code: "trial_exhausted",
127
+ message,
128
+ scansUsed: error.scansUsed,
129
+ maxScans: error.maxScans,
130
+ }, null, 2) + "\n");
131
+ }
132
+ else {
133
+ if (hasKey) {
134
+ process.stderr.write(chalk_1.default.yellow(`\n ${message}\n\n`));
135
+ }
136
+ else {
137
+ process.stderr.write(chalk_1.default.yellow("\n Monthly scan limit reached (1,000 scans/month on free tier).\n") +
138
+ chalk_1.default.white(" Run `dg login` for higher limits, or upgrade at westbayberry.com/pricing\n\n"));
139
+ }
140
+ }
141
+ process.exit(1);
142
+ return true;
143
+ }
144
+ return false;
145
+ }
146
+ function actionColor(action) {
147
+ if (action === "block")
148
+ return chalk_1.default.red;
149
+ if (action === "warn")
150
+ return chalk_1.default.yellow;
151
+ return chalk_1.default.green;
152
+ }
153
+ function pad(s, len) {
154
+ return s + " ".repeat(Math.max(0, len - s.length));
155
+ }
156
+ function truncate(s, max) {
157
+ if (s.length <= max)
158
+ return s;
159
+ return s.slice(0, max - 1) + "\u2026";
160
+ }
161
+ function groupPackages(packages) {
162
+ const map = new Map();
163
+ for (const pkg of packages) {
164
+ const fingerprint = pkg.findings.length === 0
165
+ ? `__clean_${pkg.score}`
166
+ : pkg.findings
167
+ .map((f) => `${f.category ?? ""}:${f.severity}`)
168
+ .sort()
169
+ .join("|") + `|score:${pkg.score}`;
170
+ const group = map.get(fingerprint) ?? [];
171
+ group.push(pkg);
172
+ map.set(fingerprint, group);
173
+ }
174
+ return [...map.values()]
175
+ .map((pkgs) => ({ packages: pkgs, key: pkgs[0].name }))
176
+ .sort((a, b) => b.packages[0].score - a.packages[0].score);
177
+ }
178
+ function actionBadge(score) {
179
+ if (score >= 70)
180
+ return { label: "Block", color: chalk_1.default.red };
181
+ if (score >= 60)
182
+ return { label: "Warn", color: chalk_1.default.yellow };
183
+ return { label: "Pass", color: chalk_1.default.green };
184
+ }
185
+ function renderResultStatic(result, _config) {
186
+ const lines = [];
187
+ const actionStr = result.action.toUpperCase();
188
+ const colorAction = actionColor(result.action);
189
+ // Header
190
+ lines.push("");
191
+ lines.push(` ${chalk_1.default.bold("Dependency Guardian")}`);
192
+ lines.push(` Score: ${chalk_1.default.bold(String(result.score))}${" ".repeat(Math.max(1, 50 - String(result.score).length))}${colorAction(actionStr)}`);
193
+ lines.push("");
194
+ // Summary
195
+ const flagged = result.packages.filter((p) => p.score > 0);
196
+ const clean = result.packages.filter((p) => p.score === 0);
197
+ const total = result.packages.length;
198
+ if (flagged.length > 0) {
199
+ lines.push(` ${total} package${total !== 1 ? "s" : ""} scanned ${chalk_1.default.dim("\u2502")} ${chalk_1.default.yellow(`${flagged.length} flagged`)} \u2502 ${chalk_1.default.green(`${clean.length} clean`)}`);
200
+ }
201
+ else {
202
+ lines.push(` ${total} package${total !== 1 ? "s" : ""} scanned ${chalk_1.default.dim("\u2502")} ${chalk_1.default.green("all clean")}`);
203
+ }
204
+ // License summary
205
+ const licensedPkgs = result.packages.filter((p) => p.license);
206
+ const licenseIssues = licensedPkgs.filter((p) => p.license && p.license.riskCategory !== "permissive");
207
+ if (licenseIssues.length > 0) {
208
+ lines.push(` ${chalk_1.default.dim("Licenses:")} ${chalk_1.default.yellow(`${licenseIssues.length} need review`)} \u2502 ${chalk_1.default.green(`${licensedPkgs.length - licenseIssues.length} permissive`)}`);
209
+ }
210
+ lines.push("");
211
+ if (total === 0) {
212
+ lines.push(" No packages to scan.");
213
+ lines.push("");
214
+ return lines.join("\n");
215
+ }
216
+ // Group flagged packages
217
+ const groups = groupPackages(flagged);
218
+ // Flagged packages table (only non-zero scores)
219
+ if (flagged.length > 0) {
220
+ lines.push(` ${chalk_1.default.bold("Flagged Packages")}`);
221
+ lines.push(` ${chalk_1.default.dim("\u2500".repeat(60))}`);
222
+ for (const group of groups) {
223
+ const rep = group.packages[0];
224
+ const { label, color } = actionBadge(rep.score);
225
+ const names = group.packages.length === 1
226
+ ? rep.name
227
+ : group.packages.length <= 3
228
+ ? group.packages.map((p) => p.name).join(", ")
229
+ : `${group.packages[0].name} + ${group.packages.length - 1} similar`;
230
+ const lcInfo = rep.license;
231
+ const lcStr = lcInfo ? truncate(lcInfo.spdx ?? lcInfo.raw ?? "", 14) : "";
232
+ const lcColor = !lcInfo ? chalk_1.default.dim
233
+ : lcInfo.riskCategory === "permissive" ? chalk_1.default.green
234
+ : (lcInfo.riskCategory === "no-license" || lcInfo.riskCategory === "unlicensed" || lcInfo.riskCategory === "network-copyleft") ? chalk_1.default.red
235
+ : chalk_1.default.yellow;
236
+ lines.push(` ${color(pad(label, 7))}${chalk_1.default.bold(pad(truncate(names, 36), 38))}${lcColor(pad(lcStr, 16))}${chalk_1.default.dim(`score ${rep.score}`)}`);
237
+ }
238
+ lines.push("");
239
+ }
240
+ // Clean packages (collapsed to single line)
241
+ if (clean.length > 0) {
242
+ lines.push(` ${chalk_1.default.green("\u2713")} ${chalk_1.default.dim(`${clean.length} package${clean.length !== 1 ? "s" : ""} passed with score 0`)}`);
243
+ lines.push("");
244
+ }
245
+ // Duration
246
+ if (result.durationMs) {
247
+ lines.push(` ${chalk_1.default.dim(`Completed in ${(result.durationMs / 1000).toFixed(1)}s`)}`);
248
+ lines.push("");
249
+ }
250
+ return lines.join("\n");
251
+ }
252
+ async function runStatic(config) {
253
+ const dbg = (msg) => {
254
+ if (config.debug)
255
+ process.stderr.write(chalk_1.default.dim(` [debug] ${msg}\n`));
256
+ };
257
+ if (config.mode === "off") {
258
+ process.stderr.write(chalk_1.default.dim(" Dependency Guardian: mode is off — skipping.\n"));
259
+ process.exit(0);
260
+ }
261
+ dbg(`mode=${config.mode} max=${config.maxPackages}`);
262
+ dbg(`api=${config.apiUrl}`);
263
+ // Discover packages
264
+ process.stderr.write(chalk_1.default.dim(" Discovering package changes...\n"));
265
+ let discovery = (0, lockfile_1.discoverChanges)(process.cwd(), config);
266
+ dbg(`discovery method: ${discovery.method}`);
267
+ if (discovery.pythonPackages.length > 0) {
268
+ dbg(`python packages: ${discovery.pythonPackages.length}`);
269
+ }
270
+ if (discovery.packages.length === 0 && discovery.pythonPackages.length === 0) {
271
+ // Monorepo: auto-discover and scan all subprojects
272
+ const { discoverProjects } = await Promise.resolve().then(() => __importStar(require("./discover")));
273
+ const subProjects = discoverProjects(process.cwd());
274
+ if (subProjects.length === 0) {
275
+ process.stderr.write(chalk_1.default.dim(" No package changes detected.\n"));
276
+ process.exit(0);
277
+ }
278
+ const totalPkgs = subProjects.reduce((s, p) => s + p.packageCount, 0);
279
+ process.stderr.write(chalk_1.default.dim(` Found ${subProjects.length} projects (${totalPkgs} packages total):\n`));
280
+ for (const proj of subProjects) {
281
+ process.stderr.write(chalk_1.default.dim(` ${proj.ecosystem} ${proj.relativePath} (${proj.packageCount})\n`));
282
+ }
283
+ process.stderr.write("\n");
284
+ // Scan each project and merge results
285
+ const allNpmPkgs = [];
286
+ const allPyPkgs = [];
287
+ const seen = new Set();
288
+ for (const proj of subProjects) {
289
+ try {
290
+ const projConfig = { ...config, workspace: proj.relativePath };
291
+ const projDisc = (0, lockfile_1.discoverChanges)(process.cwd(), projConfig);
292
+ for (const pkg of projDisc.packages) {
293
+ const key = `${pkg.name}@${pkg.version}`;
294
+ if (!seen.has(key)) {
295
+ seen.add(key);
296
+ allNpmPkgs.push(pkg);
297
+ }
298
+ }
299
+ for (const pkg of projDisc.pythonPackages) {
300
+ const key = `${pkg.name}@${pkg.version}`;
301
+ if (!seen.has(key)) {
302
+ seen.add(key);
303
+ allPyPkgs.push(pkg);
304
+ }
305
+ }
306
+ }
307
+ catch {
308
+ // Skip projects that fail to parse (e.g. corrupted lockfiles)
309
+ }
310
+ }
311
+ if (allNpmPkgs.length === 0 && allPyPkgs.length === 0) {
312
+ process.stderr.write(chalk_1.default.dim(" No package changes detected across projects.\n"));
313
+ process.exit(0);
314
+ }
315
+ // Replace discovery with merged results and fall through to normal scan path
316
+ discovery = {
317
+ packages: allNpmPkgs,
318
+ pythonPackages: allPyPkgs,
319
+ method: "scan-all",
320
+ skipped: [],
321
+ };
322
+ }
323
+ const packages = discovery.packages;
324
+ const totalToScan = packages.length + discovery.pythonPackages.length;
325
+ process.stderr.write(chalk_1.default.dim(` Scanning ${totalToScan} package${totalToScan !== 1 ? "s" : ""}${discovery.pythonPackages.length > 0 ? ` (${packages.length} npm + ${discovery.pythonPackages.length} Python)` : ""} (${discovery.method})...\n`));
326
+ if (discovery.skipped.length > 0 && config.maxPackages < 10000) {
327
+ process.stderr.write(chalk_1.default.dim(` Note: ${discovery.skipped.length} package(s) not scanned (--max-packages=${config.maxPackages})\n`));
328
+ }
329
+ dbg(`packages to scan: ${packages
330
+ .map((p) => `${p.name}@${p.version}`)
331
+ .slice(0, 10)
332
+ .join(", ")}${packages.length > 10 ? ` (+${packages.length - 10} more)` : ""}`);
333
+ // Call Detection API (npm)
334
+ let result;
335
+ try {
336
+ const startMs = Date.now();
337
+ if (packages.length > 0) {
338
+ result = await (0, api_1.callAnalyzeAPI)(packages, config, (done, total) => {
339
+ process.stderr.write(chalk_1.default.dim(` Analyzed ${done}/${total}...\n`));
340
+ });
341
+ }
342
+ else {
343
+ result = { score: 0, action: "pass", packages: [], safeVersions: {}, durationMs: 0 };
344
+ }
345
+ // Scan Python packages if found
346
+ if (discovery.pythonPackages.length > 0) {
347
+ process.stderr.write(chalk_1.default.dim(` Scanning ${discovery.pythonPackages.length} Python package${discovery.pythonPackages.length !== 1 ? "s" : ""}...\n`));
348
+ const pyResult = await (0, api_1.callPyPIAnalyzeAPI)(discovery.pythonPackages, config);
349
+ // Merge Python results into npm results
350
+ result.packages.push(...pyResult.packages);
351
+ result.score = Math.max(result.score, pyResult.score);
352
+ if (pyResult.action === "block" || (pyResult.action === "warn" && result.action === "pass")) {
353
+ result.action = pyResult.action;
354
+ }
355
+ if (pyResult.trialScansRemaining !== undefined) {
356
+ result.trialScansRemaining = pyResult.trialScansRemaining;
357
+ }
358
+ }
359
+ dbg(`API responded in ${Date.now() - startMs}ms, action=${result.action}, score=${result.score}`);
360
+ }
361
+ catch (error) {
362
+ if (handleTrialExhausted(error, config.json))
363
+ return;
364
+ throw error;
365
+ }
366
+ // Handle JSON output (interactive prompt, pipe, or --output)
367
+ const jsonHandled = await handleJsonOutput(result, config);
368
+ if (jsonHandled) {
369
+ printTrialBanner(result);
370
+ if (result.action === "block" && config.mode === "block")
371
+ process.exit(2);
372
+ else if (result.action === "block" || result.action === "warn")
373
+ process.exit(1);
374
+ process.exit(0);
375
+ }
376
+ // Render human-readable output
377
+ const output = renderResultStatic(result, config);
378
+ process.stdout.write(output + "\n");
379
+ printTrialBanner(result);
380
+ // Exit code
381
+ if (result.action === "block" && config.mode === "block") {
382
+ process.exit(2);
383
+ }
384
+ else if (result.action === "block" || result.action === "warn") {
385
+ process.exit(1);
386
+ }
387
+ process.exit(0);
388
+ }
389
+ async function runStaticNpm(npmArgs, config) {
390
+ const parsed = (0, npm_wrapper_1.parseNpmArgs)(npmArgs);
391
+ // Non-install commands: pass through directly
392
+ if (!parsed.shouldScan) {
393
+ const code = await (0, npm_wrapper_1.runNpm)(parsed.rawArgs);
394
+ process.exit(code);
395
+ }
396
+ // No packages specified (e.g. bare `npm install` from package.json)
397
+ if (parsed.packages.length === 0) {
398
+ const bareSpecs = (0, npm_wrapper_1.readBareInstallPackages)(process.cwd());
399
+ if (bareSpecs.length === 0) {
400
+ process.stderr.write(chalk_1.default.dim(" Dependency Guardian: no packages to scan, passing through to npm.\n"));
401
+ const code = await (0, npm_wrapper_1.runNpm)(parsed.rawArgs);
402
+ process.exit(code);
403
+ }
404
+ process.stderr.write(chalk_1.default.dim(` Resolving ${bareSpecs.length} package${bareSpecs.length !== 1 ? "s" : ""} from package.json...\n`));
405
+ const { resolved, failed } = await (0, npm_wrapper_1.resolvePackages)(bareSpecs);
406
+ if (failed.length > 0 && config.debug) {
407
+ process.stderr.write(chalk_1.default.dim(` [debug] Could not resolve: ${failed
408
+ .slice(0, 5)
409
+ .join(", ")}${failed.length > 5 ? ` (+${failed.length - 5} more)` : ""}\n`));
410
+ }
411
+ return scanAndInstallStatic(resolved, parsed, config);
412
+ }
413
+ // Resolve versions
414
+ process.stderr.write(chalk_1.default.dim(` Resolving ${parsed.packages.length} package${parsed.packages.length !== 1 ? "s" : ""}...\n`));
415
+ const { resolved, failed } = await (0, npm_wrapper_1.resolvePackages)(parsed.packages);
416
+ if (failed.length > 0) {
417
+ process.stderr.write(chalk_1.default.yellow(` Warning: Could not resolve versions for: ${failed.join(", ")}\n`));
418
+ }
419
+ if (resolved.length === 0) {
420
+ process.stderr.write(chalk_1.default.dim(" No packages to scan. Passing through to npm.\n"));
421
+ const code = await (0, npm_wrapper_1.runNpm)(parsed.rawArgs);
422
+ process.exit(code);
423
+ }
424
+ return scanAndInstallStatic(resolved, parsed, config);
425
+ }
426
+ async function scanAndInstallStatic(resolved, parsed, config) {
427
+ // Scan
428
+ process.stderr.write(chalk_1.default.dim(` Scanning ${resolved.length} package${resolved.length !== 1 ? "s" : ""}...\n`));
429
+ let result;
430
+ try {
431
+ const startMs = Date.now();
432
+ result = await (0, api_1.callAnalyzeAPI)(resolved, config);
433
+ const elapsed = Date.now() - startMs;
434
+ if (config.debug) {
435
+ process.stderr.write(chalk_1.default.dim(` [debug] API responded in ${elapsed}ms, action=${result.action}, score=${result.score}\n`));
436
+ }
437
+ }
438
+ catch (error) {
439
+ if (handleTrialExhausted(error, config.json))
440
+ return;
441
+ // API unavailable — warn and proceed
442
+ const msg = error instanceof Error ? error.message : String(error);
443
+ process.stderr.write(chalk_1.default.yellow(` Warning: Scan failed (${msg}). Proceeding with install.\n`));
444
+ const code = await (0, npm_wrapper_1.runNpm)(parsed.rawArgs);
445
+ process.exit(code);
446
+ return;
447
+ }
448
+ // Handle result
449
+ if (result.action === "pass") {
450
+ process.stderr.write(chalk_1.default.green(` ${chalk_1.default.bold("\u2713")} ${resolved.length} package${resolved.length !== 1 ? "s" : ""} scanned \u2014 all clear\n`));
451
+ printTrialBanner(result);
452
+ process.stderr.write("\n");
453
+ const code = await (0, npm_wrapper_1.runNpm)(parsed.rawArgs);
454
+ process.exit(code);
455
+ }
456
+ // Render findings
457
+ const output = renderResultStatic(result, config);
458
+ process.stdout.write(output + "\n");
459
+ printTrialBanner(result);
460
+ if (result.action === "warn") {
461
+ process.stderr.write(chalk_1.default.yellow(" Warnings detected. Proceeding with install.\n\n"));
462
+ const code = await (0, npm_wrapper_1.runNpm)(parsed.rawArgs);
463
+ process.exit(code);
464
+ }
465
+ // Block
466
+ if (result.action === "block") {
467
+ if (parsed.dgForce) {
468
+ process.stderr.write(chalk_1.default.yellow(chalk_1.default.bold(" --dg-force: Bypassing block. Install at your own risk.\n\n")));
469
+ const code = await (0, npm_wrapper_1.runNpm)(parsed.rawArgs);
470
+ process.exit(code);
471
+ }
472
+ process.stderr.write(chalk_1.default.red(chalk_1.default.bold(" BLOCKED: ")) +
473
+ chalk_1.default.red("High-risk packages detected. Install aborted.\n"));
474
+ process.stderr.write(chalk_1.default.dim(" Use --dg-force to bypass this check.\n\n"));
475
+ process.exit(2);
476
+ }
477
+ }
478
+ async function runStaticPip(pipArgs, config) {
479
+ const parsed = (0, pip_wrapper_1.parsePipArgs)(pipArgs);
480
+ // Non-install commands: pass through directly
481
+ if (!parsed.shouldScan) {
482
+ const code = await (0, pip_wrapper_1.runPip)(parsed.rawArgs);
483
+ process.exit(code);
484
+ }
485
+ // Collect package specs
486
+ let specs = parsed.packages;
487
+ if (specs.length === 0 && parsed.requirementsFile) {
488
+ specs = (0, pip_wrapper_1.parseRequirementsFile)(parsed.requirementsFile);
489
+ }
490
+ if (specs.length === 0) {
491
+ process.stderr.write(chalk_1.default.dim(" Dependency Guardian: no packages to scan, passing through to pip.\n"));
492
+ const code = await (0, pip_wrapper_1.runPip)(parsed.rawArgs);
493
+ process.exit(code);
494
+ }
495
+ // Resolve versions via PyPI API
496
+ process.stderr.write(chalk_1.default.dim(` Resolving ${specs.length} package${specs.length !== 1 ? "s" : ""} from PyPI...\n`));
497
+ const { resolved, failed } = await (0, pip_wrapper_1.resolvePackages)(specs);
498
+ if (failed.length > 0) {
499
+ process.stderr.write(chalk_1.default.yellow(` Warning: Could not resolve versions for: ${failed.join(", ")}\n`));
500
+ }
501
+ if (resolved.length === 0) {
502
+ process.stderr.write(chalk_1.default.dim(" No packages to scan. Passing through to pip.\n"));
503
+ const code = await (0, pip_wrapper_1.runPip)(parsed.rawArgs);
504
+ process.exit(code);
505
+ }
506
+ // Scan
507
+ process.stderr.write(chalk_1.default.dim(` Scanning ${resolved.length} Python package${resolved.length !== 1 ? "s" : ""}...\n`));
508
+ let result;
509
+ try {
510
+ result = await (0, api_1.callPyPIAnalyzeAPI)(resolved, config);
511
+ }
512
+ catch (error) {
513
+ if (handleTrialExhausted(error, config.json))
514
+ return;
515
+ const msg = error instanceof Error ? error.message : String(error);
516
+ process.stderr.write(chalk_1.default.yellow(` Warning: Scan failed (${msg}). Proceeding with install.\n`));
517
+ const code = await (0, pip_wrapper_1.runPip)(parsed.rawArgs);
518
+ process.exit(code);
519
+ return;
520
+ }
521
+ // Handle result
522
+ if (result.action === "pass") {
523
+ process.stderr.write(chalk_1.default.green(` ${chalk_1.default.bold("\u2713")} ${resolved.length} package${resolved.length !== 1 ? "s" : ""} scanned \u2014 all clear\n`));
524
+ printTrialBanner(result);
525
+ process.stderr.write("\n");
526
+ const code = await (0, pip_wrapper_1.runPip)(parsed.rawArgs);
527
+ process.exit(code);
528
+ }
529
+ const output = renderResultStatic(result, config);
530
+ process.stdout.write(output + "\n");
531
+ printTrialBanner(result);
532
+ if (result.action === "warn") {
533
+ process.stderr.write(chalk_1.default.yellow(" Warnings detected. Proceeding with install.\n\n"));
534
+ const code = await (0, pip_wrapper_1.runPip)(parsed.rawArgs);
535
+ process.exit(code);
536
+ }
537
+ if (result.action === "block") {
538
+ if (parsed.dgForce) {
539
+ process.stderr.write(chalk_1.default.yellow(chalk_1.default.bold(" --dg-force: Bypassing block. Install at your own risk.\n\n")));
540
+ const code = await (0, pip_wrapper_1.runPip)(parsed.rawArgs);
541
+ process.exit(code);
542
+ }
543
+ process.stderr.write(chalk_1.default.red(chalk_1.default.bold(" BLOCKED: ")) +
544
+ chalk_1.default.red("High-risk packages detected. Install aborted.\n"));
545
+ process.stderr.write(chalk_1.default.dim(" Use --dg-force to bypass this check.\n\n"));
546
+ process.exit(2);
547
+ }
548
+ }
549
+ async function runStaticLogin() {
550
+ const { createAuthSession, pollAuthSession, saveCredentials, openBrowser, getStoredApiKey, } = await Promise.resolve().then(() => __importStar(require("./auth")));
551
+ const apiUrl = process.env.DG_API_URL || "https://api.westbayberry.com";
552
+ // Check if already logged in with a valid key
553
+ const existing = getStoredApiKey();
554
+ if (existing) {
555
+ // Verify the key is still valid on the server
556
+ try {
557
+ const resp = await fetch(`${apiUrl}/v1/auth/status`, {
558
+ headers: { Authorization: `Bearer ${existing}` },
559
+ signal: AbortSignal.timeout(5000),
560
+ });
561
+ if (resp.ok) {
562
+ process.stderr.write(chalk_1.default.yellow(" Already authenticated.\n") +
563
+ chalk_1.default.dim(" Run `dg logout` first to re-authenticate.\n"));
564
+ return;
565
+ }
566
+ }
567
+ catch { }
568
+ // Key is invalid — clear it and proceed with fresh login
569
+ process.stderr.write(chalk_1.default.dim(" Stored key is invalid. Starting fresh login...\n"));
570
+ saveCredentials("");
571
+ }
572
+ process.stderr.write(chalk_1.default.dim(" Creating login session...\n"));
573
+ let session;
574
+ try {
575
+ session = await createAuthSession();
576
+ }
577
+ catch (err) {
578
+ process.stderr.write(chalk_1.default.red(` Error: ${err instanceof Error ? err.message : String(err)}\n`));
579
+ process.exit(1);
580
+ }
581
+ process.stderr.write(`\n Authenticate your account at:\n`);
582
+ process.stderr.write(` ${chalk_1.default.cyan(session.verifyUrl)}\n\n`);
583
+ process.stderr.write(chalk_1.default.dim(" Press ENTER to open in the browser...\n"));
584
+ await new Promise((resolve) => {
585
+ const onData = () => {
586
+ process.stdin.removeListener("data", onData);
587
+ if (process.stdin.unref)
588
+ process.stdin.unref();
589
+ resolve();
590
+ };
591
+ process.stdin.setEncoding("utf-8");
592
+ process.stdin.resume();
593
+ process.stdin.on("data", onData);
594
+ });
595
+ try {
596
+ openBrowser(session.verifyUrl);
597
+ }
598
+ catch {
599
+ // Browser open is best-effort
600
+ }
601
+ process.stderr.write(chalk_1.default.dim(" Waiting for authorization...\n"));
602
+ const POLL_INTERVAL = 2000;
603
+ const MAX_ATTEMPTS = 150;
604
+ for (let i = 0; i < MAX_ATTEMPTS; i++) {
605
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL));
606
+ try {
607
+ const result = await pollAuthSession(session.sessionId);
608
+ if (result.status === "complete" && result.apiKey) {
609
+ saveCredentials(result.apiKey);
610
+ process.stderr.write(chalk_1.default.green(`\n Logged in as ${result.email ?? "unknown"}\n`) +
611
+ chalk_1.default.dim(" Credentials saved.\n\n"));
612
+ return;
613
+ }
614
+ if (result.status === "expired") {
615
+ process.stderr.write(chalk_1.default.yellow("\n Session expired. Run `dg login` to try again.\n"));
616
+ process.exit(1);
617
+ }
618
+ }
619
+ catch {
620
+ // Transient network error — continue polling
621
+ }
622
+ }
623
+ process.stderr.write(chalk_1.default.yellow("\n Timed out waiting for authorization.\n"));
624
+ process.exit(1);
625
+ }