@westbayberry/dg 1.0.62 → 1.0.63

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 (2) hide show
  1. package/dist/index.mjs +889 -599
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -1589,6 +1589,49 @@ var require_source = __commonJS({
1589
1589
 
1590
1590
  // src/telemetry.ts
1591
1591
  import { platform, arch } from "node:os";
1592
+ function telemetryEnabled() {
1593
+ return process.env.DG_TELEMETRY === "1";
1594
+ }
1595
+ function classifyError(err) {
1596
+ const m = (err.message || err.name || "").toLowerCase();
1597
+ if (/http (5\d\d)/.test(m)) {
1598
+ const code = m.match(/http (5\d\d)/)?.[1];
1599
+ return `http_${code ?? "5xx"}`;
1600
+ }
1601
+ if (/timeout|timed out|etimedout/.test(m)) return "network_timeout";
1602
+ if (/socket hang up|econnreset/.test(m)) return "network_reset";
1603
+ if (/enotfound|getaddrinfo/.test(m)) return "dns_failure";
1604
+ if (/certificate|cert_|self.signed/.test(m)) return "tls_failure";
1605
+ if (/econnrefused/.test(m)) return "connection_refused";
1606
+ if (/network|fetch failed/.test(m)) return "network_other";
1607
+ if (/permission denied|eacces/.test(m)) return "permission";
1608
+ if (/enospc/.test(m)) return "disk_full";
1609
+ if (/json|syntax|unexpected token/.test(m)) return "parse_error";
1610
+ if (err.name && err.name !== "Error") return `runtime_${err.name.toLowerCase()}`;
1611
+ return "runtime_error";
1612
+ }
1613
+ function sendCrashEvent(err, ctx) {
1614
+ if (!telemetryEnabled()) return;
1615
+ const apiUrl = process.env.DG_API_URL || "https://api.westbayberry.com";
1616
+ const body = JSON.stringify({
1617
+ error_class: classifyError(err),
1618
+ dg_version: cliVersion || "unknown",
1619
+ node_version: process.version,
1620
+ os_arch: `${platform()} ${arch()}`,
1621
+ command: (process.argv[2] || "unknown").slice(0, 32),
1622
+ batch_size: ctx?.packageCount,
1623
+ duration_ms: void 0
1624
+ });
1625
+ const ctrl = new AbortController();
1626
+ const t = setTimeout(() => ctrl.abort(), 2e3);
1627
+ fetch(`${apiUrl}/v1/cli/error`, {
1628
+ method: "POST",
1629
+ headers: { "Content-Type": "application/json", "User-Agent": `dg-cli/${cliVersion}` },
1630
+ body,
1631
+ signal: ctrl.signal
1632
+ }).catch(() => {
1633
+ }).finally(() => clearTimeout(t));
1634
+ }
1592
1635
  function initTelemetry(version) {
1593
1636
  if (initialized) return;
1594
1637
  initialized = true;
@@ -1656,17 +1699,42 @@ function buildIssueUrl(error, ctx) {
1656
1699
  });
1657
1700
  return `${GH_ISSUE_URL}?${params.toString()}`;
1658
1701
  }
1702
+ function suggestionForClass(klass) {
1703
+ if (klass.startsWith("http_5")) return "Try again in a moment.";
1704
+ if (klass === "network_timeout") return "Try again \u2014 the request timed out.";
1705
+ if (klass === "network_reset" || klass === "connection_refused") return "Try again \u2014 connection was reset.";
1706
+ if (klass === "dns_failure") return "Check your network connection.";
1707
+ if (klass === "tls_failure") return "Check your network's TLS / certificate settings.";
1708
+ if (klass === "permission") return "Check file permissions for the working directory.";
1709
+ if (klass === "disk_full") return "Free up disk space and try again.";
1710
+ if (klass === "parse_error") return "Re-run with DG_DEBUG=1 for details.";
1711
+ return "Re-run with DG_DEBUG=1 for details.";
1712
+ }
1659
1713
  function captureError(error, context) {
1660
1714
  try {
1661
1715
  const err = error instanceof Error ? error : new Error(String(error));
1716
+ sendCrashEvent(err, context);
1662
1717
  const chalk18 = chalkOrPlain();
1663
1718
  const debug = process.env.DG_DEBUG === "1";
1719
+ const klass = classifyError(err);
1720
+ if (!debug) {
1721
+ const summary = redact(err.message || err.name).slice(0, 120);
1722
+ process.stderr.write(
1723
+ "\n " + chalk18.red.bold("\u2718 dg crashed: ") + chalk18.red(summary) + "\n"
1724
+ );
1725
+ process.stderr.write(
1726
+ " " + chalk18.dim(suggestionForClass(klass)) + "\n\n"
1727
+ );
1728
+ process.exitCode = 1;
1729
+ return;
1730
+ }
1664
1731
  const cmd = process.argv.slice(2).join(" ") || "(none)";
1665
1732
  process.stderr.write("\n" + chalk18.dim(SEPARATOR) + "\n\n");
1666
1733
  process.stderr.write(
1667
1734
  " " + chalk18.red.bold("\u2718 dg crashed: ") + chalk18.red(redact(err.message || err.name)) + "\n\n"
1668
1735
  );
1669
1736
  const rows = [
1737
+ ["Class", klass],
1670
1738
  ["Where", context?.command ?? "(unknown)"],
1671
1739
  ["CLI", `dg-cli@${cliVersion || "unknown"}`],
1672
1740
  ["Node", `${process.version} ${platform()} ${arch()}`],
@@ -1677,22 +1745,16 @@ function captureError(error, context) {
1677
1745
  process.stderr.write(" " + chalk18.dim(k.padEnd(labelW)) + v + "\n");
1678
1746
  }
1679
1747
  process.stderr.write("\n");
1680
- const frames = trimStack(err.stack, debug);
1748
+ const frames = trimStack(err.stack, true);
1681
1749
  if (frames.length > 0) {
1682
1750
  process.stderr.write(" " + chalk18.dim("Top of stack:") + "\n");
1683
1751
  for (const f of frames) process.stderr.write(" " + chalk18.dim(f) + "\n");
1684
- if (!debug && (err.stack?.split("\n").length ?? 0) > frames.length + 1) {
1685
- process.stderr.write(" " + chalk18.dim("\u2026 (set DG_DEBUG=1 for the full stack)") + "\n");
1686
- }
1687
1752
  process.stderr.write("\n");
1688
1753
  }
1689
1754
  if (!suppressNudge()) {
1690
1755
  const url = buildIssueUrl(err, context);
1691
- process.stderr.write(" " + chalk18.dim("Looks like a bug?") + "\n");
1756
+ process.stderr.write(" " + chalk18.dim("File an issue:") + "\n");
1692
1757
  process.stderr.write(" " + chalk18.cyan(url) + "\n");
1693
- process.stderr.write(
1694
- " " + chalk18.dim("(version, command, and a redacted stack are pre-filled.") + "\n " + chalk18.dim(" Nothing sent automatically.)") + "\n"
1695
- );
1696
1758
  }
1697
1759
  process.stderr.write("\n" + chalk18.dim(SEPARATOR) + "\n\n");
1698
1760
  process.exitCode = 1;
@@ -1782,8 +1844,6 @@ function legacyPaths() {
1782
1844
  return {
1783
1845
  dgrc: join2(home, ".dgrc.json"),
1784
1846
  cacheDir: depGuardianDir,
1785
- cacheSqlite: join2(home, ".dg", "cache.sqlite"),
1786
- routingHint: join2(home, ".dg", "scan-routing-hint.json"),
1787
1847
  updateCheck: join2(home, ".dg-update-check.json"),
1788
1848
  trialBannerOld: join2(depGuardianDir, "last-trial-banner"),
1789
1849
  aliasesShOld: join2(depGuardianDir, "aliases.sh"),
@@ -1797,9 +1857,7 @@ function dgPaths() {
1797
1857
  cacheDir: dgCacheDir(),
1798
1858
  stateDir: dgStateDir(),
1799
1859
  config: dgConfigPath(),
1800
- cacheSqlite: dgCachePath("scans.sqlite"),
1801
1860
  updateCheck: dgCachePath("update-check.json"),
1802
- routingHint: dgCachePath("routing-hint.json"),
1803
1861
  trialBanner: dgStatePath("trial-banner"),
1804
1862
  aliasesSh: dgStatePath("aliases.sh"),
1805
1863
  hooksRegistry: dgStatePath("hooks-installed.json"),
@@ -2278,6 +2336,12 @@ function recordTermsAccepted() {
2278
2336
  data.termsAcceptedAt = now;
2279
2337
  data.privacyAcceptedAt = now;
2280
2338
  writeDgrc(data);
2339
+ const verify = readConfig();
2340
+ if (typeof verify.termsAcceptedAt !== "string" || typeof verify.privacyAcceptedAt !== "string") {
2341
+ throw new Error(
2342
+ `wrote ${configPath()} but read-back did not contain termsAcceptedAt/privacyAcceptedAt \u2014 check that the file is on a writable filesystem and not shadowed by a cloud-sync conflict copy`
2343
+ );
2344
+ }
2281
2345
  }
2282
2346
  function getStackPreference() {
2283
2347
  const data = readConfig();
@@ -2464,18 +2528,11 @@ function loadDgrc() {
2464
2528
  `);
2465
2529
  }
2466
2530
  }
2467
- if (existsSync2(cwdPath) && cwdPath !== homePath && cwdPath !== legacyHomePath) {
2468
- try {
2469
- const cwd2 = JSON.parse(readFileSync2(cwdPath, "utf-8"));
2470
- warnUnknownDgrcKeys(cwd2, cwdPath);
2471
- for (const k of KNOWN_DGRC_KEYS) {
2472
- if (k === "apiKey" || k === "apiUrl") continue;
2473
- if (k in cwd2) config[k] = cwd2[k];
2474
- }
2475
- } catch {
2476
- process.stderr.write(`Warning: Failed to parse ${cwdPath}, ignoring.
2477
- `);
2478
- }
2531
+ if (cwdPath !== homePath && cwdPath !== legacyHomePath && existsSync2(cwdPath) && process.env.DG_QUIET !== "1") {
2532
+ process.stderr.write(
2533
+ `dg: note: ./.dgrc.json is ignored (repo-controlled config is no longer honored). Use ~/.dg/.dgrc.json, env vars (DG_MODE, DG_API_URL), or CLI flags.
2534
+ `
2535
+ );
2479
2536
  }
2480
2537
  return config;
2481
2538
  }
@@ -2646,7 +2703,7 @@ var init_config = __esm({
2646
2703
  dg pip install <pkg> Same, for pip
2647
2704
  dg hook install Block risky lockfile changes at commit time
2648
2705
  dg login / dg logout Manage authentication
2649
- dg update Check for and install the latest dg version
2706
+ dg update Upgrade dg to the latest version
2650
2707
  dg uninstall Remove everything dg has written locally
2651
2708
 
2652
2709
  Common flags:
@@ -2683,7 +2740,7 @@ var init_config = __esm({
2683
2740
  uninstall Remove every file dg has written locally (config, cache,
2684
2741
  hooks across repos, shell-rc lines)
2685
2742
  status Show coverage overview (auth, protect, hook, npm/pip, update)
2686
- update Check for and install the latest version
2743
+ update Upgrade dg (detects npm/pnpm/yarn/bun; --print to dry-run)
2687
2744
  wrap Show instructions to alias npm to dg (manual)
2688
2745
  help Show short help (\`dg --help-all\` for this view)
2689
2746
  version Show version number
@@ -2726,6 +2783,7 @@ var init_config = __esm({
2726
2783
  1 warn \u2014 Risks detected (advisory)
2727
2784
  2 block \u2014 High-risk packages detected (or scan refused in block mode)
2728
2785
  3 error \u2014 Internal error (API failure, config error)
2786
+ 4 analysis_incomplete \u2014 Some packages could not be fully analyzed
2729
2787
 
2730
2788
  What dg actually does:
2731
2789
  - \`dg npm install foo\` resolves the FULL dependency tree via
@@ -3177,15 +3235,29 @@ var init_npm_wrapper = __esm({
3177
3235
  });
3178
3236
 
3179
3237
  // src/update-check.ts
3180
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
3238
+ import { existsSync as existsSync4, lstatSync as lstatSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "node:fs";
3181
3239
  import { dirname as dirname3 } from "node:path";
3182
- import { execFileSync } from "node:child_process";
3183
3240
  function cacheFile() {
3184
3241
  return dgCachePath("update-check.json");
3185
3242
  }
3186
3243
  function readCache() {
3244
+ const path2 = cacheFile();
3245
+ let stat = null;
3246
+ try {
3247
+ stat = lstatSync2(path2, { throwIfNoEntry: false }) ?? null;
3248
+ } catch {
3249
+ return null;
3250
+ }
3251
+ if (!stat) return null;
3252
+ if (!stat.isFile() || stat.size > MAX_CACHE_BYTES) {
3253
+ try {
3254
+ unlinkSync2(path2);
3255
+ } catch {
3256
+ }
3257
+ return null;
3258
+ }
3187
3259
  try {
3188
- const raw = readFileSync4(cacheFile(), "utf-8");
3260
+ const raw = readFileSync4(path2, "utf-8");
3189
3261
  const data = JSON.parse(raw);
3190
3262
  if (typeof data.latest === "string" && typeof data.checkedAt === "number") {
3191
3263
  return data;
@@ -3199,7 +3271,9 @@ function writeCache(entry) {
3199
3271
  const path2 = cacheFile();
3200
3272
  const parent = dirname3(path2);
3201
3273
  if (!existsSync4(parent)) mkdirSync2(parent, { recursive: true, mode: 448 });
3202
- writeFileSync3(path2, JSON.stringify(entry), "utf-8");
3274
+ const serialized = JSON.stringify(entry);
3275
+ if (serialized.length > MAX_CACHE_BYTES) return;
3276
+ writeFileSync3(path2, serialized, { encoding: "utf-8", mode: 384 });
3203
3277
  } catch {
3204
3278
  }
3205
3279
  }
@@ -3259,47 +3333,14 @@ async function checkForUpdate(currentVersion) {
3259
3333
  }
3260
3334
  return null;
3261
3335
  }
3262
- async function runUpdate(currentVersion) {
3263
- const chalk18 = (await Promise.resolve().then(() => __toESM(require_source()))).default;
3264
- process.stderr.write(chalk18.dim(" Checking for updates...\n"));
3265
- const latest = await fetchLatestVersion();
3266
- if (!latest) {
3267
- process.stderr.write(chalk18.red(" Could not reach npm registry.\n"));
3268
- process.exit(1);
3269
- }
3270
- if (!isNewer(latest, currentVersion)) {
3271
- process.stderr.write(
3272
- chalk18.green(` Already on latest version (${currentVersion}).
3273
- `)
3274
- );
3275
- return;
3276
- }
3277
- process.stderr.write(chalk18.dim(` Installing ${PKG_NAME}@${latest}...
3278
- `));
3279
- try {
3280
- execFileSync("npm", ["install", "-g", `${PKG_NAME}@${latest}`], { stdio: "inherit" });
3281
- writeCache({ latest, checkedAt: Date.now() });
3282
- process.stderr.write(
3283
- chalk18.green(`
3284
- Updated ${currentVersion} \u2192 ${latest}
3285
- `)
3286
- );
3287
- } catch {
3288
- process.stderr.write(
3289
- chalk18.red(`
3290
- Update failed. Try manually: npm i -g ${PKG_NAME}@${latest}
3291
- `)
3292
- );
3293
- process.exit(1);
3294
- }
3295
- }
3296
- var PKG_NAME, CHECK_INTERVAL_MS;
3336
+ var PKG_NAME, CHECK_INTERVAL_MS, MAX_CACHE_BYTES;
3297
3337
  var init_update_check = __esm({
3298
3338
  "src/update-check.ts"() {
3299
3339
  "use strict";
3300
3340
  init_paths();
3301
3341
  PKG_NAME = "@westbayberry/dg";
3302
3342
  CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
3343
+ MAX_CACHE_BYTES = 64 * 1024;
3303
3344
  }
3304
3345
  });
3305
3346
 
@@ -37562,6 +37603,45 @@ var init_walker = __esm({
37562
37603
  }
37563
37604
  });
37564
37605
 
37606
+ // src/security/safe_git.ts
37607
+ import { execFileSync } from "node:child_process";
37608
+ function safeGit(args, opts) {
37609
+ return execFileSync("git", [...HARDENING_ARGS, ...args], {
37610
+ ...opts,
37611
+ encoding: "utf-8",
37612
+ stdio: opts.stdio ?? ["pipe", "pipe", "pipe"],
37613
+ env: { ...process.env, ...HARDENING_ENV, ...opts.env ?? {} }
37614
+ });
37615
+ }
37616
+ var HARDENING_ARGS, HARDENING_ENV;
37617
+ var init_safe_git = __esm({
37618
+ "src/security/safe_git.ts"() {
37619
+ "use strict";
37620
+ HARDENING_ARGS = [
37621
+ "-c",
37622
+ "core.fsmonitor=",
37623
+ "-c",
37624
+ "core.hooksPath=/dev/null",
37625
+ "-c",
37626
+ "core.editor=true",
37627
+ "-c",
37628
+ "core.pager=cat",
37629
+ "-c",
37630
+ "core.sshCommand=false",
37631
+ "-c",
37632
+ "protocol.allow=user"
37633
+ ];
37634
+ HARDENING_ENV = {
37635
+ GIT_TERMINAL_PROMPT: "0",
37636
+ GIT_OPTIONAL_LOCKS: "0",
37637
+ GIT_ASKPASS: "true",
37638
+ SSH_ASKPASS: "true",
37639
+ GIT_CONFIG_NOSYSTEM: "1",
37640
+ GIT_CONFIG_GLOBAL: "/dev/null"
37641
+ };
37642
+ }
37643
+ });
37644
+
37565
37645
  // src/state/hooks_registry.ts
37566
37646
  import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync5, chmodSync as chmodSync3 } from "node:fs";
37567
37647
  import { dirname as dirname5, isAbsolute as isAbsolute2, normalize } from "node:path";
@@ -37680,21 +37760,20 @@ __export(hook_exports, {
37680
37760
  uninstallHookFromRepo: () => uninstallHookFromRepo,
37681
37761
  verifyHookInstalled: () => verifyHookInstalled
37682
37762
  });
37683
- import { execFileSync as execFileSync2 } from "node:child_process";
37684
37763
  import {
37685
37764
  existsSync as existsSync8,
37686
- lstatSync as lstatSync2,
37765
+ lstatSync as lstatSync3,
37687
37766
  readFileSync as readFileSync8,
37688
37767
  writeFileSync as writeFileSync6,
37689
37768
  mkdirSync as mkdirSync5,
37690
37769
  chmodSync as chmodSync4,
37691
- unlinkSync as unlinkSync3
37770
+ unlinkSync as unlinkSync4
37692
37771
  } from "node:fs";
37693
37772
  import { join as join6, dirname as dirname6, resolve as resolvePath, isAbsolute as isAbsolute3 } from "node:path";
37694
37773
  function assertSafeWriteTarget(target) {
37695
37774
  const parent = dirname6(target);
37696
37775
  try {
37697
- const parentStat = lstatSync2(parent);
37776
+ const parentStat = lstatSync3(parent);
37698
37777
  if (parentStat.isSymbolicLink()) {
37699
37778
  throw new Error(
37700
37779
  `refusing to write to ${target}: parent dir ${parent} is a symlink (possible path-traversal attack)`
@@ -37706,7 +37785,7 @@ function assertSafeWriteTarget(target) {
37706
37785
  }
37707
37786
  }
37708
37787
  try {
37709
- const targetStat = lstatSync2(target);
37788
+ const targetStat = lstatSync3(target);
37710
37789
  if (targetStat.isSymbolicLink()) {
37711
37790
  throw new Error(
37712
37791
  `refusing to write to ${target}: target is a symlink (possible path-traversal attack)`
@@ -37724,20 +37803,14 @@ function safeWriteFileSync(target, content) {
37724
37803
  }
37725
37804
  function findHooksDir() {
37726
37805
  try {
37727
- return execFileSync2("git", ["rev-parse", "--git-path", "hooks"], {
37728
- encoding: "utf-8",
37729
- stdio: ["pipe", "pipe", "pipe"]
37730
- }).trim();
37806
+ return safeGit(["rev-parse", "--git-path", "hooks"], { cwd: process.cwd() }).trim();
37731
37807
  } catch {
37732
37808
  throw new Error("Not a git repository. Run this from inside a git project.");
37733
37809
  }
37734
37810
  }
37735
37811
  function findRepoRoot() {
37736
37812
  try {
37737
- return execFileSync2("git", ["rev-parse", "--show-toplevel"], {
37738
- encoding: "utf-8",
37739
- stdio: ["pipe", "pipe", "pipe"]
37740
- }).trim();
37813
+ return safeGit(["rev-parse", "--show-toplevel"], { cwd: process.cwd() }).trim();
37741
37814
  } catch {
37742
37815
  throw new Error("Not a git repository. Run this from inside a git project.");
37743
37816
  }
@@ -37799,7 +37872,7 @@ function verifyHookInstalled(info) {
37799
37872
  }
37800
37873
  if (info.framework === "bare" || info.framework === "husky") {
37801
37874
  try {
37802
- const mode = lstatSync2(info.targetFile).mode & 511;
37875
+ const mode = lstatSync3(info.targetFile).mode & 511;
37803
37876
  if ((mode & 73) === 0) {
37804
37877
  return { ok: false, reason: `hook is not executable (mode ${mode.toString(8)})` };
37805
37878
  }
@@ -37951,7 +38024,7 @@ function stripDgFromHookFile(hookPath, framework, repoPath) {
37951
38024
  return { status: "not-found", hookPath, repoPath };
37952
38025
  }
37953
38026
  if (framework === "bare" && content.trimStart().startsWith("#!/bin/sh\n" + HOOK_MARKER)) {
37954
- unlinkSync3(hookPath);
38027
+ unlinkSync4(hookPath);
37955
38028
  removeEntry(repoPath, hookPath);
37956
38029
  return { status: "removed", hookPath, repoPath };
37957
38030
  }
@@ -37964,7 +38037,7 @@ function stripDgFromHookFile(hookPath, framework, repoPath) {
37964
38037
  const after = content.slice(endIdx + MARKER_END.length).trimStart();
37965
38038
  const remaining = (before + (after ? "\n" + after : "")).trimEnd() + "\n";
37966
38039
  if (remaining.trim() === "" || remaining.trim() === "#!/bin/sh") {
37967
- unlinkSync3(hookPath);
38040
+ unlinkSync4(hookPath);
37968
38041
  } else {
37969
38042
  safeWriteFileSync(hookPath, remaining);
37970
38043
  }
@@ -38012,7 +38085,7 @@ function uninstallHookFromRepo(repoRootOverride) {
38012
38085
  return { status: "not-found", hookPath, repoPath: root };
38013
38086
  }
38014
38087
  if (content.trimStart().startsWith("#!/bin/sh\n" + HOOK_MARKER)) {
38015
- unlinkSync3(hookPath);
38088
+ unlinkSync4(hookPath);
38016
38089
  removeEntry(root, hookPath);
38017
38090
  return { status: "removed", hookPath, repoPath: root };
38018
38091
  }
@@ -38026,7 +38099,7 @@ function uninstallHookFromRepo(repoRootOverride) {
38026
38099
  removeEntry(root, hookPath);
38027
38100
  return { status: "removed", hookPath, repoPath: root };
38028
38101
  }
38029
- unlinkSync3(hookPath);
38102
+ unlinkSync4(hookPath);
38030
38103
  removeEntry(root, hookPath);
38031
38104
  return { status: "removed", hookPath, repoPath: root };
38032
38105
  }
@@ -38079,6 +38152,7 @@ var HOOK_MARKER, MARKER_START, MARKER_END, LEFTHOOK_MARKER, HOOK_SCRIPT, HOOK_SE
38079
38152
  var init_hook = __esm({
38080
38153
  "src/commands/hook.ts"() {
38081
38154
  "use strict";
38155
+ init_safe_git();
38082
38156
  init_hooks_registry();
38083
38157
  HOOK_MARKER = "# dependency-guardian-hook";
38084
38158
  MARKER_START = "# dependency-guardian-hook-start";
@@ -38167,6 +38241,41 @@ ${MARKER_END}
38167
38241
  }
38168
38242
  });
38169
38243
 
38244
+ // src/security/sanitize.ts
38245
+ import { stripVTControlCharacters } from "node:util";
38246
+ function sanitize(s) {
38247
+ return stripVTControlCharacters(s);
38248
+ }
38249
+ function sanitizeDeep(value) {
38250
+ if (typeof value === "string") {
38251
+ return stripVTControlCharacters(value);
38252
+ }
38253
+ if (value === null || value === void 0) {
38254
+ return value;
38255
+ }
38256
+ if (Array.isArray(value)) {
38257
+ return value.map((v) => sanitizeDeep(v));
38258
+ }
38259
+ if (typeof value === "object") {
38260
+ const src = value;
38261
+ const out = {};
38262
+ for (const k of Object.keys(src)) {
38263
+ const cleanKey = stripVTControlCharacters(k);
38264
+ out[cleanKey] = sanitizeDeep(src[k]);
38265
+ }
38266
+ return out;
38267
+ }
38268
+ return value;
38269
+ }
38270
+ function sanitizeResponse(response) {
38271
+ return sanitizeDeep(response);
38272
+ }
38273
+ var init_sanitize = __esm({
38274
+ "src/security/sanitize.ts"() {
38275
+ "use strict";
38276
+ }
38277
+ });
38278
+
38170
38279
  // src/lockfile/parse_package_lock.ts
38171
38280
  function addLockfileEntry(packages, name, entry) {
38172
38281
  const existing = packages.get(name);
@@ -38276,17 +38385,31 @@ var init_parse_package_lock = __esm({
38276
38385
  });
38277
38386
 
38278
38387
  // src/lockfile/parse_yarn_lock.ts
38388
+ function parseEntryKey(firstSpec) {
38389
+ const npmAliasIdx = firstSpec.indexOf("@npm:");
38390
+ if (npmAliasIdx > 0) {
38391
+ const alias = firstSpec.slice(0, npmAliasIdx);
38392
+ const realSpec = firstSpec.slice(npmAliasIdx + "@npm:".length);
38393
+ const atIdx2 = realSpec.startsWith("@") ? realSpec.indexOf("@", 1) : realSpec.indexOf("@");
38394
+ const name = atIdx2 > 0 ? realSpec.slice(0, atIdx2) : realSpec;
38395
+ return { name: name || null, alias: alias || void 0 };
38396
+ }
38397
+ const atIdx = firstSpec.startsWith("@") ? firstSpec.indexOf("@", 1) : firstSpec.indexOf("@");
38398
+ return { name: atIdx > 0 ? firstSpec.slice(0, atIdx) : null };
38399
+ }
38279
38400
  function parseYarnLock(content) {
38280
38401
  const packages = /* @__PURE__ */ new Map();
38281
38402
  const lines = content.split("\n");
38282
38403
  let currentName = null;
38404
+ let currentAlias = void 0;
38283
38405
  let currentEntry = {};
38284
38406
  const flush2 = () => {
38285
38407
  if (currentName && currentEntry.version) {
38286
38408
  addLockfileEntry(packages, currentName, {
38287
38409
  version: currentEntry.version,
38288
38410
  resolved: currentEntry.resolved,
38289
- integrity: currentEntry.integrity
38411
+ integrity: currentEntry.integrity,
38412
+ alias: currentAlias
38290
38413
  });
38291
38414
  }
38292
38415
  };
@@ -38294,6 +38417,7 @@ function parseYarnLock(content) {
38294
38417
  if (line.startsWith("#") || line.trim() === "") {
38295
38418
  flush2();
38296
38419
  currentName = null;
38420
+ currentAlias = void 0;
38297
38421
  currentEntry = {};
38298
38422
  continue;
38299
38423
  }
@@ -38301,8 +38425,9 @@ function parseYarnLock(content) {
38301
38425
  flush2();
38302
38426
  const raw = line.slice(0, -1);
38303
38427
  const firstSpec = raw.split(",")[0].trim().replace(/^"/, "").replace(/"$/, "");
38304
- const atIdx = firstSpec.startsWith("@") ? firstSpec.indexOf("@", 1) : firstSpec.indexOf("@");
38305
- currentName = atIdx > 0 ? firstSpec.slice(0, atIdx) : null;
38428
+ const parsed = parseEntryKey(firstSpec);
38429
+ currentName = parsed.name;
38430
+ currentAlias = parsed.alias;
38306
38431
  currentEntry = {};
38307
38432
  continue;
38308
38433
  }
@@ -38388,7 +38513,8 @@ function diffLockfiles(base, head, maxPackages, directDeps) {
38388
38513
  oldVersion,
38389
38514
  newVersion: headEntry.version,
38390
38515
  isDirect: directDeps?.has(name) ?? false,
38391
- isDev: headEntry.dev ?? false
38516
+ isDev: headEntry.dev ?? false,
38517
+ alias: headEntry.alias
38392
38518
  });
38393
38519
  }
38394
38520
  }
@@ -38453,7 +38579,6 @@ var init_parse_package_json = __esm({
38453
38579
  });
38454
38580
 
38455
38581
  // src/lockfile/index.ts
38456
- import { execFileSync as execFileSync3 } from "node:child_process";
38457
38582
  import { readFileSync as readFileSync9, existsSync as existsSync9, statSync as statSync2 } from "node:fs";
38458
38583
  import { join as join7 } from "node:path";
38459
38584
  function readFileSafe(path2) {
@@ -38475,7 +38600,12 @@ function discoverChanges(cwd2, config) {
38475
38600
  const pyPkgs = parsePythonDepFile(cwd2, pyFile);
38476
38601
  for (const p of pyPkgs) {
38477
38602
  if (p.version === "latest") continue;
38478
- pythonPackages.push({ name: p.name, version: p.version, previousVersion: null, isNew: true });
38603
+ pythonPackages.push({
38604
+ name: sanitize(p.name),
38605
+ version: sanitize(p.version),
38606
+ previousVersion: null,
38607
+ isNew: true
38608
+ });
38479
38609
  }
38480
38610
  break;
38481
38611
  }
@@ -38556,8 +38686,8 @@ function discoverChanges(cwd2, config) {
38556
38686
  continue;
38557
38687
  }
38558
38688
  packages.push({
38559
- name,
38560
- version: entry.version,
38689
+ name: sanitize(name),
38690
+ version: sanitize(entry.version),
38561
38691
  previousVersion: null,
38562
38692
  isNew: true
38563
38693
  });
@@ -38602,21 +38732,13 @@ function getDirectDeps(cwd2) {
38602
38732
  }
38603
38733
  function getDefaultBranch(cwd2) {
38604
38734
  try {
38605
- const ref = execFileSync3("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
38606
- cwd: cwd2,
38607
- encoding: "utf-8",
38608
- stdio: ["pipe", "pipe", "pipe"]
38609
- }).trim();
38735
+ const ref = safeGit(["symbolic-ref", "refs/remotes/origin/HEAD"], { cwd: cwd2 }).trim();
38610
38736
  const branch = ref.replace(/^refs\/remotes\/origin\//, "");
38611
38737
  if (branch) return branch;
38612
38738
  } catch {
38613
38739
  }
38614
38740
  try {
38615
- execFileSync3("git", ["rev-parse", "--verify", "refs/remotes/origin/master"], {
38616
- cwd: cwd2,
38617
- encoding: "utf-8",
38618
- stdio: ["pipe", "pipe", "pipe"]
38619
- });
38741
+ safeGit(["rev-parse", "--verify", "refs/remotes/origin/master"], { cwd: cwd2 });
38620
38742
  return "master";
38621
38743
  } catch {
38622
38744
  }
@@ -38625,19 +38747,11 @@ function getDefaultBranch(cwd2) {
38625
38747
  function getGitBaseLockfile(cwd2) {
38626
38748
  try {
38627
38749
  const defaultBranch = getDefaultBranch(cwd2);
38628
- const mergeBase = execFileSync3("git", ["merge-base", "HEAD", defaultBranch], {
38629
- cwd: cwd2,
38630
- encoding: "utf-8",
38631
- stdio: ["pipe", "pipe", "pipe"]
38632
- }).trim();
38750
+ const mergeBase = safeGit(["merge-base", "HEAD", defaultBranch], { cwd: cwd2 }).trim();
38633
38751
  if (!mergeBase) return null;
38634
38752
  for (const name of ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"]) {
38635
38753
  try {
38636
- return execFileSync3("git", ["show", `${mergeBase}:${name}`], {
38637
- cwd: cwd2,
38638
- encoding: "utf-8",
38639
- stdio: ["pipe", "pipe", "pipe"]
38640
- });
38754
+ return safeGit(["show", `${mergeBase}:${name}`], { cwd: cwd2 });
38641
38755
  } catch {
38642
38756
  continue;
38643
38757
  }
@@ -38650,27 +38764,20 @@ function getGitBaseLockfile(cwd2) {
38650
38764
  function getGitBaseFile(cwd2, filename) {
38651
38765
  try {
38652
38766
  const defaultBranch = getDefaultBranch(cwd2);
38653
- const mergeBase = execFileSync3("git", ["merge-base", "HEAD", defaultBranch], {
38654
- cwd: cwd2,
38655
- encoding: "utf-8",
38656
- stdio: ["pipe", "pipe", "pipe"]
38657
- }).trim();
38767
+ const mergeBase = safeGit(["merge-base", "HEAD", defaultBranch], { cwd: cwd2 }).trim();
38658
38768
  if (!mergeBase) return null;
38659
- return execFileSync3("git", ["show", `${mergeBase}:${filename}`], {
38660
- cwd: cwd2,
38661
- encoding: "utf-8",
38662
- stdio: ["pipe", "pipe", "pipe"]
38663
- });
38769
+ return safeGit(["show", `${mergeBase}:${filename}`], { cwd: cwd2 });
38664
38770
  } catch {
38665
38771
  return null;
38666
38772
  }
38667
38773
  }
38668
38774
  function toPackageInput(change) {
38669
38775
  return {
38670
- name: change.name,
38671
- version: change.newVersion,
38672
- previousVersion: change.oldVersion,
38673
- isNew: change.oldVersion === null
38776
+ name: sanitize(change.name),
38777
+ version: sanitize(change.newVersion),
38778
+ previousVersion: change.oldVersion === null ? null : sanitize(change.oldVersion),
38779
+ isNew: change.oldVersion === null,
38780
+ alias: change.alias ? sanitize(change.alias) : void 0
38674
38781
  };
38675
38782
  }
38676
38783
  function parsePythonDepFile(projectDir, depFile) {
@@ -38727,6 +38834,8 @@ var MAX_LOCKFILE_BYTES, SELF_PACKAGE;
38727
38834
  var init_lockfile = __esm({
38728
38835
  "src/lockfile/index.ts"() {
38729
38836
  "use strict";
38837
+ init_safe_git();
38838
+ init_sanitize();
38730
38839
  init_parse_package_lock();
38731
38840
  init_parse_yarn_lock();
38732
38841
  init_parse_pnpm_lock();
@@ -38737,44 +38846,6 @@ var init_lockfile = __esm({
38737
38846
  }
38738
38847
  });
38739
38848
 
38740
- // src/security/sanitize.ts
38741
- import { stripVTControlCharacters } from "node:util";
38742
- function sanitize(s) {
38743
- return stripVTControlCharacters(s);
38744
- }
38745
- function sanitizeFinding(f) {
38746
- const result = { severity: f.severity };
38747
- if (f.category !== void 0) result.category = sanitize(f.category);
38748
- if (f.title !== void 0) result.title = sanitize(f.title);
38749
- if (f.evidence !== void 0) result.evidence = f.evidence.map(sanitize);
38750
- return result;
38751
- }
38752
- function sanitizeResponse(response) {
38753
- const packages = response.packages.map((pkg) => ({
38754
- name: sanitize(pkg.name),
38755
- version: sanitize(pkg.version),
38756
- score: pkg.score,
38757
- findings: (pkg.findings ?? []).map(sanitizeFinding),
38758
- reasons: (pkg.reasons ?? []).map(sanitize),
38759
- recommendation: pkg.recommendation ? sanitize(pkg.recommendation) : void 0,
38760
- cached: pkg.cached,
38761
- license: pkg.license
38762
- }));
38763
- return {
38764
- score: response.score,
38765
- action: response.action,
38766
- packages,
38767
- safeVersions: response.safeVersions,
38768
- durationMs: response.durationMs,
38769
- trialScansRemaining: response.trialScansRemaining
38770
- };
38771
- }
38772
- var init_sanitize = __esm({
38773
- "src/security/sanitize.ts"() {
38774
- "use strict";
38775
- }
38776
- });
38777
-
38778
38849
  // src/api/client.ts
38779
38850
  var client_exports = {};
38780
38851
  __export(client_exports, {
@@ -38984,6 +39055,27 @@ function formatApiErrorMessage(status, rawBody, config) {
38984
39055
  }
38985
39056
  return `API returned ${status}: ${truncated}`;
38986
39057
  }
39058
+ async function readBoundedJson(response) {
39059
+ const cl = response.headers?.get?.("content-length");
39060
+ if (cl) {
39061
+ const n = Number(cl);
39062
+ if (Number.isFinite(n) && n > MAX_RESPONSE_BYTES) {
39063
+ throw new APIError("API response exceeded size limit", 502, "");
39064
+ }
39065
+ }
39066
+ if (typeof response.text === "function") {
39067
+ const text = await response.text();
39068
+ if (text.length > MAX_RESPONSE_BYTES) {
39069
+ throw new APIError("API response exceeded size limit", 502, "");
39070
+ }
39071
+ try {
39072
+ return JSON.parse(text);
39073
+ } catch {
39074
+ throw new APIError("Invalid JSON in API response", 502, "");
39075
+ }
39076
+ }
39077
+ return await response.json();
39078
+ }
38987
39079
  async function callAnalyzeBatch(packages, config, onPackageProgress) {
38988
39080
  const url = `${config.apiUrl}/v1/analyze`;
38989
39081
  const payload = {
@@ -39047,7 +39139,7 @@ async function callAnalyzeBatch(packages, config, onPackageProgress) {
39047
39139
  if (contentType.includes("application/x-ndjson")) {
39048
39140
  return readNdjsonAnalyzeResponse(response, onPackageProgress);
39049
39141
  }
39050
- const raw = await response.json();
39142
+ const raw = await readBoundedJson(response);
39051
39143
  if (!raw || typeof raw.score !== "number" || !Array.isArray(raw.packages)) {
39052
39144
  throw new APIError("Invalid API response format", 0, "");
39053
39145
  }
@@ -39060,8 +39152,12 @@ async function readNdjsonAnalyzeResponse(response, onPackageProgress) {
39060
39152
  }
39061
39153
  const decoder = new TextDecoder();
39062
39154
  let buf = "";
39155
+ let totalBytes = 0;
39063
39156
  let finalPayload = null;
39064
39157
  const processLine = (raw) => {
39158
+ if (raw.length > MAX_NDJSON_LINE_BYTES) {
39159
+ throw new APIError("API response line exceeded size limit", 502, "");
39160
+ }
39065
39161
  const line = raw.trim();
39066
39162
  if (!line) return;
39067
39163
  let event;
@@ -39087,21 +39183,47 @@ async function readNdjsonAnalyzeResponse(response, onPackageProgress) {
39087
39183
  break;
39088
39184
  case "error": {
39089
39185
  const sc = typeof event.statusCode === "number" ? event.statusCode : 500;
39090
- const msg = typeof event.error === "string" ? event.error : "Streaming scan failed";
39091
- throw new APIError(msg, sc, JSON.stringify(event));
39186
+ const msg = sanitize(typeof event.error === "string" ? event.error : "Streaming scan failed");
39187
+ throw new APIError(msg, sc, "");
39092
39188
  }
39093
39189
  case "started":
39094
39190
  break;
39095
39191
  }
39096
39192
  };
39097
- const iterable = response.body;
39098
- for await (const chunk of iterable) {
39099
- buf += decoder.decode(chunk, { stream: true });
39100
- let idx;
39101
- while ((idx = buf.indexOf("\n")) !== -1) {
39102
- const line = buf.slice(0, idx);
39103
- buf = buf.slice(idx + 1);
39104
- processLine(line);
39193
+ const reader = response.body.getReader();
39194
+ try {
39195
+ while (true) {
39196
+ const { done, value } = await reader.read();
39197
+ if (done) break;
39198
+ if (value) {
39199
+ totalBytes += value.byteLength;
39200
+ if (totalBytes > MAX_RESPONSE_BYTES) {
39201
+ try {
39202
+ await reader.cancel();
39203
+ } catch {
39204
+ }
39205
+ throw new APIError("API response exceeded size limit", 502, "");
39206
+ }
39207
+ buf += decoder.decode(value, { stream: true });
39208
+ let idx;
39209
+ while ((idx = buf.indexOf("\n")) !== -1) {
39210
+ const line = buf.slice(0, idx);
39211
+ buf = buf.slice(idx + 1);
39212
+ processLine(line);
39213
+ }
39214
+ if (buf.length > MAX_NDJSON_LINE_BYTES) {
39215
+ try {
39216
+ await reader.cancel();
39217
+ } catch {
39218
+ }
39219
+ throw new APIError("API response line exceeded size limit", 502, "");
39220
+ }
39221
+ }
39222
+ }
39223
+ } finally {
39224
+ try {
39225
+ reader.releaseLock();
39226
+ } catch {
39105
39227
  }
39106
39228
  }
39107
39229
  buf += decoder.decode();
@@ -39205,7 +39327,7 @@ async function callPyPIBatch(packages, config) {
39205
39327
  body
39206
39328
  );
39207
39329
  }
39208
- const raw = await response.json();
39330
+ const raw = await readBoundedJson(response);
39209
39331
  if (!raw || typeof raw.score !== "number" || !Array.isArray(raw.packages)) {
39210
39332
  throw new APIError("Invalid API response format", 0, "");
39211
39333
  }
@@ -39250,7 +39372,7 @@ async function fetchLicensesBatchAt(endpoint, packages, config) {
39250
39372
  const body = await response.text();
39251
39373
  throw new APIError(formatApiErrorMessage(response.status, body, config), response.status, body);
39252
39374
  }
39253
- const data = await response.json();
39375
+ const data = await readBoundedJson(response);
39254
39376
  if (!Array.isArray(data.packages)) {
39255
39377
  throw new APIError("Invalid licenses response payload", 0, "");
39256
39378
  }
@@ -39290,7 +39412,7 @@ function fetchLicenses(packages, config, onBatchComplete) {
39290
39412
  function fetchPyPILicenses(packages, config, onBatchComplete) {
39291
39413
  return batchLicenseFetch("/v1/pypi/licenses", packages, config, onBatchComplete);
39292
39414
  }
39293
- var APIError, TrialExhaustedError, ClientOutdatedError, BATCH_SIZE, ANON_BATCH_SIZE, MAX_RETRIES, RETRY_DELAY_MS, DEFAULT_BATCH_CONCURRENCY, _currentScanId, BatchPoolPartialError, LICENSE_BATCH_SIZE;
39415
+ var APIError, TrialExhaustedError, ClientOutdatedError, BATCH_SIZE, ANON_BATCH_SIZE, MAX_RETRIES, RETRY_DELAY_MS, MAX_RESPONSE_BYTES, MAX_NDJSON_LINE_BYTES, DEFAULT_BATCH_CONCURRENCY, _currentScanId, BatchPoolPartialError, LICENSE_BATCH_SIZE;
39294
39416
  var init_client = __esm({
39295
39417
  "src/api/client.ts"() {
39296
39418
  "use strict";
@@ -39328,6 +39450,8 @@ var init_client = __esm({
39328
39450
  ANON_BATCH_SIZE = 200;
39329
39451
  MAX_RETRIES = 2;
39330
39452
  RETRY_DELAY_MS = 5e3;
39453
+ MAX_RESPONSE_BYTES = 50 * 1024 * 1024;
39454
+ MAX_NDJSON_LINE_BYTES = 4 * 1024 * 1024;
39331
39455
  DEFAULT_BATCH_CONCURRENCY = 4;
39332
39456
  _currentScanId = "";
39333
39457
  BatchPoolPartialError = class extends Error {
@@ -39366,28 +39490,29 @@ async function scanProjectAtPath(cwd2, config, onProgress) {
39366
39490
  result: EMPTY_OK_RESULT(elapsedMs())
39367
39491
  };
39368
39492
  }
39493
+ let npmDone = 0;
39494
+ let pyDone = 0;
39495
+ const reportProgress = onProgress ? (current) => onProgress({ done: npmDone + pyDone, total: totalDiscovered, current }) : void 0;
39496
+ const npmPromise = npmPackages.length > 0 ? callAnalyzeAPI(
39497
+ npmPackages,
39498
+ config,
39499
+ reportProgress ? (done, _total, currentBatch) => {
39500
+ npmDone = done;
39501
+ reportProgress(currentBatch ?? []);
39502
+ } : void 0
39503
+ ) : Promise.resolve(null);
39504
+ const pyPromise = pyPackages.length > 0 ? callPyPIAnalyzeAPI(
39505
+ pyPackages,
39506
+ config,
39507
+ reportProgress ? (done) => {
39508
+ pyDone = done;
39509
+ reportProgress([]);
39510
+ } : void 0
39511
+ ) : Promise.resolve(null);
39512
+ const [npmResp, pyResp] = await Promise.all([npmPromise, pyPromise]);
39369
39513
  const responses = [];
39370
- let cumulativeDone = 0;
39371
- if (npmPackages.length > 0) {
39372
- const npmOnProgress = onProgress ? (done, _total, currentBatch) => {
39373
- onProgress({
39374
- done: cumulativeDone + done,
39375
- total: totalDiscovered,
39376
- current: currentBatch ?? []
39377
- });
39378
- } : void 0;
39379
- const npmResp = await callAnalyzeAPI(npmPackages, config, npmOnProgress);
39380
- responses.push(npmResp);
39381
- cumulativeDone += npmPackages.length;
39382
- }
39383
- if (pyPackages.length > 0) {
39384
- const pyOnProgress = onProgress ? (done, _total) => {
39385
- onProgress({ done: cumulativeDone + done, total: totalDiscovered, current: [] });
39386
- } : void 0;
39387
- const pyResp = await callPyPIAnalyzeAPI(pyPackages, config, pyOnProgress);
39388
- responses.push(pyResp);
39389
- cumulativeDone += pyPackages.length;
39390
- }
39514
+ if (npmResp) responses.push(npmResp);
39515
+ if (pyResp) responses.push(pyResp);
39391
39516
  const allPackages = responses.flatMap((r) => r.packages);
39392
39517
  if (allPackages.length === 0) {
39393
39518
  const elapsed = elapsedMs();
@@ -39475,7 +39600,7 @@ __export(protect_exports, {
39475
39600
  stripAliasSourceFromRc: () => stripAliasSourceFromRc,
39476
39601
  suggestedShellRc: () => suggestedShellRc
39477
39602
  });
39478
- import { existsSync as existsSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync7, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6, lstatSync as lstatSync3, appendFileSync } from "node:fs";
39603
+ import { existsSync as existsSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync7, unlinkSync as unlinkSync5, mkdirSync as mkdirSync6, lstatSync as lstatSync4, appendFileSync } from "node:fs";
39479
39604
  import { join as join8, dirname as dirname7 } from "node:path";
39480
39605
  import { homedir as homedir3 } from "node:os";
39481
39606
  function aliasFile() {
@@ -39483,7 +39608,7 @@ function aliasFile() {
39483
39608
  }
39484
39609
  function safeIsRegularFile(path2) {
39485
39610
  try {
39486
- const st = lstatSync3(path2);
39611
+ const st = lstatSync4(path2);
39487
39612
  return st.isFile() && !st.isSymbolicLink();
39488
39613
  } catch {
39489
39614
  return false;
@@ -39492,7 +39617,7 @@ function safeIsRegularFile(path2) {
39492
39617
  function safeWriteFileSync2(target, content, mode = 420) {
39493
39618
  const parent = dirname7(target);
39494
39619
  try {
39495
- const parentStat = lstatSync3(parent);
39620
+ const parentStat = lstatSync4(parent);
39496
39621
  if (parentStat.isSymbolicLink()) {
39497
39622
  throw new Error(`refusing to write to ${target}: parent dir is a symlink`);
39498
39623
  }
@@ -39500,7 +39625,7 @@ function safeWriteFileSync2(target, content, mode = 420) {
39500
39625
  if (e instanceof Error && e.message.startsWith("refusing")) throw e;
39501
39626
  }
39502
39627
  try {
39503
- const st = lstatSync3(target);
39628
+ const st = lstatSync4(target);
39504
39629
  if (st.isSymbolicLink()) {
39505
39630
  throw new Error(`refusing to write to ${target}: target is a symlink`);
39506
39631
  }
@@ -39545,7 +39670,7 @@ function appendAliasSourceToRc(rcPathRaw, sourceLine, opts = {}) {
39545
39670
  try {
39546
39671
  const parent = dirname7(rcPath);
39547
39672
  if (existsSync10(parent)) {
39548
- const parentStat = lstatSync3(parent);
39673
+ const parentStat = lstatSync4(parent);
39549
39674
  if (parentStat.isSymbolicLink()) {
39550
39675
  return { status: "failed", rcPath, reason: "parent directory is a symlink" };
39551
39676
  }
@@ -39556,7 +39681,7 @@ function appendAliasSourceToRc(rcPathRaw, sourceLine, opts = {}) {
39556
39681
  if (existsSync10(rcPath)) {
39557
39682
  let st;
39558
39683
  try {
39559
- st = lstatSync3(rcPath);
39684
+ st = lstatSync4(rcPath);
39560
39685
  } catch (e) {
39561
39686
  return { status: "failed", rcPath, reason: e.message };
39562
39687
  }
@@ -39720,23 +39845,24 @@ async function runProtectInit(cwd2, opts = {}) {
39720
39845
  process.stderr.write(import_chalk4.default.dim(` Mode: ${cfg.mode}${cfg.strict ? " \xB7 strict" : ""}
39721
39846
  `));
39722
39847
  process.stderr.write("\n");
39723
- if (!opts.noShell) {
39724
- try {
39725
- const path2 = writeAliasSnippet();
39726
- process.stderr.write(import_chalk4.default.bold(" Optional \u2014 alias npm/pip to route through DG:\n"));
39727
- process.stderr.write(import_chalk4.default.dim(` Wrote ${path2}.
39848
+ let aliasPath = null;
39849
+ try {
39850
+ aliasPath = writeAliasSnippet();
39851
+ } catch (e) {
39852
+ process.stderr.write(import_chalk4.default.yellow(` Could not write alias snippet: ${e.message}
39728
39853
  `));
39729
- process.stderr.write(" Add this line to your shell rc (zshrc/bashrc/profile):\n\n");
39730
- process.stderr.write(import_chalk4.default.cyan(` source ${path2}
39731
-
39854
+ }
39855
+ if (!opts.noShell && aliasPath) {
39856
+ process.stderr.write(import_chalk4.default.bold(" Optional \u2014 alias npm/pip to route through DG:\n"));
39857
+ process.stderr.write(import_chalk4.default.dim(` Wrote ${aliasPath}.
39732
39858
  `));
39733
- process.stderr.write(import_chalk4.default.dim(" Then reload your shell. `npm install` and `pip install` will\n"));
39734
- process.stderr.write(import_chalk4.default.dim(" be scanned automatically. To turn this off, remove the line.\n"));
39735
- process.stderr.write(import_chalk4.default.dim(" To turn off DG protection entirely, run `dg protect off`.\n\n"));
39736
- } catch (e) {
39737
- process.stderr.write(import_chalk4.default.yellow(` Could not write alias snippet: ${e.message}
39859
+ process.stderr.write(" Add this line to your shell rc (zshrc/bashrc/profile):\n\n");
39860
+ process.stderr.write(import_chalk4.default.cyan(` source ${aliasPath}
39861
+
39738
39862
  `));
39739
- }
39863
+ process.stderr.write(import_chalk4.default.dim(" Then reload your shell. `npm install` and `pip install` will\n"));
39864
+ process.stderr.write(import_chalk4.default.dim(" be scanned automatically. To turn this off, remove the line.\n"));
39865
+ process.stderr.write(import_chalk4.default.dim(" To turn off DG protection entirely, run `dg protect off`.\n\n"));
39740
39866
  }
39741
39867
  return 0;
39742
39868
  }
@@ -39767,7 +39893,7 @@ async function runProtectOff(cwd2, opts = {}) {
39767
39893
  void cwd2;
39768
39894
  if (opts.removeShell && aliasSnippetExists()) {
39769
39895
  try {
39770
- unlinkSync4(aliasFile());
39896
+ unlinkSync5(aliasFile());
39771
39897
  process.stderr.write(import_chalk4.default.green(" \u2713 ") + `Removed ${aliasFile()}
39772
39898
  `);
39773
39899
  removed += 1;
@@ -40277,94 +40403,46 @@ function useInit(opts = {}) {
40277
40403
  try {
40278
40404
  const config = parseConfig([process.argv[0] ?? "node", "init", "--scan-all"], false);
40279
40405
  const projects = state.projects.length > 0 ? state.projects : [{ path: opts.cwd ?? process.cwd() }];
40280
- const npmPackages = [];
40281
- const pypiPackages = [];
40282
- const seenNpm = /* @__PURE__ */ new Set();
40283
- const seenPy = /* @__PURE__ */ new Set();
40284
- for (const proj of projects) {
40285
- const isFoundProject = proj.ecosystem !== void 0;
40286
- const eco = isFoundProject ? proj.ecosystem : null;
40287
- if (eco === "pypi") {
40288
- try {
40289
- for (const pkg of parsePythonDepFile(proj.path, proj.depFile)) {
40290
- const key = `${pkg.name}@${pkg.version}`;
40291
- if (!seenPy.has(key)) {
40292
- seenPy.add(key);
40293
- pypiPackages.push(pkg);
40294
- }
40295
- }
40296
- } catch {
40297
- }
40298
- continue;
40299
- }
40406
+ const allOutcomes = [];
40407
+ const perProject = /* @__PURE__ */ new Map();
40408
+ for (let i = 0; i < projects.length; i++) {
40409
+ const proj = projects[i];
40410
+ let total = 0;
40300
40411
  try {
40301
40412
  const d = discoverChanges(proj.path, config);
40302
- for (const pkg of d.packages) {
40303
- const key = `${pkg.name}@${pkg.version}`;
40304
- if (!seenNpm.has(key)) {
40305
- seenNpm.add(key);
40306
- npmPackages.push(pkg);
40307
- }
40308
- }
40309
- for (const pkg of d.pythonPackages ?? []) {
40310
- const key = `${pkg.name}@${pkg.version}`;
40311
- if (!seenPy.has(key)) {
40312
- seenPy.add(key);
40313
- pypiPackages.push(pkg);
40314
- }
40315
- }
40413
+ total = d.packages.length + (d.pythonPackages?.length ?? 0);
40316
40414
  } catch {
40317
40415
  }
40416
+ perProject.set(i, { done: 0, total });
40318
40417
  }
40319
- const totalUnique = npmPackages.length + pypiPackages.length;
40320
- dispatch({ type: "scan_progress", progress: { done: 0, total: totalUnique, current: [] } });
40321
- const startMs = Date.now();
40322
- const responses = [];
40323
- let cumulativeDone = 0;
40324
- try {
40325
- if (npmPackages.length > 0) {
40326
- const npmResp = await callAnalyzeAPI(npmPackages, config, (done, _t, currentBatch) => {
40327
- dispatch({
40328
- type: "scan_progress",
40329
- progress: {
40330
- done: Math.min(cumulativeDone + done, totalUnique),
40331
- total: totalUnique,
40332
- current: currentBatch ?? []
40333
- }
40334
- });
40335
- });
40336
- responses.push(npmResp);
40337
- cumulativeDone += npmPackages.length;
40338
- }
40339
- if (pypiPackages.length > 0) {
40340
- const pyResp = await callPyPIAnalyzeAPI(pypiPackages, config, (done) => {
40341
- dispatch({
40342
- type: "scan_progress",
40343
- progress: {
40344
- done: Math.min(cumulativeDone + done, totalUnique),
40345
- total: totalUnique,
40346
- current: []
40347
- }
40348
- });
40349
- });
40350
- responses.push(pyResp);
40351
- cumulativeDone += pypiPackages.length;
40418
+ const aggregate = (current) => {
40419
+ let done = 0;
40420
+ let total = 0;
40421
+ for (const v of perProject.values()) {
40422
+ done += v.done;
40423
+ total += v.total;
40352
40424
  }
40353
- } catch (e) {
40354
- dispatch({
40355
- type: "scan_done",
40356
- outcome: {
40357
- status: "error",
40358
- result: null,
40359
- error: e instanceof Error ? e : new Error(String(e))
40360
- }
40425
+ return { done, total, current };
40426
+ };
40427
+ dispatch({ type: "scan_progress", progress: aggregate([]) });
40428
+ for (let i = 0; i < projects.length; i++) {
40429
+ const proj = projects[i];
40430
+ if (process.env.DG_DEBUG_WIZARD) process.stderr.write(`[wizard] scanning ${proj.path}
40431
+ `);
40432
+ const seeded = perProject.get(i);
40433
+ const outcome = await scanFn(proj.path, config, (p) => {
40434
+ const total = seeded?.total ?? p.total;
40435
+ perProject.set(i, { done: Math.min(p.done, total), total });
40436
+ dispatch({ type: "scan_progress", progress: aggregate(p.current) });
40361
40437
  });
40362
- return;
40438
+ if (process.env.DG_DEBUG_WIZARD) process.stderr.write(`[wizard] scan done ${proj.path} status=${outcome.status}
40439
+ `);
40440
+ allOutcomes.push(outcome);
40363
40441
  }
40364
40442
  const allPackages = [];
40365
40443
  const seen = /* @__PURE__ */ new Set();
40366
- for (const r of responses) {
40367
- for (const pkg of r.packages) {
40444
+ for (const outcome of allOutcomes) {
40445
+ for (const pkg of outcome.result?.result.packages ?? []) {
40368
40446
  const key = `${pkg.name}@${pkg.version}`;
40369
40447
  if (seen.has(key)) continue;
40370
40448
  seen.add(key);
@@ -40372,22 +40450,42 @@ function useInit(opts = {}) {
40372
40450
  }
40373
40451
  }
40374
40452
  const totalScanned = seen.size;
40375
- const totalDuration = Date.now() - startMs;
40453
+ const totalDuration = allOutcomes.reduce(
40454
+ (sum, o) => sum + (o.result?.durationMs ?? 0),
40455
+ 0
40456
+ );
40376
40457
  const maxScore = allPackages.length > 0 ? Math.max(0, ...allPackages.map((p) => p.score)) : 0;
40377
- const action = maxScore >= 70 ? "block" : maxScore >= 60 ? "warn" : "pass";
40458
+ const anyBlock = allPackages.some(
40459
+ (p) => p.action === "block"
40460
+ );
40461
+ const anyWarn = allPackages.some(
40462
+ (p) => p.action === "warn"
40463
+ );
40464
+ const anyIncomplete = allPackages.some(
40465
+ (p) => p.action === "analysis_incomplete"
40466
+ );
40467
+ const action = anyBlock ? "block" : anyWarn ? "warn" : anyIncomplete ? "analysis_incomplete" : "pass";
40378
40468
  if (totalScanned === 0) {
40379
- dispatch({
40380
- type: "scan_done",
40381
- outcome: {
40382
- status: totalUnique === 0 ? "no_packages" : "api_returned_empty",
40383
- result: {
40384
- result: { score: 0, action: "pass", packages: [], safeVersions: {}, durationMs: totalDuration },
40385
- durationMs: totalDuration,
40386
- scannedCount: 0,
40387
- skippedCount: 0
40469
+ const errOutcome = allOutcomes.find((o) => o.status === "error");
40470
+ const emptyApiOutcome = allOutcomes.find((o) => o.status === "api_returned_empty");
40471
+ const trialOutcome = allOutcomes.find((o) => o.status === "trial_exhausted");
40472
+ const meaningful = errOutcome ?? emptyApiOutcome ?? trialOutcome;
40473
+ if (meaningful) {
40474
+ dispatch({ type: "scan_done", outcome: meaningful });
40475
+ } else {
40476
+ dispatch({
40477
+ type: "scan_done",
40478
+ outcome: {
40479
+ status: "no_packages",
40480
+ result: {
40481
+ result: { score: 0, action: "pass", packages: [], safeVersions: {}, durationMs: totalDuration },
40482
+ durationMs: totalDuration,
40483
+ scannedCount: 0,
40484
+ skippedCount: 0
40485
+ }
40388
40486
  }
40389
- }
40390
- });
40487
+ });
40488
+ }
40391
40489
  } else {
40392
40490
  dispatch({
40393
40491
  type: "scan_done",
@@ -40455,7 +40553,6 @@ var init_useInit = __esm({
40455
40553
  init_hook();
40456
40554
  init_scan();
40457
40555
  init_lockfile();
40458
- init_client();
40459
40556
  init_config();
40460
40557
  init_protect();
40461
40558
  PROJECT_MARKERS = [
@@ -40586,6 +40683,38 @@ var init_useLogin = __esm({
40586
40683
  }
40587
40684
  });
40588
40685
 
40686
+ // src/ui/hooks/useTerminalSize.ts
40687
+ function useTerminalSize() {
40688
+ const { stdout } = use_stdout_default();
40689
+ const [size, setSize] = (0, import_react24.useState)({
40690
+ rows: stdout?.rows ?? process.stdout.rows ?? 24,
40691
+ cols: stdout?.columns ?? process.stdout.columns ?? 80
40692
+ });
40693
+ (0, import_react24.useEffect)(() => {
40694
+ const handle = () => {
40695
+ setSize({
40696
+ rows: process.stdout.rows ?? 24,
40697
+ cols: process.stdout.columns ?? 80
40698
+ });
40699
+ };
40700
+ process.stdout.setMaxListeners(process.stdout.getMaxListeners() + 1);
40701
+ process.stdout.on("resize", handle);
40702
+ return () => {
40703
+ process.stdout.off("resize", handle);
40704
+ process.stdout.setMaxListeners(Math.max(0, process.stdout.getMaxListeners() - 1));
40705
+ };
40706
+ }, []);
40707
+ return size;
40708
+ }
40709
+ var import_react24;
40710
+ var init_useTerminalSize = __esm({
40711
+ async "src/ui/hooks/useTerminalSize.ts"() {
40712
+ "use strict";
40713
+ import_react24 = __toESM(require_react());
40714
+ await init_build2();
40715
+ }
40716
+ });
40717
+
40589
40718
  // src/ui/ink-controls.ts
40590
40719
  var ink_controls_exports = {};
40591
40720
  __export(ink_controls_exports, {
@@ -40593,6 +40722,7 @@ __export(ink_controls_exports, {
40593
40722
  clearInkOutput: () => clearInkOutput,
40594
40723
  forceFullRedraw: () => forceFullRedraw,
40595
40724
  initInkInstances: () => initInkInstances,
40725
+ patchInkLogForAbsolutePositioning: () => patchInkLogForAbsolutePositioning,
40596
40726
  resetInkClear: () => resetInkClear,
40597
40727
  setInkClear: () => setInkClear,
40598
40728
  setInkInstance: () => setInkInstance
@@ -40648,6 +40778,25 @@ function forceFullRedraw() {
40648
40778
  if (wrapper) wrapper.clear();
40649
40779
  resetRealInkLastOutput();
40650
40780
  }
40781
+ function makeAbsoluteLog(stdout) {
40782
+ const log = ((str) => {
40783
+ stdout.write("\x1B[H\x1B[J" + str + "\n");
40784
+ });
40785
+ log.clear = () => {
40786
+ stdout.write("\x1B[H\x1B[J");
40787
+ };
40788
+ log.done = () => {
40789
+ };
40790
+ return log;
40791
+ }
40792
+ function patchInkLogForAbsolutePositioning() {
40793
+ if (!realInstances) return;
40794
+ const real = realInstances.get(process.stdout);
40795
+ if (!real) return;
40796
+ const newLog = makeAbsoluteLog(process.stdout);
40797
+ real.log = newLog;
40798
+ real.throttledLog = newLog;
40799
+ }
40651
40800
  function resetInkClear() {
40652
40801
  wrapper = null;
40653
40802
  }
@@ -40663,41 +40812,6 @@ var init_ink_controls = __esm({
40663
40812
  }
40664
40813
  });
40665
40814
 
40666
- // src/ui/hooks/useTerminalSize.ts
40667
- function useTerminalSize() {
40668
- const { stdout } = use_stdout_default();
40669
- const [size, setSize] = (0, import_react24.useState)({
40670
- rows: stdout?.rows ?? process.stdout.rows ?? 24,
40671
- cols: stdout?.columns ?? process.stdout.columns ?? 80
40672
- });
40673
- (0, import_react24.useEffect)(() => {
40674
- const handle = () => {
40675
- clearScreen();
40676
- forceFullRedraw();
40677
- const rows = process.stdout.rows ?? 24;
40678
- const cols = process.stdout.columns ?? 80;
40679
- setSize({ rows, cols });
40680
- };
40681
- process.stdout.setMaxListeners(process.stdout.getMaxListeners() + 1);
40682
- process.stdout.on("resize", handle);
40683
- return () => {
40684
- process.stdout.off("resize", handle);
40685
- process.stdout.setMaxListeners(Math.max(0, process.stdout.getMaxListeners() - 1));
40686
- };
40687
- }, []);
40688
- return size;
40689
- }
40690
- var import_react24;
40691
- var init_useTerminalSize = __esm({
40692
- async "src/ui/hooks/useTerminalSize.ts"() {
40693
- "use strict";
40694
- import_react24 = __toESM(require_react());
40695
- await init_build2();
40696
- init_terminal_state();
40697
- init_ink_controls();
40698
- }
40699
- });
40700
-
40701
40815
  // node_modules/cli-spinners/spinners.json
40702
40816
  var require_spinners = __commonJS({
40703
40817
  "node_modules/cli-spinners/spinners.json"(exports, module) {
@@ -45236,6 +45350,10 @@ __export(terms_gate_exports, {
45236
45350
  gateOrExit: () => gateOrExit
45237
45351
  });
45238
45352
  import * as readline from "node:readline";
45353
+ function previewInput(raw) {
45354
+ const trimmed = raw.length > 80 ? raw.slice(0, 80) + "\u2026" : raw;
45355
+ return JSON.stringify(trimmed);
45356
+ }
45239
45357
  async function ensureTermsAccepted() {
45240
45358
  if (hasAcceptedTerms()) return { accepted: true };
45241
45359
  if (process.env.DG_ACCEPT_TERMS === "1") {
@@ -45251,6 +45369,7 @@ async function ensureTermsAccepted() {
45251
45369
  import_chalk6.default.dim(
45252
45370
  `
45253
45371
  Accepted Terms + Privacy via DG_ACCEPT_TERMS=1.
45372
+ Saved to ${dgConfigPath()}.
45254
45373
  ${TERMS_URL} \xB7 ${PRIVACY_URL}
45255
45374
 
45256
45375
  `
@@ -45262,13 +45381,8 @@ async function ensureTermsAccepted() {
45262
45381
  process.stderr.write(
45263
45382
  "\n" + import_chalk6.default.bold(" Terms of Service + Privacy Policy\n\n") + " Dependency Guardian sends your lockfile package names + versions\n to api.westbayberry.com to scan them. To use this CLI, you must\n accept:\n\n \xB7 " + import_chalk6.default.cyan(TERMS_URL) + "\n \xB7 " + import_chalk6.default.cyan(PRIVACY_URL) + "\n\n Type " + import_chalk6.default.bold("agree") + " to accept, anything else to cancel: "
45264
45383
  );
45265
- const answer = await new Promise((resolve2) => {
45266
- const rl = readline.createInterface({ input: process.stdin });
45267
- rl.once("line", (line) => {
45268
- rl.close();
45269
- resolve2(line.trim().toLowerCase());
45270
- });
45271
- });
45384
+ const raw = await readOneLine();
45385
+ const answer = raw.trim().toLowerCase();
45272
45386
  if (answer === "agree") {
45273
45387
  try {
45274
45388
  recordTermsAccepted();
@@ -45278,10 +45392,17 @@ async function ensureTermsAccepted() {
45278
45392
  reason: `Could not record terms acceptance: ${e.message}`
45279
45393
  };
45280
45394
  }
45281
- process.stderr.write(import_chalk6.default.green("\n Accepted. Continuing.\n\n"));
45395
+ process.stderr.write(
45396
+ import_chalk6.default.green("\n Accepted. ") + import_chalk6.default.dim(`Saved to ${dgConfigPath()}.
45397
+
45398
+ `)
45399
+ );
45282
45400
  return { accepted: true };
45283
45401
  }
45284
- return { accepted: false, reason: "Declined at prompt." };
45402
+ return {
45403
+ accepted: false,
45404
+ reason: `Declined at prompt (received ${previewInput(raw)}; the prompt accepts only the exact word "agree").`
45405
+ };
45285
45406
  }
45286
45407
  return {
45287
45408
  accepted: false,
@@ -45292,6 +45413,21 @@ async function ensureTermsAccepted() {
45292
45413
  \xB7 Privacy: ${PRIVACY_URL}`
45293
45414
  };
45294
45415
  }
45416
+ async function readOneLine() {
45417
+ const wasPaused = process.stdin.isPaused();
45418
+ if (wasPaused) process.stdin.resume();
45419
+ try {
45420
+ return await new Promise((resolve2) => {
45421
+ const rl = readline.createInterface({ input: process.stdin, terminal: false });
45422
+ rl.once("line", (line) => {
45423
+ rl.close();
45424
+ resolve2(line);
45425
+ });
45426
+ });
45427
+ } finally {
45428
+ if (wasPaused) process.stdin.pause();
45429
+ }
45430
+ }
45295
45431
  async function gateOrExit() {
45296
45432
  const result = await ensureTermsAccepted();
45297
45433
  if (!result.accepted) {
@@ -45305,6 +45441,7 @@ var init_terms_gate = __esm({
45305
45441
  "use strict";
45306
45442
  import_chalk6 = __toESM(require_source());
45307
45443
  init_auth();
45444
+ init_paths();
45308
45445
  TERMS_URL = "https://westbayberry.com/terms";
45309
45446
  PRIVACY_URL = "https://westbayberry.com/privacy";
45310
45447
  }
@@ -45384,7 +45521,6 @@ var init_LoginApp = __esm({
45384
45521
  "Logged in as ",
45385
45522
  state.email
45386
45523
  ] }),
45387
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { dimColor: true, children: "Credentials saved." }),
45388
45524
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: " " }),
45389
45525
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { children: [
45390
45526
  "Run ",
@@ -45700,8 +45836,8 @@ function compareVer(a, b) {
45700
45836
  }
45701
45837
  return 0;
45702
45838
  }
45703
- async function pipSupportsDryRunReport() {
45704
- const v = await pipVersion();
45839
+ async function pipSupportsDryRunReport(knownVersion) {
45840
+ const v = knownVersion === void 0 ? await pipVersion() : knownVersion;
45705
45841
  if (!v) return { ok: false, version: null, reason: "pip not found" };
45706
45842
  if (compareVer(v, "23.0") < 0) {
45707
45843
  return { ok: false, version: v, reason: `pip ${v} < 23.0; needs upgrade for transitive scan` };
@@ -46023,7 +46159,7 @@ function reducer3(state, action) {
46023
46159
  case "MOVE_BROWSER": {
46024
46160
  let viewport = state.browserViewport;
46025
46161
  if (action.cursor < viewport) viewport = action.cursor;
46026
- if (action.cursor >= viewport + VISIBLE_ROWS) viewport = action.cursor - VISIBLE_ROWS + 1;
46162
+ if (action.cursor >= viewport + action.visibleRows) viewport = action.cursor - action.visibleRows + 1;
46027
46163
  return { ...state, browserCursor: action.cursor, browserViewport: viewport };
46028
46164
  }
46029
46165
  case "FOCUS":
@@ -46045,6 +46181,7 @@ var init_FileSavePrompt = __esm({
46045
46181
  import_react30 = __toESM(require_react());
46046
46182
  await init_build2();
46047
46183
  import_chalk7 = __toESM(require_source());
46184
+ await init_useTerminalSize();
46048
46185
  import_jsx_runtime8 = __toESM(require_jsx_runtime());
46049
46186
  VISIBLE_ROWS = 15;
46050
46187
  FileSavePrompt = ({
@@ -46076,9 +46213,11 @@ var init_FileSavePrompt = __esm({
46076
46213
  const q = state.filterText.toLowerCase();
46077
46214
  return dirs.filter((e) => e.name.toLowerCase().includes(q));
46078
46215
  }, [state.entries, state.filterText]);
46216
+ const { rows: termRows } = useTerminalSize();
46217
+ const visibleRows = Math.max(5, Math.min(VISIBLE_ROWS, termRows - 10));
46079
46218
  const visibleEntries = filteredEntries.slice(
46080
46219
  state.browserViewport,
46081
- state.browserViewport + VISIBLE_ROWS
46220
+ state.browserViewport + visibleRows
46082
46221
  );
46083
46222
  use_input_default((input, key) => {
46084
46223
  if (key.escape) {
@@ -46131,7 +46270,7 @@ var init_FileSavePrompt = __esm({
46131
46270
  }
46132
46271
  if (key.upArrow) {
46133
46272
  if (state.browserCursor > 0) {
46134
- dispatch({ type: "MOVE_BROWSER", cursor: state.browserCursor - 1 });
46273
+ dispatch({ type: "MOVE_BROWSER", cursor: state.browserCursor - 1, visibleRows });
46135
46274
  } else {
46136
46275
  dispatch({ type: "FOCUS", target: "filename" });
46137
46276
  }
@@ -46145,7 +46284,7 @@ var init_FileSavePrompt = __esm({
46145
46284
  return;
46146
46285
  }
46147
46286
  if (key.downArrow) {
46148
- dispatch({ type: "MOVE_BROWSER", cursor: clamp(state.browserCursor + 1, 0, filteredEntries.length - 1) });
46287
+ dispatch({ type: "MOVE_BROWSER", cursor: clamp(state.browserCursor + 1, 0, filteredEntries.length - 1), visibleRows });
46149
46288
  return;
46150
46289
  }
46151
46290
  if (key.return) {
@@ -46167,11 +46306,7 @@ var init_FileSavePrompt = __esm({
46167
46306
  const filenameDisplay = state.filename.slice(0, state.cursorPos) + (state.focus === "filename" ? import_chalk7.default.inverse(state.filename[state.cursorPos] || " ") : "") + state.filename.slice(state.cursorPos + 1);
46168
46307
  const shortDir = state.directory.replace(process.env.HOME || "", "~");
46169
46308
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 1, children: [
46170
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { children: [
46171
- import_chalk7.default.cyan("\u25C6"),
46172
- " ",
46173
- import_chalk7.default.bold("Save Scan Results")
46174
- ] }),
46309
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { children: import_chalk7.default.bold("Save Scan Results") }),
46175
46310
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { children: [
46176
46311
  "Score: ",
46177
46312
  import_chalk7.default.bold(String(score)),
@@ -46204,9 +46339,9 @@ var init_FileSavePrompt = __esm({
46204
46339
  name
46205
46340
  ] }, `${entry.name}-${i}`);
46206
46341
  }),
46207
- filteredEntries.length > VISIBLE_ROWS && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { dimColor: true, children: [
46342
+ filteredEntries.length > visibleRows && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { dimColor: true, children: [
46208
46343
  " ",
46209
- import_chalk7.default.dim(`\u2191\u2193 ${filteredEntries.length - VISIBLE_ROWS} more`)
46344
+ import_chalk7.default.dim(`\u2191\u2193 ${filteredEntries.length - visibleRows} more`)
46210
46345
  ] }),
46211
46346
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { children: "" }),
46212
46347
  state.filterText && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { dimColor: true, children: [
@@ -46372,13 +46507,13 @@ __export(static_output_exports, {
46372
46507
  runStaticNpm: () => runStaticNpm,
46373
46508
  runStaticPip: () => runStaticPip
46374
46509
  });
46375
- import { writeFileSync as writeFileSync9, readFileSync as readFileSync11, existsSync as existsSync15, mkdirSync as mkdirSync8, lstatSync as lstatSync4 } from "node:fs";
46510
+ import { writeFileSync as writeFileSync9, readFileSync as readFileSync11, existsSync as existsSync15, mkdirSync as mkdirSync8, lstatSync as lstatSync5 } from "node:fs";
46376
46511
  import { resolve as resolvePath2, dirname as dirname11 } from "node:path";
46377
46512
  function loadPackageLockJson(cwd2 = process.cwd()) {
46378
46513
  const path2 = resolvePath2(cwd2, "package-lock.json");
46379
46514
  if (!existsSync15(path2)) return null;
46380
46515
  try {
46381
- const st = lstatSync4(path2);
46516
+ const st = lstatSync5(path2);
46382
46517
  if (!st.isFile()) return null;
46383
46518
  if (st.size > MAX_PACKAGE_LOCK_BYTES) return null;
46384
46519
  return JSON.parse(readFileSync11(path2, "utf8"));
@@ -46464,7 +46599,7 @@ function printTrialBanner(result, config) {
46464
46599
  const now = Date.now();
46465
46600
  if (now - lastMs < NUDGE_INTERVAL_MS) return;
46466
46601
  mkdirSync8(dirname11(stampPath), { recursive: true, mode: 448 });
46467
- writeFileSync9(stampPath, String(now) + "\n");
46602
+ writeFileSync9(stampPath, String(now) + "\n", { mode: 384 });
46468
46603
  } catch {
46469
46604
  return;
46470
46605
  }
@@ -46532,11 +46667,22 @@ function groupPackages(packages) {
46532
46667
  }
46533
46668
  return [...map.values()].map((pkgs) => ({ packages: pkgs, key: pkgs[0].name })).sort((a, b) => b.packages[0].score - a.packages[0].score);
46534
46669
  }
46535
- function actionBadge(score) {
46536
- if (score >= 70) return { label: "Block", color: import_chalk8.default.red };
46537
- if (score >= 60) return { label: "Warn", color: import_chalk8.default.yellow };
46670
+ function actionBadge(pkg) {
46671
+ const a = pkg.action;
46672
+ if (a === "block") return { label: "Block", color: import_chalk8.default.red };
46673
+ if (a === "warn") return { label: "Warn", color: import_chalk8.default.yellow };
46674
+ if (a === "pass") return { label: "Pass", color: import_chalk8.default.green };
46675
+ if (a === "analysis_incomplete") return { label: "Unknown", color: import_chalk8.default.cyan };
46676
+ if (pkg.score >= 70) return { label: "Block", color: import_chalk8.default.red };
46677
+ if (pkg.score >= 60) return { label: "Warn", color: import_chalk8.default.yellow };
46538
46678
  return { label: "Pass", color: import_chalk8.default.green };
46539
46679
  }
46680
+ function isBlocked(p) {
46681
+ return p.action === "block" || p.action === void 0 && p.score >= 70;
46682
+ }
46683
+ function isWarned(p) {
46684
+ return p.action === "warn" || p.action === void 0 && p.score >= 60 && p.score < 70;
46685
+ }
46540
46686
  function renderResultClean(result, _config) {
46541
46687
  const lines = [];
46542
46688
  const total = result.packages.length;
@@ -46546,8 +46692,8 @@ function renderResultClean(result, _config) {
46546
46692
  lines.push("");
46547
46693
  return lines.join("\n");
46548
46694
  }
46549
- const blocked = result.packages.filter((p) => p.score >= 70);
46550
- const warned = result.packages.filter((p) => p.score >= 60 && p.score < 70);
46695
+ const blocked = result.packages.filter(isBlocked);
46696
+ const warned = result.packages.filter(isWarned);
46551
46697
  if (result.action === "pass" && blocked.length === 0 && warned.length === 0) {
46552
46698
  lines.push("");
46553
46699
  lines.push(` ${import_chalk8.default.green("\u2713")} ${import_chalk8.default.bold("Dependency Guardian")} checked ${total} package${total !== 1 ? "s" : ""}. ${import_chalk8.default.green("No risky behavior found.")}`);
@@ -46597,8 +46743,8 @@ function renderResultDetails(result, _config) {
46597
46743
  )}${colorAction(actionStr)}`
46598
46744
  );
46599
46745
  lines.push("");
46600
- const blocked = result.packages.filter((p) => p.score >= 70);
46601
- const warned = result.packages.filter((p) => p.score >= 60 && p.score < 70);
46746
+ const blocked = result.packages.filter(isBlocked);
46747
+ const warned = result.packages.filter(isWarned);
46602
46748
  const passWithScore = result.packages.filter((p) => p.score > 0 && p.score < 60);
46603
46749
  const clean = result.packages.filter((p) => p.score === 0);
46604
46750
  const total = result.packages.length;
@@ -46640,7 +46786,7 @@ function renderResultDetails(result, _config) {
46640
46786
  lines.push(` ${import_chalk8.default.dim("\u2500".repeat(60))}`);
46641
46787
  for (const group of groups) {
46642
46788
  const rep = group.packages[0];
46643
- const { label, color } = actionBadge(rep.score);
46789
+ const { label, color } = actionBadge(rep);
46644
46790
  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`;
46645
46791
  const lcInfo = rep.license;
46646
46792
  const lcStr = lcInfo ? truncate(lcInfo.spdx ?? lcInfo.raw ?? "", 14) : "";
@@ -46691,7 +46837,9 @@ async function runStatic(config) {
46691
46837
  dbg(`api=${config.apiUrl}`);
46692
46838
  process.stderr.write(import_chalk8.default.dim(" Discovering package changes...\n"));
46693
46839
  const { discoverProjectsAsync: discoverProjectsAsync2 } = await Promise.resolve().then(() => (init_walker(), walker_exports));
46840
+ const tDiscStart = Date.now();
46694
46841
  const subProjects = await discoverProjectsAsync2(process.cwd());
46842
+ if (process.env.DG_PERF) console.error(`[CLI-PERF] walker: ${Date.now() - tDiscStart}ms \u2014 ${subProjects.length} projects`);
46695
46843
  let discovery;
46696
46844
  if (subProjects.length > 1) {
46697
46845
  const totalPkgs = subProjects.reduce((s, p) => s + p.packageCount, 0);
@@ -46709,6 +46857,7 @@ async function runStatic(config) {
46709
46857
  const allNpmPkgs = [];
46710
46858
  const allPyPkgs = [];
46711
46859
  const seen = /* @__PURE__ */ new Set();
46860
+ const tParseStart = Date.now();
46712
46861
  for (const proj of subProjects) {
46713
46862
  try {
46714
46863
  const projDisc = discoverChanges(proj.path, config);
@@ -46733,6 +46882,7 @@ async function runStatic(config) {
46733
46882
  process.stderr.write(import_chalk8.default.dim(" No package changes detected across projects.\n"));
46734
46883
  process.exit(0);
46735
46884
  }
46885
+ if (process.env.DG_PERF) console.error(`[CLI-PERF] parse + dedupe: ${Date.now() - tParseStart}ms \u2014 ${allNpmPkgs.length} npm + ${allPyPkgs.length} pypi`);
46736
46886
  discovery = {
46737
46887
  packages: allNpmPkgs,
46738
46888
  pythonPackages: allPyPkgs,
@@ -47392,7 +47542,8 @@ async function runStaticLogin() {
47392
47542
  process.stderr.write(
47393
47543
  import_chalk8.default.green(`
47394
47544
  Logged in as ${result.email ?? "unknown"}
47395
- `) + import_chalk8.default.dim(" Credentials saved.\n\n")
47545
+
47546
+ `)
47396
47547
  );
47397
47548
  return;
47398
47549
  }
@@ -47498,11 +47649,11 @@ async function handleStatusCommand(cliVersion2) {
47498
47649
  let hookLine = "no pre-commit hook \u2014 `dg hook install` to gate commits";
47499
47650
  let hookOk = false;
47500
47651
  try {
47501
- const { existsSync: existsSync20, readFileSync: readFileSync13 } = await import("node:fs");
47652
+ const { existsSync: existsSync19, readFileSync: readFileSync13 } = await import("node:fs");
47502
47653
  const { join: join13 } = await import("node:path");
47503
47654
  const candidates = [join13(cwd2, ".husky", "pre-commit"), join13(cwd2, ".git", "hooks", "pre-commit")];
47504
47655
  for (const path2 of candidates) {
47505
- if (existsSync20(path2)) {
47656
+ if (existsSync19(path2)) {
47506
47657
  const content = readFileSync13(path2, "utf-8");
47507
47658
  if (content.includes("dependency-guardian")) {
47508
47659
  hookLine = `installed in ${path2}`;
@@ -47532,17 +47683,19 @@ async function handleStatusCommand(cliVersion2) {
47532
47683
  } catch {
47533
47684
  }
47534
47685
  try {
47535
- const { execFileSync: execFileSync5 } = await import("node:child_process");
47536
- const npmV = (() => {
47537
- try {
47538
- return execFileSync5("npm", ["--version"], { encoding: "utf-8", timeout: 5e3 }).trim();
47539
- } catch {
47540
- return null;
47541
- }
47542
- })();
47686
+ const { execFileSync: execFileSync2 } = await import("node:child_process");
47543
47687
  const { pipVersion: pipVersion2, pipSupportsDryRunReport: pipSupportsDryRunReport2 } = await Promise.resolve().then(() => (init_pip_wrapper(), pip_wrapper_exports));
47544
- const pipV = await pipVersion2();
47545
- const pipProbe = pipV ? await pipSupportsDryRunReport2() : null;
47688
+ const [npmV, pipV] = await Promise.all([
47689
+ Promise.resolve().then(() => {
47690
+ try {
47691
+ return execFileSync2("npm", ["--version"], { encoding: "utf-8", timeout: 5e3 }).trim();
47692
+ } catch {
47693
+ return null;
47694
+ }
47695
+ }),
47696
+ pipVersion2()
47697
+ ]);
47698
+ const pipProbe = pipV ? await pipSupportsDryRunReport2(pipV) : null;
47546
47699
  const npmLine = npmV ? `npm ${npmV} (transitive scan supported)` : "npm not found on PATH";
47547
47700
  const npmGlyph = npmV ? chalk18.green("\u2713") : chalk18.yellow("\xB7");
47548
47701
  process.stderr.write(` ${npmGlyph} ${chalk18.bold("npm")} ` + chalk18.dim(npmLine + "\n"));
@@ -47588,7 +47741,10 @@ function licenseSummary(results) {
47588
47741
  return [...byRisk.entries()].map(([risk, count]) => ({ risk, count })).sort(riskOrder);
47589
47742
  }
47590
47743
  function csvCell(v) {
47591
- const s = v === null || v === void 0 ? "" : String(v);
47744
+ let s = v === null || v === void 0 ? "" : String(v);
47745
+ if (s.length > 0 && CSV_FORMULA_PREFIXES.has(s[0])) {
47746
+ s = "'" + s;
47747
+ }
47592
47748
  if (/[",\n]/.test(s)) return `"${s.replace(/"/g, '""')}"`;
47593
47749
  return s;
47594
47750
  }
@@ -47654,12 +47810,13 @@ function formatLicensesText(results) {
47654
47810
  }
47655
47811
  return lines.join("\n");
47656
47812
  }
47657
- var import_chalk9, SINK_RANKS;
47813
+ var import_chalk9, SINK_RANKS, CSV_FORMULA_PREFIXES;
47658
47814
  var init_license_export = __esm({
47659
47815
  "src/formatters/license-export.ts"() {
47660
47816
  "use strict";
47661
47817
  import_chalk9 = __toESM(require_source());
47662
47818
  SINK_RANKS = { "no-license": 2, unknown: 1 };
47819
+ CSV_FORMULA_PREFIXES = /* @__PURE__ */ new Set(["=", "+", "-", "@", " ", "\r"]);
47663
47820
  }
47664
47821
  });
47665
47822
 
@@ -47679,7 +47836,7 @@ function renderLicenseSummary(entries, maxCols) {
47679
47836
  if (ra !== rb) return ra - rb;
47680
47837
  return b.count - a.count;
47681
47838
  });
47682
- const sep2 = import_chalk10.default.dim(" \xB7 ");
47839
+ const sep3 = import_chalk10.default.dim(" \xB7 ");
47683
47840
  const segments = [];
47684
47841
  let used = "Licenses ".length;
47685
47842
  let dropped = 0;
@@ -47694,8 +47851,8 @@ function renderLicenseSummary(entries, maxCols) {
47694
47851
  segments.push(colorForRisk(e.risk)(label));
47695
47852
  used += segLen;
47696
47853
  }
47697
- let line = `${import_chalk10.default.dim("Licenses")} ${segments.join(sep2)}`;
47698
- if (dropped > 0) line += `${sep2}${import_chalk10.default.dim(`+${dropped} more`)}`;
47854
+ let line = `${import_chalk10.default.dim("Licenses")} ${segments.join(sep3)}`;
47855
+ if (dropped > 0) line += `${sep3}${import_chalk10.default.dim(`+${dropped} more`)}`;
47699
47856
  return line;
47700
47857
  }
47701
47858
  function scoreColor(score, action) {
@@ -47755,6 +47912,7 @@ var init_ScoreHeader = __esm({
47755
47912
  "use strict";
47756
47913
  await init_build2();
47757
47914
  import_chalk10 = __toESM(require_source());
47915
+ await init_useTerminalSize();
47758
47916
  import_jsx_runtime9 = __toESM(require_jsx_runtime());
47759
47917
  LICENSE_RISK_LABELS = {
47760
47918
  permissive: "permissive",
@@ -47817,7 +47975,7 @@ var init_ScoreHeader = __esm({
47817
47975
  licenseSummary: licenseSummary2
47818
47976
  }) => {
47819
47977
  const logo = renderLogo(action);
47820
- const cols = process.stdout.columns ?? 80;
47978
+ const { cols } = useTerminalSize();
47821
47979
  const showLogo = cols >= 60;
47822
47980
  const licenseLine2 = licenseSummary2 && licenseSummary2.length > 0 ? renderLicenseSummary(licenseSummary2, showLogo ? cols - 16 : cols) : "";
47823
47981
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
@@ -47832,8 +47990,7 @@ var init_ScoreHeader = __esm({
47832
47990
  children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { flexDirection: "row", children: [
47833
47991
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, children: [
47834
47992
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { bold: true, children: [
47835
- import_chalk10.default.cyan("\u25C6"),
47836
- " Dependency Guardian ",
47993
+ "Dependency Guardian ",
47837
47994
  userStatus ? import_chalk10.default.dim(userStatus) : ""
47838
47995
  ] }),
47839
47996
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: " " }),
@@ -47988,16 +48145,19 @@ function groupRowHeight(group, level, safeVersions) {
47988
48145
  if (level === "summary") return 1 + findingsSummaryHeight(group);
47989
48146
  return 1 + findingsDetailHeight(group, safeVersions);
47990
48147
  }
48148
+ function nameVer(p) {
48149
+ return p.version ? `${p.name}@${p.version}` : p.name;
48150
+ }
47991
48151
  function groupNames(group) {
47992
- if (group.packages.length === 1) return group.packages[0].name;
48152
+ if (group.packages.length === 1) return nameVer(group.packages[0]);
47993
48153
  if (group.packages.length <= 3)
47994
- return group.packages.map((p) => p.name).join(", ");
47995
- return `${group.packages[0].name} + ${group.packages.length - 1} similar`;
48154
+ return group.packages.map(nameVer).join(", ");
48155
+ return `${nameVer(group.packages[0])} + ${group.packages.length - 1} similar`;
47996
48156
  }
47997
48157
  function affectsLine(group) {
47998
- const names = group.packages.map((p) => p.name);
47999
- if (names.length <= 5) return names.join(", ");
48000
- return names.slice(0, 5).join(", ") + ` + ${names.length - 5} more`;
48158
+ const labels = group.packages.map(nameVer);
48159
+ if (labels.length <= 5) return labels.join(", ");
48160
+ return labels.slice(0, 5).join(", ") + ` + ${labels.length - 5} more`;
48001
48161
  }
48002
48162
  function buildDetailLines(group, safeVersion, maxWidth) {
48003
48163
  const rep = group.packages[0];
@@ -48789,27 +48949,28 @@ var init_InteractiveResultsView = __esm({
48789
48949
  const clampedCursor = groups.length > 0 ? Math.min(view.cursor, groups.length - 1) : 0;
48790
48950
  if (exportMenu) {
48791
48951
  const SCOPES_RENDER = [
48792
- { value: "all", label: "All", hint: "everything: summary + every package + every license" },
48793
- { value: "summary", label: "Summary", hint: "score + counts only \u2014 one short object" },
48794
- { value: "packages", label: "Packages", hint: "flat list of name, version, score, license" },
48795
- { value: "licenses", label: "Licenses", hint: "license groups with packages-per-license" },
48796
- { value: "findings", label: "Findings (warn+block)", hint: "only warn/block packages with finding metadata" }
48952
+ { value: "all", label: "All" },
48953
+ { value: "summary", label: "Summary" },
48954
+ { value: "packages", label: "Packages" },
48955
+ { value: "licenses", label: "Licenses" },
48956
+ { value: "findings", label: "Findings (warn+block)" }
48797
48957
  ];
48798
48958
  if (licenseDetailIdxRef.current !== null) {
48799
48959
  const focus = licenseGroups[licenseDetailIdxRef.current];
48800
48960
  SCOPES_RENDER.push({
48801
48961
  value: "current-license",
48802
- label: `Current license (${focus?.spdx ?? "\u2014"})`,
48803
- hint: `only the ${focus?.count ?? 0} packages under this license`
48962
+ label: `Current license (${focus?.spdx ?? "\u2014"})`
48804
48963
  });
48805
48964
  }
48806
48965
  const FORMATS_RENDER = [
48807
- { value: "json", label: "JSON", hint: "structured, exact data \u2014 best for tooling" },
48808
- { value: "csv", label: "CSV", hint: "spreadsheet-friendly tabular rows" },
48809
- { value: "md", label: "Markdown", hint: "headings + tables \u2014 drop into a PR or doc" },
48810
- { value: "txt", label: "Plain text", hint: "human-readable copy-paste" }
48966
+ { value: "json", label: "JSON" },
48967
+ { value: "csv", label: "CSV" },
48968
+ { value: "md", label: "Markdown" },
48969
+ { value: "txt", label: "Plain text" }
48811
48970
  ];
48812
- const renderRow = (title, rows, current, isActive) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", marginBottom: 1, children: [
48971
+ const stackedLayout = termCols < 80;
48972
+ const colWidth = stackedLayout ? void 0 : Math.max(24, Math.floor((termCols - 8) / 2));
48973
+ const renderColumn = (title, rows, current, isActive) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", width: colWidth, flexShrink: 1, marginBottom: stackedLayout ? 1 : 0, children: [
48813
48974
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
48814
48975
  isActive ? import_chalk11.default.cyan("\u258C ") : " ",
48815
48976
  isActive ? import_chalk11.default.bold(title) : import_chalk11.default.dim(title)
@@ -48819,12 +48980,11 @@ var init_InteractiveResultsView = __esm({
48819
48980
  const bullet = selected ? isActive ? import_chalk11.default.cyan("\u25CF") : import_chalk11.default.green("\u25CF") : import_chalk11.default.dim("\u25CB");
48820
48981
  const text = selected ? import_chalk11.default.bold(r.label) : r.label;
48821
48982
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { children: [
48822
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { width: 5, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
48983
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { width: 5, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
48823
48984
  " ",
48824
48985
  bullet
48825
48986
  ] }) }),
48826
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { width: 26, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: text }) }),
48827
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: r.hint })
48987
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { flexShrink: 1, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { wrap: "truncate-end", children: text }) })
48828
48988
  ] }, r.value);
48829
48989
  })
48830
48990
  ] });
@@ -48842,15 +49002,13 @@ var init_InteractiveResultsView = __esm({
48842
49002
  licenseSummary: licenseSummary2
48843
49003
  }
48844
49004
  ),
48845
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingLeft: 2, paddingRight: 2, width: "100%", children: [
48846
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { bold: true, children: [
48847
- import_chalk11.default.cyan("\u25C6"),
48848
- " Export ",
48849
- import_chalk11.default.dim("(pick what to export and which format)")
48850
- ] }),
49005
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingLeft: 2, paddingRight: 2, children: [
49006
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { bold: true, children: "Export" }),
48851
49007
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: "" }),
48852
- renderRow("What", SCOPES_RENDER, exportMenu.scope, exportMenu.activeRow === "scope"),
48853
- renderRow("Format", FORMATS_RENDER, exportMenu.format, exportMenu.activeRow === "format")
49008
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: stackedLayout ? "column" : "row", children: [
49009
+ renderColumn("What", SCOPES_RENDER, exportMenu.scope, exportMenu.activeRow === "scope"),
49010
+ renderColumn("Format", FORMATS_RENDER, exportMenu.format, exportMenu.activeRow === "format")
49011
+ ] })
48854
49012
  ] }),
48855
49013
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: import_chalk11.default.dim("\u2500".repeat(Math.max(20, termCols - 4))) }),
48856
49014
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
@@ -48899,10 +49057,7 @@ var init_InteractiveResultsView = __esm({
48899
49057
  paddingRight: 2,
48900
49058
  width: "100%",
48901
49059
  children: [
48902
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { bold: true, children: [
48903
- import_chalk11.default.cyan("\u25C6"),
48904
- " Keyboard Shortcuts"
48905
- ] }),
49060
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { bold: true, children: "Keyboard Shortcuts" }),
48906
49061
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: "" }),
48907
49062
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { bold: true, children: " Navigation" }),
48908
49063
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
@@ -49042,11 +49197,7 @@ var init_InteractiveResultsView = __esm({
49042
49197
  ),
49043
49198
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingLeft: 2, paddingRight: 2, width: "100%", children: [
49044
49199
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { children: [
49045
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { bold: true, children: [
49046
- import_chalk11.default.cyan("\u25C6"),
49047
- " ",
49048
- color(g.spdx)
49049
- ] }),
49200
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { bold: true, children: color(g.spdx) }),
49050
49201
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
49051
49202
  " \xB7 ",
49052
49203
  g.risk,
@@ -49157,17 +49308,17 @@ var init_InteractiveResultsView = __esm({
49157
49308
  width: "100%",
49158
49309
  children: [
49159
49310
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { bold: true, children: [
49160
- import_chalk11.default.cyan("\u25C6"),
49161
- " Licenses ",
49311
+ "Licenses",
49312
+ " ",
49162
49313
  import_chalk11.default.dim(`(${licenseGroups.length} unique across ${totalCount} packages)`)
49163
49314
  ] }),
49164
49315
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: "" }),
49165
49316
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { children: [
49166
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { width: 2, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: " " }) }),
49167
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { width: spdxCol, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: "SPDX" }) }),
49168
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { width: riskCol, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: "Risk" }) }),
49169
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { width: countCol, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: "Count" }) }),
49170
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { width: barCol, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: "Share" }) })
49317
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { width: 2, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: " " }) }),
49318
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { width: spdxCol, flexShrink: 1, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: "SPDX" }) }),
49319
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { width: riskCol, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: "Risk" }) }),
49320
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { width: countCol, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: "Count" }) }),
49321
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { width: barCol, flexShrink: 1, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: "Share" }) })
49171
49322
  ] }),
49172
49323
  hiddenAbove > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: ` \u2191 ${hiddenAbove} more above` }),
49173
49324
  licenseGroups.slice(top, bottom).map((g, i) => {
@@ -50239,110 +50390,273 @@ var init_publish_check = __esm({
50239
50390
  }
50240
50391
  });
50241
50392
 
50242
- // src/commands/cleanup.ts
50243
- var cleanup_exports = {};
50244
- __export(cleanup_exports, {
50245
- handleCleanupCommand: () => handleCleanupCommand,
50246
- runCleanup: () => runCleanup
50393
+ // src/commands/update.ts
50394
+ var update_exports = {};
50395
+ __export(update_exports, {
50396
+ detectInstaller: () => detectInstaller,
50397
+ handleUpdateCommand: () => handleUpdateCommand,
50398
+ installPrefix: () => installPrefix,
50399
+ parseUpdateArgs: () => parseUpdateArgs,
50400
+ runUpdate: () => runUpdate
50247
50401
  });
50248
- import { existsSync as existsSync17, unlinkSync as unlinkSync5 } from "node:fs";
50402
+ import { spawnSync } from "node:child_process";
50403
+ import { accessSync, constants as constants2, realpathSync, unlinkSync as unlinkSync6 } from "node:fs";
50404
+ import { sep as sep2 } from "node:path";
50405
+ function parseUpdateArgs(args) {
50406
+ return {
50407
+ assumeYes: args.includes("--yes") || args.includes("-y"),
50408
+ printOnly: args.includes("--print") || args.includes("--dry-run")
50409
+ };
50410
+ }
50411
+ function detectInstaller(binPath) {
50412
+ let resolved;
50413
+ try {
50414
+ resolved = realpathSync(binPath);
50415
+ } catch {
50416
+ return null;
50417
+ }
50418
+ const norm = resolved.split(sep2).join("/");
50419
+ if (norm.includes("/pnpm/") || norm.includes("/.pnpm/") || process.env.PNPM_HOME && resolved.startsWith(process.env.PNPM_HOME)) {
50420
+ return {
50421
+ kind: "pnpm",
50422
+ binary: "pnpm",
50423
+ args: (pkg) => ["add", "-g", pkg]
50424
+ };
50425
+ }
50426
+ if (norm.includes("/.bun/") || norm.includes("/bun/install/") || process.env.BUN_INSTALL && resolved.startsWith(process.env.BUN_INSTALL)) {
50427
+ return {
50428
+ kind: "bun",
50429
+ binary: "bun",
50430
+ args: (pkg) => ["add", "-g", pkg]
50431
+ };
50432
+ }
50433
+ if (norm.includes("/.yarn/") || norm.includes("/yarn/global/")) {
50434
+ return {
50435
+ kind: "yarn",
50436
+ binary: "yarn",
50437
+ args: (pkg) => ["global", "add", pkg]
50438
+ };
50439
+ }
50440
+ if (norm.includes("/node_modules/")) {
50441
+ return {
50442
+ kind: "npm",
50443
+ binary: "npm",
50444
+ args: (pkg) => ["install", "-g", pkg]
50445
+ };
50446
+ }
50447
+ return null;
50448
+ }
50449
+ function installPrefix(resolvedBinPath) {
50450
+ const norm = resolvedBinPath.split(sep2).join("/");
50451
+ const idx = norm.indexOf("/node_modules/");
50452
+ if (idx < 0) return null;
50453
+ return resolvedBinPath.slice(0, idx);
50454
+ }
50455
+ function canWrite(path2) {
50456
+ try {
50457
+ accessSync(path2, constants2.W_OK);
50458
+ return true;
50459
+ } catch {
50460
+ return false;
50461
+ }
50462
+ }
50463
+ async function fetchLatest() {
50464
+ try {
50465
+ const ctl = new AbortController();
50466
+ const timer = setTimeout(() => ctl.abort(), FETCH_TIMEOUT_MS);
50467
+ const res = await fetch(
50468
+ `https://registry.npmjs.org/${encodeURIComponent(PKG_NAME2)}/latest`,
50469
+ { headers: { Accept: "application/json" }, signal: ctl.signal }
50470
+ );
50471
+ clearTimeout(timer);
50472
+ if (!res.ok) return null;
50473
+ const data = await res.json();
50474
+ return typeof data.version === "string" ? data.version : null;
50475
+ } catch {
50476
+ return null;
50477
+ }
50478
+ }
50249
50479
  async function confirm(prompt) {
50480
+ if (!process.stdin.isTTY) return false;
50250
50481
  process.stderr.write(prompt);
50251
50482
  return new Promise((resolve2) => {
50252
50483
  let buf = "";
50253
50484
  const onData = (chunk) => {
50254
50485
  buf += chunk.toString("utf8");
50255
50486
  const idx = buf.indexOf("\n");
50256
- if (idx >= 0) {
50257
- process.stdin.off("data", onData);
50258
- const answer = buf.slice(0, idx).trim().toLowerCase();
50259
- resolve2(answer === "y" || answer === "yes");
50260
- }
50487
+ if (idx < 0) return;
50488
+ process.stdin.off("data", onData);
50489
+ const ans = buf.slice(0, idx).trim().toLowerCase();
50490
+ resolve2(ans === "y" || ans === "yes");
50261
50491
  };
50262
50492
  process.stdin.on("data", onData);
50263
50493
  });
50264
50494
  }
50265
- async function revokeKey(apiKey) {
50495
+ function printUpgradeCommand(latest, kind) {
50496
+ const cmd = (() => {
50497
+ switch (kind) {
50498
+ case "pnpm":
50499
+ return `pnpm add -g ${PKG_NAME2}@${latest}`;
50500
+ case "yarn":
50501
+ return `yarn global add ${PKG_NAME2}@${latest}`;
50502
+ case "bun":
50503
+ return `bun add -g ${PKG_NAME2}@${latest}`;
50504
+ default:
50505
+ return `npm install -g ${PKG_NAME2}@${latest}`;
50506
+ }
50507
+ })();
50508
+ process.stderr.write(` Run: ${import_chalk12.default.cyan(cmd)}
50509
+
50510
+ `);
50511
+ }
50512
+ function invalidateUpdateCache() {
50266
50513
  try {
50267
- const config = parseConfig(process.argv, false);
50268
- const resp = await fetch(`${config.apiUrl}/v1/auth/revoke`, {
50269
- method: "POST",
50270
- headers: { Authorization: `Bearer ${apiKey}` },
50271
- signal: AbortSignal.timeout(5e3)
50272
- });
50273
- return resp.ok;
50514
+ unlinkSync6(dgCachePath("update-check.json"));
50274
50515
  } catch {
50275
- return false;
50276
50516
  }
50277
50517
  }
50278
- async function runCleanup(opts = {}) {
50279
- const configPath2 = dgConfigPath();
50280
- const legacyDgrc = legacyPaths().dgrc;
50281
- process.stderr.write(import_chalk12.default.bold("\n Dependency Guardian cleanup\n\n"));
50282
- process.stderr.write(import_chalk12.default.dim(" Note: `dg cleanup` is deprecated. Use `dg uninstall` for a full removal\n"));
50283
- process.stderr.write(import_chalk12.default.dim(" that also strips per-repo git hooks and shell-rc lines.\n\n"));
50284
- process.stderr.write(" This will:\n");
50285
- process.stderr.write(" 1. Revoke this device's API key on the server\n");
50286
- process.stderr.write(` 2. Remove ${configPath2}
50518
+ async function runUpdate(args, currentVersion) {
50519
+ if (args.includes("--help") || args.includes("-h")) {
50520
+ process.stdout.write(USAGE4);
50521
+ return 0;
50522
+ }
50523
+ const opts = parseUpdateArgs(args);
50524
+ process.stderr.write(import_chalk12.default.dim(" Checking npm registry...\n"));
50525
+ const latest = await fetchLatest();
50526
+ if (!latest) {
50527
+ process.stderr.write(import_chalk12.default.red(" Could not reach npm registry.\n"));
50528
+ return 1;
50529
+ }
50530
+ if (!isNewer(latest, currentVersion)) {
50531
+ process.stderr.write(
50532
+ import_chalk12.default.green(` Already on latest version (${currentVersion}).
50533
+ `)
50534
+ );
50535
+ invalidateUpdateCache();
50536
+ return 0;
50537
+ }
50538
+ process.stderr.write(
50539
+ `
50540
+ ${import_chalk12.default.bold("Update available:")} ${currentVersion} ${import_chalk12.default.dim("\u2192")} ${import_chalk12.default.cyan(latest)}
50541
+ `
50542
+ );
50543
+ const installer = detectInstaller(process.argv[1] ?? "");
50544
+ if (!installer || opts.printOnly) {
50545
+ printUpgradeCommand(latest, installer?.kind ?? null);
50546
+ return 0;
50547
+ }
50548
+ let resolvedBin;
50549
+ try {
50550
+ resolvedBin = realpathSync(process.argv[1]);
50551
+ } catch {
50552
+ printUpgradeCommand(latest, installer.kind);
50553
+ return 0;
50554
+ }
50555
+ if (process.platform === "win32") {
50556
+ process.stderr.write(
50557
+ import_chalk12.default.dim(" Detected installer: ") + import_chalk12.default.bold(installer.kind) + "\n"
50558
+ );
50559
+ process.stderr.write(
50560
+ import_chalk12.default.yellow(" Auto-upgrade not supported on Windows \u2014 run the command below.\n")
50561
+ );
50562
+ printUpgradeCommand(latest, installer.kind);
50563
+ return 0;
50564
+ }
50565
+ if (installer.kind === "npm") {
50566
+ const prefix = installPrefix(resolvedBin);
50567
+ if (prefix && !canWrite(prefix)) {
50568
+ process.stderr.write(
50569
+ import_chalk12.default.dim(" Detected installer: ") + import_chalk12.default.bold("npm") + import_chalk12.default.dim(` (prefix: ${prefix})
50570
+ `)
50571
+ );
50572
+ process.stderr.write(
50573
+ import_chalk12.default.yellow(" Prefix is not writable \u2014 needs elevated permissions.\n")
50574
+ );
50575
+ process.stderr.write(` Run: ${import_chalk12.default.cyan(`sudo npm install -g ${PKG_NAME2}@${latest}`)}
50576
+
50287
50577
  `);
50288
- process.stderr.write(" 3. Remove the shell-alias snippet\n\n");
50289
- process.stderr.write(import_chalk12.default.dim(" Git pre-commit hooks in your projects are NOT removed.\n"));
50290
- process.stderr.write(import_chalk12.default.dim(" Run `dg uninstall` to remove every dg artifact, or `dg hook uninstall`\n"));
50291
- process.stderr.write(import_chalk12.default.dim(" per repo to remove just the pre-commit hooks.\n\n"));
50578
+ return 0;
50579
+ }
50580
+ }
50581
+ process.stderr.write(
50582
+ import_chalk12.default.dim(" Detected installer: ") + import_chalk12.default.bold(installer.kind) + "\n"
50583
+ );
50584
+ const fullCmd = `${installer.binary} ${installer.args(`${PKG_NAME2}@${latest}`).join(" ")}`;
50585
+ process.stderr.write(import_chalk12.default.dim(` Command: ${fullCmd}
50586
+
50587
+ `));
50292
50588
  if (!opts.assumeYes) {
50293
50589
  if (!process.stdin.isTTY) {
50294
- process.stderr.write(import_chalk12.default.yellow(" Non-interactive shell \u2014 pass --yes to confirm without prompting.\n\n"));
50590
+ process.stderr.write(
50591
+ import_chalk12.default.yellow(" Non-interactive shell \u2014 pass --yes to proceed, or copy the command above.\n\n")
50592
+ );
50295
50593
  return 1;
50296
50594
  }
50297
- const ok = await confirm(" Continue? [y/N] ");
50595
+ const ok = await confirm(" Proceed? [y/N] ");
50596
+ process.stderr.write("\n");
50298
50597
  if (!ok) {
50299
- process.stderr.write(import_chalk12.default.dim("\n Cancelled. Nothing changed.\n\n"));
50598
+ process.stderr.write(import_chalk12.default.dim(" Cancelled.\n\n"));
50300
50599
  return 0;
50301
50600
  }
50302
- process.stderr.write("\n");
50303
50601
  }
50304
- const apiKey = getStoredApiKey();
50305
- if (apiKey) {
50306
- const revoked = await revokeKey(apiKey);
50307
- if (revoked) {
50308
- process.stderr.write(import_chalk12.default.green(" \u2713 ") + "Revoked API key on the server\n");
50309
- } else {
50310
- process.stderr.write(import_chalk12.default.yellow(" \u26A0 ") + "Couldn't reach server to revoke key \u2014 local cleanup continues\n");
50311
- }
50312
- } else {
50313
- process.stderr.write(import_chalk12.default.dim(" \xB7 No saved API key found\n"));
50602
+ const result = spawnSync(
50603
+ installer.binary,
50604
+ installer.args(`${PKG_NAME2}@${latest}`),
50605
+ { stdio: "inherit" }
50606
+ );
50607
+ if (result.error && result.error.code === "ENOENT") {
50608
+ process.stderr.write(
50609
+ import_chalk12.default.red(`
50610
+ Installer not found in PATH: ${installer.binary}
50611
+ `)
50612
+ );
50613
+ printUpgradeCommand(latest, installer.kind);
50614
+ return 1;
50314
50615
  }
50315
- for (const p of [configPath2, legacyDgrc]) {
50316
- if (existsSync17(p)) {
50317
- try {
50318
- unlinkSync5(p);
50319
- process.stderr.write(import_chalk12.default.green(" \u2713 ") + `Removed ${p}
50320
- `);
50321
- } catch (e) {
50322
- process.stderr.write(import_chalk12.default.red(" \u2718 ") + `Couldn't remove ${p}: ${e.message}
50323
- `);
50324
- }
50325
- }
50616
+ if (result.status !== 0) {
50617
+ process.stderr.write(
50618
+ import_chalk12.default.red(`
50619
+ Upgrade failed (${installer.binary} exited ${result.status ?? "with signal"}).
50620
+ `)
50621
+ );
50622
+ return result.status ?? 1;
50326
50623
  }
50327
- process.stderr.write("\n");
50328
- await runProtectOff(process.cwd(), { removeShell: true });
50329
- process.stderr.write(import_chalk12.default.bold(" Cleanup complete.\n\n"));
50330
- process.stderr.write(" Next: " + import_chalk12.default.bold.cyan("npm uninstall -g @westbayberry/dg") + "\n\n");
50624
+ invalidateUpdateCache();
50625
+ process.stderr.write(
50626
+ "\n" + import_chalk12.default.bold.green(` Updated to ${latest}.
50627
+ `) + import_chalk12.default.dim(" Re-run any in-progress dg commands to pick up the new version.\n\n")
50628
+ );
50331
50629
  return 0;
50332
50630
  }
50333
- async function handleCleanupCommand(args) {
50334
- const assumeYes = args.includes("--yes") || args.includes("-y");
50335
- return runCleanup({ assumeYes });
50631
+ function handleUpdateCommand(args, currentVersion) {
50632
+ return runUpdate(args, currentVersion);
50336
50633
  }
50337
- var import_chalk12;
50338
- var init_cleanup = __esm({
50339
- "src/commands/cleanup.ts"() {
50634
+ var import_chalk12, PKG_NAME2, FETCH_TIMEOUT_MS, USAGE4;
50635
+ var init_update = __esm({
50636
+ "src/commands/update.ts"() {
50340
50637
  "use strict";
50341
50638
  import_chalk12 = __toESM(require_source());
50342
- init_protect();
50343
- init_auth();
50344
- init_config();
50345
50639
  init_paths();
50640
+ init_update_check();
50641
+ PKG_NAME2 = "@westbayberry/dg";
50642
+ FETCH_TIMEOUT_MS = 5e3;
50643
+ USAGE4 = `
50644
+ dg update \u2014 upgrade dg to the latest version
50645
+
50646
+ Detects the package manager dg was installed with (npm / pnpm / yarn / bun)
50647
+ and runs the matching global-upgrade command. Falls back to printing the
50648
+ command on Windows or when the installer can't be detected.
50649
+
50650
+ Usage:
50651
+ dg update [options]
50652
+
50653
+ Options:
50654
+ --yes, -y Skip the confirmation prompt
50655
+ --print Show the upgrade command without running it
50656
+ --dry-run Alias for --print
50657
+ --help, -h Show this help
50658
+
50659
+ `;
50346
50660
  }
50347
50661
  });
50348
50662
 
@@ -50353,7 +50667,7 @@ __export(uninstall_exports, {
50353
50667
  parseUninstallArgs: () => parseUninstallArgs,
50354
50668
  runUninstall: () => runUninstall
50355
50669
  });
50356
- import { existsSync as existsSync18, rmSync as rmSync3, unlinkSync as unlinkSync6, statSync as statSync5 } from "node:fs";
50670
+ import { existsSync as existsSync17, rmSync as rmSync3, unlinkSync as unlinkSync7, statSync as statSync5 } from "node:fs";
50357
50671
  function parseUninstallArgs(args) {
50358
50672
  return {
50359
50673
  assumeYes: args.includes("--yes") || args.includes("-y"),
@@ -50364,7 +50678,7 @@ function parseUninstallArgs(args) {
50364
50678
  }
50365
50679
  function safeExists(p) {
50366
50680
  try {
50367
- return existsSync18(p);
50681
+ return existsSync17(p);
50368
50682
  } catch {
50369
50683
  return false;
50370
50684
  }
@@ -50480,7 +50794,7 @@ async function revokeApiKey(apiKey) {
50480
50794
  }
50481
50795
  function rmFile(path2) {
50482
50796
  try {
50483
- unlinkSync6(path2);
50797
+ unlinkSync7(path2);
50484
50798
  return { ok: true };
50485
50799
  } catch (e) {
50486
50800
  const code = e.code;
@@ -50506,7 +50820,7 @@ function isWritableTarget(path2) {
50506
50820
  }
50507
50821
  async function runUninstall(args) {
50508
50822
  if (args.includes("--help") || args.includes("-h")) {
50509
- process.stdout.write(USAGE4);
50823
+ process.stdout.write(USAGE5);
50510
50824
  return 0;
50511
50825
  }
50512
50826
  const opts = parseUninstallArgs(args);
@@ -50544,7 +50858,7 @@ async function runUninstall(args) {
50544
50858
  }
50545
50859
  if (!opts.keepHooks && !opts.soft) {
50546
50860
  for (const h of plan.hooks) {
50547
- if (!existsSync18(h.repoPath)) {
50861
+ if (!existsSync17(h.repoPath)) {
50548
50862
  process.stderr.write(import_chalk13.default.dim(` \xB7 ${h.repoPath} no longer exists \u2014 skipping registered hook
50549
50863
  `));
50550
50864
  continue;
@@ -50630,7 +50944,7 @@ async function runUninstall(args) {
50630
50944
  function handleUninstallCommand(args) {
50631
50945
  return runUninstall(args);
50632
50946
  }
50633
- var import_chalk13, REVOKE_TIMEOUT_MS, USAGE4;
50947
+ var import_chalk13, REVOKE_TIMEOUT_MS, USAGE5;
50634
50948
  var init_uninstall = __esm({
50635
50949
  "src/commands/uninstall.ts"() {
50636
50950
  "use strict";
@@ -50642,7 +50956,7 @@ var init_uninstall = __esm({
50642
50956
  init_auth();
50643
50957
  init_config();
50644
50958
  REVOKE_TIMEOUT_MS = 5e3;
50645
- USAGE4 = `
50959
+ USAGE5 = `
50646
50960
  dg uninstall \u2014 remove every file dg has written locally
50647
50961
 
50648
50962
  Usage:
@@ -50678,10 +50992,9 @@ __export(init_exports, {
50678
50992
  parseInitArgs: () => parseInitArgs,
50679
50993
  runInit: () => runInit2
50680
50994
  });
50681
- import { existsSync as existsSync19 } from "node:fs";
50995
+ import { existsSync as existsSync18 } from "node:fs";
50682
50996
  import { join as join12 } from "node:path";
50683
50997
  import * as readline2 from "node:readline";
50684
- import { execFileSync as execFileSync4 } from "node:child_process";
50685
50998
  function parseInitArgs(args) {
50686
50999
  const opts = {
50687
51000
  assumeYes: false,
@@ -50730,23 +51043,20 @@ async function confirm3(prompt, defaultYes, assumeYes) {
50730
51043
  });
50731
51044
  }
50732
51045
  function detectEcosystem(cwd2) {
50733
- const npm = existsSync19(join12(cwd2, "package.json")) || existsSync19(join12(cwd2, "package-lock.json")) || existsSync19(join12(cwd2, "pnpm-lock.yaml")) || existsSync19(join12(cwd2, "yarn.lock"));
50734
- const pypi = existsSync19(join12(cwd2, "pyproject.toml")) || existsSync19(join12(cwd2, "setup.py")) || existsSync19(join12(cwd2, "requirements.txt")) || existsSync19(join12(cwd2, "Pipfile")) || existsSync19(join12(cwd2, "poetry.lock"));
51046
+ const npm = existsSync18(join12(cwd2, "package.json")) || existsSync18(join12(cwd2, "package-lock.json")) || existsSync18(join12(cwd2, "pnpm-lock.yaml")) || existsSync18(join12(cwd2, "yarn.lock"));
51047
+ const pypi = existsSync18(join12(cwd2, "pyproject.toml")) || existsSync18(join12(cwd2, "setup.py")) || existsSync18(join12(cwd2, "requirements.txt")) || existsSync18(join12(cwd2, "Pipfile")) || existsSync18(join12(cwd2, "poetry.lock"));
50735
51048
  return { npm, pypi };
50736
51049
  }
50737
51050
  function findRepoRootOrNull(cwd2) {
50738
51051
  try {
50739
- return execFileSync4("git", ["-C", cwd2, "rev-parse", "--show-toplevel"], {
50740
- encoding: "utf-8",
50741
- stdio: ["pipe", "pipe", "pipe"]
50742
- }).trim();
51052
+ return safeGit(["rev-parse", "--show-toplevel"], { cwd: cwd2 }).trim();
50743
51053
  } catch {
50744
51054
  return null;
50745
51055
  }
50746
51056
  }
50747
51057
  async function runInit2(args) {
50748
51058
  if (args.includes("--help") || args.includes("-h")) {
50749
- process.stdout.write(USAGE5);
51059
+ process.stdout.write(USAGE6);
50750
51060
  return 0;
50751
51061
  }
50752
51062
  const opts = parseInitArgs(args);
@@ -50974,7 +51284,7 @@ async function runInit2(args) {
50974
51284
  function handleInitCommand(args) {
50975
51285
  return runInit2(args);
50976
51286
  }
50977
- var import_chalk14, USAGE5;
51287
+ var import_chalk14, USAGE6;
50978
51288
  var init_init = __esm({
50979
51289
  "src/commands/init.ts"() {
50980
51290
  "use strict";
@@ -50984,7 +51294,8 @@ var init_init = __esm({
50984
51294
  init_hooks_registry();
50985
51295
  init_protect();
50986
51296
  init_paths();
50987
- USAGE5 = `
51297
+ init_safe_git();
51298
+ USAGE6 = `
50988
51299
  dg init \u2014 fast per-project setup
50989
51300
 
50990
51301
  Usage:
@@ -51410,10 +51721,12 @@ var init_ProjectSelector = __esm({
51410
51721
  await init_build2();
51411
51722
  import_chalk17 = __toESM(require_source());
51412
51723
  init_sanitize();
51724
+ await init_useTerminalSize();
51413
51725
  import_jsx_runtime13 = __toESM(require_jsx_runtime());
51414
51726
  ProjectSelector = ({ projects, onConfirm, onCancel, userStatus }) => {
51415
51727
  const [cursor, setCursor] = (0, import_react35.useState)(0);
51416
51728
  const [selected, setSelected] = (0, import_react35.useState)(() => new Set(projects.map((_, i) => i)));
51729
+ const { cols: termCols } = useTerminalSize();
51417
51730
  use_input_default((input, key) => {
51418
51731
  if (key.upArrow) {
51419
51732
  setCursor((c) => Math.max(0, c - 1));
@@ -51441,8 +51754,6 @@ var init_ProjectSelector = __esm({
51441
51754
  const ecosystemLabel = (eco) => eco === "npm" ? import_chalk17.default.magenta("npm") : import_chalk17.default.blue("pip");
51442
51755
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 1, children: [
51443
51756
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Text, { children: [
51444
- import_chalk17.default.cyan("\u25C6"),
51445
- " ",
51446
51757
  import_chalk17.default.bold("Dependency Guardian"),
51447
51758
  " ",
51448
51759
  userStatus ? import_chalk17.default.dim(userStatus) : ""
@@ -51460,8 +51771,20 @@ var init_ProjectSelector = __esm({
51460
51771
  const isSelected = selected.has(i);
51461
51772
  const prefix = isCursor ? import_chalk17.default.cyan("\u258C") : " ";
51462
51773
  const check = isSelected ? import_chalk17.default.green("\u25C9") : import_chalk17.default.dim("\u25CB");
51463
- const line = `${prefix}${check} ${sanitize(proj.relativePath).padEnd(40)} ${ecosystemLabel(proj.ecosystem).padEnd(5)} ${proj.packageCount} packages`;
51464
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { backgroundColor: isCursor ? "#1a1a2e" : void 0, children: line }, i);
51774
+ const ecoCount = `${ecosystemLabel(proj.ecosystem)} ${proj.packageCount} packages`;
51775
+ const ecoCountPlainLen = `${proj.ecosystem} ${proj.packageCount} packages`.length;
51776
+ const fixedPrefixLen = 4;
51777
+ const pathColWidth = Math.max(20, termCols - fixedPrefixLen - ecoCountPlainLen - 3);
51778
+ const path2 = sanitize(proj.relativePath);
51779
+ const pathTruncated = path2.length > pathColWidth ? path2.slice(0, Math.max(1, pathColWidth - 1)) + "\u2026" : path2.padEnd(pathColWidth);
51780
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Text, { backgroundColor: isCursor ? "#1a1a2e" : void 0, wrap: "truncate-end", children: [
51781
+ prefix,
51782
+ check,
51783
+ " ",
51784
+ pathTruncated,
51785
+ " ",
51786
+ ecoCount
51787
+ ] }, i);
51465
51788
  }),
51466
51789
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { children: "" }),
51467
51790
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { dimColor: true, children: selected.size === 0 ? import_chalk17.default.yellow("Select at least 1 project to scan") : `${selected.size} of ${projects.length} selected` }),
@@ -51507,7 +51830,7 @@ var init_SetupBanner = __esm({
51507
51830
  paddingRight: 1,
51508
51831
  width: "100%",
51509
51832
  children: [
51510
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Text, { children: [
51833
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Text, { wrap: "truncate-end", children: [
51511
51834
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: "yellow", bold: true, children: "! Setup incomplete" }),
51512
51835
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Text, { dimColor: true, children: [
51513
51836
  " \u2014 ",
@@ -51515,7 +51838,7 @@ var init_SetupBanner = __esm({
51515
51838
  " you haven't set up yet"
51516
51839
  ] })
51517
51840
  ] }),
51518
- issues.map((issue) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Text, { children: [
51841
+ issues.map((issue) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Text, { wrap: "truncate-end", children: [
51519
51842
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { dimColor: true, children: " \xB7 " }),
51520
51843
  issue.label,
51521
51844
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { dimColor: true, children: " \u2192 " }),
@@ -51625,7 +51948,7 @@ var init_App2 = __esm({
51625
51948
  return () => clearTimeout(timer);
51626
51949
  }
51627
51950
  if (state.phase === "trial_exhausted") {
51628
- let msg = "You've used your 5 free anonymous scans. Run `dg login` to create a free account and continue scanning.\n";
51951
+ let msg = "You've used your 5 free anonymous scans. Run `dg login` to access Dependency Guardian for free.\n";
51629
51952
  try {
51630
51953
  if (getStoredApiKey()) {
51631
51954
  msg = "Your session expired. Run `dg logout` then `dg login` to re-authenticate.\n";
@@ -51708,28 +52031,10 @@ var init_App2 = __esm({
51708
52031
  ] })
51709
52032
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
51710
52033
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "yellow", bold: true, children: "You've used your 5 free anonymous scans." }),
51711
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: " " }),
51712
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "Sign in (free, 10 seconds) to unlock:" }),
51713
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Text, { children: [
51714
- " ",
51715
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "green", children: "\u2713" }),
51716
- " unlimited scans"
51717
- ] }),
51718
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Text, { children: [
51719
- " ",
51720
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "green", children: "\u2713" }),
51721
- " scan history + saved reports"
51722
- ] }),
51723
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Text, { children: [
51724
- " ",
51725
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "green", children: "\u2713" }),
51726
- " GitHub App for team / PR scanning"
51727
- ] }),
51728
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: " " }),
51729
52034
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Text, { children: [
51730
52035
  "Run ",
51731
52036
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "cyan", bold: true, children: "dg login" }),
51732
- " when you're ready."
52037
+ " to access Dependency Guardian for free."
51733
52038
  ] })
51734
52039
  ] }) });
51735
52040
  }
@@ -52656,7 +52961,7 @@ import {
52656
52961
  readFileSync as readFileSync5,
52657
52962
  writeFileSync as writeFileSync4,
52658
52963
  copyFileSync,
52659
- unlinkSync as unlinkSync2,
52964
+ unlinkSync as unlinkSync3,
52660
52965
  rmSync as rmSync2,
52661
52966
  chmodSync as chmodSync2,
52662
52967
  statSync,
@@ -52667,12 +52972,12 @@ import {
52667
52972
  import { dirname as dirname4 } from "node:path";
52668
52973
  var README = `dependency-guardian \u2014 local state for the dg CLI.
52669
52974
 
52670
- config.json your account + preferences (mode 0600)
52671
- cache/scans.sqlite 24h package-scan cache
52672
- cache/update-check.json latest-version probe (24h TTL)
52673
- cache/routing-hint.json scan-routing hint
52674
- state/aliases.sh optional shell aliases (npm/pip wrappers)
52675
- state/trial-banner throttle stamp for the trial nudge
52975
+ config.json your account + preferences (mode 0600)
52976
+ cache/update-check.json latest-version probe (24h TTL)
52977
+ state/aliases.sh optional shell aliases (npm/pip wrappers)
52978
+ state/auth-status.json 60s cache of /v1/auth/status
52979
+ state/trial-banner throttle stamp for the trial nudge
52980
+ state/bypass-log.jsonl append-only log of install-wrapper bypasses
52676
52981
  state/hooks-installed.json registry of git hooks dg installed
52677
52982
 
52678
52983
  To remove everything dg has written to your machine, run:
@@ -52700,7 +53005,7 @@ function moveFile(from, to, mode) {
52700
53005
  if (statSync(to).size !== statSync(from).size) {
52701
53006
  throw new Error(`size mismatch after copy: ${from} -> ${to}`);
52702
53007
  }
52703
- unlinkSync2(from);
53008
+ unlinkSync3(from);
52704
53009
  }
52705
53010
  function isPidAlive(pid) {
52706
53011
  if (!Number.isInteger(pid) || pid <= 0) return false;
@@ -52728,7 +53033,7 @@ function acquireLock(lockPath) {
52728
53033
  }
52729
53034
  if (!stale) return "held";
52730
53035
  try {
52731
- unlinkSync2(lockPath);
53036
+ unlinkSync3(lockPath);
52732
53037
  } catch {
52733
53038
  }
52734
53039
  }
@@ -52752,7 +53057,7 @@ function releaseLock(state) {
52752
53057
  }
52753
53058
  }
52754
53059
  try {
52755
- unlinkSync2(state.lockPath);
53060
+ unlinkSync3(state.lockPath);
52756
53061
  } catch {
52757
53062
  }
52758
53063
  }
@@ -52761,7 +53066,7 @@ function rollback(moves) {
52761
53066
  try {
52762
53067
  if (existsSync5(m.to) && !existsSync5(m.from)) {
52763
53068
  copyFileSync(m.to, m.from);
52764
- unlinkSync2(m.to);
53069
+ unlinkSync3(m.to);
52765
53070
  }
52766
53071
  } catch {
52767
53072
  }
@@ -52769,7 +53074,7 @@ function rollback(moves) {
52769
53074
  }
52770
53075
  function detectLegacy() {
52771
53076
  const legacy = legacyPaths();
52772
- return existsSync5(legacy.dgrc) || existsSync5(legacy.cacheSqlite) || existsSync5(legacy.routingHint) || existsSync5(legacy.updateCheck) || existsSync5(legacy.trialBannerOld) || existsSync5(legacy.aliasesShOld) || existsSync5(legacy.depGuardianDir);
53077
+ return existsSync5(legacy.dgrc) || existsSync5(legacy.updateCheck) || existsSync5(legacy.trialBannerOld) || existsSync5(legacy.aliasesShOld) || existsSync5(legacy.depGuardianDir);
52773
53078
  }
52774
53079
  function validateLegacyConfig(path2) {
52775
53080
  let raw;
@@ -52826,23 +53131,13 @@ function runMigrationIfNeeded() {
52826
53131
  } catch {
52827
53132
  }
52828
53133
  try {
52829
- unlinkSync2(legacy.dgrc);
53134
+ unlinkSync3(legacy.dgrc);
52830
53135
  } catch {
52831
53136
  }
52832
53137
  moves.push({ from: legacy.dgrc, to: p.config });
52833
53138
  movedCount++;
52834
53139
  }
52835
53140
  ensureDir(p.cacheDir, 448);
52836
- if (existsSync5(legacy.cacheSqlite)) {
52837
- moveFile(legacy.cacheSqlite, p.cacheSqlite, 384);
52838
- moves.push({ from: legacy.cacheSqlite, to: p.cacheSqlite });
52839
- movedCount++;
52840
- }
52841
- if (existsSync5(legacy.routingHint)) {
52842
- moveFile(legacy.routingHint, p.routingHint, 384);
52843
- moves.push({ from: legacy.routingHint, to: p.routingHint });
52844
- movedCount++;
52845
- }
52846
53141
  if (existsSync5(legacy.updateCheck)) {
52847
53142
  moveFile(legacy.updateCheck, p.updateCheck, 384);
52848
53143
  moves.push({ from: legacy.updateCheck, to: p.updateCheck });
@@ -52862,9 +53157,7 @@ function runMigrationIfNeeded() {
52862
53157
  if (existsSync5(legacy.depGuardianDir) && legacy.depGuardianDir !== p.root) {
52863
53158
  const candidates = [
52864
53159
  { from: join(legacy.depGuardianDir, "config.json"), to: p.config, mode: 384 },
52865
- { from: join(legacy.depGuardianDir, "cache", "scans.sqlite"), to: p.cacheSqlite, mode: 384 },
52866
53160
  { from: join(legacy.depGuardianDir, "cache", "update-check.json"), to: p.updateCheck, mode: 384 },
52867
- { from: join(legacy.depGuardianDir, "cache", "routing-hint.json"), to: p.routingHint, mode: 384 },
52868
53161
  { from: join(legacy.depGuardianDir, "state", "aliases.sh"), to: p.aliasesSh, mode: 420 },
52869
53162
  { from: join(legacy.depGuardianDir, "state", "trial-banner"), to: p.trialBanner, mode: 384 },
52870
53163
  { from: join(legacy.depGuardianDir, "state", "hooks-installed.json"), to: p.hooksRegistry, mode: 384 },
@@ -52941,8 +53234,8 @@ var isInteractive = process.stdout.isTTY === true && !process.env.CI && !process
52941
53234
  async function main() {
52942
53235
  const rawCommand = process.argv[2];
52943
53236
  if (!rawCommand || rawCommand === "help" || rawCommand === "--help" || rawCommand === "-h") {
52944
- const { USAGE: USAGE6 } = await Promise.resolve().then(() => (init_config(), config_exports));
52945
- process.stdout.write(USAGE6);
53237
+ const { USAGE: USAGE7 } = await Promise.resolve().then(() => (init_config(), config_exports));
53238
+ process.stdout.write(USAGE7);
52946
53239
  return;
52947
53240
  }
52948
53241
  if (rawCommand === "--help-all" || rawCommand === "help-all") {
@@ -52955,7 +53248,7 @@ async function main() {
52955
53248
  `);
52956
53249
  return;
52957
53250
  }
52958
- const KNOWN_COMMANDS = ["init", "scan", "licenses", "npm", "pip", "wrap", "login", "logout", "status", "hook", "update", "help", "version", "protect", "publish-check", "cleanup", "uninstall"];
53251
+ const KNOWN_COMMANDS = ["init", "scan", "licenses", "npm", "pip", "wrap", "login", "logout", "status", "hook", "update", "help", "version", "protect", "publish-check", "uninstall"];
52959
53252
  if (rawCommand && !rawCommand.startsWith("-") && !KNOWN_COMMANDS.includes(rawCommand)) {
52960
53253
  const chalk18 = (await Promise.resolve().then(() => __toESM(require_source()))).default;
52961
53254
  const best = closestCommand(rawCommand, KNOWN_COMMANDS);
@@ -53019,11 +53312,11 @@ async function main() {
53019
53312
  if (v === "npm" || v === "pypi") ecosystem = v;
53020
53313
  }
53021
53314
  const { runNpmPublishCheck: runNpmPublishCheck2, runPypiPublishCheck: runPypiPublishCheck2, renderPublishCheckResult: renderPublishCheckResult2 } = await Promise.resolve().then(() => (init_publish_check(), publish_check_exports));
53022
- const { existsSync: existsSync20 } = await import("node:fs");
53315
+ const { existsSync: existsSync19 } = await import("node:fs");
53023
53316
  const { join: join13 } = await import("node:path");
53024
53317
  if (ecosystem === "auto") {
53025
- if (existsSync20(join13(process.cwd(), "package.json"))) ecosystem = "npm";
53026
- else if (existsSync20(join13(process.cwd(), "pyproject.toml")) || existsSync20(join13(process.cwd(), "setup.py"))) ecosystem = "pypi";
53318
+ if (existsSync19(join13(process.cwd(), "package.json"))) ecosystem = "npm";
53319
+ else if (existsSync19(join13(process.cwd(), "pyproject.toml")) || existsSync19(join13(process.cwd(), "setup.py"))) ecosystem = "pypi";
53027
53320
  else {
53028
53321
  process.stderr.write("\n publish-check: no package.json, pyproject.toml, or setup.py in current directory.\n Pass --ecosystem npm|pypi explicitly if your project layout differs.\n\n");
53029
53322
  process.exit(3);
@@ -53084,12 +53377,8 @@ async function main() {
53084
53377
  return;
53085
53378
  }
53086
53379
  if (rawCommand === "update") {
53087
- await runUpdate(CLI_VERSION);
53088
- return;
53089
- }
53090
- if (rawCommand === "cleanup") {
53091
- const { handleCleanupCommand: handleCleanupCommand2 } = await Promise.resolve().then(() => (init_cleanup(), cleanup_exports));
53092
- const code = await handleCleanupCommand2(process.argv.slice(3));
53380
+ const { handleUpdateCommand: handleUpdateCommand2 } = await Promise.resolve().then(() => (init_update(), update_exports));
53381
+ const code = await handleUpdateCommand2(process.argv.slice(3), CLI_VERSION);
53093
53382
  process.exit(code);
53094
53383
  }
53095
53384
  if (rawCommand === "uninstall") {
@@ -53213,10 +53502,11 @@ async function main() {
53213
53502
  const { render: render3 } = await init_build2().then(() => build_exports);
53214
53503
  const React23 = await Promise.resolve().then(() => __toESM(require_react()));
53215
53504
  const { App: App3 } = await init_App2().then(() => App_exports);
53216
- const { setInkInstance: setInkInstance2, resetInkClear: resetInkClear2, initInkInstances: initInkInstances2 } = await Promise.resolve().then(() => (init_ink_controls(), ink_controls_exports));
53505
+ const { setInkInstance: setInkInstance2, resetInkClear: resetInkClear2, initInkInstances: initInkInstances2, patchInkLogForAbsolutePositioning: patchInkLogForAbsolutePositioning2 } = await Promise.resolve().then(() => (init_ink_controls(), ink_controls_exports));
53217
53506
  await initInkInstances2();
53218
53507
  const inst = render3(React23.createElement(App3, { config, userStatus, scanUsage, setupIssues }));
53219
53508
  setInkInstance2(inst);
53509
+ patchInkLogForAbsolutePositioning2();
53220
53510
  try {
53221
53511
  await inst.waitUntilExit();
53222
53512
  } finally {