@westbayberry/dg 2.1.0 → 2.3.0

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 (148) hide show
  1. package/README.md +216 -226
  2. package/dist/agents/claude-code.js +123 -0
  3. package/dist/agents/codex.js +65 -0
  4. package/dist/agents/copilot-cli.js +115 -0
  5. package/dist/agents/cursor.js +118 -0
  6. package/dist/agents/gate-posture.js +21 -0
  7. package/dist/agents/gemini.js +107 -0
  8. package/dist/agents/persistence.js +351 -0
  9. package/dist/agents/registry.js +128 -0
  10. package/dist/agents/routing.js +118 -0
  11. package/dist/agents/types.js +1 -0
  12. package/dist/agents/windsurf.js +93 -0
  13. package/dist/api/analyze.js +1 -1
  14. package/dist/audit/detectors.js +0 -11
  15. package/dist/audit/events.js +5 -21
  16. package/dist/audit/rules.js +6 -2
  17. package/dist/audit-ui/AuditApp.js +2 -0
  18. package/dist/audit-ui/components/AuditResultsView.js +55 -92
  19. package/dist/audit-ui/export.js +0 -4
  20. package/dist/audit-ui/format.js +0 -3
  21. package/dist/audit-ui/launch.js +10 -1
  22. package/dist/auth/device-login.js +4 -5
  23. package/dist/auth/login-app.js +7 -7
  24. package/dist/auth/store.js +17 -5
  25. package/dist/bin/dg.js +59 -52
  26. package/dist/commands/agents.js +275 -0
  27. package/dist/commands/audit.js +28 -3
  28. package/dist/commands/config.js +15 -7
  29. package/dist/commands/cooldown.js +389 -0
  30. package/dist/commands/decisions.js +23 -9
  31. package/dist/commands/doctor.js +1 -1
  32. package/dist/commands/help.js +1 -1
  33. package/dist/commands/licenses.js +10 -22
  34. package/dist/commands/logout.js +4 -11
  35. package/dist/commands/router.js +6 -4
  36. package/dist/commands/sbom.js +206 -0
  37. package/dist/commands/scan.js +1 -1
  38. package/dist/commands/service.js +32 -13
  39. package/dist/commands/setup.js +196 -27
  40. package/dist/commands/types.js +1 -0
  41. package/dist/commands/uninstall.js +2 -1
  42. package/dist/commands/update.js +17 -8
  43. package/dist/commands/verify.js +8 -5
  44. package/dist/config/settings.js +49 -48
  45. package/dist/decisions/remember-prompt.js +5 -10
  46. package/dist/export-ui/ExportDialog.js +198 -0
  47. package/dist/install-ui/LiveInstall.js +5 -4
  48. package/dist/install-ui/block-render.js +18 -14
  49. package/dist/install-ui/prep-spinner.js +32 -0
  50. package/dist/launcher/agent-check.js +896 -0
  51. package/dist/launcher/agent-hook-exec.js +70 -0
  52. package/dist/launcher/agent-hook-io.js +39 -0
  53. package/dist/launcher/cargo-cache.js +40 -0
  54. package/dist/launcher/classify.js +44 -7
  55. package/dist/launcher/env.js +105 -26
  56. package/dist/launcher/install-preflight.js +64 -18
  57. package/dist/launcher/live-install.js +29 -8
  58. package/dist/launcher/manifest-screen.js +171 -0
  59. package/dist/launcher/output-redaction.js +10 -4
  60. package/dist/launcher/preflight-prompt.js +18 -4
  61. package/dist/launcher/run.js +178 -103
  62. package/dist/launcher/spawn-invocation.js +21 -0
  63. package/dist/policy/cooldown.js +18 -5
  64. package/dist/policy/evaluate.js +5 -6
  65. package/dist/policy/pypi-name.js +17 -0
  66. package/dist/presentation/mode.js +3 -2
  67. package/dist/presentation/package-page.js +9 -0
  68. package/dist/presentation/theme.js +7 -7
  69. package/dist/project/dgfile.js +146 -6
  70. package/dist/project/override-trust.js +0 -0
  71. package/dist/proxy/auth.js +42 -0
  72. package/dist/proxy/ca.js +29 -9
  73. package/dist/proxy/cooldown-exemptions-file.js +33 -0
  74. package/dist/proxy/enforcement.js +55 -16
  75. package/dist/proxy/metadata-map.js +66 -12
  76. package/dist/proxy/preverified.js +55 -0
  77. package/dist/proxy/server.js +568 -53
  78. package/dist/proxy/worker.js +16 -1
  79. package/dist/publish-set/collect.js +1 -4
  80. package/dist/publish-set/npm.js +8 -5
  81. package/dist/publish-set/pack.js +9 -3
  82. package/dist/runtime/cli.js +0 -4
  83. package/dist/runtime/fatal.js +31 -0
  84. package/dist/runtime/first-run.js +14 -12
  85. package/dist/runtime/node-version.js +43 -6
  86. package/dist/runtime/nudges.js +35 -2
  87. package/dist/sbom/cyclonedx.js +211 -0
  88. package/dist/sbom-ui/SbomApp.js +158 -0
  89. package/dist/sbom-ui/components/SbomHeader.js +32 -0
  90. package/dist/sbom-ui/components/SbomList.js +52 -0
  91. package/dist/sbom-ui/inventory.js +132 -0
  92. package/dist/sbom-ui/launch.js +51 -0
  93. package/dist/sbom-ui/run.js +55 -0
  94. package/dist/sbom-ui/store.js +26 -0
  95. package/dist/scan/collect.js +10 -6
  96. package/dist/scan/command.js +17 -10
  97. package/dist/scan/discovery.js +11 -2
  98. package/dist/scan/render.js +28 -4
  99. package/dist/scan/scanner-report.js +15 -9
  100. package/dist/scan/staged.js +33 -9
  101. package/dist/scan-ui/LegacyApp.js +9 -13
  102. package/dist/scan-ui/alt-screen.js +5 -8
  103. package/dist/scan-ui/components/InteractiveResultsView.js +133 -120
  104. package/dist/scan-ui/components/ProgressBar.js +3 -14
  105. package/dist/scan-ui/components/ProjectSelector.js +1 -1
  106. package/dist/scan-ui/components/ScoreHeader.js +2 -3
  107. package/dist/scan-ui/components/SetupBanner.js +0 -6
  108. package/dist/scan-ui/format-helpers.js +61 -5
  109. package/dist/scan-ui/hooks/useResizeRepaint.js +25 -0
  110. package/dist/scan-ui/hooks/useScan.js +19 -2
  111. package/dist/scan-ui/launch.js +7 -4
  112. package/dist/scan-ui/shims.js +11 -4
  113. package/dist/scripts/detect.js +7 -2
  114. package/dist/scripts/gate.js +1 -1
  115. package/dist/security/sanitize.js +8 -4
  116. package/dist/service/state.js +28 -9
  117. package/dist/service/trust-refresh.js +92 -0
  118. package/dist/service/trust-store.js +5 -9
  119. package/dist/service/worker.js +23 -1
  120. package/dist/setup/activate-shell.js +28 -0
  121. package/dist/setup/git-hook.js +49 -4
  122. package/dist/setup/plan.js +227 -49
  123. package/dist/setup/uninstall-standalone.js +25 -0
  124. package/dist/setup-ui/gate.js +39 -0
  125. package/dist/setup-ui/offer.js +42 -0
  126. package/dist/setup-ui/selector.js +27 -0
  127. package/dist/setup-ui/tasks.js +56 -0
  128. package/dist/setup-ui/wizard.js +233 -0
  129. package/dist/standalone/uninstall.mjs +2123 -0
  130. package/dist/state/cooldown-held.js +66 -0
  131. package/dist/state/index.js +1 -0
  132. package/dist/state/locks.js +4 -2
  133. package/dist/state/store.js +2 -1
  134. package/dist/util/external-tool.js +25 -0
  135. package/dist/util/git.js +10 -3
  136. package/dist/util/report-writer.js +57 -0
  137. package/dist/verify/local.js +242 -44
  138. package/dist/verify/package-check.js +87 -29
  139. package/dist/verify/preflight.js +242 -49
  140. package/dist/verify/render.js +15 -1
  141. package/npm-shrinkwrap.json +2383 -0
  142. package/package.json +7 -8
  143. package/NOTICE +0 -5
  144. package/dist/commands/completion.js +0 -117
  145. package/dist/commands/explain.js +0 -236
  146. package/dist/commands/unavailable.js +0 -11
  147. package/dist/scripts/rebuild.js +0 -28
  148. package/dist/telemetry/events.js +0 -40
@@ -3,10 +3,11 @@ import { resolve } from "node:path";
3
3
  import { analyzePackages, AnalyzeError } from "../api/analyze.js";
4
4
  import { createTheme } from "../presentation/theme.js";
5
5
  import { provenanceLabel, provenanceDowngradeLine } from "../presentation/provenance.js";
6
+ import { packagePageUrl } from "../presentation/package-page.js";
6
7
  import { resolvePresentation } from "../presentation/mode.js";
7
8
  import { isRemotePackageSpec, isSupportedLockfilePath } from "./preflight.js";
8
9
  import { authStatus } from "../auth/store.js";
9
- import { EXIT_TOOL_ERROR, EXIT_UNAVAILABLE } from "../commands/types.js";
10
+ import { EXIT_TOOL_ERROR, EXIT_UNAVAILABLE, EXIT_USAGE_VERDICT } from "../commands/types.js";
10
11
  const REGISTRIES = { npm: "npm", pypi: "pypi" };
11
12
  const DEEP_VERIFY_HINT = "deep verify supports npm and pypi: dg verify npm:<package> or pypi:<package>";
12
13
  function parseSpec(target) {
@@ -38,10 +39,10 @@ function parseSpec(target) {
38
39
  const version = rest.slice(at + separator.length).trim() || null;
39
40
  return { ecosystem, name, version };
40
41
  }
41
- async function resolveLatest(ecosystem, name, fetchImpl) {
42
+ export async function resolveLatest(ecosystem, name, fetchImpl) {
42
43
  try {
43
44
  if (ecosystem === "npm") {
44
- const response = await fetchImpl(`https://registry.npmjs.org/${name.replace("/", "%2F")}`);
45
+ const response = await fetchImpl(`https://registry.npmjs.org/${encodeNpmPackagePath(name)}`);
45
46
  if (!response.ok) {
46
47
  return null;
47
48
  }
@@ -59,24 +60,31 @@ async function resolveLatest(ecosystem, name, fetchImpl) {
59
60
  return null;
60
61
  }
61
62
  }
63
+ function provenanceLines(version, result, theme, withPredicate) {
64
+ if (!result.provenance) {
65
+ return [];
66
+ }
67
+ const label = withPredicate && result.provenance.predicateType
68
+ ? `provenance ${provenanceLabel(result.provenance)} · ${result.provenance.predicateType}`
69
+ : `provenance ${provenanceLabel(result.provenance)}`;
70
+ const lines = [` ${theme.paint("muted", label)}`];
71
+ const downgrade = provenanceDowngradeLine(version, result.provenance);
72
+ if (downgrade) {
73
+ lines.push(` ${theme.paint("warn", `⚠ ${downgrade}`)}`);
74
+ }
75
+ return lines;
76
+ }
77
+ function reasonGlyph(action, theme) {
78
+ return action === "block" ? theme.paint("block", "✘") : action === "warn" ? theme.paint("warn", "⚠") : theme.paint("muted", "·");
79
+ }
62
80
  function renderResult(spec, version, result, theme, verbose) {
63
81
  const action = result.action ?? "pass";
64
82
  const badge = theme.badge(action);
65
83
  const lines = [`${badge} ${result.name}@${version} (${spec.ecosystem}) ${theme.paint("muted", `score ${result.score}`)}`];
66
- if (result.provenance) {
67
- const label = verbose && result.provenance.predicateType
68
- ? `provenance ${provenanceLabel(result.provenance)} · ${result.provenance.predicateType}`
69
- : `provenance ${provenanceLabel(result.provenance)}`;
70
- lines.push(` ${theme.paint("muted", label)}`);
71
- const downgrade = provenanceDowngradeLine(version, result.provenance);
72
- if (downgrade) {
73
- lines.push(` ${theme.paint("warn", `⚠ ${downgrade}`)}`);
74
- }
75
- }
84
+ lines.push(...provenanceLines(version, result, theme, verbose));
76
85
  const reasons = verbose ? result.reasons : result.reasons.slice(0, 6);
77
86
  for (const reason of reasons) {
78
- const glyph = action === "block" ? theme.paint("block", "✘") : action === "warn" ? theme.paint("warn", "⚠") : theme.paint("muted", "·");
79
- lines.push(` ${glyph} ${reason}`);
87
+ lines.push(` ${reasonGlyph(action, theme)} ${reason}`);
80
88
  }
81
89
  if (!verbose && result.reasons.length > reasons.length) {
82
90
  lines.push(` ${theme.paint("muted", `… ${result.reasons.length - reasons.length} more — rerun with --verbose`)}`);
@@ -92,8 +100,43 @@ function renderResult(spec, version, result, theme, verbose) {
92
100
  if (result.recommendation) {
93
101
  lines.push(` ${theme.paint("muted", result.recommendation)}`);
94
102
  }
103
+ const page = packagePageUrl(spec.ecosystem, result.name);
104
+ if (page) {
105
+ lines.push(` ${theme.paint("muted", `→ ${page}`)}`);
106
+ }
95
107
  return `${lines.join("\n")}\n`;
96
108
  }
109
+ const FREE_REASON_CAP = 3;
110
+ const FREE_SCANS_FOOTER_THRESHOLD = 5_000;
111
+ function renderFreeResult(spec, version, result, theme, freeScansRemaining) {
112
+ const action = result.action ?? "pass";
113
+ const lines = [`${theme.badge(action)} ${result.name}@${version} (${spec.ecosystem})`];
114
+ lines.push(...provenanceLines(version, result, theme, false));
115
+ if (action === "pass") {
116
+ lines.push(` ${theme.paint("muted", result.reasons[0] ?? "no risk signals")}`);
117
+ }
118
+ else {
119
+ const reasons = result.reasons.slice(0, FREE_REASON_CAP);
120
+ for (const reason of reasons) {
121
+ lines.push(` ${reasonGlyph(action, theme)} ${reason}`);
122
+ }
123
+ if (result.reasons.length > reasons.length) {
124
+ lines.push(` ${theme.paint("muted", `… ${result.reasons.length - reasons.length} more — sign in to see all:`)} ${theme.paint("accent", "dg login")}`);
125
+ }
126
+ }
127
+ const page = packagePageUrl(spec.ecosystem, result.name);
128
+ if (page) {
129
+ lines.push(` ${theme.paint("muted", `→ ${page}`)}`);
130
+ }
131
+ lines.push(` ${theme.paint("muted", "full findings, license, JSON output:")} ${theme.paint("accent", "dg login")}`);
132
+ if (freeScansRemaining !== undefined && freeScansRemaining < FREE_SCANS_FOOTER_THRESHOLD) {
133
+ lines.push(` ${theme.paint("muted", `${freeScansRemaining.toLocaleString()} free package checks left this month`)}`);
134
+ }
135
+ return `${lines.join("\n")}\n`;
136
+ }
137
+ function encodeNpmPackagePath(name) {
138
+ return name.split("/").map((segment) => encodeURIComponent(segment)).join("%2f");
139
+ }
97
140
  function exitCodeFor(action) {
98
141
  if (action === "block") {
99
142
  return 2;
@@ -109,39 +152,41 @@ function exitCodeFor(action) {
109
152
  export async function runPackageCheck(target, io = {}, options = {}) {
110
153
  const fetchImpl = io.fetchImpl ?? fetch;
111
154
  const theme = createTheme(resolvePresentation().color);
112
- if (!authStatus(io.env).authenticated) {
113
- const accent = (text) => theme.paint("accent", text);
114
- const muted = (text) => theme.paint("muted", text);
155
+ const accent = (text) => theme.paint("accent", text);
156
+ const muted = (text) => theme.paint("muted", text);
157
+ const authenticated = authStatus(io.env).authenticated;
158
+ if (!authenticated && (options.format === "json" || options.outputPath)) {
115
159
  return {
116
160
  exitCode: EXIT_UNAVAILABLE,
117
161
  stdout: "",
118
- stderr: `\n ${theme.paint("warn", "⚠")} Checking a package before you install it requires ${accent("sign-in")}.\n` +
162
+ stderr: `\n ${theme.paint("warn", "⚠")} ${muted("--json and --output for registry checks require sign-in.")}\n` +
119
163
  ` ${accent("dg login")}${muted(" · see plans at")} ${accent("westbayberry.com/pricing")}\n\n`
120
164
  };
121
165
  }
122
166
  const parsed = parseSpec(target);
123
167
  if (parsed === null) {
124
168
  return {
125
- exitCode: 2,
169
+ exitCode: EXIT_USAGE_VERDICT,
126
170
  stdout: "",
127
171
  stderr: `dg verify: add a registry, e.g. dg verify npm:${target} or dg verify pypi:${target}\n`
128
172
  };
129
173
  }
130
174
  if ("error" in parsed) {
131
- return { exitCode: 2, stdout: "", stderr: `dg verify: ${parsed.error}\n` };
175
+ return { exitCode: EXIT_USAGE_VERDICT, stdout: "", stderr: `dg verify: ${parsed.error}\n` };
132
176
  }
133
177
  let version = parsed.version;
134
178
  if (!version) {
135
179
  version = await resolveLatest(parsed.ecosystem, parsed.name, fetchImpl);
136
180
  if (!version) {
137
181
  return {
138
- exitCode: 1,
182
+ exitCode: exitCodeFor("analysis_incomplete"),
139
183
  stdout: "",
140
184
  stderr: `dg verify: could not resolve the latest version of ${parsed.name} on ${parsed.ecosystem}\n`
141
185
  };
142
186
  }
143
187
  }
144
188
  let result;
189
+ let freeScansRemaining;
145
190
  try {
146
191
  const response = await analyzePackages([{ name: parsed.name, version }], {
147
192
  ecosystem: parsed.ecosystem,
@@ -149,15 +194,26 @@ export async function runPackageCheck(target, io = {}, options = {}) {
149
194
  ...(io.env ? { env: io.env } : {})
150
195
  });
151
196
  result = response.packages.find((entry) => entry.name === parsed.name) ?? response.packages[0];
197
+ freeScansRemaining = response.freeScansRemaining;
152
198
  }
153
199
  catch (error) {
200
+ if (error instanceof AnalyzeError && error.code === "quota_exceeded") {
201
+ const loginLine = authenticated ? "" : ` ${muted("Run")} ${accent("dg login")} ${muted("to connect your account.")}\n`;
202
+ return {
203
+ exitCode: exitCodeFor("analysis_incomplete"),
204
+ stdout: "",
205
+ stderr: `dg verify: ${error.message}\n${loginLine} ${muted("see plans at")} ${accent("westbayberry.com/pricing")}\n`
206
+ };
207
+ }
154
208
  const message = error instanceof AnalyzeError ? error.message : error instanceof Error ? error.message : "could not reach the scanner";
155
- return { exitCode: 1, stdout: "", stderr: `dg verify: ${message}\n` };
209
+ return { exitCode: exitCodeFor("analysis_incomplete"), stdout: "", stderr: `dg verify: ${message}\n` };
156
210
  }
157
211
  if (!result) {
158
- return { exitCode: 1, stdout: "", stderr: `dg verify: scanner returned no result for ${parsed.name}\n` };
212
+ return { exitCode: exitCodeFor("analysis_incomplete"), stdout: "", stderr: `dg verify: scanner returned no result for ${parsed.name}\n` };
159
213
  }
160
- const action = result.action ?? "pass";
214
+ // A missing per-package action means the scanner did not return a verdict;
215
+ // treat that as incomplete, never as a clean pass.
216
+ const action = result.action ?? "analysis_incomplete";
161
217
  const rendered = options.format === "json"
162
218
  ? `${JSON.stringify({
163
219
  target,
@@ -171,7 +227,9 @@ export async function runPackageCheck(target, io = {}, options = {}) {
171
227
  ...(result.provenance ? { provenance: result.provenance } : {}),
172
228
  ...(result.recommendation ? { recommendation: result.recommendation } : {})
173
229
  }, null, 2)}\n`
174
- : renderResult(parsed, version, result, theme, options.verbose ?? false);
230
+ : authenticated
231
+ ? renderResult(parsed, version, result, theme, options.verbose ?? false)
232
+ : renderFreeResult(parsed, version, result, theme, freeScansRemaining);
175
233
  if (options.outputPath) {
176
234
  try {
177
235
  writeFileSync(resolve(options.outputPath), rendered, "utf8");
@@ -248,20 +306,20 @@ export async function maybeVerifyPackage(args) {
248
306
  if (outputFlagMissingPath) {
249
307
  return {
250
308
  handled: true,
251
- result: { exitCode: 2, stdout: "", stderr: `dg verify: ${outputFlagMissingPath} requires a path. Run 'dg verify --help'.\n` }
309
+ result: { exitCode: EXIT_USAGE_VERDICT, stdout: "", stderr: `dg verify: ${outputFlagMissingPath} requires a path. Run 'dg verify --help'.\n` }
252
310
  };
253
311
  }
254
312
  if (unknownFlag) {
255
313
  return {
256
314
  handled: true,
257
- result: { exitCode: 2, stdout: "", stderr: `dg verify: unknown option '${unknownFlag}'. Run 'dg verify --help'.\n` }
315
+ result: { exitCode: EXIT_USAGE_VERDICT, stdout: "", stderr: `dg verify: unknown option '${unknownFlag}'. Run 'dg verify --help'.\n` }
258
316
  };
259
317
  }
260
318
  if (sarif) {
261
319
  return {
262
320
  handled: true,
263
321
  result: {
264
- exitCode: 2,
322
+ exitCode: EXIT_USAGE_VERDICT,
265
323
  stdout: "",
266
324
  stderr: "dg verify: --sarif applies to local artifacts and lockfiles; registry package checks support --json.\n"
267
325
  }