@westbayberry/dg 1.0.33 → 1.0.35

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 (3) hide show
  1. package/README.md +2 -9
  2. package/dist/index.mjs +1287 -568
  3. package/package.json +3 -2
package/dist/index.mjs CHANGED
@@ -38,6 +38,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
38
38
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
39
39
  mod
40
40
  ));
41
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
41
42
 
42
43
  // src/auth.ts
43
44
  var auth_exports = {};
@@ -53,7 +54,7 @@ __export(auth_exports, {
53
54
  import { readFileSync, writeFileSync, chmodSync, unlinkSync, existsSync } from "node:fs";
54
55
  import { join } from "node:path";
55
56
  import { homedir } from "node:os";
56
- import { exec } from "node:child_process";
57
+ import { spawn } from "node:child_process";
57
58
  import { randomUUID } from "node:crypto";
58
59
  async function createAuthSession() {
59
60
  let res;
@@ -154,24 +155,35 @@ function getOrCreateDeviceId() {
154
155
  return deviceId;
155
156
  }
156
157
  function openBrowser(url) {
157
- if (!/^https?:\/\//i.test(url)) return;
158
- const escaped = url.replace(/"/g, '\\"');
158
+ try {
159
+ const parsed = new URL(url);
160
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return;
161
+ } catch {
162
+ return;
163
+ }
159
164
  let cmd;
165
+ let args;
160
166
  switch (process.platform) {
161
167
  case "darwin":
162
- cmd = `open "${escaped}"`;
168
+ cmd = "open";
169
+ args = [url];
163
170
  break;
164
171
  case "linux":
165
- cmd = `xdg-open "${escaped}"`;
172
+ cmd = "xdg-open";
173
+ args = [url];
166
174
  break;
167
175
  case "win32":
168
- cmd = `start "" "${escaped}"`;
176
+ cmd = "cmd.exe";
177
+ args = ["/c", "start", "", url];
169
178
  break;
170
179
  default:
171
180
  return;
172
181
  }
173
- exec(cmd, () => {
174
- });
182
+ try {
183
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
184
+ child.unref();
185
+ } catch {
186
+ }
175
187
  }
176
188
  var WEB_BASE, CONFIG_FILE;
177
189
  var init_auth = __esm({
@@ -211,6 +223,22 @@ function loadDgrc() {
211
223
  }
212
224
  return {};
213
225
  }
226
+ function validateApiUrl(url) {
227
+ try {
228
+ const parsed = new URL(url);
229
+ const isLocal = parsed.hostname === "localhost" || parsed.hostname.startsWith("127.");
230
+ if (parsed.protocol !== "https:" && !isLocal) {
231
+ process.stderr.write(`Error: API URL must use HTTPS (got ${parsed.protocol}). Use localhost for local testing.
232
+ `);
233
+ process.exit(1);
234
+ }
235
+ return url;
236
+ } catch {
237
+ process.stderr.write(`Error: Invalid API URL: ${url}
238
+ `);
239
+ process.exit(1);
240
+ }
241
+ }
214
242
  function getVersion() {
215
243
  try {
216
244
  const thisDir = dirname(fileURLToPath(import.meta.url));
@@ -222,28 +250,46 @@ function getVersion() {
222
250
  return "1.0.0";
223
251
  }
224
252
  }
225
- function parseConfig(argv) {
226
- const { values, positionals } = parseArgs({
227
- args: argv.slice(2),
228
- options: {
229
- "api-url": { type: "string" },
230
- mode: { type: "string" },
231
- "block-threshold": { type: "string" },
232
- "warn-threshold": { type: "string" },
233
- "max-packages": { type: "string" },
234
- allowlist: { type: "string" },
235
- json: { type: "boolean", default: false },
236
- "scan-all": { type: "boolean", default: false },
237
- "base-lockfile": { type: "string" },
238
- workspace: { type: "string", short: "w" },
239
- debug: { type: "boolean", default: false },
240
- "no-config": { type: "boolean", default: false },
241
- help: { type: "boolean", default: false },
242
- version: { type: "boolean", default: false }
243
- },
244
- allowPositionals: true,
245
- strict: false
246
- });
253
+ function parseConfig(argv, strictFlags = true) {
254
+ let values;
255
+ let positionals;
256
+ try {
257
+ const parsed = parseArgs({
258
+ args: argv.slice(2),
259
+ options: {
260
+ "api-url": { type: "string" },
261
+ mode: { type: "string" },
262
+ "max-packages": { type: "string" },
263
+ json: { type: "boolean", default: false },
264
+ "scan-all": { type: "boolean", default: false },
265
+ "base-lockfile": { type: "string" },
266
+ workspace: { type: "string", short: "w" },
267
+ debug: { type: "boolean", default: false },
268
+ help: { type: "boolean", default: false },
269
+ version: { type: "boolean", default: false },
270
+ // Recognized but handled server-side via policy dashboard
271
+ "no-config": { type: "boolean", default: false },
272
+ allowlist: { type: "string" },
273
+ "block-threshold": { type: "string" },
274
+ "warn-threshold": { type: "string" }
275
+ },
276
+ allowPositionals: true,
277
+ strict: strictFlags
278
+ });
279
+ values = parsed.values;
280
+ positionals = parsed.positionals;
281
+ } catch (err) {
282
+ const raw = err instanceof Error ? err.message : String(err);
283
+ const match = raw.match(/Unknown option '([^']+)'/);
284
+ const msg = match ? `Unknown option '${match[1]}'.` : raw;
285
+ process.stderr.write(`
286
+ Error: ${msg}
287
+ `);
288
+ process.stderr.write(` Run 'dg --help' for available options.
289
+
290
+ `);
291
+ process.exit(1);
292
+ }
247
293
  if (values.help) {
248
294
  process.stdout.write(USAGE);
249
295
  process.exit(0);
@@ -254,8 +300,7 @@ function parseConfig(argv) {
254
300
  process.exit(0);
255
301
  }
256
302
  const command = positionals[0] ?? "scan";
257
- const noConfig = values["no-config"];
258
- const dgrc = noConfig ? {} : loadDgrc();
303
+ const dgrc = loadDgrc();
259
304
  const apiKey = dgrc.apiKey && typeof dgrc.apiKey === "string" && dgrc.apiKey.startsWith("dg_live_") ? dgrc.apiKey : null;
260
305
  const deviceId = getOrCreateDeviceId();
261
306
  const modeRaw = values.mode ?? process.env.DG_MODE ?? dgrc.mode ?? "warn";
@@ -266,19 +311,8 @@ function parseConfig(argv) {
266
311
  );
267
312
  process.exit(1);
268
313
  }
269
- const allowlistRaw = values.allowlist ?? process.env.DG_ALLOWLIST ?? "";
270
- const blockThreshold = Number(values["block-threshold"] ?? dgrc.blockThreshold ?? "70");
271
- const warnThreshold = Number(values["warn-threshold"] ?? dgrc.warnThreshold ?? "60");
272
314
  const maxPackages = Number(values["max-packages"] ?? dgrc.maxPackages ?? "200");
273
315
  const debug = values.debug || process.env.DG_DEBUG === "1";
274
- if (isNaN(blockThreshold) || blockThreshold < 0 || blockThreshold > 100) {
275
- process.stderr.write("Error: --block-threshold must be a number between 0 and 100\n");
276
- process.exit(1);
277
- }
278
- if (isNaN(warnThreshold) || warnThreshold < 0 || warnThreshold > 100) {
279
- process.stderr.write("Error: --warn-threshold must be a number between 0 and 100\n");
280
- process.exit(1);
281
- }
282
316
  if (isNaN(maxPackages) || maxPackages < 1 || maxPackages > 1e4) {
283
317
  process.stderr.write("Error: --max-packages must be a number between 1 and 10000\n");
284
318
  process.exit(1);
@@ -286,12 +320,11 @@ function parseConfig(argv) {
286
320
  return {
287
321
  apiKey,
288
322
  deviceId,
289
- apiUrl: values["api-url"] ?? process.env.DG_API_URL ?? dgrc.apiUrl ?? "https://api.westbayberry.com",
323
+ apiUrl: validateApiUrl(
324
+ values["api-url"] ?? process.env.DG_API_URL ?? dgrc.apiUrl ?? "https://api.westbayberry.com"
325
+ ),
290
326
  mode: modeRaw,
291
- blockThreshold,
292
- warnThreshold,
293
327
  maxPackages,
294
- allowlist: allowlistRaw ? allowlistRaw.split(",").map((s) => s.trim()).filter(Boolean) : dgrc.allowlist ?? [],
295
328
  json: values.json,
296
329
  scanAll: values["scan-all"],
297
330
  baseLockfile: values["base-lockfile"] ?? null,
@@ -312,11 +345,13 @@ var init_config = __esm({
312
345
  dependency-guardian scan [options]
313
346
  dg scan [options]
314
347
  dg npm install <pkg> [npm-flags]
348
+ dg pip install <pkg> [pip-flags]
315
349
  dg wrap
316
350
 
317
351
  Commands:
318
352
  scan Scan dependencies (auto-discovers npm + Python projects)
319
353
  npm Wrap npm commands \u2014 scans packages before installing
354
+ pip Wrap pip commands \u2014 scans packages before installing
320
355
  hook install Install git pre-commit hook to scan lockfile changes
321
356
  hook uninstall Remove the pre-commit hook
322
357
  update Check for and install the latest version
@@ -327,16 +362,12 @@ var init_config = __esm({
327
362
  Options:
328
363
  --api-url <url> API base URL (default: https://api.westbayberry.com)
329
364
  --mode <mode> block | warn | off (default: warn)
330
- --block-threshold <n> Score threshold for blocking (default: 70)
331
- --warn-threshold <n> Score threshold for warnings (default: 60)
332
365
  --max-packages <n> Max packages per scan (default: 200)
333
- --allowlist <pkgs> Comma-separated package names to skip
334
366
  --json Output JSON for CI parsing
335
367
  --scan-all Scan all packages, not just changed
336
368
  --base-lockfile <path> Path to base lockfile for explicit diff
337
369
  --workspace <dir> Scan a specific workspace subdirectory
338
370
  --debug Show diagnostic output (discovery, batches, timing)
339
- --no-config Skip loading .dgrc.json config file
340
371
  --help Show this help message
341
372
  --version Show version number
342
373
 
@@ -347,7 +378,6 @@ var init_config = __esm({
347
378
  Environment Variables:
348
379
  DG_API_URL API base URL
349
380
  DG_MODE Mode (block/warn/off)
350
- DG_ALLOWLIST Comma-separated allowlist
351
381
  DG_DEBUG Enable debug output (set to 1)
352
382
  DG_WORKSPACE Workspace subdirectory to scan
353
383
 
@@ -370,7 +400,7 @@ var init_config = __esm({
370
400
  });
371
401
 
372
402
  // src/npm-wrapper.ts
373
- import { spawn } from "node:child_process";
403
+ import { spawn as spawn2 } from "node:child_process";
374
404
  import { readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
375
405
  import { join as join3 } from "node:path";
376
406
  function parseNpmArgs(args) {
@@ -451,7 +481,7 @@ function parsePackageSpec(spec) {
451
481
  }
452
482
  async function resolveVersion(spec) {
453
483
  return new Promise((resolve) => {
454
- const child = spawn("npm", ["view", spec, "version"], {
484
+ const child = spawn2("npm", ["view", spec, "version"], {
455
485
  stdio: ["pipe", "pipe", "pipe"]
456
486
  });
457
487
  let stdout = "";
@@ -509,7 +539,7 @@ async function resolvePackages(specs) {
509
539
  }
510
540
  function runNpm(args) {
511
541
  return new Promise((resolve) => {
512
- const child = spawn("npm", args, {
542
+ const child = spawn2("npm", args, {
513
543
  stdio: "inherit",
514
544
  shell: false
515
545
  });
@@ -3440,15 +3470,15 @@ var require_react_development = __commonJS({
3440
3470
  var dispatcher = resolveDispatcher();
3441
3471
  return dispatcher.useState(initialState);
3442
3472
  }
3443
- function useReducer5(reducer4, initialArg, init) {
3473
+ function useReducer6(reducer5, initialArg, init) {
3444
3474
  var dispatcher = resolveDispatcher();
3445
- return dispatcher.useReducer(reducer4, initialArg, init);
3475
+ return dispatcher.useReducer(reducer5, initialArg, init);
3446
3476
  }
3447
- function useRef8(initialValue) {
3477
+ function useRef9(initialValue) {
3448
3478
  var dispatcher = resolveDispatcher();
3449
3479
  return dispatcher.useRef(initialValue);
3450
3480
  }
3451
- function useEffect14(create2, deps) {
3481
+ function useEffect16(create2, deps) {
3452
3482
  var dispatcher = resolveDispatcher();
3453
3483
  return dispatcher.useEffect(create2, deps);
3454
3484
  }
@@ -3460,7 +3490,7 @@ var require_react_development = __commonJS({
3460
3490
  var dispatcher = resolveDispatcher();
3461
3491
  return dispatcher.useLayoutEffect(create2, deps);
3462
3492
  }
3463
- function useCallback5(callback, deps) {
3493
+ function useCallback6(callback, deps) {
3464
3494
  var dispatcher = resolveDispatcher();
3465
3495
  return dispatcher.useCallback(callback, deps);
3466
3496
  }
@@ -4227,18 +4257,18 @@ var require_react_development = __commonJS({
4227
4257
  exports.memo = memo;
4228
4258
  exports.startTransition = startTransition;
4229
4259
  exports.unstable_act = act;
4230
- exports.useCallback = useCallback5;
4260
+ exports.useCallback = useCallback6;
4231
4261
  exports.useContext = useContext7;
4232
4262
  exports.useDebugValue = useDebugValue;
4233
4263
  exports.useDeferredValue = useDeferredValue;
4234
- exports.useEffect = useEffect14;
4264
+ exports.useEffect = useEffect16;
4235
4265
  exports.useId = useId;
4236
4266
  exports.useImperativeHandle = useImperativeHandle;
4237
4267
  exports.useInsertionEffect = useInsertionEffect;
4238
4268
  exports.useLayoutEffect = useLayoutEffect2;
4239
4269
  exports.useMemo = useMemo4;
4240
- exports.useReducer = useReducer5;
4241
- exports.useRef = useRef8;
4270
+ exports.useReducer = useReducer6;
4271
+ exports.useRef = useRef9;
4242
4272
  exports.useState = useState9;
4243
4273
  exports.useSyncExternalStore = useSyncExternalStore;
4244
4274
  exports.useTransition = useTransition;
@@ -11949,9 +11979,9 @@ var require_react_reconciler_development = __commonJS({
11949
11979
  module.exports = function $$$reconciler($$$hostConfig) {
11950
11980
  var exports2 = {};
11951
11981
  "use strict";
11952
- var React17 = require_react();
11982
+ var React18 = require_react();
11953
11983
  var Scheduler = require_scheduler();
11954
- var ReactSharedInternals = React17.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
11984
+ var ReactSharedInternals = React18.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
11955
11985
  var suppressWarning = false;
11956
11986
  function setSuppressWarning(newSuppressWarning) {
11957
11987
  {
@@ -12015,7 +12045,7 @@ var require_react_reconciler_development = __commonJS({
12015
12045
  var HostPortal = 4;
12016
12046
  var HostComponent = 5;
12017
12047
  var HostText = 6;
12018
- var Fragment4 = 7;
12048
+ var Fragment7 = 7;
12019
12049
  var Mode = 8;
12020
12050
  var ContextConsumer = 9;
12021
12051
  var ContextProvider = 10;
@@ -12155,7 +12185,7 @@ var require_react_reconciler_development = __commonJS({
12155
12185
  return "DehydratedFragment";
12156
12186
  case ForwardRef:
12157
12187
  return getWrappedName$1(type, type.render, "ForwardRef");
12158
- case Fragment4:
12188
+ case Fragment7:
12159
12189
  return "Fragment";
12160
12190
  case HostComponent:
12161
12191
  return type;
@@ -15289,7 +15319,7 @@ var require_react_reconciler_development = __commonJS({
15289
15319
  }
15290
15320
  }
15291
15321
  function updateFragment2(returnFiber, current2, fragment, lanes, key) {
15292
- if (current2 === null || current2.tag !== Fragment4) {
15322
+ if (current2 === null || current2.tag !== Fragment7) {
15293
15323
  var created = createFiberFromFragment(fragment, returnFiber.mode, lanes, key);
15294
15324
  created.return = returnFiber;
15295
15325
  return created;
@@ -15692,7 +15722,7 @@ var require_react_reconciler_development = __commonJS({
15692
15722
  if (child.key === key) {
15693
15723
  var elementType = element.type;
15694
15724
  if (elementType === REACT_FRAGMENT_TYPE) {
15695
- if (child.tag === Fragment4) {
15725
+ if (child.tag === Fragment7) {
15696
15726
  deleteRemainingChildren(returnFiber, child.sibling);
15697
15727
  var existing = useFiber(child, element.props.children);
15698
15728
  existing.return = returnFiber;
@@ -16940,7 +16970,7 @@ var require_react_reconciler_development = __commonJS({
16940
16970
  function basicStateReducer(state, action) {
16941
16971
  return typeof action === "function" ? action(state) : action;
16942
16972
  }
16943
- function mountReducer(reducer4, initialArg, init) {
16973
+ function mountReducer(reducer5, initialArg, init) {
16944
16974
  var hook = mountWorkInProgressHook();
16945
16975
  var initialState;
16946
16976
  if (init !== void 0) {
@@ -16954,20 +16984,20 @@ var require_react_reconciler_development = __commonJS({
16954
16984
  interleaved: null,
16955
16985
  lanes: NoLanes,
16956
16986
  dispatch: null,
16957
- lastRenderedReducer: reducer4,
16987
+ lastRenderedReducer: reducer5,
16958
16988
  lastRenderedState: initialState
16959
16989
  };
16960
16990
  hook.queue = queue;
16961
16991
  var dispatch = queue.dispatch = dispatchReducerAction.bind(null, currentlyRenderingFiber$1, queue);
16962
16992
  return [hook.memoizedState, dispatch];
16963
16993
  }
16964
- function updateReducer(reducer4, initialArg, init) {
16994
+ function updateReducer(reducer5, initialArg, init) {
16965
16995
  var hook = updateWorkInProgressHook();
16966
16996
  var queue = hook.queue;
16967
16997
  if (queue === null) {
16968
16998
  throw new Error("Should have a queue. This is likely a bug in React. Please file an issue.");
16969
16999
  }
16970
- queue.lastRenderedReducer = reducer4;
17000
+ queue.lastRenderedReducer = reducer5;
16971
17001
  var current2 = currentHook;
16972
17002
  var baseQueue = current2.baseQueue;
16973
17003
  var pendingQueue = queue.pending;
@@ -17029,7 +17059,7 @@ var require_react_reconciler_development = __commonJS({
17029
17059
  newState = update.eagerState;
17030
17060
  } else {
17031
17061
  var action = update.action;
17032
- newState = reducer4(newState, action);
17062
+ newState = reducer5(newState, action);
17033
17063
  }
17034
17064
  }
17035
17065
  update = update.next;
@@ -17062,13 +17092,13 @@ var require_react_reconciler_development = __commonJS({
17062
17092
  var dispatch = queue.dispatch;
17063
17093
  return [hook.memoizedState, dispatch];
17064
17094
  }
17065
- function rerenderReducer(reducer4, initialArg, init) {
17095
+ function rerenderReducer(reducer5, initialArg, init) {
17066
17096
  var hook = updateWorkInProgressHook();
17067
17097
  var queue = hook.queue;
17068
17098
  if (queue === null) {
17069
17099
  throw new Error("Should have a queue. This is likely a bug in React. Please file an issue.");
17070
17100
  }
17071
- queue.lastRenderedReducer = reducer4;
17101
+ queue.lastRenderedReducer = reducer5;
17072
17102
  var dispatch = queue.dispatch;
17073
17103
  var lastRenderPhaseUpdate = queue.pending;
17074
17104
  var newState = hook.memoizedState;
@@ -17078,7 +17108,7 @@ var require_react_reconciler_development = __commonJS({
17078
17108
  var update = firstRenderPhaseUpdate;
17079
17109
  do {
17080
17110
  var action = update.action;
17081
- newState = reducer4(newState, action);
17111
+ newState = reducer5(newState, action);
17082
17112
  update = update.next;
17083
17113
  } while (update !== firstRenderPhaseUpdate);
17084
17114
  if (!objectIs(newState, hook.memoizedState)) {
@@ -17758,13 +17788,13 @@ var require_react_reconciler_development = __commonJS({
17758
17788
  ReactCurrentDispatcher$1.current = prevDispatcher;
17759
17789
  }
17760
17790
  },
17761
- useReducer: function(reducer4, initialArg, init) {
17791
+ useReducer: function(reducer5, initialArg, init) {
17762
17792
  currentHookNameInDev = "useReducer";
17763
17793
  mountHookTypesDev();
17764
17794
  var prevDispatcher = ReactCurrentDispatcher$1.current;
17765
17795
  ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
17766
17796
  try {
17767
- return mountReducer(reducer4, initialArg, init);
17797
+ return mountReducer(reducer5, initialArg, init);
17768
17798
  } finally {
17769
17799
  ReactCurrentDispatcher$1.current = prevDispatcher;
17770
17800
  }
@@ -17862,13 +17892,13 @@ var require_react_reconciler_development = __commonJS({
17862
17892
  ReactCurrentDispatcher$1.current = prevDispatcher;
17863
17893
  }
17864
17894
  },
17865
- useReducer: function(reducer4, initialArg, init) {
17895
+ useReducer: function(reducer5, initialArg, init) {
17866
17896
  currentHookNameInDev = "useReducer";
17867
17897
  updateHookTypesDev();
17868
17898
  var prevDispatcher = ReactCurrentDispatcher$1.current;
17869
17899
  ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
17870
17900
  try {
17871
- return mountReducer(reducer4, initialArg, init);
17901
+ return mountReducer(reducer5, initialArg, init);
17872
17902
  } finally {
17873
17903
  ReactCurrentDispatcher$1.current = prevDispatcher;
17874
17904
  }
@@ -17966,13 +17996,13 @@ var require_react_reconciler_development = __commonJS({
17966
17996
  ReactCurrentDispatcher$1.current = prevDispatcher;
17967
17997
  }
17968
17998
  },
17969
- useReducer: function(reducer4, initialArg, init) {
17999
+ useReducer: function(reducer5, initialArg, init) {
17970
18000
  currentHookNameInDev = "useReducer";
17971
18001
  updateHookTypesDev();
17972
18002
  var prevDispatcher = ReactCurrentDispatcher$1.current;
17973
18003
  ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
17974
18004
  try {
17975
- return updateReducer(reducer4, initialArg, init);
18005
+ return updateReducer(reducer5, initialArg, init);
17976
18006
  } finally {
17977
18007
  ReactCurrentDispatcher$1.current = prevDispatcher;
17978
18008
  }
@@ -18070,13 +18100,13 @@ var require_react_reconciler_development = __commonJS({
18070
18100
  ReactCurrentDispatcher$1.current = prevDispatcher;
18071
18101
  }
18072
18102
  },
18073
- useReducer: function(reducer4, initialArg, init) {
18103
+ useReducer: function(reducer5, initialArg, init) {
18074
18104
  currentHookNameInDev = "useReducer";
18075
18105
  updateHookTypesDev();
18076
18106
  var prevDispatcher = ReactCurrentDispatcher$1.current;
18077
18107
  ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnRerenderInDEV;
18078
18108
  try {
18079
- return rerenderReducer(reducer4, initialArg, init);
18109
+ return rerenderReducer(reducer5, initialArg, init);
18080
18110
  } finally {
18081
18111
  ReactCurrentDispatcher$1.current = prevDispatcher;
18082
18112
  }
@@ -18182,14 +18212,14 @@ var require_react_reconciler_development = __commonJS({
18182
18212
  ReactCurrentDispatcher$1.current = prevDispatcher;
18183
18213
  }
18184
18214
  },
18185
- useReducer: function(reducer4, initialArg, init) {
18215
+ useReducer: function(reducer5, initialArg, init) {
18186
18216
  currentHookNameInDev = "useReducer";
18187
18217
  warnInvalidHookAccess();
18188
18218
  mountHookTypesDev();
18189
18219
  var prevDispatcher = ReactCurrentDispatcher$1.current;
18190
18220
  ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
18191
18221
  try {
18192
- return mountReducer(reducer4, initialArg, init);
18222
+ return mountReducer(reducer5, initialArg, init);
18193
18223
  } finally {
18194
18224
  ReactCurrentDispatcher$1.current = prevDispatcher;
18195
18225
  }
@@ -18303,14 +18333,14 @@ var require_react_reconciler_development = __commonJS({
18303
18333
  ReactCurrentDispatcher$1.current = prevDispatcher;
18304
18334
  }
18305
18335
  },
18306
- useReducer: function(reducer4, initialArg, init) {
18336
+ useReducer: function(reducer5, initialArg, init) {
18307
18337
  currentHookNameInDev = "useReducer";
18308
18338
  warnInvalidHookAccess();
18309
18339
  updateHookTypesDev();
18310
18340
  var prevDispatcher = ReactCurrentDispatcher$1.current;
18311
18341
  ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
18312
18342
  try {
18313
- return updateReducer(reducer4, initialArg, init);
18343
+ return updateReducer(reducer5, initialArg, init);
18314
18344
  } finally {
18315
18345
  ReactCurrentDispatcher$1.current = prevDispatcher;
18316
18346
  }
@@ -18424,14 +18454,14 @@ var require_react_reconciler_development = __commonJS({
18424
18454
  ReactCurrentDispatcher$1.current = prevDispatcher;
18425
18455
  }
18426
18456
  },
18427
- useReducer: function(reducer4, initialArg, init) {
18457
+ useReducer: function(reducer5, initialArg, init) {
18428
18458
  currentHookNameInDev = "useReducer";
18429
18459
  warnInvalidHookAccess();
18430
18460
  updateHookTypesDev();
18431
18461
  var prevDispatcher = ReactCurrentDispatcher$1.current;
18432
18462
  ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
18433
18463
  try {
18434
- return rerenderReducer(reducer4, initialArg, init);
18464
+ return rerenderReducer(reducer5, initialArg, init);
18435
18465
  } finally {
18436
18466
  ReactCurrentDispatcher$1.current = prevDispatcher;
18437
18467
  }
@@ -21183,7 +21213,7 @@ var require_react_reconciler_development = __commonJS({
21183
21213
  var _resolvedProps2 = workInProgress2.elementType === type ? _unresolvedProps2 : resolveDefaultProps(type, _unresolvedProps2);
21184
21214
  return updateForwardRef(current2, workInProgress2, type, _resolvedProps2, renderLanes2);
21185
21215
  }
21186
- case Fragment4:
21216
+ case Fragment7:
21187
21217
  return updateFragment(current2, workInProgress2, renderLanes2);
21188
21218
  case Mode:
21189
21219
  return updateMode(current2, workInProgress2, renderLanes2);
@@ -21620,7 +21650,7 @@ var require_react_reconciler_development = __commonJS({
21620
21650
  case SimpleMemoComponent:
21621
21651
  case FunctionComponent:
21622
21652
  case ForwardRef:
21623
- case Fragment4:
21653
+ case Fragment7:
21624
21654
  case Mode:
21625
21655
  case Profiler:
21626
21656
  case ContextConsumer:
@@ -26388,7 +26418,7 @@ var require_react_reconciler_development = __commonJS({
26388
26418
  return fiber;
26389
26419
  }
26390
26420
  function createFiberFromFragment(elements, mode, lanes, key) {
26391
- var fiber = createFiber(Fragment4, elements, key, mode);
26421
+ var fiber = createFiber(Fragment7, elements, key, mode);
26392
26422
  fiber.lanes = lanes;
26393
26423
  return fiber;
26394
26424
  }
@@ -27588,9 +27618,9 @@ var init_ansi_styles = __esm({
27588
27618
 
27589
27619
  // node_modules/wrap-ansi/index.js
27590
27620
  function wrapAnsi(string, columns, options) {
27591
- return String(string).normalize().replaceAll("\r\n", "\n").split("\n").map((line) => exec2(line, columns, options)).join("\n");
27621
+ return String(string).normalize().replaceAll("\r\n", "\n").split("\n").map((line) => exec(line, columns, options)).join("\n");
27592
27622
  }
27593
- var ESCAPES, END_CODE, ANSI_ESCAPE_BELL, ANSI_CSI, ANSI_OSC, ANSI_SGR_TERMINATOR, ANSI_ESCAPE_LINK, wrapAnsiCode, wrapAnsiHyperlink, wordLengths, wrapWord, stringVisibleTrimSpacesRight, exec2;
27623
+ var ESCAPES, END_CODE, ANSI_ESCAPE_BELL, ANSI_CSI, ANSI_OSC, ANSI_SGR_TERMINATOR, ANSI_ESCAPE_LINK, wrapAnsiCode, wrapAnsiHyperlink, wordLengths, wrapWord, stringVisibleTrimSpacesRight, exec;
27594
27624
  var init_wrap_ansi = __esm({
27595
27625
  "node_modules/wrap-ansi/index.js"() {
27596
27626
  init_string_width();
@@ -27662,7 +27692,7 @@ var init_wrap_ansi = __esm({
27662
27692
  }
27663
27693
  return words.slice(0, last).join(" ") + words.slice(last).join("");
27664
27694
  };
27665
- exec2 = (string, columns, options = {}) => {
27695
+ exec = (string, columns, options = {}) => {
27666
27696
  if (options.trim !== false && string.trim() === "") {
27667
27697
  return "";
27668
27698
  }
@@ -30753,7 +30783,7 @@ var require_websocket = __commonJS({
30753
30783
  var tls = __require("tls");
30754
30784
  var { randomBytes, createHash } = __require("crypto");
30755
30785
  var { Duplex, Readable } = __require("stream");
30756
- var { URL } = __require("url");
30786
+ var { URL: URL2 } = __require("url");
30757
30787
  var PerMessageDeflate = require_permessage_deflate();
30758
30788
  var Receiver2 = require_receiver();
30759
30789
  var Sender2 = require_sender();
@@ -31246,11 +31276,11 @@ var require_websocket = __commonJS({
31246
31276
  );
31247
31277
  }
31248
31278
  let parsedUrl;
31249
- if (address instanceof URL) {
31279
+ if (address instanceof URL2) {
31250
31280
  parsedUrl = address;
31251
31281
  } else {
31252
31282
  try {
31253
- parsedUrl = new URL(address);
31283
+ parsedUrl = new URL2(address);
31254
31284
  } catch (e) {
31255
31285
  throw new SyntaxError(`Invalid URL: ${address}`);
31256
31286
  }
@@ -31387,7 +31417,7 @@ var require_websocket = __commonJS({
31387
31417
  req.abort();
31388
31418
  let addr;
31389
31419
  try {
31390
- addr = new URL(location, address);
31420
+ addr = new URL2(location, address);
31391
31421
  } catch (e) {
31392
31422
  const err = new SyntaxError(`Invalid URL: ${location}`);
31393
31423
  emitErrorAndClose(websocket, err);
@@ -37902,7 +37932,7 @@ var require_react_jsx_runtime_development = __commonJS({
37902
37932
  if (process.env.NODE_ENV !== "production") {
37903
37933
  (function() {
37904
37934
  "use strict";
37905
- var React17 = require_react();
37935
+ var React18 = require_react();
37906
37936
  var REACT_ELEMENT_TYPE = Symbol.for("react.element");
37907
37937
  var REACT_PORTAL_TYPE = Symbol.for("react.portal");
37908
37938
  var REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
@@ -37928,7 +37958,7 @@ var require_react_jsx_runtime_development = __commonJS({
37928
37958
  }
37929
37959
  return null;
37930
37960
  }
37931
- var ReactSharedInternals = React17.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
37961
+ var ReactSharedInternals = React18.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
37932
37962
  function error(format) {
37933
37963
  {
37934
37964
  {
@@ -38778,11 +38808,11 @@ var require_react_jsx_runtime_development = __commonJS({
38778
38808
  return jsxWithValidation(type, props, key, false);
38779
38809
  }
38780
38810
  }
38781
- var jsx11 = jsxWithValidationDynamic;
38782
- var jsxs13 = jsxWithValidationStatic;
38811
+ var jsx12 = jsxWithValidationDynamic;
38812
+ var jsxs14 = jsxWithValidationStatic;
38783
38813
  exports.Fragment = REACT_FRAGMENT_TYPE;
38784
- exports.jsx = jsx11;
38785
- exports.jsxs = jsxs13;
38814
+ exports.jsx = jsx12;
38815
+ exports.jsxs = jsxs14;
38786
38816
  })();
38787
38817
  }
38788
38818
  }
@@ -38919,6 +38949,38 @@ var init_LoginApp = __esm({
38919
38949
  }
38920
38950
  });
38921
38951
 
38952
+ // src/sanitize.ts
38953
+ import { stripVTControlCharacters } from "node:util";
38954
+ function sanitize(s) {
38955
+ return stripVTControlCharacters(s);
38956
+ }
38957
+ function sanitizeResponse(response) {
38958
+ return {
38959
+ ...response,
38960
+ packages: response.packages.map((pkg) => ({
38961
+ ...pkg,
38962
+ name: sanitize(pkg.name),
38963
+ version: sanitize(pkg.version),
38964
+ findings: pkg.findings.map((f) => ({
38965
+ ...f,
38966
+ id: sanitize(f.id ?? ""),
38967
+ title: sanitize(f.title),
38968
+ evidence: f.evidence.map(sanitize)
38969
+ })),
38970
+ reasons: (pkg.reasons ?? []).map(sanitize),
38971
+ recommendation: pkg.recommendation ? sanitize(pkg.recommendation) : void 0
38972
+ })),
38973
+ safeVersions: Object.fromEntries(
38974
+ Object.entries(response.safeVersions).map(([k, v]) => [sanitize(k), sanitize(v)])
38975
+ )
38976
+ };
38977
+ }
38978
+ var init_sanitize = __esm({
38979
+ "src/sanitize.ts"() {
38980
+ "use strict";
38981
+ }
38982
+ });
38983
+
38922
38984
  // src/api.ts
38923
38985
  function buildHeaders(config) {
38924
38986
  const headers = {
@@ -38969,7 +39031,7 @@ async function callAnalyzeAPI(packages, config, onProgress) {
38969
39031
  results.push(result);
38970
39032
  }
38971
39033
  if (process.env.DG_PERF) console.error(`[CLI-PERF] total: ${packages.length} packages \u2192 ${Date.now() - tTotal}ms`);
38972
- return mergeResponses(results, config);
39034
+ return mergeResponses(results);
38973
39035
  }
38974
39036
  async function callBatchWithRetry(packages, config) {
38975
39037
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
@@ -38992,10 +39054,10 @@ async function callBatchWithRetry(packages, config) {
38992
39054
  }
38993
39055
  throw new Error("Exhausted retries");
38994
39056
  }
38995
- function mergeResponses(results, config) {
39057
+ function mergeResponses(results) {
38996
39058
  const allPackages = results.flatMap((r) => r.packages);
38997
39059
  const maxScore = Math.max(0, ...allPackages.map((p) => p.score));
38998
- const action = maxScore >= config.blockThreshold ? "block" : maxScore >= config.warnThreshold ? "warn" : "pass";
39060
+ const action = maxScore >= 70 ? "block" : maxScore >= 60 ? "warn" : "pass";
38999
39061
  const safeVersions = {};
39000
39062
  for (const r of results) {
39001
39063
  Object.assign(safeVersions, r.safeVersions);
@@ -39016,11 +39078,7 @@ async function callAnalyzeBatch(packages, config) {
39016
39078
  version: p.version,
39017
39079
  previousVersion: p.previousVersion,
39018
39080
  isNew: p.isNew
39019
- })),
39020
- config: {
39021
- blockThreshold: config.blockThreshold,
39022
- warnThreshold: config.warnThreshold
39023
- }
39081
+ }))
39024
39082
  };
39025
39083
  const controller = new AbortController();
39026
39084
  const timeoutId = setTimeout(() => controller.abort(), 18e4);
@@ -39065,7 +39123,11 @@ async function callAnalyzeBatch(packages, config) {
39065
39123
  body
39066
39124
  );
39067
39125
  }
39068
- return await response.json();
39126
+ const raw = await response.json();
39127
+ if (!raw || typeof raw.score !== "number" || !Array.isArray(raw.packages)) {
39128
+ throw new APIError("Invalid API response format", 0, "");
39129
+ }
39130
+ return sanitizeResponse(raw);
39069
39131
  }
39070
39132
  async function callPyPIAnalyzeAPI(packages, config, onProgress) {
39071
39133
  const batchSize = config.apiKey ? BATCH_SIZE : ANON_BATCH_SIZE;
@@ -39084,7 +39146,7 @@ async function callPyPIAnalyzeAPI(packages, config, onProgress) {
39084
39146
  if (onProgress) onProgress(completed, packages.length);
39085
39147
  results.push(result);
39086
39148
  }
39087
- return mergeResponses(results, config);
39149
+ return mergeResponses(results);
39088
39150
  }
39089
39151
  async function callPyPIBatch(packages, config) {
39090
39152
  const url = `${config.apiUrl}/v1/pypi/analyze`;
@@ -39094,11 +39156,7 @@ async function callPyPIBatch(packages, config) {
39094
39156
  version: p.version,
39095
39157
  previousVersion: p.previousVersion ?? null,
39096
39158
  isNew: p.isNew ?? true
39097
- })),
39098
- config: {
39099
- blockThreshold: config.blockThreshold,
39100
- warnThreshold: config.warnThreshold
39101
- }
39159
+ }))
39102
39160
  };
39103
39161
  const controller = new AbortController();
39104
39162
  const timeoutId = setTimeout(() => controller.abort(), 18e4);
@@ -39139,12 +39197,17 @@ async function callPyPIBatch(packages, config) {
39139
39197
  const body = await response.text();
39140
39198
  throw new APIError(`API returned ${response.status}: ${body}`, response.status, body);
39141
39199
  }
39142
- return await response.json();
39200
+ const raw = await response.json();
39201
+ if (!raw || typeof raw.score !== "number" || !Array.isArray(raw.packages)) {
39202
+ throw new APIError("Invalid API response format", 0, "");
39203
+ }
39204
+ return sanitizeResponse(raw);
39143
39205
  }
39144
39206
  var APIError, TrialExhaustedError, BATCH_SIZE, ANON_BATCH_SIZE, MAX_RETRIES, RETRY_DELAY_MS;
39145
39207
  var init_api = __esm({
39146
39208
  "src/api.ts"() {
39147
39209
  "use strict";
39210
+ init_sanitize();
39148
39211
  APIError = class extends Error {
39149
39212
  constructor(message, statusCode, body) {
39150
39213
  super(message);
@@ -39179,11 +39242,15 @@ function parseLockfile(content) {
39179
39242
  const name = extractPackageName(path);
39180
39243
  if (name && !packages.has(name)) {
39181
39244
  const e = entry;
39245
+ const deps = e.dependencies;
39182
39246
  packages.set(name, {
39183
39247
  version: e.version ?? "",
39184
39248
  resolved: e.resolved,
39185
39249
  integrity: e.integrity,
39186
- dev: e.dev
39250
+ dev: e.dev,
39251
+ optional: e.optional,
39252
+ hasPlatformRestriction: !!(e.os || e.cpu),
39253
+ dependencies: deps && typeof deps === "object" ? deps : void 0
39187
39254
  });
39188
39255
  }
39189
39256
  }
@@ -39405,26 +39472,40 @@ var init_parse_package_json = __esm({
39405
39472
  });
39406
39473
 
39407
39474
  // src/lockfile.ts
39408
- import { execFileSync } from "node:child_process";
39409
- import { readFileSync as readFileSync6, existsSync as existsSync5 } from "node:fs";
39475
+ import { execFileSync as execFileSync2 } from "node:child_process";
39476
+ import { readFileSync as readFileSync6, existsSync as existsSync5, statSync } from "node:fs";
39410
39477
  import { join as join5 } from "node:path";
39478
+ function readFileSafe(path) {
39479
+ const size = statSync(path).size;
39480
+ if (size > MAX_LOCKFILE_BYTES) {
39481
+ throw new Error(`Lockfile too large (${(size / 1024 / 1024).toFixed(0)} MB, max 50 MB): ${path}`);
39482
+ }
39483
+ return readFileSync6(path, "utf-8");
39484
+ }
39411
39485
  function discoverChanges(cwd2, config) {
39412
39486
  if (config.workspace) {
39413
39487
  cwd2 = join5(cwd2, config.workspace);
39414
39488
  }
39415
39489
  const lockfileInfo = findLockfile(cwd2);
39416
39490
  if (!lockfileInfo) {
39491
+ const pythonFiles = ["requirements.txt", "Pipfile.lock", "poetry.lock"];
39492
+ const hasPython = pythonFiles.some((f) => existsSync5(join5(cwd2, f)));
39493
+ if (hasPython) {
39494
+ return { packages: [], method: "fallback", skipped: [] };
39495
+ }
39417
39496
  throw new Error(
39418
- "No lockfile found (package-lock.json, yarn.lock, or pnpm-lock.yaml). Run from your project root or use --base-lockfile."
39497
+ "No lockfile found (package-lock.json, yarn.lock, pnpm-lock.yaml, requirements.txt, Pipfile.lock, or poetry.lock). Run from your project root or use --base-lockfile."
39419
39498
  );
39420
39499
  }
39421
- const headContent = readFileSync6(lockfileInfo.path, "utf-8");
39500
+ const headContent = readFileSafe(lockfileInfo.path);
39422
39501
  const headParsed = parseLockfileByType(headContent, lockfileInfo.type);
39423
39502
  const directDeps = getDirectDeps(cwd2);
39424
39503
  if (config.scanAll) {
39425
39504
  const packages2 = [];
39426
39505
  for (const [name, entry] of headParsed.packages) {
39427
39506
  if (packages2.length >= config.maxPackages) break;
39507
+ if (entry.optional && entry.hasPlatformRestriction) continue;
39508
+ if (name === SELF_PACKAGE) continue;
39428
39509
  packages2.push({
39429
39510
  name,
39430
39511
  version: entry.version,
@@ -39438,11 +39519,11 @@ function discoverChanges(cwd2, config) {
39438
39519
  if (!existsSync5(config.baseLockfile)) {
39439
39520
  throw new Error(`Base lockfile not found: ${config.baseLockfile}`);
39440
39521
  }
39441
- const baseContent2 = readFileSync6(config.baseLockfile, "utf-8");
39522
+ const baseContent2 = readFileSafe(config.baseLockfile);
39442
39523
  const baseParsed = parseLockfile(baseContent2);
39443
39524
  const diff2 = diffLockfiles(baseParsed, headParsed, config.maxPackages, directDeps);
39444
39525
  return {
39445
- packages: diff2.changes.map(toPackageInput),
39526
+ packages: diff2.changes.map(toPackageInput).filter((p) => p.name !== SELF_PACKAGE),
39446
39527
  method: "base-lockfile",
39447
39528
  skipped: diff2.skipped
39448
39529
  };
@@ -39452,14 +39533,14 @@ function discoverChanges(cwd2, config) {
39452
39533
  const baseParsed = parseLockfile(baseContent);
39453
39534
  const diff2 = diffLockfiles(baseParsed, headParsed, config.maxPackages, directDeps);
39454
39535
  return {
39455
- packages: diff2.changes.map(toPackageInput),
39536
+ packages: diff2.changes.map(toPackageInput).filter((p) => p.name !== SELF_PACKAGE),
39456
39537
  method: "git-diff",
39457
39538
  skipped: diff2.skipped
39458
39539
  };
39459
39540
  }
39460
39541
  const pkgJsonPath = join5(cwd2, "package.json");
39461
39542
  if (existsSync5(pkgJsonPath)) {
39462
- const headPkgJson = readFileSync6(pkgJsonPath, "utf-8");
39543
+ const headPkgJson = readFileSafe(pkgJsonPath);
39463
39544
  const basePkgJson = getGitBaseFile(cwd2, "package.json");
39464
39545
  if (basePkgJson !== null) {
39465
39546
  const diff2 = diffPackageJsons(basePkgJson, headPkgJson, config.maxPackages);
@@ -39471,7 +39552,7 @@ function discoverChanges(cwd2, config) {
39471
39552
  };
39472
39553
  });
39473
39554
  return {
39474
- packages: resolved.map(toPackageInput),
39555
+ packages: resolved.map(toPackageInput).filter((p) => p.name !== SELF_PACKAGE),
39475
39556
  method: "fallback",
39476
39557
  skipped: []
39477
39558
  };
@@ -39480,6 +39561,8 @@ function discoverChanges(cwd2, config) {
39480
39561
  const packages = [];
39481
39562
  for (const [name, entry] of headParsed.packages) {
39482
39563
  if (packages.length >= config.maxPackages) break;
39564
+ if (entry.optional && entry.hasPlatformRestriction) continue;
39565
+ if (name === SELF_PACKAGE) continue;
39483
39566
  packages.push({
39484
39567
  name,
39485
39568
  version: entry.version,
@@ -39514,7 +39597,7 @@ function parseLockfileByType(content, type) {
39514
39597
  }
39515
39598
  function getDirectDeps(cwd2) {
39516
39599
  try {
39517
- const content = readFileSync6(join5(cwd2, "package.json"), "utf-8");
39600
+ const content = readFileSafe(join5(cwd2, "package.json"));
39518
39601
  const pkg = JSON.parse(content);
39519
39602
  return /* @__PURE__ */ new Set([
39520
39603
  ...Object.keys(pkg.dependencies ?? {}),
@@ -39526,7 +39609,7 @@ function getDirectDeps(cwd2) {
39526
39609
  }
39527
39610
  function getGitBaseLockfile(cwd2) {
39528
39611
  try {
39529
- const mergeBase = execFileSync("git", ["merge-base", "HEAD", "main"], {
39612
+ const mergeBase = execFileSync2("git", ["merge-base", "HEAD", "main"], {
39530
39613
  cwd: cwd2,
39531
39614
  encoding: "utf-8",
39532
39615
  stdio: ["pipe", "pipe", "pipe"]
@@ -39534,7 +39617,7 @@ function getGitBaseLockfile(cwd2) {
39534
39617
  if (!mergeBase) return null;
39535
39618
  for (const name of ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"]) {
39536
39619
  try {
39537
- return execFileSync("git", ["show", `${mergeBase}:${name}`], {
39620
+ return execFileSync2("git", ["show", `${mergeBase}:${name}`], {
39538
39621
  cwd: cwd2,
39539
39622
  encoding: "utf-8",
39540
39623
  stdio: ["pipe", "pipe", "pipe"]
@@ -39550,13 +39633,13 @@ function getGitBaseLockfile(cwd2) {
39550
39633
  }
39551
39634
  function getGitBaseFile(cwd2, filename) {
39552
39635
  try {
39553
- const mergeBase = execFileSync("git", ["merge-base", "HEAD", "main"], {
39636
+ const mergeBase = execFileSync2("git", ["merge-base", "HEAD", "main"], {
39554
39637
  cwd: cwd2,
39555
39638
  encoding: "utf-8",
39556
39639
  stdio: ["pipe", "pipe", "pipe"]
39557
39640
  }).trim();
39558
39641
  if (!mergeBase) return null;
39559
- return execFileSync("git", ["show", `${mergeBase}:${filename}`], {
39642
+ return execFileSync2("git", ["show", `${mergeBase}:${filename}`], {
39560
39643
  cwd: cwd2,
39561
39644
  encoding: "utf-8",
39562
39645
  stdio: ["pipe", "pipe", "pipe"]
@@ -39574,7 +39657,7 @@ function toPackageInput(change) {
39574
39657
  };
39575
39658
  }
39576
39659
  function parsePythonDepFile(projectDir, depFile) {
39577
- const content = readFileSync6(join5(projectDir, depFile), "utf-8");
39660
+ const content = readFileSafe(join5(projectDir, depFile));
39578
39661
  if (depFile === "Pipfile.lock") {
39579
39662
  try {
39580
39663
  const parsed = JSON.parse(content);
@@ -39622,6 +39705,7 @@ function parsePythonDepFile(projectDir, depFile) {
39622
39705
  }
39623
39706
  return packages;
39624
39707
  }
39708
+ var MAX_LOCKFILE_BYTES, SELF_PACKAGE;
39625
39709
  var init_lockfile = __esm({
39626
39710
  "src/lockfile.ts"() {
39627
39711
  "use strict";
@@ -39630,6 +39714,208 @@ var init_lockfile = __esm({
39630
39714
  init_parse_pnpm_lock();
39631
39715
  init_diff2();
39632
39716
  init_parse_package_json();
39717
+ MAX_LOCKFILE_BYTES = 50 * 1024 * 1024;
39718
+ SELF_PACKAGE = "@westbayberry/dg";
39719
+ }
39720
+ });
39721
+
39722
+ // src/pip-wrapper.ts
39723
+ import { spawn as spawn4 } from "node:child_process";
39724
+ import { readFileSync as readFileSync7, existsSync as existsSync6 } from "node:fs";
39725
+ function parsePipArgs(args) {
39726
+ let dgForce = false;
39727
+ const filtered = [];
39728
+ for (const arg of args) {
39729
+ if (arg === "--dg-force") {
39730
+ dgForce = true;
39731
+ } else {
39732
+ filtered.push(arg);
39733
+ }
39734
+ }
39735
+ const command = filtered[0] ?? "";
39736
+ const shouldScan = INSTALL_COMMANDS2.has(command);
39737
+ const packages = [];
39738
+ let requirementsFile;
39739
+ if (shouldScan) {
39740
+ for (let i = 1; i < filtered.length; i++) {
39741
+ const arg = filtered[i];
39742
+ if (arg === "-r" || arg === "--requirement") {
39743
+ if (i + 1 < filtered.length) {
39744
+ requirementsFile = filtered[i + 1];
39745
+ i++;
39746
+ }
39747
+ continue;
39748
+ }
39749
+ if (arg.startsWith("-")) {
39750
+ if (pipFlagTakesValue(arg)) i++;
39751
+ continue;
39752
+ }
39753
+ packages.push(arg);
39754
+ }
39755
+ }
39756
+ return {
39757
+ command,
39758
+ packages,
39759
+ rawArgs: filtered,
39760
+ dgForce,
39761
+ shouldScan,
39762
+ requirementsFile
39763
+ };
39764
+ }
39765
+ function pipFlagTakesValue(flag) {
39766
+ const valueFlags = [
39767
+ "--index-url",
39768
+ "-i",
39769
+ "--extra-index-url",
39770
+ "--constraint",
39771
+ "-c",
39772
+ "--requirement",
39773
+ "-r",
39774
+ "--target",
39775
+ "-t",
39776
+ "--prefix",
39777
+ "--src",
39778
+ "--root",
39779
+ "--config-settings"
39780
+ ];
39781
+ for (const f of valueFlags) {
39782
+ if (flag === f) return true;
39783
+ }
39784
+ return false;
39785
+ }
39786
+ function parseRequirementsFile(filePath) {
39787
+ if (!existsSync6(filePath)) return [];
39788
+ try {
39789
+ const content = readFileSync7(filePath, "utf-8");
39790
+ const specs = [];
39791
+ for (const rawLine of content.split("\n")) {
39792
+ let line = rawLine.trim();
39793
+ if (!line) continue;
39794
+ if (line.startsWith("#")) continue;
39795
+ if (line.startsWith("-e") || line.startsWith("--editable")) continue;
39796
+ if (/^(git|svn|hg|bzr)\+/.test(line)) continue;
39797
+ if (line.startsWith("-r") || line.startsWith("--requirement")) continue;
39798
+ if (line.startsWith("--")) continue;
39799
+ const commentIdx = line.indexOf(" #");
39800
+ if (commentIdx !== -1) line = line.slice(0, commentIdx).trim();
39801
+ const markerIdx = line.indexOf(";");
39802
+ if (markerIdx !== -1) line = line.slice(0, markerIdx).trim();
39803
+ if (line) specs.push(line);
39804
+ }
39805
+ return specs;
39806
+ } catch {
39807
+ return [];
39808
+ }
39809
+ }
39810
+ function parsePipSpec(spec) {
39811
+ const match = spec.match(/^([a-zA-Z0-9][-a-zA-Z0-9._]*)(([><=!~]=?).+)?$/);
39812
+ if (!match) return { name: spec, versionSpec: null };
39813
+ return {
39814
+ name: match[1],
39815
+ versionSpec: match[2] ?? null
39816
+ };
39817
+ }
39818
+ async function resolvePipVersion(spec) {
39819
+ const { name, versionSpec } = parsePipSpec(spec);
39820
+ if (versionSpec?.startsWith("==")) {
39821
+ const version = versionSpec.slice(2);
39822
+ try {
39823
+ const controller = new AbortController();
39824
+ const timer = setTimeout(() => controller.abort(), 15e3);
39825
+ const resp = await fetch(`https://pypi.org/pypi/${name}/${version}/json`, {
39826
+ signal: controller.signal
39827
+ });
39828
+ clearTimeout(timer);
39829
+ if (resp.ok) return version;
39830
+ return null;
39831
+ } catch {
39832
+ return null;
39833
+ }
39834
+ }
39835
+ try {
39836
+ const controller = new AbortController();
39837
+ const timer = setTimeout(() => controller.abort(), 15e3);
39838
+ const resp = await fetch(`https://pypi.org/pypi/${name}/json`, {
39839
+ signal: controller.signal
39840
+ });
39841
+ clearTimeout(timer);
39842
+ if (!resp.ok) return null;
39843
+ const data = await resp.json();
39844
+ return data.info?.version ?? null;
39845
+ } catch {
39846
+ return null;
39847
+ }
39848
+ }
39849
+ async function resolvePackages2(specs) {
39850
+ const results = await Promise.allSettled(
39851
+ specs.map(async (spec) => {
39852
+ const { name } = parsePipSpec(spec);
39853
+ const version = await resolvePipVersion(spec);
39854
+ return { spec, name, version };
39855
+ })
39856
+ );
39857
+ const resolved = [];
39858
+ const failed = [];
39859
+ for (const result of results) {
39860
+ if (result.status === "rejected") {
39861
+ failed.push("unknown");
39862
+ continue;
39863
+ }
39864
+ const { spec, name, version } = result.value;
39865
+ if (version) {
39866
+ resolved.push({
39867
+ name,
39868
+ version,
39869
+ previousVersion: null,
39870
+ isNew: true
39871
+ });
39872
+ } else {
39873
+ failed.push(spec);
39874
+ }
39875
+ }
39876
+ return { resolved, failed };
39877
+ }
39878
+ async function detectPipBinary() {
39879
+ if (cachedPipBinary) return cachedPipBinary;
39880
+ const candidates = ["python3 -m pip", "python -m pip", "pip3", "pip"];
39881
+ for (const cmd of candidates) {
39882
+ try {
39883
+ const parts = cmd.split(" ");
39884
+ const code = await new Promise((resolve) => {
39885
+ const child = spawn4(parts[0], [...parts.slice(1), "--version"], {
39886
+ stdio: ["pipe", "pipe", "pipe"],
39887
+ timeout: 5e3
39888
+ });
39889
+ child.on("close", (c) => resolve(c ?? 1));
39890
+ child.on("error", () => resolve(1));
39891
+ });
39892
+ if (code === 0) {
39893
+ cachedPipBinary = cmd;
39894
+ return cmd;
39895
+ }
39896
+ } catch {
39897
+ }
39898
+ }
39899
+ throw new Error("pip not found. Install pip or ensure 'python -m pip' is available.");
39900
+ }
39901
+ async function runPip(args) {
39902
+ const pipCmd = await detectPipBinary();
39903
+ const parts = pipCmd.split(" ");
39904
+ return new Promise((resolve) => {
39905
+ const child = spawn4(parts[0], [...parts.slice(1), ...args], {
39906
+ stdio: "inherit",
39907
+ shell: false
39908
+ });
39909
+ child.on("close", (code) => resolve(code ?? 1));
39910
+ child.on("error", () => resolve(1));
39911
+ });
39912
+ }
39913
+ var INSTALL_COMMANDS2, cachedPipBinary;
39914
+ var init_pip_wrapper = __esm({
39915
+ "src/pip-wrapper.ts"() {
39916
+ "use strict";
39917
+ INSTALL_COMMANDS2 = /* @__PURE__ */ new Set(["install", "update"]);
39918
+ cachedPipBinary = null;
39633
39919
  }
39634
39920
  });
39635
39921
 
@@ -39638,7 +39924,7 @@ var discover_exports = {};
39638
39924
  __export(discover_exports, {
39639
39925
  discoverProjects: () => discoverProjects
39640
39926
  });
39641
- import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync, statSync } from "node:fs";
39927
+ import { existsSync as existsSync7, readFileSync as readFileSync8, readdirSync, lstatSync } from "node:fs";
39642
39928
  import { join as join6, relative, basename } from "node:path";
39643
39929
  function discoverProjects(root) {
39644
39930
  const projects = [];
@@ -39649,7 +39935,7 @@ function walk(dir, root, depth, out) {
39649
39935
  if (depth > MAX_DEPTH) return;
39650
39936
  for (const lockfile of NPM_LOCKFILES) {
39651
39937
  const lockPath = join6(dir, lockfile);
39652
- if (existsSync6(lockPath)) {
39938
+ if (existsSync7(lockPath)) {
39653
39939
  const count = countNpmPackages(lockPath);
39654
39940
  if (count > 0) {
39655
39941
  out.push({
@@ -39665,7 +39951,7 @@ function walk(dir, root, depth, out) {
39665
39951
  }
39666
39952
  for (const depFile of PYTHON_DEPFILES) {
39667
39953
  const depPath = join6(dir, depFile);
39668
- if (existsSync6(depPath)) {
39954
+ if (existsSync7(depPath)) {
39669
39955
  const count = countPythonPackages(depPath, depFile);
39670
39956
  if (count > 0) {
39671
39957
  out.push({
@@ -39689,7 +39975,8 @@ function walk(dir, root, depth, out) {
39689
39975
  if (SKIP_DIRS.has(entry) || entry.startsWith(".")) continue;
39690
39976
  const full = join6(dir, entry);
39691
39977
  try {
39692
- if (statSync(full).isDirectory()) {
39978
+ const st = lstatSync(full);
39979
+ if (st.isDirectory() && !st.isSymbolicLink()) {
39693
39980
  walk(full, root, depth + 1, out);
39694
39981
  }
39695
39982
  } catch {
@@ -39699,7 +39986,7 @@ function walk(dir, root, depth, out) {
39699
39986
  function countNpmPackages(lockPath) {
39700
39987
  try {
39701
39988
  const name = basename(lockPath);
39702
- const content = readFileSync7(lockPath, "utf-8");
39989
+ const content = readFileSync8(lockPath, "utf-8");
39703
39990
  if (name === "yarn.lock") {
39704
39991
  return (content.match(/^\S.*:$/gm) || []).length;
39705
39992
  }
@@ -39720,7 +40007,7 @@ function countNpmPackages(lockPath) {
39720
40007
  }
39721
40008
  function countPythonPackages(depPath, depFile) {
39722
40009
  try {
39723
- const content = readFileSync7(depPath, "utf-8");
40010
+ const content = readFileSync8(depPath, "utf-8");
39724
40011
  if (depFile === "Pipfile.lock") {
39725
40012
  const parsed = JSON.parse(content);
39726
40013
  const defaultCount = Object.keys(parsed.default || {}).length;
@@ -39777,40 +40064,50 @@ __export(static_output_exports, {
39777
40064
  renderResultStatic: () => renderResultStatic,
39778
40065
  runStatic: () => runStatic,
39779
40066
  runStaticLogin: () => runStaticLogin,
39780
- runStaticNpm: () => runStaticNpm
40067
+ runStaticNpm: () => runStaticNpm,
40068
+ runStaticPip: () => runStaticPip
39781
40069
  });
39782
40070
  function printTrialBanner(result) {
39783
40071
  if (result.trialScansRemaining === void 0) return;
39784
- const remaining = result.trialScansRemaining;
39785
- if (remaining > 0) {
39786
- process.stderr.write(
39787
- import_chalk4.default.dim(` ${remaining} free scan${remaining !== 1 ? "s" : ""} remaining. `) + import_chalk4.default.dim(`Run \`dg login\` for unlimited scans.
39788
- `)
39789
- );
39790
- } else {
39791
- process.stderr.write(
39792
- import_chalk4.default.yellow(` No free scans remaining. `) + import_chalk4.default.yellow(`Run \`dg login\` to continue scanning.
40072
+ process.stderr.write(
40073
+ import_chalk4.default.dim(` Free tier \xB7 Run \`dg login\` for finding details and higher rate limits.
39793
40074
  `)
39794
- );
39795
- }
40075
+ );
39796
40076
  }
39797
- function handleTrialExhausted2(error) {
40077
+ function handleTrialExhausted2(error, jsonMode = false) {
39798
40078
  if (error instanceof TrialExhaustedError) {
39799
- process.stderr.write(
39800
- import_chalk4.default.yellow("\n Free trial scans used up.\n") + import_chalk4.default.white(" Run `dg login` to create a free account and continue scanning.\n\n")
39801
- );
40079
+ let hasKey = false;
40080
+ try {
40081
+ const { getStoredApiKey: getStoredApiKey2 } = (init_auth(), __toCommonJS(auth_exports));
40082
+ hasKey = !!getStoredApiKey2();
40083
+ } catch {
40084
+ }
40085
+ const message = hasKey ? "Your API key may be invalid or expired. Run `dg logout` then `dg login` to re-authenticate." : "Free trial scans used up. Run `dg login` to create a free account and continue scanning.";
40086
+ if (jsonMode) {
40087
+ process.stdout.write(JSON.stringify({
40088
+ error: true,
40089
+ code: "trial_exhausted",
40090
+ message,
40091
+ scansUsed: error.scansUsed,
40092
+ maxScans: error.maxScans
40093
+ }, null, 2) + "\n");
40094
+ } else {
40095
+ if (hasKey) {
40096
+ process.stderr.write(import_chalk4.default.yellow(`
40097
+ ${message}
40098
+
40099
+ `));
40100
+ } else {
40101
+ process.stderr.write(
40102
+ import_chalk4.default.yellow("\n Free trial scans used up.\n") + import_chalk4.default.white(" Run `dg login` to create a free account and continue scanning.\n\n")
40103
+ );
40104
+ }
40105
+ }
39802
40106
  process.exit(1);
39803
40107
  return true;
39804
40108
  }
39805
40109
  return false;
39806
40110
  }
39807
- function severityColor(sev) {
39808
- if (sev >= 5) return (s) => import_chalk4.default.bold.red(s);
39809
- if (sev >= 4) return import_chalk4.default.red;
39810
- if (sev >= 3) return import_chalk4.default.yellow;
39811
- if (sev >= 2) return import_chalk4.default.cyan;
39812
- return import_chalk4.default.dim;
39813
- }
39814
40111
  function actionColor(action) {
39815
40112
  if (action === "block") return import_chalk4.default.red;
39816
40113
  if (action === "warn") return import_chalk4.default.yellow;
@@ -39833,9 +40130,9 @@ function groupPackages(packages) {
39833
40130
  }
39834
40131
  return [...map.values()].map((pkgs) => ({ packages: pkgs, key: pkgs[0].name })).sort((a, b) => b.packages[0].score - a.packages[0].score);
39835
40132
  }
39836
- function actionBadge(score, config) {
39837
- if (score >= config.blockThreshold) return { label: "BLOCK", color: import_chalk4.default.red };
39838
- if (score >= config.warnThreshold) return { label: "WARN", color: import_chalk4.default.yellow };
40133
+ function actionBadge(score) {
40134
+ if (score >= 70) return { label: "BLOCK", color: import_chalk4.default.red };
40135
+ if (score >= 60) return { label: "WARN", color: import_chalk4.default.yellow };
39839
40136
  return { label: "pass", color: import_chalk4.default.green };
39840
40137
  }
39841
40138
  function renderResultStatic(result, config) {
@@ -39865,6 +40162,15 @@ function renderResultStatic(result, config) {
39865
40162
  ` ${total} package${total !== 1 ? "s" : ""} scanned ${import_chalk4.default.dim("\u2502")} ${import_chalk4.default.green("all clean")}`
39866
40163
  );
39867
40164
  }
40165
+ const licensedPkgs = result.packages.filter((p) => p.license);
40166
+ const licenseIssues = licensedPkgs.filter(
40167
+ (p) => p.license && p.license.riskCategory !== "permissive"
40168
+ );
40169
+ if (licenseIssues.length > 0) {
40170
+ lines.push(
40171
+ ` ${import_chalk4.default.dim("Licenses:")} ${import_chalk4.default.yellow(`${licenseIssues.length} need review`)} \u2502 ${import_chalk4.default.green(`${licensedPkgs.length - licenseIssues.length} permissive`)}`
40172
+ );
40173
+ }
39868
40174
  lines.push("");
39869
40175
  if (total === 0) {
39870
40176
  lines.push(" No packages to scan.");
@@ -39877,10 +40183,13 @@ function renderResultStatic(result, config) {
39877
40183
  lines.push(` ${import_chalk4.default.dim("\u2500".repeat(60))}`);
39878
40184
  for (const group of groups) {
39879
40185
  const rep = group.packages[0];
39880
- const { label, color } = actionBadge(rep.score, config);
40186
+ const { label, color } = actionBadge(rep.score);
39881
40187
  const names = group.packages.length === 1 ? rep.name : group.packages.length <= 3 ? group.packages.map((p) => p.name).join(", ") : `${group.packages[0].name} + ${group.packages.length - 1} similar`;
40188
+ const lcInfo = rep.license;
40189
+ const lcStr = lcInfo ? truncate(lcInfo.spdx ?? lcInfo.raw ?? "", 14) : "";
40190
+ const lcColor = !lcInfo ? import_chalk4.default.dim : lcInfo.riskCategory === "permissive" ? import_chalk4.default.green : lcInfo.riskCategory === "no-license" || lcInfo.riskCategory === "unlicensed" || lcInfo.riskCategory === "network-copyleft" ? import_chalk4.default.red : import_chalk4.default.yellow;
39882
40191
  lines.push(
39883
- ` ${color(pad(label, 7))}${import_chalk4.default.bold(pad(truncate(names, 50), 52))}${import_chalk4.default.dim(`score ${rep.score}`)}`
40192
+ ` ${color(pad(label, 7))}${import_chalk4.default.bold(pad(truncate(names, 36), 38))}${lcColor(pad(lcStr, 16))}${import_chalk4.default.dim(`score ${rep.score}`)}`
39884
40193
  );
39885
40194
  }
39886
40195
  lines.push("");
@@ -39891,46 +40200,6 @@ function renderResultStatic(result, config) {
39891
40200
  );
39892
40201
  lines.push("");
39893
40202
  }
39894
- for (const group of groups) {
39895
- const rep = group.packages[0];
39896
- const names = group.packages.length === 1 ? `${rep.name}@${rep.version}` : group.packages.length <= 3 ? group.packages.map((p) => `${p.name}@${p.version}`).join(", ") : `${rep.name}@${rep.version} + ${group.packages.length - 1} identical packages`;
39897
- lines.push(
39898
- ` ${import_chalk4.default.dim("\u2500\u2500")} ${import_chalk4.default.bold(`${names} (score: ${rep.score})`)}`
39899
- );
39900
- if (group.packages.length > 3) {
39901
- lines.push(
39902
- ` ${import_chalk4.default.dim("Affects: " + group.packages.map((p) => p.name).join(", "))}`
39903
- );
39904
- }
39905
- lines.push("");
39906
- const visibleFindings = rep.findings.filter((f) => f.severity > 1 || f.critical).sort((a, b) => b.severity - a.severity);
39907
- for (const finding of visibleFindings) {
39908
- const sevLabel = SEVERITY_LABELS[finding.severity] ?? "INFO";
39909
- const colorFn = severityColor(finding.severity);
39910
- lines.push(
39911
- ` ${colorFn(pad(sevLabel, 10))}${finding.id} \u2014 ${finding.title}`
39912
- );
39913
- const evidenceLimit = 3;
39914
- for (let i = 0; i < Math.min(finding.evidence.length, evidenceLimit); i++) {
39915
- lines.push(
39916
- ` ${" ".repeat(10)}${import_chalk4.default.dim(truncate(finding.evidence[i], 76))}`
39917
- );
39918
- }
39919
- if (finding.evidence.length > evidenceLimit) {
39920
- lines.push(
39921
- ` ${" ".repeat(10)}${import_chalk4.default.dim(`... and ${finding.evidence.length - evidenceLimit} more`)}`
39922
- );
39923
- }
39924
- lines.push("");
39925
- }
39926
- const safeVersion = result.safeVersions[rep.name];
39927
- if (safeVersion) {
39928
- lines.push(
39929
- ` ${import_chalk4.default.green(`Safe version: ${rep.name}@${safeVersion}`)}`
39930
- );
39931
- lines.push("");
39932
- }
39933
- }
39934
40203
  if (result.durationMs) {
39935
40204
  lines.push(
39936
40205
  ` ${import_chalk4.default.dim(`Completed in ${(result.durationMs / 1e3).toFixed(1)}s`)}`
@@ -39952,7 +40221,7 @@ async function runStatic(config) {
39952
40221
  process.exit(0);
39953
40222
  }
39954
40223
  dbg(
39955
- `mode=${config.mode} block=${config.blockThreshold} warn=${config.warnThreshold} max=${config.maxPackages}`
40224
+ `mode=${config.mode} max=${config.maxPackages}`
39956
40225
  );
39957
40226
  dbg(`api=${config.apiUrl}`);
39958
40227
  process.stderr.write(import_chalk4.default.dim(" Discovering package changes...\n"));
@@ -39979,15 +40248,7 @@ async function runStatic(config) {
39979
40248
  }
39980
40249
  process.exit(0);
39981
40250
  }
39982
- const packages = discovery.packages.filter(
39983
- (p) => !config.allowlist.includes(p.name)
39984
- );
39985
- if (packages.length === 0) {
39986
- process.stderr.write(
39987
- import_chalk4.default.dim(" All changed packages are allowlisted.\n")
39988
- );
39989
- process.exit(0);
39990
- }
40251
+ const packages = discovery.packages;
39991
40252
  process.stderr.write(
39992
40253
  import_chalk4.default.dim(
39993
40254
  ` Scanning ${packages.length} package${packages.length !== 1 ? "s" : ""} (${discovery.method})...
@@ -40016,7 +40277,7 @@ async function runStatic(config) {
40016
40277
  `API responded in ${Date.now() - startMs}ms, action=${result.action}, score=${result.score}`
40017
40278
  );
40018
40279
  } catch (error) {
40019
- if (handleTrialExhausted2(error)) return;
40280
+ if (handleTrialExhausted2(error, config.json)) return;
40020
40281
  throw error;
40021
40282
  }
40022
40283
  const output = renderResultStatic(result, config);
@@ -40088,24 +40349,16 @@ async function runStaticNpm(npmArgs, config) {
40088
40349
  return scanAndInstallStatic(resolved, parsed, config);
40089
40350
  }
40090
40351
  async function scanAndInstallStatic(resolved, parsed, config) {
40091
- const toScan = resolved.filter((p) => !config.allowlist.includes(p.name));
40092
- if (toScan.length === 0) {
40093
- process.stderr.write(
40094
- import_chalk4.default.dim(" All packages are allowlisted. Passing through to npm.\n")
40095
- );
40096
- const code = await runNpm(parsed.rawArgs);
40097
- process.exit(code);
40098
- }
40099
40352
  process.stderr.write(
40100
40353
  import_chalk4.default.dim(
40101
- ` Scanning ${toScan.length} package${toScan.length !== 1 ? "s" : ""}...
40354
+ ` Scanning ${resolved.length} package${resolved.length !== 1 ? "s" : ""}...
40102
40355
  `
40103
40356
  )
40104
40357
  );
40105
40358
  let result;
40106
40359
  try {
40107
40360
  const startMs = Date.now();
40108
- result = await callAnalyzeAPI(toScan, config);
40361
+ result = await callAnalyzeAPI(resolved, config);
40109
40362
  const elapsed = Date.now() - startMs;
40110
40363
  if (config.debug) {
40111
40364
  process.stderr.write(
@@ -40116,7 +40369,7 @@ async function scanAndInstallStatic(resolved, parsed, config) {
40116
40369
  );
40117
40370
  }
40118
40371
  } catch (error) {
40119
- if (handleTrialExhausted2(error)) return;
40372
+ if (handleTrialExhausted2(error, config.json)) return;
40120
40373
  const msg = error instanceof Error ? error.message : String(error);
40121
40374
  process.stderr.write(
40122
40375
  import_chalk4.default.yellow(
@@ -40131,7 +40384,7 @@ async function scanAndInstallStatic(resolved, parsed, config) {
40131
40384
  if (result.action === "pass") {
40132
40385
  process.stderr.write(
40133
40386
  import_chalk4.default.green(
40134
- ` ${import_chalk4.default.bold("\u2713")} ${toScan.length} package${toScan.length !== 1 ? "s" : ""} scanned \u2014 all clear
40387
+ ` ${import_chalk4.default.bold("\u2713")} ${resolved.length} package${resolved.length !== 1 ? "s" : ""} scanned \u2014 all clear
40135
40388
  `
40136
40389
  )
40137
40390
  );
@@ -40171,6 +40424,102 @@ async function scanAndInstallStatic(resolved, parsed, config) {
40171
40424
  process.exit(2);
40172
40425
  }
40173
40426
  }
40427
+ async function runStaticPip(pipArgs, config) {
40428
+ const parsed = parsePipArgs(pipArgs);
40429
+ if (!parsed.shouldScan) {
40430
+ const code = await runPip(parsed.rawArgs);
40431
+ process.exit(code);
40432
+ }
40433
+ let specs = parsed.packages;
40434
+ if (specs.length === 0 && parsed.requirementsFile) {
40435
+ specs = parseRequirementsFile(parsed.requirementsFile);
40436
+ }
40437
+ if (specs.length === 0) {
40438
+ process.stderr.write(
40439
+ import_chalk4.default.dim(" Dependency Guardian: no packages to scan, passing through to pip.\n")
40440
+ );
40441
+ const code = await runPip(parsed.rawArgs);
40442
+ process.exit(code);
40443
+ }
40444
+ process.stderr.write(
40445
+ import_chalk4.default.dim(
40446
+ ` Resolving ${specs.length} package${specs.length !== 1 ? "s" : ""} from PyPI...
40447
+ `
40448
+ )
40449
+ );
40450
+ const { resolved, failed } = await resolvePackages2(specs);
40451
+ if (failed.length > 0) {
40452
+ process.stderr.write(
40453
+ import_chalk4.default.yellow(` Warning: Could not resolve versions for: ${failed.join(", ")}
40454
+ `)
40455
+ );
40456
+ }
40457
+ if (resolved.length === 0) {
40458
+ process.stderr.write(
40459
+ import_chalk4.default.dim(" No packages to scan. Passing through to pip.\n")
40460
+ );
40461
+ const code = await runPip(parsed.rawArgs);
40462
+ process.exit(code);
40463
+ }
40464
+ process.stderr.write(
40465
+ import_chalk4.default.dim(
40466
+ ` Scanning ${resolved.length} Python package${resolved.length !== 1 ? "s" : ""}...
40467
+ `
40468
+ )
40469
+ );
40470
+ let result;
40471
+ try {
40472
+ result = await callPyPIAnalyzeAPI(resolved, config);
40473
+ } catch (error) {
40474
+ if (handleTrialExhausted2(error, config.json)) return;
40475
+ const msg = error instanceof Error ? error.message : String(error);
40476
+ process.stderr.write(
40477
+ import_chalk4.default.yellow(` Warning: Scan failed (${msg}). Proceeding with install.
40478
+ `)
40479
+ );
40480
+ const code = await runPip(parsed.rawArgs);
40481
+ process.exit(code);
40482
+ return;
40483
+ }
40484
+ if (result.action === "pass") {
40485
+ process.stderr.write(
40486
+ import_chalk4.default.green(
40487
+ ` ${import_chalk4.default.bold("\u2713")} ${resolved.length} package${resolved.length !== 1 ? "s" : ""} scanned \u2014 all clear
40488
+ `
40489
+ )
40490
+ );
40491
+ printTrialBanner(result);
40492
+ process.stderr.write("\n");
40493
+ const code = await runPip(parsed.rawArgs);
40494
+ process.exit(code);
40495
+ }
40496
+ const output = renderResultStatic(result, config);
40497
+ process.stdout.write(output + "\n");
40498
+ printTrialBanner(result);
40499
+ if (result.action === "warn") {
40500
+ process.stderr.write(
40501
+ import_chalk4.default.yellow(" Warnings detected. Proceeding with install.\n\n")
40502
+ );
40503
+ const code = await runPip(parsed.rawArgs);
40504
+ process.exit(code);
40505
+ }
40506
+ if (result.action === "block") {
40507
+ if (parsed.dgForce) {
40508
+ process.stderr.write(
40509
+ import_chalk4.default.yellow(import_chalk4.default.bold(" --dg-force: Bypassing block. Install at your own risk.\n\n"))
40510
+ );
40511
+ const code = await runPip(parsed.rawArgs);
40512
+ process.exit(code);
40513
+ }
40514
+ process.stderr.write(
40515
+ import_chalk4.default.red(import_chalk4.default.bold(" BLOCKED: ")) + import_chalk4.default.red("High-risk packages detected. Install aborted.\n")
40516
+ );
40517
+ process.stderr.write(
40518
+ import_chalk4.default.dim(" Use --dg-force to bypass this check.\n\n")
40519
+ );
40520
+ process.exit(2);
40521
+ }
40522
+ }
40174
40523
  async function runStaticLogin() {
40175
40524
  const {
40176
40525
  createAuthSession: createAuthSession2,
@@ -40248,7 +40597,7 @@ async function runStaticLogin() {
40248
40597
  );
40249
40598
  process.exit(1);
40250
40599
  }
40251
- var import_chalk4, SEVERITY_LABELS;
40600
+ var import_chalk4;
40252
40601
  var init_static_output = __esm({
40253
40602
  "src/static-output.ts"() {
40254
40603
  "use strict";
@@ -40256,13 +40605,7 @@ var init_static_output = __esm({
40256
40605
  init_api();
40257
40606
  init_lockfile();
40258
40607
  init_npm_wrapper();
40259
- SEVERITY_LABELS = {
40260
- 5: "CRITICAL",
40261
- 4: "HIGH",
40262
- 3: "MEDIUM",
40263
- 2: "LOW",
40264
- 1: "INFO"
40265
- };
40608
+ init_pip_wrapper();
40266
40609
  }
40267
40610
  });
40268
40611
 
@@ -40271,10 +40614,10 @@ var hook_exports = {};
40271
40614
  __export(hook_exports, {
40272
40615
  handleHookCommand: () => handleHookCommand
40273
40616
  });
40274
- import { execFileSync as execFileSync2 } from "node:child_process";
40617
+ import { execFileSync as execFileSync3 } from "node:child_process";
40275
40618
  import {
40276
- existsSync as existsSync7,
40277
- readFileSync as readFileSync8,
40619
+ existsSync as existsSync8,
40620
+ readFileSync as readFileSync9,
40278
40621
  writeFileSync as writeFileSync3,
40279
40622
  mkdirSync,
40280
40623
  chmodSync as chmodSync2,
@@ -40283,7 +40626,7 @@ import {
40283
40626
  import { join as join7 } from "node:path";
40284
40627
  function findGitDir() {
40285
40628
  try {
40286
- return execFileSync2("git", ["rev-parse", "--git-dir"], {
40629
+ return execFileSync3("git", ["rev-parse", "--git-dir"], {
40287
40630
  encoding: "utf-8",
40288
40631
  stdio: ["pipe", "pipe", "pipe"]
40289
40632
  }).trim();
@@ -40296,8 +40639,8 @@ function installHook() {
40296
40639
  const hooksDir = join7(gitDir, "hooks");
40297
40640
  const hookPath = join7(hooksDir, "pre-commit");
40298
40641
  mkdirSync(hooksDir, { recursive: true });
40299
- if (existsSync7(hookPath)) {
40300
- const existing = readFileSync8(hookPath, "utf-8");
40642
+ if (existsSync8(hookPath)) {
40643
+ const existing = readFileSync9(hookPath, "utf-8");
40301
40644
  if (existing.includes(HOOK_MARKER)) {
40302
40645
  process.stderr.write(" Hook already installed.\n");
40303
40646
  return;
@@ -40318,11 +40661,11 @@ function installHook() {
40318
40661
  function uninstallHook() {
40319
40662
  const gitDir = findGitDir();
40320
40663
  const hookPath = join7(gitDir, "hooks", "pre-commit");
40321
- if (!existsSync7(hookPath)) {
40664
+ if (!existsSync8(hookPath)) {
40322
40665
  process.stderr.write(" No hook to remove.\n");
40323
40666
  return;
40324
40667
  }
40325
- const content = readFileSync8(hookPath, "utf-8");
40668
+ const content = readFileSync9(hookPath, "utf-8");
40326
40669
  if (!content.includes(HOOK_MARKER)) {
40327
40670
  process.stderr.write(
40328
40671
  " No Dependency Guardian hook found in pre-commit.\n"
@@ -40517,19 +40860,10 @@ function useNpmWrapper(npmArgs, config) {
40517
40860
  dispatch({ type: "DONE", exitCode: code });
40518
40861
  return;
40519
40862
  }
40520
- const toScan = resolved.filter(
40521
- (p) => !config.allowlist.includes(p.name)
40522
- );
40523
- if (toScan.length === 0) {
40524
- dispatch({ type: "INSTALLING" });
40525
- const code = await runNpm(parsed.rawArgs);
40526
- dispatch({ type: "DONE", exitCode: code });
40527
- return;
40528
- }
40529
- dispatch({ type: "SCANNING", count: toScan.length });
40530
- const result = await callAnalyzeAPI(toScan, config);
40863
+ dispatch({ type: "SCANNING", count: resolved.length });
40864
+ const result = await callAnalyzeAPI(resolved, config);
40531
40865
  if (result.action === "pass") {
40532
- dispatch({ type: "PASS", count: toScan.length });
40866
+ dispatch({ type: "PASS", count: resolved.length });
40533
40867
  dispatch({ type: "INSTALLING" });
40534
40868
  const code = await runNpm(parsed.rawArgs);
40535
40869
  dispatch({ type: "DONE", exitCode: code });
@@ -40728,6 +41062,7 @@ var init_ScoreHeader = __esm({
40728
41062
  severityCounts
40729
41063
  }) => {
40730
41064
  const logo = renderLogo(action);
41065
+ const showLogo = (process.stdout.columns ?? 80) >= 60;
40731
41066
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
40732
41067
  Box_default,
40733
41068
  {
@@ -40767,7 +41102,7 @@ var init_ScoreHeader = __esm({
40767
41102
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: `\x1B]8;;https://westbayberry.com\x07westbayberry.com\x1B]8;;\x07` })
40768
41103
  ] })
40769
41104
  ] }),
40770
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { flexDirection: "column", marginLeft: 2, children: logo.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: line }, i)) })
41105
+ showLogo && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { flexDirection: "column", marginLeft: 2, children: logo.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: line }, i)) })
40771
41106
  ] })
40772
41107
  }
40773
41108
  );
@@ -40801,16 +41136,16 @@ function groupPackages2(packages) {
40801
41136
  }
40802
41137
  return [...map.values()].map((pkgs) => ({ packages: pkgs, key: pkgs[0].name })).sort((a, b) => b.packages[0].score - a.packages[0].score);
40803
41138
  }
40804
- function severityColor2(sev) {
41139
+ function severityColor(sev) {
40805
41140
  if (sev >= 5) return (s) => import_chalk6.default.bold.red(s);
40806
41141
  if (sev >= 4) return import_chalk6.default.magenta;
40807
41142
  if (sev >= 3) return import_chalk6.default.yellow;
40808
41143
  if (sev >= 2) return import_chalk6.default.dim;
40809
41144
  return import_chalk6.default.dim;
40810
41145
  }
40811
- function actionBadge2(score, config) {
40812
- if (score >= config.blockThreshold) return { label: "BLOCK", color: import_chalk6.default.red };
40813
- if (score >= config.warnThreshold) return { label: "WARN", color: import_chalk6.default.yellow };
41146
+ function actionBadge2(score) {
41147
+ if (score >= 70) return { label: "BLOCK", color: import_chalk6.default.red };
41148
+ if (score >= 60) return { label: "WARN", color: import_chalk6.default.yellow };
40814
41149
  return { label: "PASS", color: import_chalk6.default.green };
40815
41150
  }
40816
41151
  function truncate2(s, max) {
@@ -40819,7 +41154,7 @@ function truncate2(s, max) {
40819
41154
  function pad2(s, len) {
40820
41155
  return s + " ".repeat(Math.max(0, len - s.length));
40821
41156
  }
40822
- var import_chalk6, import_jsx_runtime5, SEVERITY_LABELS2, EVIDENCE_LIMIT, ResultsView;
41157
+ var import_chalk6, import_jsx_runtime5, SEVERITY_LABELS, EVIDENCE_LIMIT, ResultsView;
40823
41158
  var init_ResultsView = __esm({
40824
41159
  async "src/ui/components/ResultsView.tsx"() {
40825
41160
  "use strict";
@@ -40828,7 +41163,7 @@ var init_ResultsView = __esm({
40828
41163
  await init_ScoreHeader();
40829
41164
  await init_DurationLine();
40830
41165
  import_jsx_runtime5 = __toESM(require_jsx_runtime());
40831
- SEVERITY_LABELS2 = {
41166
+ SEVERITY_LABELS = {
40832
41167
  5: "CRITICAL",
40833
41168
  4: "HIGH",
40834
41169
  3: "MEDIUM",
@@ -40867,7 +41202,7 @@ var init_ResultsView = __esm({
40867
41202
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { dimColor: true, children: "\u2500".repeat(60) }),
40868
41203
  groups.map((group) => {
40869
41204
  const rep = group.packages[0];
40870
- const { label, color } = actionBadge2(rep.score, config);
41205
+ const { label, color } = actionBadge2(rep.score);
40871
41206
  const names = group.packages.length === 1 ? rep.name : group.packages.length <= 3 ? group.packages.map((p) => p.name).join(", ") : `${group.packages[0].name} + ${group.packages.length - 1} similar`;
40872
41207
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { children: [
40873
41208
  " ",
@@ -40903,8 +41238,8 @@ var init_ResultsView = __esm({
40903
41238
  ] }),
40904
41239
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { children: " " }),
40905
41240
  visibleFindings.map((finding, idx) => {
40906
- const sevLabel = SEVERITY_LABELS2[finding.severity] ?? "INFO";
40907
- const colorFn = severityColor2(finding.severity);
41241
+ const sevLabel = SEVERITY_LABELS[finding.severity] ?? "INFO";
41242
+ const colorFn = severityColor(finding.severity);
40908
41243
  const evidenceSlice = finding.evidence.slice(0, EVIDENCE_LIMIT);
40909
41244
  const overflow = finding.evidence.length - EVIDENCE_LIMIT;
40910
41245
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", children: [
@@ -41002,6 +41337,7 @@ var init_ErrorView = __esm({
41002
41337
  "use strict";
41003
41338
  await init_build2();
41004
41339
  import_chalk8 = __toESM(require_source());
41340
+ init_sanitize();
41005
41341
  import_jsx_runtime7 = __toESM(require_jsx_runtime());
41006
41342
  ErrorView = ({ error }) => {
41007
41343
  const hint = getHint(error);
@@ -41015,7 +41351,7 @@ var init_ErrorView = __esm({
41015
41351
  paddingRight: 1,
41016
41352
  children: [
41017
41353
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: import_chalk8.default.red.bold("\u2718 Error") }),
41018
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: error.message }),
41354
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: sanitize(error.message) }),
41019
41355
  hint && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { children: [
41020
41356
  import_chalk8.default.yellow("\u2192"),
41021
41357
  " ",
@@ -41161,22 +41497,349 @@ var init_NpmWrapperApp = __esm({
41161
41497
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ErrorView, { error: new Error(state.message) });
41162
41498
  case "passthrough":
41163
41499
  return null;
41164
- case "trial_exhausted":
41165
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 2, children: [
41500
+ case "trial_exhausted": {
41501
+ let hasKey = false;
41502
+ try {
41503
+ const { getStoredApiKey: getStoredApiKey2 } = (init_auth(), __toCommonJS(auth_exports));
41504
+ hasKey = !!getStoredApiKey2();
41505
+ } catch {
41506
+ }
41507
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { flexDirection: "column", paddingLeft: 2, children: hasKey ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
41508
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "yellow", bold: true, children: "Your API key may be invalid or expired." }),
41509
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { children: [
41510
+ "Run ",
41511
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "cyan", bold: true, children: "dg logout" }),
41512
+ " then ",
41513
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "cyan", bold: true, children: "dg login" }),
41514
+ " to re-authenticate."
41515
+ ] })
41516
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
41166
41517
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "yellow", bold: true, children: "Free trial scans used up." }),
41167
41518
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { children: [
41168
41519
  "Run ",
41169
41520
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "cyan", bold: true, children: "dg login" }),
41170
41521
  " to create a free account and continue scanning."
41171
41522
  ] })
41523
+ ] }) });
41524
+ }
41525
+ }
41526
+ };
41527
+ }
41528
+ });
41529
+
41530
+ // src/ui/hooks/usePipWrapper.ts
41531
+ function reducer3(_state, action) {
41532
+ switch (action.type) {
41533
+ case "PASSTHROUGH":
41534
+ return { phase: "passthrough" };
41535
+ case "RESOLVING":
41536
+ return { phase: "resolving", count: action.count };
41537
+ case "SCANNING":
41538
+ return { phase: "scanning", count: action.count };
41539
+ case "PASS":
41540
+ return { phase: "pass", count: action.count };
41541
+ case "WARN":
41542
+ return { phase: "warn", result: action.result };
41543
+ case "BLOCKED":
41544
+ return { phase: "blocked", result: action.result, dgForce: action.dgForce };
41545
+ case "INSTALLING":
41546
+ return { phase: "installing" };
41547
+ case "DONE":
41548
+ return { phase: "done", exitCode: action.exitCode };
41549
+ case "ERROR":
41550
+ return { phase: "error", message: action.message, proceed: action.proceed };
41551
+ case "TRIAL_EXHAUSTED":
41552
+ return { phase: "trial_exhausted" };
41553
+ }
41554
+ }
41555
+ function usePipWrapper(pipArgs, config) {
41556
+ const [state, dispatch] = (0, import_react27.useReducer)(reducer3, { phase: "resolving", count: 0 });
41557
+ const started = (0, import_react27.useRef)(false);
41558
+ const parsedRef = (0, import_react27.useRef)(parsePipArgs(pipArgs));
41559
+ const pendingInstall = (0, import_react27.useRef)(null);
41560
+ const rejectRef = (0, import_react27.useRef)(null);
41561
+ const confirmInstall = () => {
41562
+ if (pendingInstall.current) {
41563
+ pendingInstall.current();
41564
+ pendingInstall.current = null;
41565
+ rejectRef.current = null;
41566
+ }
41567
+ };
41568
+ const rejectInstall = () => {
41569
+ if (rejectRef.current) {
41570
+ rejectRef.current();
41571
+ pendingInstall.current = null;
41572
+ rejectRef.current = null;
41573
+ }
41574
+ };
41575
+ (0, import_react27.useEffect)(() => {
41576
+ if (started.current) return;
41577
+ started.current = true;
41578
+ const parsed = parsedRef.current;
41579
+ (async () => {
41580
+ try {
41581
+ if (!parsed.shouldScan) {
41582
+ dispatch({ type: "PASSTHROUGH" });
41583
+ const code = await runPip(parsed.rawArgs);
41584
+ dispatch({ type: "DONE", exitCode: code });
41585
+ return;
41586
+ }
41587
+ let specs;
41588
+ if (parsed.packages.length === 0 && parsed.requirementsFile) {
41589
+ specs = parseRequirementsFile(parsed.requirementsFile);
41590
+ if (specs.length === 0) {
41591
+ dispatch({ type: "PASSTHROUGH" });
41592
+ const code = await runPip(parsed.rawArgs);
41593
+ dispatch({ type: "DONE", exitCode: code });
41594
+ return;
41595
+ }
41596
+ } else if (parsed.packages.length === 0) {
41597
+ dispatch({ type: "PASSTHROUGH" });
41598
+ const code = await runPip(parsed.rawArgs);
41599
+ dispatch({ type: "DONE", exitCode: code });
41600
+ return;
41601
+ } else {
41602
+ specs = parsed.packages;
41603
+ }
41604
+ dispatch({ type: "RESOLVING", count: specs.length });
41605
+ const { resolved, failed } = await resolvePackages2(specs);
41606
+ if (resolved.length === 0) {
41607
+ dispatch({ type: "PASSTHROUGH" });
41608
+ const code = await runPip(parsed.rawArgs);
41609
+ dispatch({ type: "DONE", exitCode: code });
41610
+ return;
41611
+ }
41612
+ dispatch({ type: "SCANNING", count: resolved.length });
41613
+ const result = await callPyPIAnalyzeAPI(resolved, config);
41614
+ if (result.action === "pass") {
41615
+ dispatch({ type: "PASS", count: resolved.length });
41616
+ dispatch({ type: "INSTALLING" });
41617
+ const code = await runPip(parsed.rawArgs);
41618
+ dispatch({ type: "DONE", exitCode: code });
41619
+ return;
41620
+ }
41621
+ if (result.action === "warn") {
41622
+ dispatch({ type: "WARN", result });
41623
+ process.stdout.write(renderResultStatic(result, config) + "\n");
41624
+ dispatch({ type: "INSTALLING" });
41625
+ const code = await runPip(parsed.rawArgs);
41626
+ dispatch({ type: "DONE", exitCode: code });
41627
+ return;
41628
+ }
41629
+ if (result.action === "block") {
41630
+ process.stdout.write(renderResultStatic(result, config) + "\n");
41631
+ if (parsed.dgForce) {
41632
+ dispatch({ type: "BLOCKED", result, dgForce: true });
41633
+ dispatch({ type: "INSTALLING" });
41634
+ const code = await runPip(parsed.rawArgs);
41635
+ dispatch({ type: "DONE", exitCode: code });
41636
+ return;
41637
+ }
41638
+ dispatch({ type: "BLOCKED", result, dgForce: false });
41639
+ const shouldProceed = await new Promise((resolve) => {
41640
+ pendingInstall.current = () => resolve(true);
41641
+ rejectRef.current = () => resolve(false);
41642
+ });
41643
+ if (shouldProceed) {
41644
+ dispatch({ type: "INSTALLING" });
41645
+ const code = await runPip(parsed.rawArgs);
41646
+ dispatch({ type: "DONE", exitCode: code });
41647
+ } else {
41648
+ dispatch({ type: "DONE", exitCode: 2 });
41649
+ }
41650
+ return;
41651
+ }
41652
+ } catch (error) {
41653
+ if (error instanceof TrialExhaustedError) {
41654
+ dispatch({ type: "TRIAL_EXHAUSTED" });
41655
+ return;
41656
+ }
41657
+ const message = error instanceof Error ? error.message : String(error);
41658
+ dispatch({ type: "ERROR", message, proceed: true });
41659
+ dispatch({ type: "INSTALLING" });
41660
+ const code = await runPip(parsedRef.current.rawArgs);
41661
+ dispatch({ type: "DONE", exitCode: code });
41662
+ }
41663
+ })();
41664
+ }, [pipArgs, config]);
41665
+ return { state, confirmInstall, rejectInstall };
41666
+ }
41667
+ var import_react27;
41668
+ var init_usePipWrapper = __esm({
41669
+ "src/ui/hooks/usePipWrapper.ts"() {
41670
+ "use strict";
41671
+ import_react27 = __toESM(require_react());
41672
+ init_pip_wrapper();
41673
+ init_api();
41674
+ init_static_output();
41675
+ }
41676
+ });
41677
+
41678
+ // src/ui/PipWrapperApp.tsx
41679
+ var PipWrapperApp_exports = {};
41680
+ __export(PipWrapperApp_exports, {
41681
+ PipWrapperApp: () => PipWrapperApp
41682
+ });
41683
+ var import_react28, import_jsx_runtime9, PipWrapperApp;
41684
+ var init_PipWrapperApp = __esm({
41685
+ async "src/ui/PipWrapperApp.tsx"() {
41686
+ "use strict";
41687
+ import_react28 = __toESM(require_react());
41688
+ await init_build2();
41689
+ init_usePipWrapper();
41690
+ await init_Spinner();
41691
+ await init_ResultsView();
41692
+ await init_ConfirmPrompt();
41693
+ await init_ErrorView();
41694
+ import_jsx_runtime9 = __toESM(require_jsx_runtime());
41695
+ PipWrapperApp = ({
41696
+ pipArgs,
41697
+ config
41698
+ }) => {
41699
+ const { state, confirmInstall, rejectInstall } = usePipWrapper(pipArgs, config);
41700
+ const { exit } = use_app_default();
41701
+ (0, import_react28.useEffect)(() => {
41702
+ if (state.phase === "done") {
41703
+ process.exitCode = state.exitCode;
41704
+ const timer = setTimeout(() => exit(), 0);
41705
+ return () => clearTimeout(timer);
41706
+ }
41707
+ if (state.phase === "trial_exhausted") {
41708
+ process.exitCode = 1;
41709
+ const timer = setTimeout(() => exit(), 0);
41710
+ return () => clearTimeout(timer);
41711
+ }
41712
+ if (state.phase === "passthrough") {
41713
+ }
41714
+ }, [state, exit]);
41715
+ const handleConfirm = (0, import_react28.useCallback)(() => {
41716
+ confirmInstall();
41717
+ }, [confirmInstall]);
41718
+ const handleReject = (0, import_react28.useCallback)(() => {
41719
+ rejectInstall();
41720
+ }, [rejectInstall]);
41721
+ switch (state.phase) {
41722
+ case "resolving":
41723
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
41724
+ Spinner2,
41725
+ {
41726
+ label: `Resolving ${state.count} package${state.count !== 1 ? "s" : ""} from PyPI...`
41727
+ }
41728
+ );
41729
+ case "scanning":
41730
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
41731
+ Spinner2,
41732
+ {
41733
+ label: `Scanning ${state.count} Python package${state.count !== 1 ? "s" : ""}...`
41734
+ }
41735
+ );
41736
+ case "pass":
41737
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: "green", children: [
41738
+ "\u2713",
41739
+ " ",
41740
+ state.count,
41741
+ " package",
41742
+ state.count !== 1 ? "s" : "",
41743
+ " scanned",
41744
+ " \u2014 ",
41745
+ "all clear"
41746
+ ] });
41747
+ case "warn":
41748
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { flexDirection: "column", children: [
41749
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
41750
+ ResultsView,
41751
+ {
41752
+ result: state.result,
41753
+ config,
41754
+ durationMs: state.result.durationMs
41755
+ }
41756
+ ),
41757
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "yellow", children: "Warnings detected. Proceeding with install..." })
41758
+ ] });
41759
+ case "blocked":
41760
+ if (state.dgForce) {
41761
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { flexDirection: "column", children: [
41762
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
41763
+ ResultsView,
41764
+ {
41765
+ result: state.result,
41766
+ config,
41767
+ durationMs: state.result.durationMs
41768
+ }
41769
+ ),
41770
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "yellow", bold: true, children: "--dg-force: Bypassing block. Install at your own risk." })
41771
+ ] });
41772
+ }
41773
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { flexDirection: "column", children: [
41774
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
41775
+ ResultsView,
41776
+ {
41777
+ result: state.result,
41778
+ config,
41779
+ durationMs: state.result.durationMs
41780
+ }
41781
+ ),
41782
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: "red", bold: true, children: [
41783
+ "BLOCKED:",
41784
+ " ",
41785
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "red", children: "High-risk packages detected." })
41786
+ ] }),
41787
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
41788
+ ConfirmPrompt,
41789
+ {
41790
+ message: "Proceed with install anyway?",
41791
+ onConfirm: handleConfirm,
41792
+ onReject: handleReject
41793
+ }
41794
+ )
41172
41795
  ] });
41796
+ case "installing":
41797
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Spinner2, { label: "Installing with pip..." });
41798
+ case "done":
41799
+ return null;
41800
+ case "error":
41801
+ if (state.proceed) {
41802
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: "yellow", children: [
41803
+ "Warning: Scan failed (",
41804
+ state.message,
41805
+ "). Proceeding with install."
41806
+ ] });
41807
+ }
41808
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ErrorView, { error: new Error(state.message) });
41809
+ case "passthrough":
41810
+ return null;
41811
+ case "trial_exhausted": {
41812
+ let hasKey = false;
41813
+ try {
41814
+ const { getStoredApiKey: getStoredApiKey2 } = (init_auth(), __toCommonJS(auth_exports));
41815
+ hasKey = !!getStoredApiKey2();
41816
+ } catch {
41817
+ }
41818
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box_default, { flexDirection: "column", paddingLeft: 2, children: hasKey ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
41819
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "yellow", bold: true, children: "Your API key may be invalid or expired." }),
41820
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { children: [
41821
+ "Run ",
41822
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "cyan", bold: true, children: "dg logout" }),
41823
+ " then ",
41824
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "cyan", bold: true, children: "dg login" }),
41825
+ " to re-authenticate."
41826
+ ] })
41827
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
41828
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "yellow", bold: true, children: "Free trial scans used up." }),
41829
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { children: [
41830
+ "Run ",
41831
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "cyan", bold: true, children: "dg login" }),
41832
+ " to create a free account and continue scanning."
41833
+ ] })
41834
+ ] }) });
41835
+ }
41173
41836
  }
41174
41837
  };
41175
41838
  }
41176
41839
  });
41177
41840
 
41178
41841
  // src/ui/hooks/useScan.ts
41179
- function reducer3(_state, action) {
41842
+ function reducer4(_state, action) {
41180
41843
  switch (action.type) {
41181
41844
  case "PROJECTS_FOUND":
41182
41845
  return { phase: "selecting", projects: action.projects };
@@ -41197,10 +41860,10 @@ function reducer3(_state, action) {
41197
41860
  }
41198
41861
  }
41199
41862
  function useScan(config) {
41200
- const [state, dispatch] = (0, import_react27.useReducer)(reducer3, { phase: "discovering" });
41201
- const started = (0, import_react27.useRef)(false);
41202
- const [multiProjects, setMultiProjects] = (0, import_react27.useState)(null);
41203
- (0, import_react27.useEffect)(() => {
41863
+ const [state, dispatch] = (0, import_react29.useReducer)(reducer4, { phase: "discovering" });
41864
+ const started = (0, import_react29.useRef)(false);
41865
+ const [multiProjects, setMultiProjects] = (0, import_react29.useState)(null);
41866
+ (0, import_react29.useEffect)(() => {
41204
41867
  if (started.current) return;
41205
41868
  started.current = true;
41206
41869
  const projects = discoverProjects(process.cwd());
@@ -41211,10 +41874,9 @@ function useScan(config) {
41211
41874
  }
41212
41875
  try {
41213
41876
  const discovery = discoverChanges(process.cwd(), config);
41214
- const packages = discovery.packages.filter((p) => !config.allowlist.includes(p.name));
41877
+ const packages = discovery.packages;
41215
41878
  if (packages.length === 0) {
41216
- const message = discovery.packages.length === 0 ? "No package changes detected." : "All changed packages are allowlisted.";
41217
- dispatch({ type: "DISCOVERY_EMPTY", message });
41879
+ dispatch({ type: "DISCOVERY_EMPTY", message: "No package changes detected." });
41218
41880
  return;
41219
41881
  }
41220
41882
  dispatch({ type: "DISCOVERY_COMPLETE", packages, skippedCount: discovery.skipped.length });
@@ -41228,11 +41890,11 @@ function useScan(config) {
41228
41890
  scanProjects(projects, config, dispatch);
41229
41891
  }
41230
41892
  }, [config]);
41231
- const scanSelectedProjects = (0, import_react27.useCallback)((projects) => {
41893
+ const scanSelectedProjects = (0, import_react29.useCallback)((projects) => {
41232
41894
  dispatch({ type: "DISCOVERY_COMPLETE", packages: [], skippedCount: 0 });
41233
41895
  scanProjects(projects, config, dispatch);
41234
41896
  }, [config]);
41235
- const restartSelection = (0, import_react27.useCallback)(() => {
41897
+ const restartSelection = (0, import_react29.useCallback)(() => {
41236
41898
  if (!multiProjects) return;
41237
41899
  dispatch({ type: "RESTART_SELECTION", projects: multiProjects });
41238
41900
  }, [multiProjects]);
@@ -41273,7 +41935,7 @@ async function scanProjects(projects, config, dispatch) {
41273
41935
  const discovery = discoverChanges(proj.path, fullScanConfig);
41274
41936
  for (const pkg of discovery.packages) {
41275
41937
  const key = `${pkg.name}@${pkg.version}`;
41276
- if (!config.allowlist.includes(pkg.name) && !seenNpm.has(key)) {
41938
+ if (!seenNpm.has(key)) {
41277
41939
  seenNpm.add(key);
41278
41940
  npmPackages.push(pkg);
41279
41941
  }
@@ -41286,7 +41948,7 @@ async function scanProjects(projects, config, dispatch) {
41286
41948
  const packages = parsePythonDepFile(proj.path, proj.depFile);
41287
41949
  for (const pkg of packages) {
41288
41950
  const key = `${pkg.name}@${pkg.version}`;
41289
- if (!config.allowlist.includes(pkg.name) && !seenPypi.has(key)) {
41951
+ if (!seenPypi.has(key)) {
41290
41952
  seenPypi.add(key);
41291
41953
  pypiPackages.push(pkg);
41292
41954
  }
@@ -41335,7 +41997,7 @@ async function scanProjects(projects, config, dispatch) {
41335
41997
  }
41336
41998
  const allPackages = results.flatMap((r) => r.packages);
41337
41999
  const maxScore = Math.max(0, ...allPackages.map((p) => p.score));
41338
- const action = maxScore >= config.blockThreshold ? "block" : maxScore >= config.warnThreshold ? "warn" : "pass";
42000
+ const action = maxScore >= 70 ? "block" : maxScore >= 60 ? "warn" : "pass";
41339
42001
  const safeVersions = {};
41340
42002
  for (const r of results) Object.assign(safeVersions, r.safeVersions);
41341
42003
  const merged = {
@@ -41354,11 +42016,11 @@ async function scanProjects(projects, config, dispatch) {
41354
42016
  dispatch({ type: "ERROR", error: error instanceof Error ? error : new Error(String(error)) });
41355
42017
  }
41356
42018
  }
41357
- var import_react27;
42019
+ var import_react29;
41358
42020
  var init_useScan = __esm({
41359
42021
  "src/ui/hooks/useScan.ts"() {
41360
42022
  "use strict";
41361
- import_react27 = __toESM(require_react());
42023
+ import_react29 = __toESM(require_react());
41362
42024
  init_lockfile();
41363
42025
  init_api();
41364
42026
  init_discover();
@@ -41368,7 +42030,7 @@ var init_useScan = __esm({
41368
42030
 
41369
42031
  // src/ui/components/ProgressBar.tsx
41370
42032
  function estimateScanSeconds(packageCount) {
41371
- return Math.ceil(8 + packageCount * 0.12);
42033
+ return Math.ceil(Math.max(5, packageCount * 0.35 - 10));
41372
42034
  }
41373
42035
  function formatTime(seconds) {
41374
42036
  if (seconds < 60) return `${seconds}s`;
@@ -41376,23 +42038,23 @@ function formatTime(seconds) {
41376
42038
  const s = seconds % 60;
41377
42039
  return s > 0 ? `${m}m ${s}s` : `${m}m`;
41378
42040
  }
41379
- var import_react28, import_chalk9, import_jsx_runtime9, ProgressBar;
42041
+ var import_react30, import_chalk9, import_jsx_runtime10, ProgressBar;
41380
42042
  var init_ProgressBar = __esm({
41381
42043
  async "src/ui/components/ProgressBar.tsx"() {
41382
42044
  "use strict";
41383
- import_react28 = __toESM(require_react());
42045
+ import_react30 = __toESM(require_react());
41384
42046
  await init_build2();
41385
42047
  await init_build3();
41386
42048
  import_chalk9 = __toESM(require_source());
41387
- import_jsx_runtime9 = __toESM(require_jsx_runtime());
42049
+ import_jsx_runtime10 = __toESM(require_jsx_runtime());
41388
42050
  ProgressBar = ({
41389
42051
  value,
41390
42052
  total,
41391
42053
  label
41392
42054
  }) => {
41393
- const startRef = (0, import_react28.useRef)(Date.now());
41394
- const [elapsed, setElapsed] = (0, import_react28.useState)(0);
41395
- (0, import_react28.useEffect)(() => {
42055
+ const startRef = (0, import_react30.useRef)(Date.now());
42056
+ const [elapsed, setElapsed] = (0, import_react30.useState)(0);
42057
+ (0, import_react30.useEffect)(() => {
41396
42058
  const timer = setInterval(() => {
41397
42059
  setElapsed(Math.floor((Date.now() - startRef.current) / 1e3));
41398
42060
  }, 1e3);
@@ -41409,32 +42071,32 @@ var init_ProgressBar = __esm({
41409
42071
  const empty = barWidth - filled;
41410
42072
  const filledBar = "\u2501".repeat(filled);
41411
42073
  const emptyBar = "\u2501".repeat(empty);
41412
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 2, children: [
41413
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { children: [
42074
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 2, children: [
42075
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
41414
42076
  import_chalk9.default.cyan("\u25C6"),
41415
42077
  " ",
41416
42078
  import_chalk9.default.bold("Dependency Guardian")
41417
42079
  ] }),
41418
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: "" }),
41419
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { children: [
41420
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "cyan", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(build_default, { type: "dots" }) }),
41421
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { children: [
42080
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: "" }),
42081
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { children: [
42082
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: "cyan", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(build_default, { type: "dots" }) }),
42083
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
41422
42084
  " Scanning ",
41423
42085
  total,
41424
42086
  " packages... "
41425
42087
  ] }),
41426
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { dimColor: true, children: timeInfo })
42088
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: timeInfo })
41427
42089
  ] }),
41428
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { children: [
41429
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: " " }),
41430
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "green", children: filledBar }),
41431
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { dimColor: true, children: emptyBar }),
41432
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { children: [
42090
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { children: [
42091
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: " " }),
42092
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: "green", children: filledBar }),
42093
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: emptyBar }),
42094
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
41433
42095
  " ",
41434
42096
  counter
41435
42097
  ] })
41436
42098
  ] }),
41437
- label && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { dimColor: true, children: [
42099
+ label && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
41438
42100
  " ",
41439
42101
  import_chalk9.default.dim("\u203A"),
41440
42102
  " ",
@@ -41447,9 +42109,9 @@ var init_ProgressBar = __esm({
41447
42109
 
41448
42110
  // src/ui/hooks/useExpandAnimation.ts
41449
42111
  function useExpandAnimation(targetHeight, active, durationMs = 180) {
41450
- const [visibleLines, setVisibleLines] = (0, import_react29.useState)(0);
41451
- const timerRef = (0, import_react29.useRef)(null);
41452
- (0, import_react29.useEffect)(() => {
42112
+ const [visibleLines, setVisibleLines] = (0, import_react31.useState)(0);
42113
+ const timerRef = (0, import_react31.useRef)(null);
42114
+ (0, import_react31.useEffect)(() => {
41453
42115
  if (timerRef.current) {
41454
42116
  clearInterval(timerRef.current);
41455
42117
  timerRef.current = null;
@@ -41483,22 +42145,22 @@ function useExpandAnimation(targetHeight, active, durationMs = 180) {
41483
42145
  isAnimating: active && visibleLines > 0 && visibleLines < targetHeight
41484
42146
  };
41485
42147
  }
41486
- var import_react29;
42148
+ var import_react31;
41487
42149
  var init_useExpandAnimation = __esm({
41488
42150
  "src/ui/hooks/useExpandAnimation.ts"() {
41489
42151
  "use strict";
41490
- import_react29 = __toESM(require_react());
42152
+ import_react31 = __toESM(require_react());
41491
42153
  }
41492
42154
  });
41493
42155
 
41494
42156
  // src/ui/hooks/useTerminalSize.ts
41495
42157
  function useTerminalSize() {
41496
42158
  const { stdout } = use_stdout_default();
41497
- const [size, setSize] = (0, import_react30.useState)({
42159
+ const [size, setSize] = (0, import_react32.useState)({
41498
42160
  rows: stdout?.rows ?? process.stdout.rows ?? 24,
41499
42161
  cols: stdout?.columns ?? process.stdout.columns ?? 80
41500
42162
  });
41501
- (0, import_react30.useEffect)(() => {
42163
+ (0, import_react32.useEffect)(() => {
41502
42164
  const handle = () => {
41503
42165
  const rows = process.stdout.rows ?? 24;
41504
42166
  const cols = process.stdout.columns ?? 80;
@@ -41516,11 +42178,11 @@ function useTerminalSize() {
41516
42178
  }, []);
41517
42179
  return size;
41518
42180
  }
41519
- var import_react30;
42181
+ var import_react32;
41520
42182
  var init_useTerminalSize = __esm({
41521
42183
  async "src/ui/hooks/useTerminalSize.ts"() {
41522
42184
  "use strict";
41523
- import_react30 = __toESM(require_react());
42185
+ import_react32 = __toESM(require_react());
41524
42186
  await init_build2();
41525
42187
  }
41526
42188
  });
@@ -41536,10 +42198,10 @@ function groupPackages3(packages) {
41536
42198
  }
41537
42199
  return [...map.entries()].map(([fingerprint, pkgs]) => ({ packages: pkgs, key: fingerprint })).sort((a, b) => b.packages[0].score - a.packages[0].score);
41538
42200
  }
41539
- function actionBadge3(score, config) {
41540
- if (score >= config.blockThreshold)
42201
+ function actionBadge3(score) {
42202
+ if (score >= 70)
41541
42203
  return { label: "BLOCK", color: import_chalk10.default.red };
41542
- if (score >= config.warnThreshold)
42204
+ if (score >= 60)
41543
42205
  return { label: "WARN", color: import_chalk10.default.yellow };
41544
42206
  return { label: "PASS", color: import_chalk10.default.green };
41545
42207
  }
@@ -41553,6 +42215,7 @@ function findingsSummaryHeight(group) {
41553
42215
  const rep = group.packages[0];
41554
42216
  const visibleFindings = rep.findings.filter((f) => f.severity > 1 || f.critical);
41555
42217
  let h = visibleFindings.length;
42218
+ if (rep.license) h += 1;
41556
42219
  if (visibleFindings.length === 0 && rep.score > 0) {
41557
42220
  h += (rep.reasons ?? []).length;
41558
42221
  h += 1;
@@ -41564,6 +42227,7 @@ function findingsDetailHeight(group, safeVersions) {
41564
42227
  const rep = group.packages[0];
41565
42228
  const visibleFindings = rep.findings.filter((f) => f.severity > 1 || f.critical).sort((a, b) => b.severity - a.severity);
41566
42229
  let h = 0;
42230
+ if (rep.license) h += 1;
41567
42231
  if (group.packages.length > 3) h += 1;
41568
42232
  if (visibleFindings.length === 0 && rep.score > 0) {
41569
42233
  h += (rep.reasons ?? []).length;
@@ -41605,88 +42269,26 @@ function buildDetailLines(group, safeVersion, maxWidth) {
41605
42269
  const evidenceWidth = Math.max(30, maxWidth - 12);
41606
42270
  if (group.packages.length > 3) {
41607
42271
  lines.push(
41608
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
42272
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { dimColor: true, children: [
41609
42273
  "Affects: ",
41610
42274
  affectsLine(group)
41611
42275
  ] }, "affects")
41612
42276
  );
41613
- lines.push(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: "" }, "affects-gap"));
41614
- }
41615
- if (visibleFindings.length === 0 && rep.score > 0) {
41616
- const reasons = rep.reasons ?? [];
41617
- for (let i = 0; i < reasons.length; i++) {
41618
- lines.push(
41619
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: truncate3(reasons[i], maxWidth - 4) }, `reason-${i}`)
41620
- );
41621
- }
41622
- lines.push(
41623
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
41624
- import_chalk10.default.yellow("\u2192"),
41625
- " ",
41626
- import_chalk10.default.yellow("Upgrade to Pro"),
41627
- " ",
41628
- import_chalk10.default.dim("to see finding details \u2014 dg login")
41629
- ] }, "upgrade")
41630
- );
41631
- return lines;
41632
- }
41633
- const hasEvidence = visibleFindings.some((f) => f.evidence && f.evidence.length > 0);
41634
- for (let idx = 0; idx < visibleFindings.length; idx++) {
41635
- const finding = visibleFindings[idx];
41636
- const sevLabel = SEVERITY_LABELS3[finding.severity] ?? "INFO";
41637
- const sevColor = SEVERITY_COLORS[finding.severity] ?? SEVERITY_COLORS[1];
41638
- const evidence = finding.evidence ?? [];
41639
- const findingId = finding.id ?? finding.category ?? "";
41640
- if (idx > 0) lines.push(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: "" }, `gap-${idx}`));
41641
- lines.push(
41642
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
41643
- sevColor(pad3(sevLabel, 5)),
41644
- " ",
41645
- findingId
41646
- ] }, `${findingId}-${idx}-badge`)
41647
- );
41648
- lines.push(
41649
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
41650
- " ",
41651
- finding.title
41652
- ] }, `${findingId}-${idx}-title`)
41653
- );
41654
- const evidenceCap = Math.min(evidence.length, DETAIL_EVIDENCE_LIMIT);
41655
- for (let i = 0; i < evidenceCap; i++) {
41656
- lines.push(
41657
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
41658
- " ",
41659
- import_chalk10.default.dim("\u203A"),
41660
- " ",
41661
- truncate3(evidence[i], evidenceWidth)
41662
- ] }, `${findingId}-${idx}-ev-${i}`)
41663
- );
41664
- }
41665
- if (evidence.length > DETAIL_EVIDENCE_LIMIT) {
41666
- lines.push(
41667
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
41668
- " ",
41669
- import_chalk10.default.dim(`+${evidence.length - DETAIL_EVIDENCE_LIMIT} more`)
41670
- ] }, `${findingId}-${idx}-overflow`)
41671
- );
41672
- }
42277
+ lines.push(/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }, "affects-gap"));
41673
42278
  }
41674
- if (visibleFindings.length > 0 && !hasEvidence) {
41675
- lines.push(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: "" }, "upgrade-gap"));
42279
+ if (rep.score > 0) {
41676
42280
  lines.push(
41677
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
41678
- import_chalk10.default.yellow("\u2192"),
41679
- " ",
41680
- import_chalk10.default.yellow("Upgrade to Team"),
41681
- " ",
41682
- import_chalk10.default.dim("for evidence and file locations")
41683
- ] }, "upgrade-evidence")
42281
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { dimColor: true, children: [
42282
+ "Score: ",
42283
+ rep.score,
42284
+ "/100"
42285
+ ] }, "score-info")
41684
42286
  );
41685
42287
  }
41686
42288
  if (rep.recommendation) {
41687
- lines.push(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: "" }, "rec-gap"));
42289
+ lines.push(/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }, "rec-gap"));
41688
42290
  lines.push(
41689
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42291
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
41690
42292
  import_chalk10.default.dim("Rec:"),
41691
42293
  " ",
41692
42294
  import_chalk10.default.cyan(truncate3(rep.recommendation, maxWidth - 8))
@@ -41695,7 +42297,7 @@ function buildDetailLines(group, safeVersion, maxWidth) {
41695
42297
  }
41696
42298
  if (safeVersion) {
41697
42299
  lines.push(
41698
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: import_chalk10.default.green(`Safe: ${rep.name}@${safeVersion}`) }, "safe")
42300
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: import_chalk10.default.green(`Safe: ${rep.name}@${safeVersion}`) }, "safe")
41699
42301
  );
41700
42302
  }
41701
42303
  return lines;
@@ -41710,18 +42312,34 @@ function viewReducer(_state, action) {
41710
42312
  return { cursor: action.cursor, expandedIndex: action.expandedIndex, expandLevel: action.expandLevel, viewport: action.viewport };
41711
42313
  }
41712
42314
  }
41713
- var import_react31, import_chalk10, import_jsx_runtime10, SEVERITY_LABELS3, SEVERITY_COLORS, EVIDENCE_LIMIT2, FIXED_CHROME, DETAIL_PANE_CHROME, DETAIL_EVIDENCE_LIMIT, InteractiveResultsView, T, FindingsSummary;
42315
+ function licenseLine(rep) {
42316
+ const lc = rep.license;
42317
+ if (!lc) return null;
42318
+ const spdx = lc.spdx ?? lc.raw ?? "";
42319
+ const desc = LICENSE_DESCRIPTIONS[lc.riskCategory] ?? "";
42320
+ const lcColor = lc.riskCategory === "permissive" ? import_chalk10.default.green : lc.riskCategory === "no-license" || lc.riskCategory === "unlicensed" || lc.riskCategory === "network-copyleft" ? import_chalk10.default.red : import_chalk10.default.yellow;
42321
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42322
+ T.branch,
42323
+ " ",
42324
+ lcColor(spdx),
42325
+ " ",
42326
+ import_chalk10.default.dim("\u2014"),
42327
+ " ",
42328
+ import_chalk10.default.dim(desc)
42329
+ ] }, "license-info");
42330
+ }
42331
+ var import_react33, import_chalk10, import_jsx_runtime11, SEVERITY_LABELS2, SEVERITY_COLORS, EVIDENCE_LIMIT2, FIXED_CHROME, DETAIL_PANE_CHROME, InteractiveResultsView, T, LICENSE_DESCRIPTIONS, FindingsSummary;
41714
42332
  var init_InteractiveResultsView = __esm({
41715
42333
  async "src/ui/components/InteractiveResultsView.tsx"() {
41716
42334
  "use strict";
41717
- import_react31 = __toESM(require_react());
42335
+ import_react33 = __toESM(require_react());
41718
42336
  await init_build2();
41719
42337
  import_chalk10 = __toESM(require_source());
41720
42338
  await init_ScoreHeader();
41721
42339
  init_useExpandAnimation();
41722
42340
  await init_useTerminalSize();
41723
- import_jsx_runtime10 = __toESM(require_jsx_runtime());
41724
- SEVERITY_LABELS3 = {
42341
+ import_jsx_runtime11 = __toESM(require_jsx_runtime());
42342
+ SEVERITY_LABELS2 = {
41725
42343
  5: "CRIT",
41726
42344
  4: "HIGH",
41727
42345
  3: "MED",
@@ -41738,7 +42356,6 @@ var init_InteractiveResultsView = __esm({
41738
42356
  EVIDENCE_LIMIT2 = 2;
41739
42357
  FIXED_CHROME = 21;
41740
42358
  DETAIL_PANE_CHROME = 20;
41741
- DETAIL_EVIDENCE_LIMIT = 10;
41742
42359
  InteractiveResultsView = ({
41743
42360
  result,
41744
42361
  config,
@@ -41747,24 +42364,24 @@ var init_InteractiveResultsView = __esm({
41747
42364
  onBack,
41748
42365
  discoveredTotal
41749
42366
  }) => {
41750
- const flagged = (0, import_react31.useMemo)(
42367
+ const flagged = (0, import_react33.useMemo)(
41751
42368
  () => result.packages.filter((p) => p.score > 0),
41752
42369
  [result.packages]
41753
42370
  );
41754
- const clean = (0, import_react31.useMemo)(
42371
+ const clean = (0, import_react33.useMemo)(
41755
42372
  () => result.packages.filter((p) => p.score === 0),
41756
42373
  [result.packages]
41757
42374
  );
41758
42375
  const total = result.packages.length;
41759
- const [searchQuery, setSearchQuery] = (0, import_react31.useState)("");
41760
- const allGroups = (0, import_react31.useMemo)(() => groupPackages3(flagged), [flagged]);
42376
+ const [searchQuery, setSearchQuery] = (0, import_react33.useState)("");
42377
+ const allGroups = (0, import_react33.useMemo)(() => groupPackages3(flagged), [flagged]);
41761
42378
  const allGroupCount = allGroups.length;
41762
- const groups = (0, import_react31.useMemo)(() => {
42379
+ const groups = (0, import_react33.useMemo)(() => {
41763
42380
  if (!searchQuery) return allGroups;
41764
42381
  const q = searchQuery.toLowerCase();
41765
42382
  return allGroups.filter((g) => g.packages.some((p) => p.name.toLowerCase().includes(q)));
41766
42383
  }, [allGroups, searchQuery]);
41767
- const severityCounts = (0, import_react31.useMemo)(() => {
42384
+ const severityCounts = (0, import_react33.useMemo)(() => {
41768
42385
  const counts = {};
41769
42386
  for (const pkg of flagged) {
41770
42387
  const maxSev = pkg.findings.reduce((m, f) => Math.max(m, f.severity), 0);
@@ -41772,28 +42389,28 @@ var init_InteractiveResultsView = __esm({
41772
42389
  }
41773
42390
  return counts;
41774
42391
  }, [flagged]);
41775
- const [view, dispatchView] = (0, import_react31.useReducer)(viewReducer, {
42392
+ const [view, dispatchView] = (0, import_react33.useReducer)(viewReducer, {
41776
42393
  cursor: 0,
41777
42394
  expandLevel: null,
41778
42395
  expandedIndex: null,
41779
42396
  viewport: 0
41780
42397
  });
41781
- const viewRef = (0, import_react31.useRef)(view);
42398
+ const viewRef = (0, import_react33.useRef)(view);
41782
42399
  viewRef.current = view;
41783
- const [detailPane, setDetailPane] = (0, import_react31.useState)(null);
41784
- const detailPaneRef = (0, import_react31.useRef)(detailPane);
42400
+ const [detailPane, setDetailPane] = (0, import_react33.useState)(null);
42401
+ const detailPaneRef = (0, import_react33.useRef)(detailPane);
41785
42402
  detailPaneRef.current = detailPane;
41786
- const [showHelp, setShowHelp] = (0, import_react31.useState)(false);
41787
- const showHelpRef = (0, import_react31.useRef)(showHelp);
42403
+ const [showHelp, setShowHelp] = (0, import_react33.useState)(false);
42404
+ const showHelpRef = (0, import_react33.useRef)(showHelp);
41788
42405
  showHelpRef.current = showHelp;
41789
- const [searchMode, setSearchMode] = (0, import_react31.useState)(false);
41790
- const searchModeRef = (0, import_react31.useRef)(searchMode);
42406
+ const [searchMode, setSearchMode] = (0, import_react33.useState)(false);
42407
+ const searchModeRef = (0, import_react33.useRef)(searchMode);
41791
42408
  searchModeRef.current = searchMode;
41792
42409
  const { rows: termRows, cols: termCols } = useTerminalSize();
41793
42410
  const availableRows = Math.max(5, termRows - FIXED_CHROME);
41794
42411
  const innerWidth = Math.max(40, termCols - 6);
41795
42412
  const detailGroupIdx = detailPane?.groupIndex ?? -1;
41796
- const detailLines = (0, import_react31.useMemo)(() => {
42413
+ const detailLines = (0, import_react33.useMemo)(() => {
41797
42414
  if (detailGroupIdx < 0) return [];
41798
42415
  const group = groups[detailGroupIdx];
41799
42416
  if (!group) return [];
@@ -41803,7 +42420,7 @@ var init_InteractiveResultsView = __esm({
41803
42420
  const getLevel = (idx) => {
41804
42421
  return view.expandedIndex === idx ? view.expandLevel : null;
41805
42422
  };
41806
- const expandTargetHeight = (0, import_react31.useMemo)(() => {
42423
+ const expandTargetHeight = (0, import_react33.useMemo)(() => {
41807
42424
  if (view.expandedIndex === null || view.expandLevel === null) return 0;
41808
42425
  const group = groups[view.expandedIndex];
41809
42426
  if (!group) return 0;
@@ -41819,7 +42436,7 @@ var init_InteractiveResultsView = __esm({
41819
42436
  if (idx === view.expandedIndex) return 1 + animVisibleLines;
41820
42437
  return groupRowHeight(group, level, result.safeVersions);
41821
42438
  };
41822
- const visibleEnd = (0, import_react31.useMemo)(() => {
42439
+ const visibleEnd = (0, import_react33.useMemo)(() => {
41823
42440
  let consumed = 0;
41824
42441
  let end = view.viewport;
41825
42442
  while (end < groups.length) {
@@ -41851,13 +42468,22 @@ var init_InteractiveResultsView = __esm({
41851
42468
  }
41852
42469
  return newStart;
41853
42470
  };
41854
- (0, import_react31.useEffect)(() => {
42471
+ (0, import_react33.useEffect)(() => {
41855
42472
  if (groups.length === 0) return;
41856
42473
  const { cursor, expandedIndex, expandLevel, viewport } = viewRef.current;
41857
42474
  const clamped = Math.min(viewport, Math.max(0, groups.length - 1));
41858
42475
  const newVp = adjustViewport(cursor, expandedIndex, expandLevel, clamped);
41859
42476
  dispatchView({ type: "MOVE", cursor, viewport: newVp });
41860
42477
  }, [availableRows]);
42478
+ (0, import_react33.useEffect)(() => {
42479
+ const dp = detailPaneRef.current;
42480
+ if (dp && detailLines.length > 0) {
42481
+ const maxScroll = Math.max(0, detailLines.length - detailContentRows);
42482
+ if (dp.scroll > maxScroll) {
42483
+ setDetailPane({ groupIndex: dp.groupIndex, scroll: maxScroll });
42484
+ }
42485
+ }
42486
+ }, [detailContentRows, detailLines.length]);
41861
42487
  use_input_default((input, key) => {
41862
42488
  if (groups.length === 0) {
41863
42489
  if (input === "q" || key.return) onExit();
@@ -41899,13 +42525,18 @@ var init_InteractiveResultsView = __esm({
41899
42525
  setDetailPane({ groupIndex: dp.groupIndex, scroll: 0 });
41900
42526
  } else if (input === "G") {
41901
42527
  setDetailPane({ groupIndex: dp.groupIndex, scroll: maxScroll });
41902
- } else if (input === "e" || input === "b" || key.escape) {
42528
+ } else if (input === " " || input === "e" || input === "b" || key.escape) {
41903
42529
  setDetailPane(null);
41904
42530
  } else if (input === "q") {
41905
42531
  onExit();
41906
42532
  }
41907
42533
  return;
41908
42534
  }
42535
+ if (groups.length === 0) {
42536
+ if (input === "q") onExit();
42537
+ else if (input === "/") setSearchMode(true);
42538
+ return;
42539
+ }
41909
42540
  const { cursor, expandLevel: expLvl, expandedIndex: expIdx, viewport: vpStart } = viewRef.current;
41910
42541
  if (key.upArrow || input === "k") {
41911
42542
  const next = Math.max(0, cursor - 1);
@@ -41942,7 +42573,7 @@ var init_InteractiveResultsView = __esm({
41942
42573
  }
41943
42574
  const newVp = adjustViewport(cursor, newExpIdx, newExpLvl, vpStart);
41944
42575
  dispatchView({ type: "EXPAND", expandedIndex: newExpIdx, expandLevel: newExpLvl, viewport: newVp });
41945
- } else if (input === "e") {
42576
+ } else if (input === " " || input === "e") {
41946
42577
  setDetailPane({ groupIndex: view.cursor, scroll: 0 });
41947
42578
  } else if (input === "/") {
41948
42579
  setSearchMode(true);
@@ -41955,11 +42586,13 @@ var init_InteractiveResultsView = __esm({
41955
42586
  const visibleGroups = groups.slice(view.viewport, visibleEnd);
41956
42587
  const aboveCount = view.viewport;
41957
42588
  const belowCount = groups.length - visibleEnd;
41958
- const nameCol = Math.max(20, innerWidth - 22);
42589
+ const lcCol = 16;
42590
+ const nameCol = Math.max(20, innerWidth - 22 - lcCol);
42591
+ const clampedCursor = groups.length > 0 ? Math.min(view.cursor, groups.length - 1) : 0;
41959
42592
  if (showHelp) {
41960
42593
  const isDetail = detailPane !== null;
41961
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", children: [
41962
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
42594
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", children: [
42595
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
41963
42596
  ScoreHeader,
41964
42597
  {
41965
42598
  score: result.score,
@@ -41970,7 +42603,7 @@ var init_InteractiveResultsView = __esm({
41970
42603
  severityCounts
41971
42604
  }
41972
42605
  ),
41973
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
42606
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
41974
42607
  Box_default,
41975
42608
  {
41976
42609
  flexDirection: "column",
@@ -41980,82 +42613,82 @@ var init_InteractiveResultsView = __esm({
41980
42613
  paddingRight: 2,
41981
42614
  width: "100%",
41982
42615
  children: [
41983
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { bold: true, children: [
42616
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { bold: true, children: [
41984
42617
  import_chalk10.default.cyan("\u25C6"),
41985
42618
  " Keyboard Shortcuts"
41986
42619
  ] }),
41987
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: "" }),
41988
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { bold: true, children: " Navigation" }),
41989
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42620
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
42621
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { bold: true, children: " Navigation" }),
42622
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
41990
42623
  " ",
41991
42624
  import_chalk10.default.cyan("\u2191 k"),
41992
42625
  " ",
41993
42626
  import_chalk10.default.dim("Move up")
41994
42627
  ] }),
41995
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42628
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
41996
42629
  " ",
41997
42630
  import_chalk10.default.cyan("\u2193 j"),
41998
42631
  " ",
41999
42632
  import_chalk10.default.dim("Move down")
42000
42633
  ] }),
42001
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42634
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42002
42635
  " ",
42003
42636
  import_chalk10.default.cyan("g"),
42004
42637
  " ",
42005
42638
  import_chalk10.default.dim("Jump to top")
42006
42639
  ] }),
42007
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42640
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42008
42641
  " ",
42009
42642
  import_chalk10.default.cyan("G"),
42010
42643
  " ",
42011
42644
  import_chalk10.default.dim("Jump to bottom")
42012
42645
  ] }),
42013
- !isDetail && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42646
+ !isDetail && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42014
42647
  " ",
42015
42648
  import_chalk10.default.cyan("PgUp"),
42016
42649
  " ",
42017
42650
  import_chalk10.default.dim("Page up")
42018
42651
  ] }),
42019
- !isDetail && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42652
+ !isDetail && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42020
42653
  " ",
42021
42654
  import_chalk10.default.cyan("PgDn"),
42022
42655
  " ",
42023
42656
  import_chalk10.default.dim("Page down")
42024
42657
  ] }),
42025
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: "" }),
42026
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { bold: true, children: " Actions" }),
42027
- !isDetail && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42658
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
42659
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { bold: true, children: " Actions" }),
42660
+ !isDetail && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42028
42661
  " ",
42029
42662
  import_chalk10.default.cyan("\u23CE"),
42030
42663
  " ",
42031
42664
  import_chalk10.default.dim("Toggle summary")
42032
42665
  ] }),
42033
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42666
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42667
+ " ",
42668
+ import_chalk10.default.cyan("space"),
42034
42669
  " ",
42035
- import_chalk10.default.cyan("e"),
42036
- " ",
42037
42670
  import_chalk10.default.dim(isDetail ? "Back to list" : "Open detail view")
42038
42671
  ] }),
42039
- !isDetail && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42672
+ !isDetail && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42040
42673
  " ",
42041
42674
  import_chalk10.default.cyan("/"),
42042
42675
  " ",
42043
42676
  import_chalk10.default.dim("Search packages")
42044
42677
  ] }),
42045
- !isDetail && onBack && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42678
+ !isDetail && onBack && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42046
42679
  " ",
42047
42680
  import_chalk10.default.cyan("b"),
42048
42681
  " ",
42049
42682
  import_chalk10.default.dim("Back to project selector")
42050
42683
  ] }),
42051
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42684
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42052
42685
  " ",
42053
42686
  import_chalk10.default.cyan("q"),
42054
42687
  " ",
42055
42688
  import_chalk10.default.dim("Quit")
42056
42689
  ] }),
42057
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: "" }),
42058
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
42690
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
42691
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { dimColor: true, children: [
42059
42692
  " Press ",
42060
42693
  import_chalk10.default.bold.cyan("?"),
42061
42694
  " or ",
@@ -42073,13 +42706,13 @@ var init_InteractiveResultsView = __esm({
42073
42706
  setDetailPane(null);
42074
42707
  } else {
42075
42708
  const dpRep = dpGroup.packages[0];
42076
- const { color: dpColor } = actionBadge3(dpRep.score, config);
42709
+ const { color: dpColor } = actionBadge3(dpRep.score);
42077
42710
  const dpScroll = detailPane.scroll;
42078
42711
  const dpAbove = dpScroll;
42079
42712
  const dpBelow = Math.max(0, detailLines.length - dpScroll - detailContentRows);
42080
42713
  const dpVisible = detailLines.slice(dpScroll, dpScroll + detailContentRows);
42081
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", children: [
42082
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
42714
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", children: [
42715
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
42083
42716
  ScoreHeader,
42084
42717
  {
42085
42718
  score: result.score,
@@ -42090,7 +42723,7 @@ var init_InteractiveResultsView = __esm({
42090
42723
  severityCounts
42091
42724
  }
42092
42725
  ),
42093
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
42726
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
42094
42727
  Box_default,
42095
42728
  {
42096
42729
  flexDirection: "column",
@@ -42100,18 +42733,21 @@ var init_InteractiveResultsView = __esm({
42100
42733
  paddingRight: 1,
42101
42734
  width: "100%",
42102
42735
  children: [
42103
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { justifyContent: "space-between", children: [
42104
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { bold: true, children: groupNames(dpGroup) }),
42105
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: dpColor(`score ${dpRep.score}`) })
42736
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { justifyContent: "space-between", children: [
42737
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { bold: true, children: [
42738
+ groupNames(dpGroup),
42739
+ dpRep.license ? import_chalk10.default.dim(" \xB7 ") + (dpRep.license.riskCategory === "permissive" ? import_chalk10.default.green(dpRep.license.spdx ?? dpRep.license.raw ?? "") : dpRep.license.riskCategory === "no-license" || dpRep.license.riskCategory === "network-copyleft" ? import_chalk10.default.red(dpRep.license.spdx ?? dpRep.license.raw ?? "No license") : import_chalk10.default.yellow(dpRep.license.spdx ?? dpRep.license.raw ?? "")) : ""
42740
+ ] }),
42741
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: dpColor(`score ${dpRep.score}`) })
42106
42742
  ] }),
42107
- dpAbove > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
42743
+ dpAbove > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { dimColor: true, children: [
42108
42744
  import_chalk10.default.cyan(" \u2191"),
42109
42745
  " ",
42110
42746
  dpAbove,
42111
42747
  " more above"
42112
42748
  ] }),
42113
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { flexDirection: "column", marginLeft: 2, children: dpVisible }),
42114
- dpBelow > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
42749
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { flexDirection: "column", marginLeft: 2, children: dpVisible }),
42750
+ dpBelow > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { dimColor: true, children: [
42115
42751
  import_chalk10.default.cyan(" \u2193"),
42116
42752
  " ",
42117
42753
  dpBelow,
@@ -42120,7 +42756,7 @@ var init_InteractiveResultsView = __esm({
42120
42756
  ]
42121
42757
  }
42122
42758
  ),
42123
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
42759
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
42124
42760
  Box_default,
42125
42761
  {
42126
42762
  flexDirection: "column",
@@ -42130,36 +42766,31 @@ var init_InteractiveResultsView = __esm({
42130
42766
  paddingRight: 1,
42131
42767
  width: "100%",
42132
42768
  children: [
42133
- clean.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42769
+ clean.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42134
42770
  import_chalk10.default.green("\u2713"),
42135
42771
  " ",
42136
42772
  import_chalk10.default.green.bold(String(clean.length)),
42137
42773
  " ",
42138
42774
  import_chalk10.default.dim(`package${clean.length !== 1 ? "s" : ""} passed with score 0`)
42139
42775
  ] }),
42140
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { justifyContent: "space-between", children: [
42141
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
42776
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { justifyContent: "space-between", children: [
42777
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { dimColor: true, children: [
42142
42778
  (durationMs / 1e3).toFixed(1),
42143
42779
  "s"
42144
42780
  ] }),
42145
- result.trialScansRemaining !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
42146
- result.trialScansRemaining,
42147
- " free scan",
42148
- result.trialScansRemaining !== 1 ? "s" : "",
42149
- " remaining"
42150
- ] })
42781
+ result.trialScansRemaining !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { dimColor: true, children: "Free tier \xB7 dg login for finding details" })
42151
42782
  ] })
42152
42783
  ]
42153
42784
  }
42154
42785
  ),
42155
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: import_chalk10.default.dim("\u2500".repeat(Math.min(60, termCols - 4))) }),
42156
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42786
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { dimColor: true, children: import_chalk10.default.dim("\u2500".repeat(Math.min(60, termCols - 4))) }),
42787
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42157
42788
  " ",
42158
42789
  import_chalk10.default.bold.cyan("\u2191\u2193"),
42159
42790
  " ",
42160
42791
  import_chalk10.default.dim("scroll"),
42161
42792
  " ",
42162
- import_chalk10.default.bold.cyan("e"),
42793
+ import_chalk10.default.bold.cyan("space"),
42163
42794
  " ",
42164
42795
  import_chalk10.default.dim("back"),
42165
42796
  " ",
@@ -42170,8 +42801,8 @@ var init_InteractiveResultsView = __esm({
42170
42801
  ] });
42171
42802
  }
42172
42803
  }
42173
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", children: [
42174
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
42804
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", children: [
42805
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
42175
42806
  ScoreHeader,
42176
42807
  {
42177
42808
  score: result.score,
@@ -42182,7 +42813,7 @@ var init_InteractiveResultsView = __esm({
42182
42813
  severityCounts
42183
42814
  }
42184
42815
  ),
42185
- groups.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
42816
+ groups.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
42186
42817
  Box_default,
42187
42818
  {
42188
42819
  flexDirection: "column",
@@ -42192,11 +42823,11 @@ var init_InteractiveResultsView = __esm({
42192
42823
  paddingRight: 1,
42193
42824
  width: "100%",
42194
42825
  children: [
42195
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { justifyContent: "space-between", children: [
42196
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { bold: true, children: "Flagged Packages" }),
42197
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: searchQuery ? `${groups.length} of ${allGroupCount}` : `${view.cursor + 1}/${groups.length}` })
42826
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { justifyContent: "space-between", children: [
42827
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { bold: true, children: "Flagged Packages" }),
42828
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { dimColor: true, children: searchQuery ? `${groups.length} of ${allGroupCount}` : `${clampedCursor + 1}/${groups.length}` })
42198
42829
  ] }),
42199
- aboveCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
42830
+ aboveCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { dimColor: true, children: [
42200
42831
  import_chalk10.default.cyan(" \u2191"),
42201
42832
  " ",
42202
42833
  aboveCount,
@@ -42204,27 +42835,32 @@ var init_InteractiveResultsView = __esm({
42204
42835
  ] }),
42205
42836
  visibleGroups.map((group, visIdx) => {
42206
42837
  const globalIdx = view.viewport + visIdx;
42207
- const isCursor = globalIdx === view.cursor;
42838
+ const isCursor = globalIdx === clampedCursor;
42208
42839
  const level = getLevel(globalIdx);
42209
42840
  const rep = group.packages[0];
42210
- const { label, color } = actionBadge3(rep.score, config);
42841
+ const { label, color } = actionBadge3(rep.score);
42211
42842
  const names = groupNames(group);
42212
42843
  const chevron = level !== null ? "\u25BE" : "\u25B8";
42213
42844
  const scoreStr = String(rep.score);
42214
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", children: [
42215
- isCursor ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42845
+ const lcInfo = rep.license;
42846
+ const lcStr = lcInfo ? truncate3(lcInfo.spdx ?? lcInfo.raw ?? "", lcCol - 2) : "";
42847
+ const lcColor = !lcInfo ? import_chalk10.default.dim : lcInfo.riskCategory === "permissive" ? import_chalk10.default.green : lcInfo.riskCategory === "no-license" || lcInfo.riskCategory === "unlicensed" || lcInfo.riskCategory === "network-copyleft" ? import_chalk10.default.red : import_chalk10.default.yellow;
42848
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", children: [
42849
+ isCursor ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42216
42850
  import_chalk10.default.cyan("\u258C"),
42217
42851
  ` ${chevron} `,
42218
42852
  color(pad3(label, 6)),
42219
42853
  import_chalk10.default.bold(pad3(truncate3(names, nameCol - 2), nameCol)),
42854
+ lcColor(pad3(lcStr, lcCol)),
42220
42855
  color(scoreStr.padStart(3))
42221
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42856
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42222
42857
  ` ${import_chalk10.default.dim(chevron)} `,
42223
42858
  color(pad3(label, 6)),
42224
42859
  pad3(truncate3(names, nameCol - 2), nameCol),
42860
+ lcColor(pad3(lcStr, lcCol)),
42225
42861
  color(scoreStr.padStart(3))
42226
42862
  ] }),
42227
- level === "summary" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
42863
+ level === "summary" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
42228
42864
  FindingsSummary,
42229
42865
  {
42230
42866
  group,
@@ -42234,7 +42870,7 @@ var init_InteractiveResultsView = __esm({
42234
42870
  )
42235
42871
  ] }, group.key);
42236
42872
  }),
42237
- belowCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
42873
+ belowCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { dimColor: true, children: [
42238
42874
  import_chalk10.default.cyan(" \u2193"),
42239
42875
  " ",
42240
42876
  belowCount,
@@ -42243,7 +42879,7 @@ var init_InteractiveResultsView = __esm({
42243
42879
  ]
42244
42880
  }
42245
42881
  ),
42246
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
42882
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
42247
42883
  Box_default,
42248
42884
  {
42249
42885
  flexDirection: "column",
@@ -42253,14 +42889,14 @@ var init_InteractiveResultsView = __esm({
42253
42889
  paddingRight: 1,
42254
42890
  width: "100%",
42255
42891
  children: [
42256
- clean.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42892
+ clean.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42257
42893
  import_chalk10.default.green("\u2713"),
42258
42894
  " ",
42259
42895
  import_chalk10.default.green.bold(String(clean.length)),
42260
42896
  " ",
42261
42897
  import_chalk10.default.dim(`package${clean.length !== 1 ? "s" : ""} passed with score 0`)
42262
42898
  ] }),
42263
- discoveredTotal !== void 0 && discoveredTotal > total && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
42899
+ discoveredTotal !== void 0 && discoveredTotal > total && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { dimColor: true, children: [
42264
42900
  "Scanned ",
42265
42901
  total,
42266
42902
  " of ",
@@ -42268,23 +42904,18 @@ var init_InteractiveResultsView = __esm({
42268
42904
  " packages ",
42269
42905
  import_chalk10.default.dim("\u2014 dg login for full scans")
42270
42906
  ] }),
42271
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { justifyContent: "space-between", children: [
42272
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
42907
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { justifyContent: "space-between", children: [
42908
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { dimColor: true, children: [
42273
42909
  (durationMs / 1e3).toFixed(1),
42274
42910
  "s"
42275
42911
  ] }),
42276
- result.trialScansRemaining !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
42277
- result.trialScansRemaining,
42278
- " free scan",
42279
- result.trialScansRemaining !== 1 ? "s" : "",
42280
- " remaining"
42281
- ] })
42912
+ result.trialScansRemaining !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { dimColor: true, children: "Free tier \xB7 dg login for finding details" })
42282
42913
  ] })
42283
42914
  ]
42284
42915
  }
42285
42916
  ),
42286
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: import_chalk10.default.dim("\u2500".repeat(Math.min(60, termCols - 4))) }),
42287
- searchMode ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42917
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { dimColor: true, children: import_chalk10.default.dim("\u2500".repeat(Math.min(60, termCols - 4))) }),
42918
+ searchMode ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42288
42919
  " ",
42289
42920
  import_chalk10.default.bold.cyan("/"),
42290
42921
  " ",
@@ -42292,9 +42923,9 @@ var init_InteractiveResultsView = __esm({
42292
42923
  import_chalk10.default.cyan("\u2588"),
42293
42924
  " ",
42294
42925
  import_chalk10.default.dim("Esc clear")
42295
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
42926
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42296
42927
  " ",
42297
- groups.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
42928
+ groups.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
42298
42929
  import_chalk10.default.bold.cyan("\u2191\u2193"),
42299
42930
  " ",
42300
42931
  import_chalk10.default.dim("navigate"),
@@ -42303,7 +42934,7 @@ var init_InteractiveResultsView = __esm({
42303
42934
  " ",
42304
42935
  import_chalk10.default.dim("toggle"),
42305
42936
  " ",
42306
- import_chalk10.default.bold.cyan("e"),
42937
+ import_chalk10.default.bold.cyan("space"),
42307
42938
  " ",
42308
42939
  import_chalk10.default.dim("detail"),
42309
42940
  " ",
@@ -42315,7 +42946,7 @@ var init_InteractiveResultsView = __esm({
42315
42946
  " ",
42316
42947
  import_chalk10.default.dim("help"),
42317
42948
  " ",
42318
- onBack && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
42949
+ onBack && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
42319
42950
  import_chalk10.default.bold.cyan("b"),
42320
42951
  " ",
42321
42952
  import_chalk10.default.dim("back"),
@@ -42324,7 +42955,7 @@ var init_InteractiveResultsView = __esm({
42324
42955
  import_chalk10.default.bold.cyan("q"),
42325
42956
  " ",
42326
42957
  import_chalk10.default.dim("quit")
42327
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
42958
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
42328
42959
  "Press ",
42329
42960
  import_chalk10.default.bold.cyan("q"),
42330
42961
  " or ",
@@ -42341,16 +42972,28 @@ var init_InteractiveResultsView = __esm({
42341
42972
  pipe: import_chalk10.default.dim("\u2502"),
42342
42973
  blank: " "
42343
42974
  };
42975
+ LICENSE_DESCRIPTIONS = {
42976
+ "permissive": "Permissive \u2014 free to use, modify, and distribute. Include the copyright notice.",
42977
+ "weak-copyleft": "Weak copyleft \u2014 changes to this library must be shared, but your code stays private.",
42978
+ "strong-copyleft": "Strong copyleft \u2014 your entire project must be open-sourced under the same license.",
42979
+ "network-copyleft": "Network copyleft \u2014 even SaaS/server use requires releasing your source code.",
42980
+ "no-license": "No license found \u2014 legally all rights reserved. Use may require permission from the author.",
42981
+ "unlicensed": "Explicitly unlicensed \u2014 proprietary software. A commercial agreement is required.",
42982
+ "unknown": "Unrecognized license \u2014 have your legal team review before using.",
42983
+ "deferred": "License declared in a file \u2014 check the LICENSE file in the package."
42984
+ };
42344
42985
  FindingsSummary = ({ group, maxWidth, maxLines }) => {
42345
42986
  const rep = group.packages[0];
42346
42987
  const visibleFindings = rep.findings.filter((f) => f.severity > 1 || f.critical).sort((a, b) => b.severity - a.severity);
42347
42988
  const hasAffects = group.packages.length > 3;
42348
42989
  const allLines = [];
42990
+ const lcLine = licenseLine(rep);
42991
+ if (lcLine) allLines.push(lcLine);
42349
42992
  if (visibleFindings.length === 0 && rep.score > 0) {
42350
42993
  const reasons = rep.reasons ?? [];
42351
42994
  for (let i = 0; i < reasons.length; i++) {
42352
42995
  allLines.push(
42353
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
42996
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { dimColor: true, children: [
42354
42997
  i < reasons.length - 1 || hasAffects ? T.branch : T.branch,
42355
42998
  " ",
42356
42999
  truncate3(reasons[i], maxWidth - 8)
@@ -42358,7 +43001,7 @@ var init_InteractiveResultsView = __esm({
42358
43001
  );
42359
43002
  }
42360
43003
  allLines.push(
42361
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
43004
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { dimColor: true, children: [
42362
43005
  import_chalk10.default.yellow(" \u2192"),
42363
43006
  " ",
42364
43007
  import_chalk10.default.yellow("Upgrade to Pro"),
@@ -42371,10 +43014,10 @@ var init_InteractiveResultsView = __esm({
42371
43014
  const f = visibleFindings[idx];
42372
43015
  const isLast = !hasAffects && idx === visibleFindings.length - 1;
42373
43016
  const connector = isLast ? T.last : T.branch;
42374
- const sevLabel = SEVERITY_LABELS3[f.severity] ?? "INFO";
43017
+ const sevLabel = SEVERITY_LABELS2[f.severity] ?? "INFO";
42375
43018
  const sevColor = SEVERITY_COLORS[f.severity] ?? SEVERITY_COLORS[1];
42376
43019
  allLines.push(
42377
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
43020
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42378
43021
  connector,
42379
43022
  " ",
42380
43023
  sevColor(pad3(sevLabel, 5)),
@@ -42385,7 +43028,7 @@ var init_InteractiveResultsView = __esm({
42385
43028
  }
42386
43029
  if (hasAffects) {
42387
43030
  allLines.push(
42388
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
43031
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { dimColor: true, children: [
42389
43032
  T.last,
42390
43033
  " ",
42391
43034
  truncate3(affectsLine(group), maxWidth - 8)
@@ -42393,23 +43036,24 @@ var init_InteractiveResultsView = __esm({
42393
43036
  );
42394
43037
  }
42395
43038
  const linesToShow = maxLines !== void 0 ? allLines.slice(0, maxLines) : allLines;
42396
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { flexDirection: "column", marginLeft: 5, children: linesToShow });
43039
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { flexDirection: "column", marginLeft: 5, children: linesToShow });
42397
43040
  };
42398
43041
  }
42399
43042
  });
42400
43043
 
42401
43044
  // src/ui/components/ProjectSelector.tsx
42402
- var import_react32, import_chalk11, import_jsx_runtime11, ProjectSelector;
43045
+ var import_react34, import_chalk11, import_jsx_runtime12, ProjectSelector;
42403
43046
  var init_ProjectSelector = __esm({
42404
43047
  async "src/ui/components/ProjectSelector.tsx"() {
42405
43048
  "use strict";
42406
- import_react32 = __toESM(require_react());
43049
+ import_react34 = __toESM(require_react());
42407
43050
  await init_build2();
42408
43051
  import_chalk11 = __toESM(require_source());
42409
- import_jsx_runtime11 = __toESM(require_jsx_runtime());
43052
+ init_sanitize();
43053
+ import_jsx_runtime12 = __toESM(require_jsx_runtime());
42410
43054
  ProjectSelector = ({ projects, onConfirm, onCancel }) => {
42411
- const [cursor, setCursor] = (0, import_react32.useState)(0);
42412
- const [selected, setSelected] = (0, import_react32.useState)(() => new Set(projects.map((_, i) => i)));
43055
+ const [cursor, setCursor] = (0, import_react34.useState)(0);
43056
+ const [selected, setSelected] = (0, import_react34.useState)(() => new Set(projects.map((_, i) => i)));
42413
43057
  use_input_default((input, key) => {
42414
43058
  if (key.upArrow) {
42415
43059
  setCursor((c) => Math.max(0, c - 1));
@@ -42435,30 +43079,30 @@ var init_ProjectSelector = __esm({
42435
43079
  }
42436
43080
  });
42437
43081
  const ecosystemLabel = (eco) => eco === "npm" ? import_chalk11.default.magenta("npm") : import_chalk11.default.blue("pip");
42438
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 1, children: [
42439
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
43082
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 1, children: [
43083
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { children: [
42440
43084
  import_chalk11.default.cyan("\u25C6"),
42441
43085
  " ",
42442
43086
  import_chalk11.default.bold("Dependency Guardian")
42443
43087
  ] }),
42444
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
42445
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { bold: true, children: [
43088
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { children: "" }),
43089
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { bold: true, children: [
42446
43090
  "Found ",
42447
43091
  projects.length,
42448
43092
  " project",
42449
43093
  projects.length !== 1 ? "s" : ""
42450
43094
  ] }),
42451
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
43095
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { children: "" }),
42452
43096
  projects.map((proj, i) => {
42453
43097
  const isCursor = i === cursor;
42454
43098
  const isSelected = selected.has(i);
42455
43099
  const prefix = isCursor ? import_chalk11.default.cyan("\u258C") : " ";
42456
43100
  const check = isSelected ? import_chalk11.default.green("\u25C9") : import_chalk11.default.dim("\u25CB");
42457
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
43101
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { children: [
42458
43102
  prefix,
42459
43103
  check,
42460
43104
  " ",
42461
- proj.relativePath.padEnd(40),
43105
+ sanitize(proj.relativePath).padEnd(40),
42462
43106
  " ",
42463
43107
  ecosystemLabel(proj.ecosystem).padEnd(5),
42464
43108
  " ",
@@ -42466,10 +43110,10 @@ var init_ProjectSelector = __esm({
42466
43110
  " packages"
42467
43111
  ] }, i);
42468
43112
  }),
42469
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
42470
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { dimColor: true, children: selected.size === 0 ? import_chalk11.default.yellow("Select at least 1 project to scan") : `${selected.size} of ${projects.length} selected` }),
42471
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
42472
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
43113
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { children: "" }),
43114
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { dimColor: true, children: selected.size === 0 ? import_chalk11.default.yellow("Select at least 1 project to scan") : `${selected.size} of ${projects.length} selected` }),
43115
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { children: "" }),
43116
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { children: [
42473
43117
  import_chalk11.default.bold.cyan("space"),
42474
43118
  " ",
42475
43119
  import_chalk11.default.dim("toggle"),
@@ -42478,9 +43122,9 @@ var init_ProjectSelector = __esm({
42478
43122
  " ",
42479
43123
  import_chalk11.default.dim("all"),
42480
43124
  " ",
42481
- import_chalk11.default.bold.cyan("\u23CE"),
43125
+ import_chalk11.default.bold.hex("#FFD700")("\u23CE"),
42482
43126
  " ",
42483
- import_chalk11.default.dim("scan"),
43127
+ import_chalk11.default.bold.hex("#FFD700")("scan"),
42484
43128
  " ",
42485
43129
  import_chalk11.default.bold.cyan("q"),
42486
43130
  " ",
@@ -42496,11 +43140,11 @@ var App_exports = {};
42496
43140
  __export(App_exports, {
42497
43141
  App: () => App2
42498
43142
  });
42499
- var import_react33, import_jsx_runtime12, App2;
43143
+ var import_react35, import_jsx_runtime13, App2;
42500
43144
  var init_App2 = __esm({
42501
43145
  async "src/ui/App.tsx"() {
42502
43146
  "use strict";
42503
- import_react33 = __toESM(require_react());
43147
+ import_react35 = __toESM(require_react());
42504
43148
  await init_build2();
42505
43149
  init_useScan();
42506
43150
  await init_Spinner();
@@ -42509,17 +43153,19 @@ var init_App2 = __esm({
42509
43153
  await init_ErrorView();
42510
43154
  await init_ProjectSelector();
42511
43155
  await init_useTerminalSize();
42512
- import_jsx_runtime12 = __toESM(require_jsx_runtime());
43156
+ import_jsx_runtime13 = __toESM(require_jsx_runtime());
42513
43157
  App2 = ({ config }) => {
42514
43158
  const { state, scanSelectedProjects, restartSelection } = useScan(config);
42515
43159
  const { exit } = use_app_default();
42516
43160
  useTerminalSize();
42517
- const prevPhaseRef = (0, import_react33.useRef)(state.phase);
42518
- const altScreenActiveRef = (0, import_react33.useRef)(false);
42519
- (0, import_react33.useEffect)(() => {
43161
+ const prevPhaseRef = (0, import_react35.useRef)(state.phase);
43162
+ const altScreenActiveRef = (0, import_react35.useRef)(false);
43163
+ (0, import_react35.useEffect)(() => {
42520
43164
  if (!process.stdout.isTTY) return;
42521
43165
  process.stdout.write("\x1B[?1049h");
42522
43166
  process.stdout.write("\x1B[2J\x1B[H");
43167
+ process.stdout.write("\x1B[?1003l");
43168
+ process.stdout.write("\x1B[?1000l");
42523
43169
  altScreenActiveRef.current = true;
42524
43170
  return () => {
42525
43171
  if (altScreenActiveRef.current) {
@@ -42529,20 +43175,20 @@ var init_App2 = __esm({
42529
43175
  process.stdout.write("\x1B[?25h");
42530
43176
  };
42531
43177
  }, []);
42532
- (0, import_react33.useEffect)(() => {
43178
+ (0, import_react35.useEffect)(() => {
42533
43179
  if (prevPhaseRef.current !== state.phase && process.stdout.isTTY) {
42534
43180
  process.stdout.write("\x1B[2J\x1B[H");
42535
43181
  }
42536
43182
  prevPhaseRef.current = state.phase;
42537
43183
  }, [state.phase]);
42538
- const leaveAltScreen = (0, import_react33.useCallback)(() => {
43184
+ const leaveAltScreen = (0, import_react35.useCallback)(() => {
42539
43185
  if (altScreenActiveRef.current && process.stdout.isTTY) {
42540
43186
  process.stdout.write("\x1B[?1049l");
42541
43187
  altScreenActiveRef.current = false;
42542
43188
  }
42543
43189
  process.stdout.write("\x1B[?25h");
42544
43190
  }, []);
42545
- const handleResultsExit = (0, import_react33.useCallback)(() => {
43191
+ const handleResultsExit = (0, import_react35.useCallback)(() => {
42546
43192
  if (state.phase === "results") {
42547
43193
  const { result } = state;
42548
43194
  if (result.action === "block" && config.mode === "block") {
@@ -42556,13 +43202,13 @@ var init_App2 = __esm({
42556
43202
  leaveAltScreen();
42557
43203
  exit();
42558
43204
  }, [state, config, exit, leaveAltScreen]);
42559
- const exitWithMessage = (0, import_react33.useCallback)((message, exitCode) => {
43205
+ const exitWithMessage = (0, import_react35.useCallback)((message, exitCode) => {
42560
43206
  process.exitCode = exitCode;
42561
43207
  leaveAltScreen();
42562
43208
  process.stderr.write(message);
42563
43209
  return setTimeout(() => exit(), 0);
42564
43210
  }, [exit, leaveAltScreen]);
42565
- (0, import_react33.useEffect)(() => {
43211
+ (0, import_react35.useEffect)(() => {
42566
43212
  if (state.phase === "empty") {
42567
43213
  const timer = exitWithMessage(`${state.message}
42568
43214
  `, 0);
@@ -42574,10 +43220,15 @@ var init_App2 = __esm({
42574
43220
  return () => clearTimeout(timer);
42575
43221
  }
42576
43222
  if (state.phase === "trial_exhausted") {
42577
- const timer = exitWithMessage(
42578
- "Free trial scans used up. Run `dg login` to create a free account and continue scanning.\n",
42579
- 1
42580
- );
43223
+ let msg = "Free trial scans used up. Run `dg login` to create a free account and continue scanning.\n";
43224
+ try {
43225
+ const { getStoredApiKey: getStoredApiKey2 } = (init_auth(), __toCommonJS(auth_exports));
43226
+ if (getStoredApiKey2()) {
43227
+ msg = "Your API key may be invalid or expired. Run `dg logout` then `dg login` to re-authenticate.\n";
43228
+ }
43229
+ } catch {
43230
+ }
43231
+ const timer = exitWithMessage(msg, 1);
42581
43232
  return () => clearTimeout(timer);
42582
43233
  }
42583
43234
  }, [state, exitWithMessage]);
@@ -42593,9 +43244,9 @@ var init_App2 = __esm({
42593
43244
  const content = (() => {
42594
43245
  switch (state.phase) {
42595
43246
  case "discovering":
42596
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Spinner2, { label: `Searching for dependencies in ${process.cwd()} ...` });
43247
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Spinner2, { label: `Searching for dependencies in ${process.cwd()} ...` });
42597
43248
  case "selecting":
42598
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
43249
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
42599
43250
  ProjectSelector,
42600
43251
  {
42601
43252
  projects: state.projects,
@@ -42608,7 +43259,7 @@ var init_App2 = __esm({
42608
43259
  }
42609
43260
  );
42610
43261
  case "scanning":
42611
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
43262
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
42612
43263
  ProgressBar,
42613
43264
  {
42614
43265
  value: state.done,
@@ -42617,7 +43268,7 @@ var init_App2 = __esm({
42617
43268
  }
42618
43269
  );
42619
43270
  case "results":
42620
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
43271
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
42621
43272
  InteractiveResultsView,
42622
43273
  {
42623
43274
  result: state.result,
@@ -42629,21 +43280,37 @@ var init_App2 = __esm({
42629
43280
  }
42630
43281
  );
42631
43282
  case "empty":
42632
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { dimColor: true, children: state.message });
43283
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { dimColor: true, children: state.message });
42633
43284
  case "error":
42634
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ErrorView, { error: state.error });
42635
- case "trial_exhausted":
42636
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 2, children: [
42637
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: "yellow", bold: true, children: "Free trial scans used up." }),
42638
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { children: [
43285
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ErrorView, { error: state.error });
43286
+ case "trial_exhausted": {
43287
+ let hasKey = false;
43288
+ try {
43289
+ const { getStoredApiKey: getStoredApiKey2 } = (init_auth(), __toCommonJS(auth_exports));
43290
+ hasKey = !!getStoredApiKey2();
43291
+ } catch {
43292
+ }
43293
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Box_default, { flexDirection: "column", paddingLeft: 2, children: hasKey ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
43294
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: "yellow", bold: true, children: "Your API key may be invalid or expired." }),
43295
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Text, { children: [
43296
+ "Run ",
43297
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: "cyan", bold: true, children: "dg logout" }),
43298
+ " then ",
43299
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: "cyan", bold: true, children: "dg login" }),
43300
+ " to re-authenticate."
43301
+ ] })
43302
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
43303
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: "yellow", bold: true, children: "Free trial scans used up." }),
43304
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Text, { children: [
42639
43305
  "Run ",
42640
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: "cyan", bold: true, children: "dg login" }),
43306
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: "cyan", bold: true, children: "dg login" }),
42641
43307
  " to create a free account and continue scanning."
42642
43308
  ] })
42643
- ] });
43309
+ ] }) });
43310
+ }
42644
43311
  }
42645
43312
  })();
42646
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Box_default, { flexDirection: "column", children: content });
43313
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Box_default, { flexDirection: "column", children: content });
42647
43314
  };
42648
43315
  }
42649
43316
  });
@@ -42656,7 +43323,7 @@ init_npm_wrapper();
42656
43323
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
42657
43324
  import { homedir as homedir3 } from "node:os";
42658
43325
  import { join as join4 } from "node:path";
42659
- import { spawn as spawn2, execSync } from "node:child_process";
43326
+ import { spawn as spawn3, execFileSync } from "node:child_process";
42660
43327
  var PKG_NAME = "@westbayberry/dg";
42661
43328
  var CACHE_FILE = join4(homedir3(), ".dg-update-check.json");
42662
43329
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
@@ -42709,7 +43376,7 @@ function isNewer(latest, current) {
42709
43376
  }
42710
43377
  function spawnBackgroundUpdate(version) {
42711
43378
  try {
42712
- const child = spawn2("npm", ["install", "-g", `${PKG_NAME}@${version}`], {
43379
+ const child = spawn3("npm", ["install", "-g", `${PKG_NAME}@${version}`], {
42713
43380
  detached: true,
42714
43381
  stdio: "ignore"
42715
43382
  });
@@ -42755,7 +43422,7 @@ async function runUpdate(currentVersion) {
42755
43422
  process.stderr.write(chalk10.dim(` Installing ${PKG_NAME}@${latest}...
42756
43423
  `));
42757
43424
  try {
42758
- execSync(`npm install -g ${PKG_NAME}@${latest}`, { stdio: "inherit" });
43425
+ execFileSync("npm", ["install", "-g", `${PKG_NAME}@${latest}`], { stdio: "inherit" });
42759
43426
  writeCache({ latest, checkedAt: Date.now() });
42760
43427
  process.stderr.write(
42761
43428
  chalk10.green(`
@@ -42774,6 +43441,27 @@ async function runUpdate(currentVersion) {
42774
43441
 
42775
43442
  // src/bin.ts
42776
43443
  var CLI_VERSION = getVersion();
43444
+ function closestCommand(input, commands) {
43445
+ let best = "", bestDist = Infinity;
43446
+ for (const cmd of commands) {
43447
+ const m = input.length, n = cmd.length;
43448
+ const dp = Array.from({ length: m + 1 }, () => new Array(n + 1));
43449
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
43450
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
43451
+ for (let i = 1; i <= m; i++)
43452
+ for (let j = 1; j <= n; j++)
43453
+ dp[i][j] = input[i - 1] === cmd[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
43454
+ if (dp[m][n] < bestDist) {
43455
+ bestDist = dp[m][n];
43456
+ best = cmd;
43457
+ }
43458
+ }
43459
+ return bestDist <= 3 ? best : null;
43460
+ }
43461
+ process.on("SIGINT", () => {
43462
+ process.stderr.write("\n");
43463
+ process.exit(130);
43464
+ });
42777
43465
  var isInteractive = process.stdout.isTTY === true && !process.env.CI && !process.env.NO_COLOR;
42778
43466
  async function main() {
42779
43467
  const rawCommand = process.argv[2];
@@ -42794,9 +43482,9 @@ async function main() {
42794
43482
  if (rawCommand === "login") {
42795
43483
  if (isInteractive) {
42796
43484
  const { render: render3 } = await init_build2().then(() => build_exports);
42797
- const React18 = await Promise.resolve().then(() => __toESM(require_react()));
43485
+ const React19 = await Promise.resolve().then(() => __toESM(require_react()));
42798
43486
  const { LoginApp: LoginApp2 } = await init_LoginApp().then(() => LoginApp_exports);
42799
- const { waitUntilExit } = render3(React18.createElement(LoginApp2));
43487
+ const { waitUntilExit } = render3(React19.createElement(LoginApp2));
42800
43488
  await waitUntilExit();
42801
43489
  } else {
42802
43490
  const { runStaticLogin: runStaticLogin2 } = await Promise.resolve().then(() => (init_static_output(), static_output_exports));
@@ -42820,12 +43508,29 @@ async function main() {
42820
43508
  process.stderr.write(chalk10.green(" Logged out.\n"));
42821
43509
  return;
42822
43510
  }
42823
- const config = parseConfig(process.argv);
43511
+ const KNOWN_COMMANDS = ["scan", "npm", "pip", "wrap", "login", "hook", "update", "logout"];
43512
+ if (rawCommand && !rawCommand.startsWith("-") && !KNOWN_COMMANDS.includes(rawCommand)) {
43513
+ const chalk10 = (await Promise.resolve().then(() => __toESM(require_source()))).default;
43514
+ const best = closestCommand(rawCommand, KNOWN_COMMANDS);
43515
+ const hint = best ? ` Did you mean '${best}'?` : "";
43516
+ process.stderr.write(`
43517
+ ${chalk10.bold.red("Error:")} Unknown command '${rawCommand}'.${hint}
43518
+ `);
43519
+ process.stderr.write(chalk10.dim(` Run 'dg --help' for available commands.
43520
+
43521
+ `));
43522
+ process.exit(1);
43523
+ }
43524
+ const strictFlags = rawCommand !== "npm" && rawCommand !== "pip";
43525
+ const config = parseConfig(process.argv, strictFlags);
42824
43526
  const updatePromise = checkForUpdate(CLI_VERSION).catch(() => null);
42825
43527
  if (config.json || !isInteractive) {
42826
43528
  if (rawCommand === "npm") {
42827
43529
  const { runStaticNpm: runStaticNpm2 } = await Promise.resolve().then(() => (init_static_output(), static_output_exports));
42828
43530
  await runStaticNpm2(process.argv.slice(3), config);
43531
+ } else if (rawCommand === "pip") {
43532
+ const { runStaticPip: runStaticPip2 } = await Promise.resolve().then(() => (init_static_output(), static_output_exports));
43533
+ await runStaticPip2(process.argv.slice(3), config);
42829
43534
  } else {
42830
43535
  const { runStatic: runStatic2 } = await Promise.resolve().then(() => (init_static_output(), static_output_exports));
42831
43536
  await runStatic2(config);
@@ -42838,11 +43543,17 @@ async function main() {
42838
43543
  return;
42839
43544
  }
42840
43545
  const { render: render2 } = await init_build2().then(() => build_exports);
42841
- const React17 = await Promise.resolve().then(() => __toESM(require_react()));
43546
+ const React18 = await Promise.resolve().then(() => __toESM(require_react()));
42842
43547
  if (rawCommand === "npm") {
42843
43548
  const { NpmWrapperApp: NpmWrapperApp2 } = await init_NpmWrapperApp().then(() => NpmWrapperApp_exports);
42844
43549
  const { waitUntilExit } = render2(
42845
- React17.createElement(NpmWrapperApp2, { config, npmArgs: process.argv.slice(3) })
43550
+ React18.createElement(NpmWrapperApp2, { config, npmArgs: process.argv.slice(3) })
43551
+ );
43552
+ await waitUntilExit();
43553
+ } else if (rawCommand === "pip") {
43554
+ const { PipWrapperApp: PipWrapperApp2 } = await init_PipWrapperApp().then(() => PipWrapperApp_exports);
43555
+ const { waitUntilExit } = render2(
43556
+ React18.createElement(PipWrapperApp2, { config, pipArgs: process.argv.slice(3) })
42846
43557
  );
42847
43558
  await waitUntilExit();
42848
43559
  } else {
@@ -42853,7 +43564,7 @@ async function main() {
42853
43564
  }
42854
43565
  const { App: App3 } = await init_App2().then(() => App_exports);
42855
43566
  const { waitUntilExit } = render2(
42856
- React17.createElement(App3, { config })
43567
+ React18.createElement(App3, { config })
42857
43568
  );
42858
43569
  await waitUntilExit();
42859
43570
  }
@@ -42864,17 +43575,25 @@ async function main() {
42864
43575
  }
42865
43576
  }
42866
43577
  main().catch((err) => {
42867
- try {
42868
- const chalk10 = require_source();
42869
- process.stderr.write(`
43578
+ if (process.argv.includes("--json")) {
43579
+ process.stdout.write(JSON.stringify({
43580
+ error: true,
43581
+ code: err.statusCode ? "api_error" : "internal_error",
43582
+ message: err.message
43583
+ }, null, 2) + "\n");
43584
+ } else {
43585
+ try {
43586
+ const chalk10 = require_source();
43587
+ process.stderr.write(`
42870
43588
  ${chalk10.bold.red("Error:")} ${err.message}
42871
43589
 
42872
43590
  `);
42873
- } catch {
42874
- process.stderr.write(`
43591
+ } catch {
43592
+ process.stderr.write(`
42875
43593
  Error: ${err.message}
42876
43594
 
42877
43595
  `);
43596
+ }
42878
43597
  }
42879
43598
  process.exit(3);
42880
43599
  });