@westbayberry/dg 2.0.11 → 2.2.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 (143) hide show
  1. package/README.md +216 -226
  2. package/dist/agents/claude-code.js +113 -0
  3. package/dist/agents/codex.js +65 -0
  4. package/dist/agents/copilot-cli.js +115 -0
  5. package/dist/agents/cursor.js +113 -0
  6. package/dist/agents/gemini.js +107 -0
  7. package/dist/agents/persistence.js +285 -0
  8. package/dist/agents/registry.js +127 -0
  9. package/dist/agents/types.js +1 -0
  10. package/dist/agents/windsurf.js +93 -0
  11. package/dist/api/analyze.js +6 -4
  12. package/dist/audit/detectors.js +0 -11
  13. package/dist/audit/events.js +5 -21
  14. package/dist/audit-ui/AuditApp.js +2 -0
  15. package/dist/audit-ui/components/AuditResultsView.js +55 -92
  16. package/dist/audit-ui/export.js +0 -4
  17. package/dist/audit-ui/format.js +0 -3
  18. package/dist/audit-ui/launch.js +10 -1
  19. package/dist/auth/device-login.js +4 -5
  20. package/dist/auth/login-app.js +7 -7
  21. package/dist/auth/store.js +8 -3
  22. package/dist/bin/dg.js +59 -52
  23. package/dist/commands/agents.js +231 -0
  24. package/dist/commands/audit.js +22 -3
  25. package/dist/commands/config.js +26 -10
  26. package/dist/commands/cooldown.js +389 -0
  27. package/dist/commands/decisions.js +169 -0
  28. package/dist/commands/doctor.js +1 -1
  29. package/dist/commands/help.js +1 -1
  30. package/dist/commands/licenses.js +10 -22
  31. package/dist/commands/logout.js +4 -11
  32. package/dist/commands/router.js +8 -4
  33. package/dist/commands/sbom.js +206 -0
  34. package/dist/commands/scan.js +3 -2
  35. package/dist/commands/service.js +32 -13
  36. package/dist/commands/setup.js +197 -27
  37. package/dist/commands/status.js +5 -2
  38. package/dist/commands/types.js +1 -0
  39. package/dist/commands/update.js +17 -8
  40. package/dist/commands/verify.js +8 -5
  41. package/dist/config/settings.js +154 -65
  42. package/dist/decisions/apply.js +128 -0
  43. package/dist/decisions/remember-prompt.js +92 -0
  44. package/dist/export-ui/ExportDialog.js +198 -0
  45. package/dist/install-ui/LiveInstall.js +2 -2
  46. package/dist/install-ui/block-render.js +21 -4
  47. package/dist/install-ui/prep-spinner.js +32 -0
  48. package/dist/install-ui/prompt.js +14 -0
  49. package/dist/launcher/agent-check.js +466 -0
  50. package/dist/launcher/agent-hook-exec.js +70 -0
  51. package/dist/launcher/agent-hook-io.js +31 -0
  52. package/dist/launcher/cargo-cache.js +40 -0
  53. package/dist/launcher/classify.js +17 -6
  54. package/dist/launcher/env.js +71 -24
  55. package/dist/launcher/install-preflight.js +167 -17
  56. package/dist/launcher/live-install.js +25 -5
  57. package/dist/launcher/output-redaction.js +7 -4
  58. package/dist/launcher/preflight-prompt.js +43 -3
  59. package/dist/launcher/run.js +100 -86
  60. package/dist/launcher/spawn-invocation.js +21 -0
  61. package/dist/policy/cooldown.js +117 -0
  62. package/dist/policy/evaluate.js +5 -21
  63. package/dist/policy/pypi-name.js +17 -0
  64. package/dist/presentation/mode.js +3 -2
  65. package/dist/presentation/package-page.js +9 -0
  66. package/dist/presentation/provenance.js +23 -0
  67. package/dist/presentation/theme.js +7 -7
  68. package/dist/project/dgfile.js +446 -0
  69. package/dist/proxy/auth.js +42 -0
  70. package/dist/proxy/ca.js +29 -9
  71. package/dist/proxy/cooldown-exemptions-file.js +33 -0
  72. package/dist/proxy/enforcement.js +57 -17
  73. package/dist/proxy/metadata-map.js +66 -4
  74. package/dist/proxy/preverified.js +55 -0
  75. package/dist/proxy/server.js +473 -45
  76. package/dist/proxy/worker.js +16 -1
  77. package/dist/publish-set/collect.js +1 -4
  78. package/dist/publish-set/npm.js +8 -5
  79. package/dist/publish-set/pack.js +9 -3
  80. package/dist/runtime/cli.js +0 -4
  81. package/dist/runtime/fatal.js +31 -0
  82. package/dist/runtime/first-run.js +12 -11
  83. package/dist/runtime/node-version.js +43 -6
  84. package/dist/runtime/nudges.js +35 -2
  85. package/dist/sbom/cyclonedx.js +211 -0
  86. package/dist/sbom-ui/SbomApp.js +158 -0
  87. package/dist/sbom-ui/components/SbomHeader.js +32 -0
  88. package/dist/sbom-ui/components/SbomList.js +52 -0
  89. package/dist/sbom-ui/inventory.js +128 -0
  90. package/dist/sbom-ui/launch.js +51 -0
  91. package/dist/sbom-ui/run.js +55 -0
  92. package/dist/sbom-ui/store.js +26 -0
  93. package/dist/scan/collect.js +10 -6
  94. package/dist/scan/command.js +51 -17
  95. package/dist/scan/discovery.js +11 -2
  96. package/dist/scan/render.js +63 -8
  97. package/dist/scan/scanner-report.js +42 -9
  98. package/dist/scan/staged.js +71 -11
  99. package/dist/scan-ui/LegacyApp.js +12 -16
  100. package/dist/scan-ui/alt-screen.js +5 -8
  101. package/dist/scan-ui/components/InteractiveResultsView.js +193 -123
  102. package/dist/scan-ui/components/ProgressBar.js +3 -14
  103. package/dist/scan-ui/components/ProjectSelector.js +1 -1
  104. package/dist/scan-ui/components/ScoreHeader.js +2 -3
  105. package/dist/scan-ui/components/SetupBanner.js +0 -6
  106. package/dist/scan-ui/format-helpers.js +61 -5
  107. package/dist/scan-ui/hooks/useResizeRepaint.js +25 -0
  108. package/dist/scan-ui/hooks/useScan.js +46 -4
  109. package/dist/scan-ui/launch.js +7 -4
  110. package/dist/scan-ui/shims.js +14 -4
  111. package/dist/scripts/detect.js +158 -0
  112. package/dist/scripts/gate.js +170 -0
  113. package/dist/service/state.js +27 -8
  114. package/dist/service/trust-refresh.js +92 -0
  115. package/dist/service/worker.js +23 -1
  116. package/dist/setup/activate-shell.js +28 -0
  117. package/dist/setup/git-hook.js +49 -4
  118. package/dist/setup/plan.js +98 -29
  119. package/dist/setup-ui/gate.js +39 -0
  120. package/dist/setup-ui/offer.js +42 -0
  121. package/dist/setup-ui/selector.js +27 -0
  122. package/dist/setup-ui/tasks.js +56 -0
  123. package/dist/setup-ui/wizard.js +225 -0
  124. package/dist/state/cooldown-held.js +66 -0
  125. package/dist/state/index.js +1 -0
  126. package/dist/state/locks.js +4 -2
  127. package/dist/state/store.js +2 -1
  128. package/dist/util/external-tool.js +25 -0
  129. package/dist/util/git.js +10 -3
  130. package/dist/util/json-file.js +24 -0
  131. package/dist/util/report-writer.js +57 -0
  132. package/dist/util/tty-prompt.js +13 -6
  133. package/dist/verify/local.js +240 -42
  134. package/dist/verify/package-check.js +86 -18
  135. package/dist/verify/preflight.js +242 -49
  136. package/dist/verify/render.js +15 -1
  137. package/npm-shrinkwrap.json +2383 -0
  138. package/package.json +14 -8
  139. package/NOTICE +0 -5
  140. package/dist/commands/completion.js +0 -116
  141. package/dist/commands/explain.js +0 -232
  142. package/dist/commands/unavailable.js +0 -11
  143. package/dist/telemetry/events.js +0 -40
@@ -1,13 +1,15 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { brotliDecompressSync, gunzipSync, inflateRawSync, inflateSync } from "node:zlib";
2
3
  export function extractRegistryMetadataIdentities(metadataUrl, response) {
4
+ const decoded = { headers: response.headers, body: decodeContentEncoding(response.headers, response.body) };
3
5
  // PyPI Simple index (PEP 503 HTML or PEP 691 JSON) — the pip/uv/pipx flow.
4
6
  if (isPypiSimpleIndexUrl(metadataUrl)) {
5
- return extractPypiSimpleIdentities(metadataUrl, response);
7
+ return extractPypiSimpleIdentities(metadataUrl, decoded);
6
8
  }
7
- if (!looksLikeJson(response.headers["content-type"])) {
9
+ if (!looksLikeJson(decoded.headers["content-type"])) {
8
10
  return [];
9
11
  }
10
- const parsed = parseJson(response.body);
12
+ const parsed = parseJson(decoded.body);
11
13
  if (!isRecord(parsed)) {
12
14
  return [];
13
15
  }
@@ -16,6 +18,42 @@ export function extractRegistryMetadataIdentities(metadataUrl, response) {
16
18
  ...extractPypiIdentities(metadataUrl, parsed)
17
19
  ];
18
20
  }
21
+ // Content-Encoding lists encodings in the order they were applied; decode in
22
+ // reverse. Any unknown token or decode failure returns the body untouched so
23
+ // the response falls back to artifact handling instead of crashing the proxy.
24
+ function decodeContentEncoding(headers, body) {
25
+ const raw = headers["content-encoding"];
26
+ const encodings = (Array.isArray(raw) ? raw.join(",") : String(raw ?? ""))
27
+ .split(",")
28
+ .map((token) => token.trim().toLowerCase())
29
+ .filter((token) => token.length > 0 && token !== "identity");
30
+ let decoded = body;
31
+ for (const encoding of encodings.reverse()) {
32
+ try {
33
+ if (encoding === "gzip" || encoding === "x-gzip") {
34
+ decoded = gunzipSync(decoded);
35
+ }
36
+ else if (encoding === "br") {
37
+ decoded = brotliDecompressSync(decoded);
38
+ }
39
+ else if (encoding === "deflate") {
40
+ try {
41
+ decoded = inflateSync(decoded);
42
+ }
43
+ catch {
44
+ decoded = inflateRawSync(decoded);
45
+ }
46
+ }
47
+ else {
48
+ return body;
49
+ }
50
+ }
51
+ catch {
52
+ return body;
53
+ }
54
+ }
55
+ return decoded;
56
+ }
19
57
  // A registry INDEX/metadata request (not an artifact download). These must be
20
58
  // passed through untouched — never verified as a package — so e.g. pip's
21
59
  // `pypi.org/simple/<pkg>/` index is not mistaken for a package named "simple".
@@ -202,7 +240,9 @@ function fallbackIdentity(artifactUrl, classification) {
202
240
  const ecosystem = ecosystemForManager(classification.manager);
203
241
  const parsed = ecosystem === "pypi"
204
242
  ? parsePypiArtifactFilename(decodeURIComponent(artifactUrl.pathname.split("/").filter(Boolean).at(-1) ?? "")) ?? parsePackageVersionFromUrl(artifactUrl)
205
- : parsePackageVersionFromUrl(artifactUrl);
243
+ : ecosystem === "cargo"
244
+ ? parseCargoArtifactUrl(artifactUrl) ?? parsePackageVersionFromUrl(artifactUrl)
245
+ : parsePackageVersionFromUrl(artifactUrl);
206
246
  const requested = requestedIdentityFromArgs(classification, parsed.name);
207
247
  return {
208
248
  ecosystem,
@@ -213,6 +253,28 @@ function fallbackIdentity(artifactUrl, classification) {
213
253
  sourceKind: "url-fallback"
214
254
  };
215
255
  }
256
+ // crates.io serves downloads at /api/v1/crates/{name}/{version}/download,
257
+ // static.crates.io at /crates/{name}/{version}/download and as
258
+ // {name}-{version}.crate files. Crate versions always start with a digit
259
+ // (semver), so the filename split is unambiguous even for names with dashes.
260
+ function parseCargoArtifactUrl(artifactUrl) {
261
+ const downloadMatch = /^\/(?:api\/v1\/)?crates\/([^/]+)\/([^/]+)\/download\/?$/.exec(artifactUrl.pathname);
262
+ if (downloadMatch && downloadMatch[1] && downloadMatch[2]) {
263
+ return {
264
+ name: decodeURIComponent(downloadMatch[1]),
265
+ version: decodeURIComponent(downloadMatch[2])
266
+ };
267
+ }
268
+ const file = decodeURIComponent(artifactUrl.pathname.split("/").filter(Boolean).at(-1) ?? "");
269
+ if (/\.crate$/i.test(file)) {
270
+ const stem = file.slice(0, -".crate".length);
271
+ const match = /^(.+)-(\d[0-9A-Za-z.+-]*)$/.exec(stem);
272
+ if (match && match[1] && match[2]) {
273
+ return { name: match[1], version: match[2] };
274
+ }
275
+ }
276
+ return null;
277
+ }
216
278
  function parsePackageVersionFromUrl(artifactUrl) {
217
279
  const parts = artifactUrl.pathname.split("/").filter(Boolean).map((part) => decodeURIComponent(part));
218
280
  const file = parts.at(-1) ?? artifactUrl.hostname;
@@ -0,0 +1,55 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ const PREVERIFIED_FILE = "preverified.json";
4
+ export function preverifiedKey(ecosystem, name, version) {
5
+ const canonicalName = ecosystem === "pypi" ? name.toLowerCase().replace(/[-_.]+/g, "-") : name;
6
+ return `${ecosystem}:${canonicalName}@${version}`;
7
+ }
8
+ export function writePreverifiedFile(sessionDir, entries) {
9
+ writeFileSync(join(sessionDir, PREVERIFIED_FILE), `${JSON.stringify(entries)}\n`, {
10
+ encoding: "utf8",
11
+ mode: 0o600
12
+ });
13
+ }
14
+ let loaded = null;
15
+ export function loadPreverifiedMap(sessionDir) {
16
+ if (loaded?.dir === sessionDir) {
17
+ return loaded.map;
18
+ }
19
+ const path = join(sessionDir, PREVERIFIED_FILE);
20
+ if (!existsSync(path)) {
21
+ return new Map();
22
+ }
23
+ const map = new Map();
24
+ try {
25
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
26
+ if (Array.isArray(parsed)) {
27
+ for (const entry of parsed) {
28
+ if (isPreverifiedEntry(entry)) {
29
+ map.set(preverifiedKey(entry.ecosystem, entry.name, entry.version), entry);
30
+ }
31
+ }
32
+ }
33
+ }
34
+ catch {
35
+ return new Map();
36
+ }
37
+ loaded = { dir: sessionDir, map };
38
+ return map;
39
+ }
40
+ export function resetPreverifiedCache() {
41
+ loaded = null;
42
+ }
43
+ function isPreverifiedEntry(value) {
44
+ if (typeof value !== "object" || value === null) {
45
+ return false;
46
+ }
47
+ const entry = value;
48
+ return ((entry.ecosystem === "npm" || entry.ecosystem === "pypi") &&
49
+ typeof entry.name === "string" && entry.name.length > 0 &&
50
+ typeof entry.version === "string" && entry.version.length > 0 &&
51
+ (entry.action === "pass" || entry.action === "warn") &&
52
+ (entry.reason === undefined || typeof entry.reason === "string") &&
53
+ (entry.scannedSha256 === undefined || typeof entry.scannedSha256 === "string") &&
54
+ typeof entry.cooldownEvaluated === "boolean");
55
+ }