@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
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { AGENT_IDS, AGENTS, agentLabel, collectAgentOffers, getAgent, isAgentId, resolveAgentHookContext, } from "../agents/registry.js";
|
|
3
|
+
import { AgentHookError } from "../agents/persistence.js";
|
|
4
|
+
import { applyAgentRouting, removeAgentRouting, resolveServiceRoutingEnv, routingInstalled } from "../agents/routing.js";
|
|
5
|
+
import { GATE_ENABLE_HINT, GATE_OFF_COVERAGE_GAPS, readNetworkGatePosture } from "../agents/gate-posture.js";
|
|
6
|
+
import { resolvePresentation } from "../presentation/mode.js";
|
|
7
|
+
import { createTheme } from "../presentation/theme.js";
|
|
8
|
+
import { tildifyPath as tildify } from "../setup/plan.js";
|
|
9
|
+
import { EXIT_USAGE } from "./types.js";
|
|
10
|
+
const VERBS = ["on", "off", "--check", "--print"];
|
|
11
|
+
const LEGACY_VERB_MAP = {
|
|
12
|
+
install: "on",
|
|
13
|
+
on: "on",
|
|
14
|
+
off: "off",
|
|
15
|
+
remove: "off",
|
|
16
|
+
uninstall: "off",
|
|
17
|
+
"--check": "--check",
|
|
18
|
+
"--print": "--print",
|
|
19
|
+
};
|
|
20
|
+
function parseAgentsArgs(args) {
|
|
21
|
+
if (args.length === 0) {
|
|
22
|
+
return { verb: "status", agents: [], recordFixture: false };
|
|
23
|
+
}
|
|
24
|
+
let rest = [...args];
|
|
25
|
+
let verb = null;
|
|
26
|
+
const first = rest[0] ?? "";
|
|
27
|
+
if (isAgentId(first)) {
|
|
28
|
+
const legacy = rest[1] ?? "install";
|
|
29
|
+
const mapped = LEGACY_VERB_MAP[legacy];
|
|
30
|
+
if (!mapped || rest.length > 3 || (rest.length === 3 && (rest[2] !== "--record-fixture" || mapped !== "on"))) {
|
|
31
|
+
return { error: `unknown subcommand '${rest[1] ?? ""}'. Use: dg agents [on|off|--check|--print] [agent]` };
|
|
32
|
+
}
|
|
33
|
+
return { verb: mapped, agents: [first], recordFixture: rest[2] === "--record-fixture" };
|
|
34
|
+
}
|
|
35
|
+
if (VERBS.includes(first)) {
|
|
36
|
+
verb = first;
|
|
37
|
+
rest = rest.slice(1);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
return { error: `unknown subcommand '${first}'. Use: dg agents [on|off|--check|--print] [agent]` };
|
|
41
|
+
}
|
|
42
|
+
const agents = [];
|
|
43
|
+
let recordFixture = false;
|
|
44
|
+
for (const arg of rest) {
|
|
45
|
+
if (arg === "--record-fixture") {
|
|
46
|
+
recordFixture = true;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (!isAgentId(arg)) {
|
|
50
|
+
return { error: `unknown agent '${arg}'. Agents: ${AGENT_IDS.join(", ")}` };
|
|
51
|
+
}
|
|
52
|
+
agents.push(arg);
|
|
53
|
+
}
|
|
54
|
+
if (recordFixture && verb !== "on") {
|
|
55
|
+
return { error: "--record-fixture only applies to 'dg agents on'" };
|
|
56
|
+
}
|
|
57
|
+
return { verb, agents, recordFixture };
|
|
58
|
+
}
|
|
59
|
+
function agentRow(agent, home, env) {
|
|
60
|
+
const integration = AGENTS[agent];
|
|
61
|
+
if (!integration.detect(home)) {
|
|
62
|
+
return { agent, state: "not found", detail: "" };
|
|
63
|
+
}
|
|
64
|
+
const probe = integration.probeHookSupport(home);
|
|
65
|
+
if (!probe.supported) {
|
|
66
|
+
return { agent, state: "unsupported", detail: probe.detail };
|
|
67
|
+
}
|
|
68
|
+
const ctx = resolveAgentHookContext(agent, { env, home });
|
|
69
|
+
const hooked = integration.verify(ctx).find((check) => check.name === integration.isInstalledCheckName)?.ok ?? false;
|
|
70
|
+
if (hooked) {
|
|
71
|
+
return { agent, state: "protected", detail: tildify(ctx.settingsPath, home) };
|
|
72
|
+
}
|
|
73
|
+
return { agent, state: "detected", detail: `dg agents on ${agent}` };
|
|
74
|
+
}
|
|
75
|
+
function renderStatusCard(rows, theme) {
|
|
76
|
+
const accent = (text) => theme.paint("accent", text);
|
|
77
|
+
const muted = (text) => theme.paint("muted", text);
|
|
78
|
+
const glyph = (state) => {
|
|
79
|
+
if (state === "protected") {
|
|
80
|
+
return theme.paint("pass", "✓");
|
|
81
|
+
}
|
|
82
|
+
if (state === "detected") {
|
|
83
|
+
return accent("·");
|
|
84
|
+
}
|
|
85
|
+
if (state === "unsupported") {
|
|
86
|
+
return theme.paint("warn", "✗");
|
|
87
|
+
}
|
|
88
|
+
return muted("–");
|
|
89
|
+
};
|
|
90
|
+
const labelWidth = Math.max(...rows.map((row) => agentLabel(row.agent).length));
|
|
91
|
+
const stateWidth = Math.max(...rows.map((row) => row.state.length));
|
|
92
|
+
const lines = [
|
|
93
|
+
"",
|
|
94
|
+
` ${accent("AI agents")} ${muted("— installs route through dg before fetching")}`,
|
|
95
|
+
""
|
|
96
|
+
];
|
|
97
|
+
for (const row of rows) {
|
|
98
|
+
const label = agentLabel(row.agent).padEnd(labelWidth);
|
|
99
|
+
const state = row.state === "protected" ? theme.paint("pass", row.state.padEnd(stateWidth)) : muted(row.state.padEnd(stateWidth));
|
|
100
|
+
const detail = row.state === "detected" ? accent(row.detail) : muted(row.detail);
|
|
101
|
+
lines.push(` ${glyph(row.state)} ${label} ${state} ${detail}`.trimEnd());
|
|
102
|
+
}
|
|
103
|
+
lines.push("", ` ${muted("protect all:")} ${accent("dg agents on")} ${muted("·")} ${muted("remove:")} ${accent("dg agents off")}`, "");
|
|
104
|
+
return lines.join("\n");
|
|
105
|
+
}
|
|
106
|
+
function gatePostureLines(env, theme) {
|
|
107
|
+
const accent = (text) => theme.paint("accent", text);
|
|
108
|
+
const muted = (text) => theme.paint("muted", text);
|
|
109
|
+
const posture = readNetworkGatePosture(env);
|
|
110
|
+
if (posture.live) {
|
|
111
|
+
return [` ${theme.paint("pass", "✓ network gate live")} ${muted(`at ${posture.proxyUrl} — installs are screened at fetch by artifact hash, however the command is written`)}`];
|
|
112
|
+
}
|
|
113
|
+
const lines = [
|
|
114
|
+
` ${theme.paint("warn", "⚠ network gate OFF — static pre-screen only")}`,
|
|
115
|
+
` ${muted("these are NOT screened until the gate is on:")}`,
|
|
116
|
+
];
|
|
117
|
+
for (const gap of GATE_OFF_COVERAGE_GAPS) {
|
|
118
|
+
lines.push(` ${muted(`· ${gap}`)}`);
|
|
119
|
+
}
|
|
120
|
+
lines.push(` ${muted("enable full fetch-time screening:")} ${accent(GATE_ENABLE_HINT)}`);
|
|
121
|
+
return lines;
|
|
122
|
+
}
|
|
123
|
+
async function applyAgents(targets, env, home, theme, recordFixture) {
|
|
124
|
+
const accent = (text) => theme.paint("accent", text);
|
|
125
|
+
const muted = (text) => theme.paint("muted", text);
|
|
126
|
+
const lines = [];
|
|
127
|
+
let failures = 0;
|
|
128
|
+
for (const agent of targets) {
|
|
129
|
+
const integration = AGENTS[agent];
|
|
130
|
+
const probe = integration.probeHookSupport(home);
|
|
131
|
+
if (!probe.supported) {
|
|
132
|
+
failures += 1;
|
|
133
|
+
lines.push(` ${theme.paint("warn", `✗ ${integration.label}`)} ${muted(`— ${probe.detail}. The dg PATH shim still covers installs typed in its terminal.`)}`);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const ctx = resolveAgentHookContext(agent, { env, home });
|
|
137
|
+
const dgCommand = recordFixture ? `${ctx.dgCommand} --record-fixture` : ctx.dgCommand;
|
|
138
|
+
try {
|
|
139
|
+
await integration.apply({ ...ctx, dgCommand });
|
|
140
|
+
lines.push(` ${theme.paint("pass", `✓ ${integration.label} installs route through dg`)} ${muted(`(${tildify(ctx.settingsPath, home)})`)}`);
|
|
141
|
+
// When the persistent proxy is live, also route the agent's real fetches
|
|
142
|
+
// through it so a wrapped/dynamic install the static hook can't decode is
|
|
143
|
+
// still screened at fetch time.
|
|
144
|
+
const routing = resolveServiceRoutingEnv(env);
|
|
145
|
+
if (!("error" in routing)) {
|
|
146
|
+
const r = applyAgentRouting(agent, routing.env, home, env);
|
|
147
|
+
lines.push(r.applied
|
|
148
|
+
? ` ${muted(" ↳ fetches also routed through the live dg proxy (fetch-time screening)")}`
|
|
149
|
+
: ` ${muted(` ↳ routing skipped: ${r.detail}`)}`);
|
|
150
|
+
}
|
|
151
|
+
if (integration.maturity === "unverified") {
|
|
152
|
+
lines.push(` ${muted(` hook installed from ${integration.label}'s documented schema; payload format not yet verified against a live ${integration.label} run`)}`);
|
|
153
|
+
}
|
|
154
|
+
if (integration.postInstallNote) {
|
|
155
|
+
lines.push(` ${muted(` ${integration.postInstallNote}`)}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
failures += 1;
|
|
160
|
+
const message = error instanceof AgentHookError ? error.message : error instanceof Error ? error.message : "agent hook error";
|
|
161
|
+
lines.push(` ${theme.paint("warn", `✗ ${integration.label}`)} ${muted(`— ${message}`)}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (lines.length === 0) {
|
|
165
|
+
return { exitCode: 0, stdout: ` ${muted("No unprotected agents detected.")} ${muted("See")} ${accent("dg agents")}\n`, stderr: "" };
|
|
166
|
+
}
|
|
167
|
+
lines.push("", ...gatePostureLines(env, theme), "", ` ${muted("Reverse:")} ${accent("dg agents off")}`);
|
|
168
|
+
return { exitCode: failures > 0 && failures === targets.length ? 1 : 0, stdout: `${lines.join("\n")}\n`, stderr: "" };
|
|
169
|
+
}
|
|
170
|
+
async function removeAgents(targets, env, home, theme) {
|
|
171
|
+
const muted = (text) => theme.paint("muted", text);
|
|
172
|
+
const lines = [];
|
|
173
|
+
for (const agent of targets) {
|
|
174
|
+
const integration = AGENTS[agent];
|
|
175
|
+
const ctx = resolveAgentHookContext(agent, { env, home });
|
|
176
|
+
try {
|
|
177
|
+
const result = await integration.remove(ctx);
|
|
178
|
+
removeAgentRouting(agent, home, env);
|
|
179
|
+
lines.push(result.removed
|
|
180
|
+
? ` ${theme.paint("pass", `✓ ${integration.label} hook removed`)} ${muted(`(${tildify(ctx.settingsPath, home)})`)}`
|
|
181
|
+
: ` ${muted(`· ${integration.label} — no dg hook was installed`)}`);
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
const message = error instanceof AgentHookError ? error.message : error instanceof Error ? error.message : "agent hook error";
|
|
185
|
+
lines.push(` ${theme.paint("warn", `✗ ${integration.label}`)} ${muted(`— ${message}`)}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (lines.length === 0) {
|
|
189
|
+
lines.push(` ${muted("No agent hooks to remove.")}`);
|
|
190
|
+
}
|
|
191
|
+
return { exitCode: 0, stdout: `${lines.join("\n")}\n`, stderr: "" };
|
|
192
|
+
}
|
|
193
|
+
function checkAgents(targets, env, home) {
|
|
194
|
+
const lines = [];
|
|
195
|
+
let allOk = true;
|
|
196
|
+
for (const agent of targets) {
|
|
197
|
+
const ctx = resolveAgentHookContext(agent, { env, home });
|
|
198
|
+
const checks = getAgent(agent).verify(ctx);
|
|
199
|
+
lines.push(`${agentLabel(agent)}:`);
|
|
200
|
+
for (const check of checks) {
|
|
201
|
+
lines.push(` ${check.ok ? "✓" : "✗"} ${check.name}: ${check.detail}`);
|
|
202
|
+
allOk = allOk && check.ok;
|
|
203
|
+
}
|
|
204
|
+
const routed = routingInstalled(agent, home, env);
|
|
205
|
+
lines.push(` ${routed ? "✓" : "·"} fetch-time routing: ${routed ? "installs routed through the dg proxy" : "not routed (start dg service, then re-run dg agents on)"}`);
|
|
206
|
+
}
|
|
207
|
+
const posture = readNetworkGatePosture(env);
|
|
208
|
+
lines.push("", "network gate:");
|
|
209
|
+
if (posture.live) {
|
|
210
|
+
lines.push(` ✓ live at ${posture.proxyUrl} — installs screened at fetch by artifact hash, however the command is written`);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
lines.push(" ⚠ OFF — static pre-screen only. NOT screened until the gate is on:");
|
|
214
|
+
for (const gap of GATE_OFF_COVERAGE_GAPS) {
|
|
215
|
+
lines.push(` · ${gap}`);
|
|
216
|
+
}
|
|
217
|
+
lines.push(` enable: ${GATE_ENABLE_HINT}`);
|
|
218
|
+
}
|
|
219
|
+
return { exitCode: allOk ? 0 : 1, stdout: `${lines.join("\n")}\n`, stderr: "" };
|
|
220
|
+
}
|
|
221
|
+
function printAgents(targets, env, home) {
|
|
222
|
+
const lines = [];
|
|
223
|
+
for (const agent of targets) {
|
|
224
|
+
const integration = AGENTS[agent];
|
|
225
|
+
const ctx = resolveAgentHookContext(agent, { env, home });
|
|
226
|
+
const probe = integration.probeHookSupport(home);
|
|
227
|
+
lines.push(`dg agents on ${agent} would write a dg hook to:`, ` ${ctx.settingsPath}`, "running:", ` ${ctx.dgCommand}`, `probe: ${probe.supported ? "supported" : "unsupported"} — ${probe.detail}`, `Reverse with: dg agents off ${agent} (or dg uninstall)`);
|
|
228
|
+
}
|
|
229
|
+
return { exitCode: 0, stdout: `${lines.join("\n")}\n`, stderr: "" };
|
|
230
|
+
}
|
|
231
|
+
export async function runAgentsCommand(args, env = process.env, home) {
|
|
232
|
+
const parsed = parseAgentsArgs(args);
|
|
233
|
+
if ("error" in parsed) {
|
|
234
|
+
return { exitCode: EXIT_USAGE, stdout: "", stderr: `dg agents: ${parsed.error}\n` };
|
|
235
|
+
}
|
|
236
|
+
const resolvedHome = home ?? env.HOME ?? homedir();
|
|
237
|
+
const theme = createTheme(resolvePresentation().color);
|
|
238
|
+
if (parsed.verb === "status") {
|
|
239
|
+
const rows = AGENT_IDS.map((agent) => agentRow(agent, resolvedHome, env));
|
|
240
|
+
return { exitCode: 0, stdout: `${renderStatusCard(rows, theme)}\n`, stderr: "" };
|
|
241
|
+
}
|
|
242
|
+
if (parsed.verb === "on") {
|
|
243
|
+
const targets = parsed.agents.length > 0 ? parsed.agents : collectAgentOffers({ env, home: resolvedHome }).map((offer) => offer.agent);
|
|
244
|
+
return applyAgents(targets, env, resolvedHome, theme, parsed.recordFixture);
|
|
245
|
+
}
|
|
246
|
+
if (parsed.verb === "off") {
|
|
247
|
+
const targets = parsed.agents.length > 0
|
|
248
|
+
? parsed.agents
|
|
249
|
+
: AGENT_IDS.filter((agent) => agentRow(agent, resolvedHome, env).state === "protected");
|
|
250
|
+
return removeAgents(targets, env, resolvedHome, theme);
|
|
251
|
+
}
|
|
252
|
+
const targets = parsed.agents.length > 0
|
|
253
|
+
? parsed.agents
|
|
254
|
+
: AGENT_IDS.filter((agent) => AGENTS[agent].detect(resolvedHome));
|
|
255
|
+
if (targets.length === 0) {
|
|
256
|
+
return { exitCode: 1, stdout: "", stderr: "dg agents: no AI agents detected on this machine.\n" };
|
|
257
|
+
}
|
|
258
|
+
return parsed.verb === "--check" ? checkAgents(targets, env, resolvedHome) : printAgents(targets, env, resolvedHome);
|
|
259
|
+
}
|
|
260
|
+
async function handle({ args }) {
|
|
261
|
+
return runAgentsCommand(args);
|
|
262
|
+
}
|
|
263
|
+
export const agentsCommand = {
|
|
264
|
+
name: "agents",
|
|
265
|
+
aliases: ["hook"],
|
|
266
|
+
summary: "Route AI coding agents' package installs through dg's firewall",
|
|
267
|
+
usage: "dg agents [on|off|--check|--print] [agent ...]",
|
|
268
|
+
details: [
|
|
269
|
+
"Bare 'dg agents' shows every supported agent and whether its installs are protected.",
|
|
270
|
+
"'dg agents on' installs a native pre-command hook into every detected agent (or name one, e.g. 'dg agents on claude-code') so the shell installs it runs are checked by dg and blocked before a malicious package is fetched. Reversible via 'dg agents off' or 'dg uninstall'.",
|
|
271
|
+
],
|
|
272
|
+
args: [{ name: "agent", summary: AGENT_IDS.join(" | ") }],
|
|
273
|
+
examples: ["dg agents", "dg agents on", "dg agents on claude-code", "dg agents --check", "dg agents off"],
|
|
274
|
+
handler: handle,
|
|
275
|
+
};
|
package/dist/commands/audit.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, readFileSync, statSync
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, resolve } from "node:path";
|
|
3
|
+
import { writeReportAtomic } from "../util/report-writer.js";
|
|
3
4
|
import { EXIT_TOOL_ERROR, EXIT_USAGE_VERDICT } from "./types.js";
|
|
4
5
|
import { resolvePresentation } from "../presentation/mode.js";
|
|
5
6
|
import { createTheme } from "../presentation/theme.js";
|
|
@@ -34,6 +35,15 @@ export const auditCommand = {
|
|
|
34
35
|
],
|
|
35
36
|
handler: (context) => runAuditCommand(context.args)
|
|
36
37
|
};
|
|
38
|
+
function privatePackageResult(scope) {
|
|
39
|
+
const theme = createTheme(resolvePresentation().color);
|
|
40
|
+
const name = typeof scope.packageJson?.name === "string" ? scope.packageJson.name : basename(scope.root);
|
|
41
|
+
return {
|
|
42
|
+
exitCode: 0,
|
|
43
|
+
stdout: `${theme.paint("pass", "✓")} ${name} is private — npm refuses to publish it, so there is nothing to audit ${theme.paint("muted", "(remove \"private\": true to audit it for publish)")}\n`,
|
|
44
|
+
stderr: ""
|
|
45
|
+
};
|
|
46
|
+
}
|
|
37
47
|
function gather(args) {
|
|
38
48
|
const parsed = parseAuditArgs(args);
|
|
39
49
|
if ("error" in parsed) {
|
|
@@ -43,6 +53,9 @@ function gather(args) {
|
|
|
43
53
|
if ("error" in scope) {
|
|
44
54
|
return { error: scope.error, usage: false };
|
|
45
55
|
}
|
|
56
|
+
if (scope.ecosystem === "npm" && scope.packageJson?.private === true) {
|
|
57
|
+
return { privatePackage: scope };
|
|
58
|
+
}
|
|
46
59
|
const set = scope.ecosystem === "pypi" ? pypiPublishSet(scope.root) : npmPublishSet(scope.root);
|
|
47
60
|
const files = set.relPaths
|
|
48
61
|
.map((relPath) => buildAuditFile(scope.root, relPath))
|
|
@@ -69,10 +82,10 @@ function finalize(gathered, deep) {
|
|
|
69
82
|
deep
|
|
70
83
|
};
|
|
71
84
|
const theme = createTheme(resolvePresentation().color);
|
|
72
|
-
const rendered = parsed.format === "json" ? `${JSON.stringify(report, null, 2)}\n` : renderReport(report, theme);
|
|
85
|
+
const rendered = parsed.format === "json" ? `${JSON.stringify({ schemaVersion: 1, ...report }, null, 2)}\n` : renderReport(report, theme);
|
|
73
86
|
if (parsed.outputPath) {
|
|
74
87
|
try {
|
|
75
|
-
|
|
88
|
+
writeReportAtomic(resolve(parsed.outputPath), rendered);
|
|
76
89
|
}
|
|
77
90
|
catch (error) {
|
|
78
91
|
return { exitCode: EXIT_TOOL_ERROR, stdout: "", stderr: `dg audit could not write ${parsed.outputPath}: ${error instanceof Error ? error.message : "write error"}\n` };
|
|
@@ -89,6 +102,9 @@ function runAuditCommand(args) {
|
|
|
89
102
|
if ("error" in gathered) {
|
|
90
103
|
return gatherError(gathered);
|
|
91
104
|
}
|
|
105
|
+
if ("privatePackage" in gathered) {
|
|
106
|
+
return privatePackageResult(gathered.privatePackage);
|
|
107
|
+
}
|
|
92
108
|
const decision = deepDecision(gathered.scope, gathered.parsed.local);
|
|
93
109
|
return finalize(gathered, { ran: false, reason: decision.reason });
|
|
94
110
|
}
|
|
@@ -104,6 +120,9 @@ export async function maybeAudit(args) {
|
|
|
104
120
|
if ("error" in gathered) {
|
|
105
121
|
return { handled: true, result: gatherError(gathered) };
|
|
106
122
|
}
|
|
123
|
+
if ("privatePackage" in gathered) {
|
|
124
|
+
return { handled: true, result: privatePackageResult(gathered.privatePackage) };
|
|
125
|
+
}
|
|
107
126
|
let decision = deepDecision(gathered.scope, gathered.parsed.local);
|
|
108
127
|
if (!decision.upload && canPromptConsent(gathered)) {
|
|
109
128
|
if (!(await teamPolicyBlocksUpload())) {
|
|
@@ -112,6 +131,12 @@ export async function maybeAudit(args) {
|
|
|
112
131
|
}
|
|
113
132
|
}
|
|
114
133
|
}
|
|
134
|
+
// Org policy must gate every upload, not just the consent prompt: a pre-granted
|
|
135
|
+
// consent (DG_AUDIT_UPLOAD=1 / config) must not transmit the tarball when the
|
|
136
|
+
// organization has disabled artifact upload.
|
|
137
|
+
if (decision.upload && (await teamPolicyBlocksUpload())) {
|
|
138
|
+
decision = { upload: false, reason: "deep audit upload is disabled by your organization's policy" };
|
|
139
|
+
}
|
|
115
140
|
if (shouldLaunchAuditTui({ format: gathered.parsed.format, outputPath: gathered.parsed.outputPath })) {
|
|
116
141
|
const uploadAbort = new AbortController();
|
|
117
142
|
const deepPromise = decision.upload
|
package/dist/commands/config.js
CHANGED
|
@@ -3,12 +3,15 @@ import { ConfigError, getConfigValue, isConfigKey, listConfig, loadUserConfig, s
|
|
|
3
3
|
export const configCommand = {
|
|
4
4
|
name: "config",
|
|
5
5
|
summary: "Inspect or edit trusted dg configuration.",
|
|
6
|
-
usage: "dg config <get|set|unset|list> [key] [value] [--json]",
|
|
6
|
+
usage: "dg config <get|set|unset|list> [key] [value] [--json] [--all]",
|
|
7
7
|
args: [
|
|
8
8
|
{ name: "<action>", summary: "list | get <key> | set <key> <value> | unset <key>." },
|
|
9
9
|
{ name: "[key]", summary: "e.g. policy.mode, gitHook.onWarn, cooldown.age, cooldown.exempt, api.baseUrl." }
|
|
10
10
|
],
|
|
11
|
-
flags: [
|
|
11
|
+
flags: [
|
|
12
|
+
{ flag: "--json", summary: "Machine-readable output for list/get." },
|
|
13
|
+
{ flag: "--all", summary: "Include advanced keys in list (per-ecosystem cooldowns, internal flags)." }
|
|
14
|
+
],
|
|
12
15
|
examples: [
|
|
13
16
|
"dg config list",
|
|
14
17
|
"dg config get policy.mode",
|
|
@@ -26,7 +29,8 @@ export const configCommand = {
|
|
|
26
29
|
};
|
|
27
30
|
function configHandler(args) {
|
|
28
31
|
const json = args.includes("--json");
|
|
29
|
-
const
|
|
32
|
+
const all = args.includes("--all");
|
|
33
|
+
const filtered = args.filter((arg) => arg !== "--json" && arg !== "--all");
|
|
30
34
|
const [action, key, value, extra] = filtered;
|
|
31
35
|
if (!action || extra) {
|
|
32
36
|
return usageError("expected get, set, unset, or list");
|
|
@@ -37,10 +41,10 @@ function configHandler(args) {
|
|
|
37
41
|
if (key || value) {
|
|
38
42
|
return usageError("list does not accept a key or value");
|
|
39
43
|
}
|
|
40
|
-
const entries = listConfig(config);
|
|
44
|
+
const entries = listConfig(config, all);
|
|
41
45
|
return {
|
|
42
46
|
exitCode: 0,
|
|
43
|
-
stdout: json ? `${JSON.stringify(Object.fromEntries(entries.map((entry) => [entry.key, entry.value])), null, 2)}\n` : renderConfigList(entries),
|
|
47
|
+
stdout: json ? `${JSON.stringify(Object.fromEntries(entries.map((entry) => [entry.key, entry.value])), null, 2)}\n` : renderConfigList(entries, !all),
|
|
44
48
|
stderr: ""
|
|
45
49
|
};
|
|
46
50
|
}
|
|
@@ -93,8 +97,12 @@ function configHandler(args) {
|
|
|
93
97
|
throw error;
|
|
94
98
|
}
|
|
95
99
|
}
|
|
96
|
-
function renderConfigList(entries) {
|
|
97
|
-
|
|
100
|
+
function renderConfigList(entries, truncated) {
|
|
101
|
+
const lines = entries.map((entry) => `${entry.key}=${entry.value}`);
|
|
102
|
+
if (truncated) {
|
|
103
|
+
lines.push("(advanced keys hidden — dg config list --all)");
|
|
104
|
+
}
|
|
105
|
+
return `${lines.join("\n")}\n`;
|
|
98
106
|
}
|
|
99
107
|
function usageError(message) {
|
|
100
108
|
return {
|