@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
package/dist/commands/setup.js
CHANGED
|
@@ -1,34 +1,40 @@
|
|
|
1
1
|
import { EXIT_UNAVAILABLE, EXIT_USAGE } from "./types.js";
|
|
2
2
|
import { LockBusyError } from "../state/index.js";
|
|
3
|
-
import { activationCommand as shellActivationCommand, applySetupPlanWithLock, buildSetupPlan, renderSetupPlan } from "../setup/plan.js";
|
|
3
|
+
import { activationCommand as shellActivationCommand, applySetupPlanWithLock, buildSetupPlan, renderSetupPlan, tildifyPath as tildify } from "../setup/plan.js";
|
|
4
|
+
import { activateShell, activationOffer } from "../setup/activate-shell.js";
|
|
4
5
|
import { buildServiceSetupPlan, configureService, renderServicePlan } from "../service/state.js";
|
|
5
6
|
import { optionalSupportGate } from "../setup/optional-support.js";
|
|
6
7
|
import { resolvePresentation } from "../presentation/mode.js";
|
|
7
8
|
import { createTheme } from "../presentation/theme.js";
|
|
8
9
|
import { closeSync, openSync, readSync } from "node:fs";
|
|
9
|
-
import { homedir } from "node:os";
|
|
10
10
|
import { authStatus } from "../auth/store.js";
|
|
11
|
+
import { resolveWebBase } from "../auth/device-login.js";
|
|
12
|
+
import { applyAgentHook, collectAgentOffers } from "../agents/registry.js";
|
|
13
|
+
import { applyGitHook, commitGuardOffer } from "../setup/git-hook.js";
|
|
14
|
+
import { markSecurityNotesShown, securityNotesShown } from "../setup-ui/gate.js";
|
|
11
15
|
export const setupCommand = {
|
|
12
16
|
name: "setup",
|
|
13
|
-
summary: "Protect
|
|
14
|
-
usage: "dg setup [--print] [--yes] [--shell <auto|zsh|bash|fish>] [--service]",
|
|
17
|
+
summary: "Protect this machine — shell installs, AI-agent installs, and a repo's commits.",
|
|
18
|
+
usage: "dg setup [--print] [--yes] [--agents] [--guard-commit] [--shell <auto|zsh|bash|fish>] [--service]",
|
|
15
19
|
flags: [
|
|
16
20
|
{ flag: "--print", summary: "Preview the exact write plan and change nothing." },
|
|
17
|
-
{ flag: "--yes", summary: "Apply
|
|
21
|
+
{ flag: "--yes", summary: "Apply the shell firewall only, without prompts (add --agents / --guard-commit to opt in the rest)." },
|
|
22
|
+
{ flag: "--agents", summary: "With --yes: also route every detected AI agent's installs through dg." },
|
|
23
|
+
{ flag: "--guard-commit", summary: "With --yes: also scan this repo's commits (per-repo)." },
|
|
18
24
|
{ flag: "--shell", value: "<auto|zsh|bash|fish>", summary: "Target shell rc to write (default: auto-detect)." },
|
|
19
25
|
{ flag: "--service", summary: "Set up service mode (Pro/Team; persistent proxy + managed CA)." }
|
|
20
26
|
],
|
|
21
|
-
examples: ["dg setup --print", "dg setup --yes", "dg setup --shell zsh --yes"],
|
|
27
|
+
examples: ["dg setup", "dg setup --print", "dg setup --yes --agents", "dg setup --shell zsh --yes"],
|
|
22
28
|
details: [
|
|
23
|
-
"In a terminal
|
|
24
|
-
"
|
|
29
|
+
"In a terminal it shows one consent screen listing everything it will protect — your shell's installs plus every detected AI agent, with the exact files it writes — and applies them on a single yes. Commits in the current repo are a separate per-repo question.",
|
|
30
|
+
"Non-interactively, --yes applies just the shell firewall; add --agents and/or --guard-commit to opt those in. --print previews. Writes only dg-owned reversible files; undo it all with dg uninstall."
|
|
25
31
|
],
|
|
26
32
|
handler: (context) => setupHandler(context.args)
|
|
27
33
|
};
|
|
28
34
|
const WINDOWS_UNSUPPORTED = "dg setup (automatic interception) does not support Windows yet.\n" +
|
|
29
35
|
"You can still protect installs on Windows: prefix the command with dg,\n" +
|
|
30
36
|
"for example `dg pip install <pkg>` or `dg npm install <pkg>`.\n";
|
|
31
|
-
function setupHandler(args) {
|
|
37
|
+
async function setupHandler(args) {
|
|
32
38
|
const parsed = parseSetupArgs(args);
|
|
33
39
|
if ("error" in parsed) {
|
|
34
40
|
return {
|
|
@@ -54,12 +60,16 @@ function setupHandler(args) {
|
|
|
54
60
|
if (parsed.printOnly) {
|
|
55
61
|
return {
|
|
56
62
|
exitCode: 0,
|
|
57
|
-
stdout: renderedPlan
|
|
63
|
+
stdout: `${renderedPlan}${printableSurfaceOffers()}`,
|
|
58
64
|
stderr: ""
|
|
59
65
|
};
|
|
60
66
|
}
|
|
61
67
|
if (!parsed.yes) {
|
|
62
68
|
if (isInteractive()) {
|
|
69
|
+
if (resolvePresentation().mode === "rich") {
|
|
70
|
+
const { runSetupWizard } = await import("../setup-ui/wizard.js");
|
|
71
|
+
return runSetupWizard(plan, { autoActivate: true });
|
|
72
|
+
}
|
|
63
73
|
return runInteractiveSetup(plan);
|
|
64
74
|
}
|
|
65
75
|
return {
|
|
@@ -81,23 +91,34 @@ function setupHandler(args) {
|
|
|
81
91
|
}
|
|
82
92
|
throw error;
|
|
83
93
|
}
|
|
94
|
+
const extra = await applyRequestedSurfaces(parsed);
|
|
95
|
+
const theme = createTheme(resolvePresentation().color);
|
|
84
96
|
return {
|
|
85
97
|
exitCode: 0,
|
|
86
|
-
stdout: `${
|
|
98
|
+
stdout: `${theme.paint("pass", "✓")} Setup complete — active in new terminals ${theme.paint("muted", `(wrote ${tildify(plan.shimDir)} and ${tildify(plan.rcPath)}; details: dg doctor)`)}\n${extra}Activate this shell now: ${theme.paint("accent", activationCommand(plan))}\n`,
|
|
87
99
|
stderr: ""
|
|
88
100
|
};
|
|
89
101
|
}
|
|
90
102
|
function isInteractive() {
|
|
91
103
|
return Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
92
104
|
}
|
|
93
|
-
function runInteractiveSetup(plan) {
|
|
105
|
+
async function runInteractiveSetup(plan) {
|
|
94
106
|
const theme = createTheme(resolvePresentation().color);
|
|
95
107
|
const accent = (text) => theme.paint("accent", text);
|
|
96
108
|
const muted = (text) => theme.paint("muted", text);
|
|
97
109
|
const err = process.stderr;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
110
|
+
const agents = collectAgentOffers();
|
|
111
|
+
const repo = commitGuardOffer();
|
|
112
|
+
if (!securityNotesShown()) {
|
|
113
|
+
err.write(renderSecurityNotesText(theme));
|
|
114
|
+
err.write(` ${muted("Press Enter to agree")} `);
|
|
115
|
+
if (readLineFromTty() !== null) {
|
|
116
|
+
markSecurityNotesShown();
|
|
117
|
+
}
|
|
118
|
+
err.write("\n");
|
|
119
|
+
}
|
|
120
|
+
err.write(renderConsentScreen(consentSurfaces(plan, agents, repo !== null), theme));
|
|
121
|
+
const answer = promptYesNoSync(` ${accent("Protect installs on this machine?")}`, theme, true);
|
|
101
122
|
if (answer === "no-tty") {
|
|
102
123
|
return {
|
|
103
124
|
exitCode: EXIT_USAGE,
|
|
@@ -121,21 +142,152 @@ function runInteractiveSetup(plan) {
|
|
|
121
142
|
}
|
|
122
143
|
throw error;
|
|
123
144
|
}
|
|
124
|
-
err.write(`\n ${theme.paint("pass", "✓
|
|
125
|
-
|
|
145
|
+
err.write(`\n ${theme.paint("pass", "✓ shell installs protected — active in new terminals")}\n`);
|
|
146
|
+
await applyAgentOffers(agents, theme, err);
|
|
147
|
+
promptCommitSurface(repo, theme, err);
|
|
148
|
+
return finishWithActivation(plan, theme, err);
|
|
149
|
+
}
|
|
150
|
+
export function consentSurfaces(plan, agents, repoOffered) {
|
|
151
|
+
const surfaces = [
|
|
152
|
+
{ label: "shell installs", detail: `writes ${tildify(plan.rcPath)}`, deferred: false }
|
|
153
|
+
];
|
|
154
|
+
for (const offer of agents) {
|
|
155
|
+
surfaces.push({ label: `${offer.label} installs`, detail: `writes ${tildify(offer.ctx.settingsPath)}`, deferred: false });
|
|
156
|
+
}
|
|
157
|
+
if (repoOffered) {
|
|
158
|
+
surfaces.push({ label: "commits in this repo", detail: "asked separately", deferred: true });
|
|
159
|
+
}
|
|
160
|
+
return surfaces;
|
|
161
|
+
}
|
|
162
|
+
export function renderConsentScreen(surfaces, theme) {
|
|
163
|
+
const accent = (text) => theme.paint("accent", text);
|
|
164
|
+
const muted = (text) => theme.paint("muted", text);
|
|
165
|
+
const width = Math.max(...surfaces.map((surface) => surface.label.length));
|
|
166
|
+
const lines = [
|
|
167
|
+
"",
|
|
168
|
+
` Scans ${accent("npm")}/${accent("pip")}/${accent("yarn")}/${muted("…")} installs automatically — no ${accent("dg")} prefix.`,
|
|
169
|
+
` ${muted("Reversible with")} ${accent("dg uninstall")}${muted(".")}`,
|
|
170
|
+
"",
|
|
171
|
+
` ${muted("Sets up:")}`
|
|
172
|
+
];
|
|
173
|
+
for (const surface of surfaces) {
|
|
174
|
+
const label = surface.label.padEnd(width);
|
|
175
|
+
lines.push(surface.deferred ? ` ${muted(label)} ${muted(surface.detail)}` : ` ${accent(label)} ${muted(surface.detail)}`);
|
|
176
|
+
}
|
|
177
|
+
lines.push("", "");
|
|
178
|
+
return lines.join("\n");
|
|
179
|
+
}
|
|
180
|
+
function finishWithActivation(plan, theme, err) {
|
|
181
|
+
const offer = activationOffer();
|
|
182
|
+
if (offer === "none") {
|
|
183
|
+
return { exitCode: 0, stdout: "", stderr: "" };
|
|
184
|
+
}
|
|
185
|
+
const accent = (text) => theme.paint("accent", text);
|
|
186
|
+
const muted = (text) => theme.paint("muted", text);
|
|
187
|
+
if (offer === "prompt" && promptYesNoSync(`\n ${accent("Activate in this terminal now?")}`, theme, true) === "yes") {
|
|
188
|
+
err.write(` ${muted("Starting a protected shell — type")} ${accent("exit")} ${muted("to return to your previous one.")}\n`);
|
|
189
|
+
return { exitCode: activateShell(), stdout: "", stderr: "" };
|
|
190
|
+
}
|
|
191
|
+
err.write(`\n ${muted("Activate this shell now:")} ${accent(activationCommand(plan))}\n`);
|
|
126
192
|
return { exitCode: 0, stdout: "", stderr: "" };
|
|
127
193
|
}
|
|
194
|
+
async function applyAgentOffers(offers, theme, err) {
|
|
195
|
+
const muted = (text) => theme.paint("muted", text);
|
|
196
|
+
for (const offer of offers) {
|
|
197
|
+
try {
|
|
198
|
+
await applyAgentHook(offer.ctx);
|
|
199
|
+
err.write(` ${theme.paint("pass", `✓ ${offer.label} installs route through dg`)} ${muted(`(${tildify(offer.ctx.settingsPath)})`)}\n`);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
err.write(` ${theme.paint("warn", `✗ ${offer.label}`)} ${muted(`— ${error instanceof Error ? error.message : "unknown error"}`)}\n`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function promptCommitSurface(repo, theme, err) {
|
|
207
|
+
if (!repo) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const accent = (text) => theme.paint("accent", text);
|
|
211
|
+
const muted = (text) => theme.paint("muted", text);
|
|
212
|
+
err.write(`\n ${muted("This is a git repo. Scan its commits before they land?")}\n`);
|
|
213
|
+
if (promptYesNoSync(` ${accent("Guard commits in this repo?")}`, theme, false) !== "yes") {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
applyGitHook(repo);
|
|
218
|
+
err.write(` ${theme.paint("pass", "✓ commits in this repo are now scanned")}\n`);
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
err.write(` ${theme.paint("warn", `✗ commits in this repo`)} ${muted(`— ${error instanceof Error ? error.message : "unknown error"}`)}\n`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function applyRequestedSurfaces(parsed) {
|
|
225
|
+
const lines = [];
|
|
226
|
+
if (parsed.agents) {
|
|
227
|
+
for (const offer of collectAgentOffers()) {
|
|
228
|
+
try {
|
|
229
|
+
await applyAgentHook(offer.ctx);
|
|
230
|
+
lines.push(`${offer.label} installs now route through dg (${tildify(offer.ctx.settingsPath)}).`);
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
lines.push(`could not hook ${offer.label}: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (parsed.guardCommit) {
|
|
238
|
+
const repo = commitGuardOffer();
|
|
239
|
+
if (repo) {
|
|
240
|
+
try {
|
|
241
|
+
applyGitHook(repo);
|
|
242
|
+
lines.push("commits in this repo are now scanned.");
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
lines.push(`could not install the commit guard: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return lines.length > 0 ? `${lines.join("\n")}\n` : "";
|
|
250
|
+
}
|
|
251
|
+
function printableSurfaceOffers() {
|
|
252
|
+
const lines = [];
|
|
253
|
+
for (const offer of collectAgentOffers()) {
|
|
254
|
+
lines.push(`would offer: route ${offer.label} installs through dg (--agents)`);
|
|
255
|
+
}
|
|
256
|
+
if (commitGuardOffer()) {
|
|
257
|
+
lines.push("would offer: scan this repo's commits (--guard-commit)");
|
|
258
|
+
}
|
|
259
|
+
return lines.length > 0 ? `\n${lines.join("\n")}\n` : "";
|
|
260
|
+
}
|
|
128
261
|
function activationCommand(plan) {
|
|
129
262
|
return shellActivationCommand(plan.shell, tildify(plan.rcPath));
|
|
130
263
|
}
|
|
131
|
-
function
|
|
132
|
-
|
|
264
|
+
function renderSecurityNotesText(theme) {
|
|
265
|
+
const muted = (text) => theme.paint("muted", text);
|
|
266
|
+
const webBase = resolveWebBase(process.env);
|
|
267
|
+
return [
|
|
268
|
+
"",
|
|
269
|
+
` ${theme.paint("accent", "Security notes")}`,
|
|
270
|
+
"",
|
|
271
|
+
" 1. dg can make mistakes.",
|
|
272
|
+
" A PASS verdict does not guarantee a package is safe. You are",
|
|
273
|
+
" responsible for what you install and should review new",
|
|
274
|
+
" dependencies.",
|
|
275
|
+
"",
|
|
276
|
+
" 2. By continuing you confirm you have read and understand the",
|
|
277
|
+
" Terms of Service and Privacy Policy.",
|
|
278
|
+
` ${muted(`${webBase}/terms`)}`,
|
|
279
|
+
` ${muted(`${webBase}/privacy`)}`,
|
|
280
|
+
"",
|
|
281
|
+
""
|
|
282
|
+
].join("\n");
|
|
283
|
+
}
|
|
284
|
+
function readLineFromTty() {
|
|
133
285
|
let tty;
|
|
134
286
|
try {
|
|
135
287
|
tty = openSync("/dev/tty", "rs");
|
|
136
288
|
}
|
|
137
289
|
catch {
|
|
138
|
-
return
|
|
290
|
+
return null;
|
|
139
291
|
}
|
|
140
292
|
try {
|
|
141
293
|
const byte = Buffer.alloc(1);
|
|
@@ -160,22 +312,31 @@ function promptYesNoSync(question, theme) {
|
|
|
160
312
|
}
|
|
161
313
|
answer += char;
|
|
162
314
|
}
|
|
163
|
-
|
|
164
|
-
return normalized === "y" || normalized === "yes" ? "yes" : "no";
|
|
315
|
+
return answer;
|
|
165
316
|
}
|
|
166
317
|
finally {
|
|
167
318
|
closeSync(tty);
|
|
168
319
|
}
|
|
169
320
|
}
|
|
170
|
-
function
|
|
171
|
-
|
|
172
|
-
|
|
321
|
+
function promptYesNoSync(question, theme, defaultYes = false) {
|
|
322
|
+
process.stderr.write(`${question} ${theme.paint("muted", defaultYes ? "[Y/n]" : "[y/N]")} `);
|
|
323
|
+
const answer = readLineFromTty();
|
|
324
|
+
if (answer === null) {
|
|
325
|
+
return "no-tty";
|
|
326
|
+
}
|
|
327
|
+
const normalized = answer.trim().toLowerCase();
|
|
328
|
+
if (normalized === "") {
|
|
329
|
+
return defaultYes ? "yes" : "no";
|
|
330
|
+
}
|
|
331
|
+
return normalized === "y" || normalized === "yes" ? "yes" : "no";
|
|
173
332
|
}
|
|
174
333
|
function parseSetupArgs(args) {
|
|
175
334
|
let printOnly = false;
|
|
176
335
|
let yes = false;
|
|
177
336
|
let shell = "auto";
|
|
178
337
|
let service = false;
|
|
338
|
+
let agents = false;
|
|
339
|
+
let guardCommit = false;
|
|
179
340
|
for (let index = 0; index < args.length; index += 1) {
|
|
180
341
|
const arg = args[index];
|
|
181
342
|
if (arg === "--print") {
|
|
@@ -187,6 +348,12 @@ function parseSetupArgs(args) {
|
|
|
187
348
|
else if (arg === "--service") {
|
|
188
349
|
service = true;
|
|
189
350
|
}
|
|
351
|
+
else if (arg === "--agents") {
|
|
352
|
+
agents = true;
|
|
353
|
+
}
|
|
354
|
+
else if (arg === "--guard-commit") {
|
|
355
|
+
guardCommit = true;
|
|
356
|
+
}
|
|
190
357
|
else if (arg === "--python-hook" || arg?.startsWith("--python-hook=")) {
|
|
191
358
|
return {
|
|
192
359
|
exitCode: EXIT_UNAVAILABLE,
|
|
@@ -251,7 +418,9 @@ function parseSetupArgs(args) {
|
|
|
251
418
|
printOnly,
|
|
252
419
|
yes,
|
|
253
420
|
shell,
|
|
254
|
-
service
|
|
421
|
+
service,
|
|
422
|
+
agents,
|
|
423
|
+
guardCommit
|
|
255
424
|
};
|
|
256
425
|
}
|
|
257
426
|
function parseShell(value) {
|
|
@@ -270,10 +439,11 @@ function serviceSetupHandler(parsed) {
|
|
|
270
439
|
};
|
|
271
440
|
}
|
|
272
441
|
if (!authStatus().authenticated) {
|
|
442
|
+
const theme = createTheme(resolvePresentation().color);
|
|
273
443
|
return {
|
|
274
444
|
exitCode: EXIT_UNAVAILABLE,
|
|
275
445
|
stdout: "",
|
|
276
|
-
stderr: "dg setup --service is a Pro or Team feature.
|
|
446
|
+
stderr: `${theme.paint("warn", "dg setup --service is a Pro or Team feature")} — ${theme.paint("accent", "dg login")} ${theme.paint("muted", "with a paid account, then re-run")} ${theme.paint("muted", "· westbayberry.com/pricing")}\n`
|
|
277
447
|
};
|
|
278
448
|
}
|
|
279
449
|
if (!parsed.yes) {
|
package/dist/commands/status.js
CHANGED
|
@@ -3,6 +3,7 @@ import { envAuthToken } from "../auth/env-token.js";
|
|
|
3
3
|
import { fetchAccountStatus } from "../auth/device-login.js";
|
|
4
4
|
import { formatUsage } from "../scan-ui/format-helpers.js";
|
|
5
5
|
import { loadUserConfig } from "../config/settings.js";
|
|
6
|
+
import { describeCooldownSettings } from "../policy/cooldown.js";
|
|
6
7
|
import { resolvePresentation } from "../presentation/mode.js";
|
|
7
8
|
import { createTheme } from "../presentation/theme.js";
|
|
8
9
|
import { currentShellActivation, doctorReport } from "../setup/plan.js";
|
|
@@ -49,14 +50,15 @@ async function buildStatusReport(io) {
|
|
|
49
50
|
const checks = doctorReport().checks;
|
|
50
51
|
const checkPassed = (name) => checks.find((check) => check.name === name)?.status === "pass";
|
|
51
52
|
const auth = safeAuthStatus();
|
|
52
|
-
const
|
|
53
|
+
const config = loadUserConfig();
|
|
53
54
|
return {
|
|
54
55
|
account: auth,
|
|
55
56
|
usage: await fetchStatusUsage(auth.connected, io),
|
|
56
57
|
protection: { shims: checkPassed("shims"), path: checkPassed("path"), configured: checkPassed("shell-rc") },
|
|
57
58
|
reloadHint: currentShellActivation(),
|
|
58
59
|
commitGuard: safeCommitGuard(),
|
|
59
|
-
policy: { mode: policy.mode, trustProjectAllowlists: policy.trustProjectAllowlists }
|
|
60
|
+
policy: { mode: config.policy.mode, trustProjectAllowlists: config.policy.trustProjectAllowlists },
|
|
61
|
+
cooldown: describeCooldownSettings(config, process.env)
|
|
60
62
|
};
|
|
61
63
|
}
|
|
62
64
|
async function fetchStatusUsage(connected, io) {
|
|
@@ -123,6 +125,7 @@ function renderStatus(report, theme) {
|
|
|
123
125
|
lines.push(` Commit guard ${guard}`);
|
|
124
126
|
}
|
|
125
127
|
lines.push(` Policy ${report.policy.mode} mode; project allowlists ${report.policy.trustProjectAllowlists ? "trusted" : "untrusted"}`);
|
|
128
|
+
lines.push(` Cooldown ${report.cooldown === "off" ? "off — new releases install immediately" : `${report.cooldown} release-age gate on new installs`}`);
|
|
126
129
|
lines.push("");
|
|
127
130
|
lines.push(`Full diagnostics: ${theme.paint("accent", "dg doctor")}`);
|
|
128
131
|
return `${lines.join("\n")}\n`;
|
package/dist/commands/types.js
CHANGED
package/dist/commands/update.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { toolInvocation } from "../util/external-tool.js";
|
|
3
3
|
import { EXIT_USAGE } from "./types.js";
|
|
4
4
|
import { dgVersion } from "./version.js";
|
|
5
5
|
const PACKAGE_NAME = "@westbayberry/dg";
|
|
6
|
+
const VERSION_PATTERN = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/u;
|
|
6
7
|
export const updateCommand = {
|
|
7
8
|
name: "update",
|
|
8
9
|
summary: "Check for dg CLI updates.",
|
|
@@ -26,7 +27,7 @@ function runUpdateCommand(args, commandName) {
|
|
|
26
27
|
if (parsed.format === "json") {
|
|
27
28
|
return {
|
|
28
29
|
exitCode: report.status === "unknown" ? 1 : 0,
|
|
29
|
-
stdout: `${JSON.stringify(report, null, 2)}\n`,
|
|
30
|
+
stdout: `${JSON.stringify({ schemaVersion: 1, ...report }, null, 2)}\n`,
|
|
30
31
|
stderr: ""
|
|
31
32
|
};
|
|
32
33
|
}
|
|
@@ -62,17 +63,22 @@ function parseUpdateArgs(args) {
|
|
|
62
63
|
};
|
|
63
64
|
}
|
|
64
65
|
export function readLatestVersion(timeoutMs = 5000) {
|
|
65
|
-
const injected = process.env.DG_UPDATE_LATEST_VERSION;
|
|
66
|
+
const injected = process.env.NODE_ENV === "test" ? process.env.DG_UPDATE_LATEST_VERSION : undefined;
|
|
66
67
|
if (injected) {
|
|
67
|
-
return injected;
|
|
68
|
+
return validVersion(injected);
|
|
68
69
|
}
|
|
69
|
-
const
|
|
70
|
+
const invocation = toolInvocation("npm", ["view", PACKAGE_NAME, "version", "--json"]);
|
|
71
|
+
if (!invocation) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const result = spawnSync(invocation.command, [...invocation.args], {
|
|
70
75
|
encoding: "utf8",
|
|
71
76
|
env: {
|
|
72
77
|
...process.env,
|
|
73
78
|
npm_config_ignore_scripts: "true"
|
|
74
79
|
},
|
|
75
|
-
timeout: timeoutMs
|
|
80
|
+
timeout: timeoutMs,
|
|
81
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments
|
|
76
82
|
});
|
|
77
83
|
if (result.status !== 0) {
|
|
78
84
|
return null;
|
|
@@ -83,12 +89,15 @@ export function readLatestVersion(timeoutMs = 5000) {
|
|
|
83
89
|
}
|
|
84
90
|
try {
|
|
85
91
|
const parsed = JSON.parse(raw);
|
|
86
|
-
return typeof parsed === "string" ? parsed : null;
|
|
92
|
+
return typeof parsed === "string" ? validVersion(parsed) : null;
|
|
87
93
|
}
|
|
88
94
|
catch {
|
|
89
|
-
return raw.replace(/^"|"$/gu, "");
|
|
95
|
+
return validVersion(raw.replace(/^"|"$/gu, ""));
|
|
90
96
|
}
|
|
91
97
|
}
|
|
98
|
+
function validVersion(value) {
|
|
99
|
+
return VERSION_PATTERN.test(value) ? value : null;
|
|
100
|
+
}
|
|
92
101
|
function buildUpdateReport(latestVersion) {
|
|
93
102
|
if (!latestVersion) {
|
|
94
103
|
return {
|
package/dist/commands/verify.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EXIT_UNAVAILABLE,
|
|
1
|
+
import { EXIT_ANALYSIS_INCOMPLETE, EXIT_UNAVAILABLE, EXIT_USAGE_VERDICT } from "./types.js";
|
|
2
2
|
import { writeFileSync } from "node:fs";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { resolve } from "node:path";
|
|
@@ -22,14 +22,14 @@ export const verifyCommand = {
|
|
|
22
22
|
],
|
|
23
23
|
examples: ["dg verify npm:react", "dg verify pypi:requests@2.31.0", "dg verify ./pkg.tgz --verbose", "dg verify package-lock.json --json"],
|
|
24
24
|
details: [
|
|
25
|
-
"dg verify npm:react (or pypi:requests, with an optional @version — defaults to latest) runs a real scanner check on a published package before you install it.
|
|
25
|
+
"dg verify npm:react (or pypi:requests, with an optional @version — defaults to latest) runs a real scanner check on a published package before you install it. Signed-out runs show the verdict and top reasons; dg login unlocks full findings, license info, --json, and --output.",
|
|
26
26
|
"Local paths, workspaces, tgz/zip/wheel artifacts, and lockfiles are verified offline as advisory preflight (free); proxy enforcement remains authoritative for network artifact fetches."
|
|
27
27
|
],
|
|
28
28
|
handler: (context) => {
|
|
29
29
|
const parsed = parseVerifyArgs(context.args);
|
|
30
30
|
if ("error" in parsed) {
|
|
31
31
|
return {
|
|
32
|
-
exitCode:
|
|
32
|
+
exitCode: EXIT_USAGE_VERDICT,
|
|
33
33
|
stdout: "",
|
|
34
34
|
stderr: `dg verify: ${parsed.error}. Usage: dg verify <spec|path|lockfile> [--verbose] [--json|--sarif] [--output <path>]\n`
|
|
35
35
|
};
|
|
@@ -52,7 +52,7 @@ export const verifyCommand = {
|
|
|
52
52
|
}
|
|
53
53
|
catch (error) {
|
|
54
54
|
return {
|
|
55
|
-
exitCode:
|
|
55
|
+
exitCode: EXIT_ANALYSIS_INCOMPLETE,
|
|
56
56
|
stdout: "",
|
|
57
57
|
stderr: `dg verify could not write ${parsed.outputPath}: ${error instanceof Error ? error.message : "unknown write error"}\n`
|
|
58
58
|
};
|
|
@@ -147,5 +147,8 @@ function exitCodeForReport(report) {
|
|
|
147
147
|
if (report.status === "block") {
|
|
148
148
|
return 2;
|
|
149
149
|
}
|
|
150
|
-
|
|
150
|
+
if (report.status === "error") {
|
|
151
|
+
return EXIT_ANALYSIS_INCOMPLETE;
|
|
152
|
+
}
|
|
153
|
+
return report.status === "warn" ? 1 : 0;
|
|
151
154
|
}
|