@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.
- package/README.md +216 -226
- package/dist/agents/claude-code.js +123 -0
- package/dist/agents/codex.js +65 -0
- package/dist/agents/copilot-cli.js +115 -0
- package/dist/agents/cursor.js +118 -0
- package/dist/agents/gate-posture.js +21 -0
- package/dist/agents/gemini.js +107 -0
- package/dist/agents/persistence.js +351 -0
- package/dist/agents/registry.js +128 -0
- package/dist/agents/routing.js +118 -0
- package/dist/agents/types.js +1 -0
- package/dist/agents/windsurf.js +93 -0
- package/dist/api/analyze.js +1 -1
- package/dist/audit/detectors.js +0 -11
- package/dist/audit/events.js +5 -21
- package/dist/audit/rules.js +6 -2
- 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 +17 -5
- package/dist/bin/dg.js +59 -52
- package/dist/commands/agents.js +275 -0
- package/dist/commands/audit.js +28 -3
- package/dist/commands/config.js +15 -7
- package/dist/commands/cooldown.js +389 -0
- package/dist/commands/decisions.js +23 -9
- 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 +6 -4
- package/dist/commands/sbom.js +206 -0
- package/dist/commands/scan.js +1 -1
- package/dist/commands/service.js +32 -13
- package/dist/commands/setup.js +196 -27
- package/dist/commands/types.js +1 -0
- package/dist/commands/uninstall.js +2 -1
- package/dist/commands/update.js +17 -8
- package/dist/commands/verify.js +8 -5
- package/dist/config/settings.js +49 -48
- package/dist/decisions/remember-prompt.js +5 -10
- package/dist/export-ui/ExportDialog.js +198 -0
- package/dist/install-ui/LiveInstall.js +5 -4
- package/dist/install-ui/block-render.js +18 -14
- package/dist/install-ui/prep-spinner.js +32 -0
- package/dist/launcher/agent-check.js +896 -0
- package/dist/launcher/agent-hook-exec.js +70 -0
- package/dist/launcher/agent-hook-io.js +39 -0
- package/dist/launcher/cargo-cache.js +40 -0
- package/dist/launcher/classify.js +44 -7
- package/dist/launcher/env.js +105 -26
- package/dist/launcher/install-preflight.js +64 -18
- package/dist/launcher/live-install.js +29 -8
- package/dist/launcher/manifest-screen.js +171 -0
- package/dist/launcher/output-redaction.js +10 -4
- package/dist/launcher/preflight-prompt.js +18 -4
- package/dist/launcher/run.js +178 -103
- package/dist/launcher/spawn-invocation.js +21 -0
- package/dist/policy/cooldown.js +18 -5
- package/dist/policy/evaluate.js +5 -6
- 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/theme.js +7 -7
- package/dist/project/dgfile.js +146 -6
- package/dist/project/override-trust.js +0 -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 +55 -16
- package/dist/proxy/metadata-map.js +66 -12
- package/dist/proxy/preverified.js +55 -0
- package/dist/proxy/server.js +568 -53
- 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 +14 -12
- 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 +132 -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 +17 -10
- package/dist/scan/discovery.js +11 -2
- package/dist/scan/render.js +28 -4
- package/dist/scan/scanner-report.js +15 -9
- package/dist/scan/staged.js +33 -9
- package/dist/scan-ui/LegacyApp.js +9 -13
- package/dist/scan-ui/alt-screen.js +5 -8
- package/dist/scan-ui/components/InteractiveResultsView.js +133 -120
- 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 +19 -2
- package/dist/scan-ui/launch.js +7 -4
- package/dist/scan-ui/shims.js +11 -4
- package/dist/scripts/detect.js +7 -2
- package/dist/scripts/gate.js +1 -1
- package/dist/security/sanitize.js +8 -4
- package/dist/service/state.js +28 -9
- package/dist/service/trust-refresh.js +92 -0
- package/dist/service/trust-store.js +5 -9
- 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 +227 -49
- package/dist/setup/uninstall-standalone.js +25 -0
- 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 +233 -0
- package/dist/standalone/uninstall.mjs +2123 -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/report-writer.js +57 -0
- package/dist/verify/local.js +242 -44
- package/dist/verify/package-check.js +87 -29
- package/dist/verify/preflight.js +242 -49
- package/dist/verify/render.js +15 -1
- package/npm-shrinkwrap.json +2383 -0
- package/package.json +7 -8
- package/NOTICE +0 -5
- package/dist/commands/completion.js +0 -117
- package/dist/commands/explain.js +0 -236
- package/dist/commands/unavailable.js +0 -11
- package/dist/scripts/rebuild.js +0 -28
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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", "⚠")}
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
209
|
+
return { exitCode: exitCodeFor("analysis_incomplete"), stdout: "", stderr: `dg verify: ${message}\n` };
|
|
156
210
|
}
|
|
157
211
|
if (!result) {
|
|
158
|
-
return { exitCode:
|
|
212
|
+
return { exitCode: exitCodeFor("analysis_incomplete"), stdout: "", stderr: `dg verify: scanner returned no result for ${parsed.name}\n` };
|
|
159
213
|
}
|
|
160
|
-
|
|
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
|
-
:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
}
|