@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.
- package/README.md +216 -226
- package/dist/agents/claude-code.js +113 -0
- package/dist/agents/codex.js +65 -0
- package/dist/agents/copilot-cli.js +115 -0
- package/dist/agents/cursor.js +113 -0
- package/dist/agents/gemini.js +107 -0
- package/dist/agents/persistence.js +285 -0
- package/dist/agents/registry.js +127 -0
- package/dist/agents/types.js +1 -0
- package/dist/agents/windsurf.js +93 -0
- package/dist/api/analyze.js +6 -4
- package/dist/audit/detectors.js +0 -11
- package/dist/audit/events.js +5 -21
- package/dist/audit-ui/AuditApp.js +2 -0
- package/dist/audit-ui/components/AuditResultsView.js +55 -92
- package/dist/audit-ui/export.js +0 -4
- package/dist/audit-ui/format.js +0 -3
- package/dist/audit-ui/launch.js +10 -1
- package/dist/auth/device-login.js +4 -5
- package/dist/auth/login-app.js +7 -7
- package/dist/auth/store.js +8 -3
- package/dist/bin/dg.js +59 -52
- package/dist/commands/agents.js +231 -0
- package/dist/commands/audit.js +22 -3
- package/dist/commands/config.js +26 -10
- package/dist/commands/cooldown.js +389 -0
- package/dist/commands/decisions.js +169 -0
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/help.js +1 -1
- package/dist/commands/licenses.js +10 -22
- package/dist/commands/logout.js +4 -11
- package/dist/commands/router.js +8 -4
- package/dist/commands/sbom.js +206 -0
- package/dist/commands/scan.js +3 -2
- package/dist/commands/service.js +32 -13
- package/dist/commands/setup.js +197 -27
- package/dist/commands/status.js +5 -2
- package/dist/commands/types.js +1 -0
- package/dist/commands/update.js +17 -8
- package/dist/commands/verify.js +8 -5
- package/dist/config/settings.js +154 -65
- package/dist/decisions/apply.js +128 -0
- package/dist/decisions/remember-prompt.js +92 -0
- package/dist/export-ui/ExportDialog.js +198 -0
- package/dist/install-ui/LiveInstall.js +2 -2
- package/dist/install-ui/block-render.js +21 -4
- package/dist/install-ui/prep-spinner.js +32 -0
- package/dist/install-ui/prompt.js +14 -0
- package/dist/launcher/agent-check.js +466 -0
- package/dist/launcher/agent-hook-exec.js +70 -0
- package/dist/launcher/agent-hook-io.js +31 -0
- package/dist/launcher/cargo-cache.js +40 -0
- package/dist/launcher/classify.js +17 -6
- package/dist/launcher/env.js +71 -24
- package/dist/launcher/install-preflight.js +167 -17
- package/dist/launcher/live-install.js +25 -5
- package/dist/launcher/output-redaction.js +7 -4
- package/dist/launcher/preflight-prompt.js +43 -3
- package/dist/launcher/run.js +100 -86
- package/dist/launcher/spawn-invocation.js +21 -0
- package/dist/policy/cooldown.js +117 -0
- package/dist/policy/evaluate.js +5 -21
- package/dist/policy/pypi-name.js +17 -0
- package/dist/presentation/mode.js +3 -2
- package/dist/presentation/package-page.js +9 -0
- package/dist/presentation/provenance.js +23 -0
- package/dist/presentation/theme.js +7 -7
- package/dist/project/dgfile.js +446 -0
- package/dist/proxy/auth.js +42 -0
- package/dist/proxy/ca.js +29 -9
- package/dist/proxy/cooldown-exemptions-file.js +33 -0
- package/dist/proxy/enforcement.js +57 -17
- package/dist/proxy/metadata-map.js +66 -4
- package/dist/proxy/preverified.js +55 -0
- package/dist/proxy/server.js +473 -45
- package/dist/proxy/worker.js +16 -1
- package/dist/publish-set/collect.js +1 -4
- package/dist/publish-set/npm.js +8 -5
- package/dist/publish-set/pack.js +9 -3
- package/dist/runtime/cli.js +0 -4
- package/dist/runtime/fatal.js +31 -0
- package/dist/runtime/first-run.js +12 -11
- package/dist/runtime/node-version.js +43 -6
- package/dist/runtime/nudges.js +35 -2
- package/dist/sbom/cyclonedx.js +211 -0
- package/dist/sbom-ui/SbomApp.js +158 -0
- package/dist/sbom-ui/components/SbomHeader.js +32 -0
- package/dist/sbom-ui/components/SbomList.js +52 -0
- package/dist/sbom-ui/inventory.js +128 -0
- package/dist/sbom-ui/launch.js +51 -0
- package/dist/sbom-ui/run.js +55 -0
- package/dist/sbom-ui/store.js +26 -0
- package/dist/scan/collect.js +10 -6
- package/dist/scan/command.js +51 -17
- package/dist/scan/discovery.js +11 -2
- package/dist/scan/render.js +63 -8
- package/dist/scan/scanner-report.js +42 -9
- package/dist/scan/staged.js +71 -11
- package/dist/scan-ui/LegacyApp.js +12 -16
- package/dist/scan-ui/alt-screen.js +5 -8
- package/dist/scan-ui/components/InteractiveResultsView.js +193 -123
- package/dist/scan-ui/components/ProgressBar.js +3 -14
- package/dist/scan-ui/components/ProjectSelector.js +1 -1
- package/dist/scan-ui/components/ScoreHeader.js +2 -3
- package/dist/scan-ui/components/SetupBanner.js +0 -6
- package/dist/scan-ui/format-helpers.js +61 -5
- package/dist/scan-ui/hooks/useResizeRepaint.js +25 -0
- package/dist/scan-ui/hooks/useScan.js +46 -4
- package/dist/scan-ui/launch.js +7 -4
- package/dist/scan-ui/shims.js +14 -4
- package/dist/scripts/detect.js +158 -0
- package/dist/scripts/gate.js +170 -0
- package/dist/service/state.js +27 -8
- package/dist/service/trust-refresh.js +92 -0
- package/dist/service/worker.js +23 -1
- package/dist/setup/activate-shell.js +28 -0
- package/dist/setup/git-hook.js +49 -4
- package/dist/setup/plan.js +98 -29
- package/dist/setup-ui/gate.js +39 -0
- package/dist/setup-ui/offer.js +42 -0
- package/dist/setup-ui/selector.js +27 -0
- package/dist/setup-ui/tasks.js +56 -0
- package/dist/setup-ui/wizard.js +225 -0
- package/dist/state/cooldown-held.js +66 -0
- package/dist/state/index.js +1 -0
- package/dist/state/locks.js +4 -2
- package/dist/state/store.js +2 -1
- package/dist/util/external-tool.js +25 -0
- package/dist/util/git.js +10 -3
- package/dist/util/json-file.js +24 -0
- package/dist/util/report-writer.js +57 -0
- package/dist/util/tty-prompt.js +13 -6
- package/dist/verify/local.js +240 -42
- package/dist/verify/package-check.js +86 -18
- package/dist/verify/preflight.js +242 -49
- package/dist/verify/render.js +15 -1
- package/npm-shrinkwrap.json +2383 -0
- package/package.json +14 -8
- package/NOTICE +0 -5
- package/dist/commands/completion.js +0 -116
- package/dist/commands/explain.js +0 -232
- package/dist/commands/unavailable.js +0 -11
- 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,
|
|
7
|
+
return extractPypiSimpleIdentities(metadataUrl, decoded);
|
|
6
8
|
}
|
|
7
|
-
if (!looksLikeJson(
|
|
9
|
+
if (!looksLikeJson(decoded.headers["content-type"])) {
|
|
8
10
|
return [];
|
|
9
11
|
}
|
|
10
|
-
const parsed = parseJson(
|
|
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
|
-
:
|
|
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
|
+
}
|