@westbayberry/dg 2.3.2 → 2.3.4
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 +95 -208
- package/dist/agents/copilot-cli.js +7 -2
- package/dist/agents/persistence.js +83 -8
- package/dist/agents/registry.js +5 -2
- package/dist/agents/routing.js +31 -14
- package/dist/api/analyze.js +138 -41
- package/dist/audit/deep.js +2 -1
- package/dist/audit/detectors.js +23 -2
- package/dist/audit/rules.js +4 -1
- package/dist/audit-ui/export.js +5 -4
- package/dist/auth/device-login.js +33 -11
- package/dist/auth/login-app.js +17 -12
- package/dist/auth/store.js +65 -9
- package/dist/bin/dg.js +1 -1
- package/dist/commands/audit.js +12 -20
- package/dist/commands/{guard-commit.js → commit-guard.js} +12 -12
- package/dist/commands/cooldown.js +2 -1
- package/dist/commands/decisions.js +8 -7
- package/dist/commands/help.js +11 -3
- package/dist/commands/licenses.js +17 -8
- package/dist/commands/login.js +58 -21
- package/dist/commands/router.js +12 -2
- package/dist/commands/sbom.js +20 -5
- package/dist/commands/scan.js +5 -4
- package/dist/commands/service.js +1 -1
- package/dist/commands/setup.js +12 -12
- package/dist/commands/status.js +4 -4
- package/dist/commands/uninstall.js +1 -1
- package/dist/commands/update.js +20 -0
- package/dist/commands/verify.js +4 -4
- package/dist/config/settings.js +81 -12
- package/dist/export-ui/ExportDialog.js +1 -1
- package/dist/gate/cooldown-request.js +22 -0
- package/dist/gate/egress-guard.js +185 -0
- package/dist/gate/host-match.js +48 -0
- package/dist/gate/verdict-core.js +101 -0
- package/dist/gate/verdict-fetch.js +214 -0
- package/dist/launcher/agent-check.js +1096 -42
- package/dist/launcher/agent-hook-exec.js +8 -1
- package/dist/launcher/agent-hook-io.js +5 -3
- package/dist/launcher/classify.js +110 -26
- package/dist/launcher/env.js +84 -18
- package/dist/launcher/install-preflight.js +26 -3
- package/dist/launcher/live-install.js +4 -2
- package/dist/launcher/manifest-screen.js +88 -7
- package/dist/launcher/output-redaction.js +153 -15
- package/dist/launcher/preflight-prompt.js +1 -1
- package/dist/launcher/resolve-real-binary.js +8 -3
- package/dist/launcher/run.js +237 -40
- package/dist/origin/artifact-cache.js +55 -0
- package/dist/origin/cargo-config.js +12 -0
- package/dist/origin/gate-mode.js +7 -0
- package/dist/origin/platform-paths.js +14 -0
- package/dist/origin/precedence.js +345 -0
- package/dist/origin/server.js +611 -0
- package/dist/origin/spike-worker.js +35 -0
- package/dist/origin/worker.js +116 -0
- package/dist/presentation/package-page.js +21 -1
- package/dist/presentation/safe-version.js +11 -0
- package/dist/presentation/sarif.js +109 -0
- package/dist/project/dgfile.js +54 -17
- package/dist/project/override-trust.js +0 -0
- package/dist/proxy/enforcement.js +27 -7
- package/dist/proxy/metadata-map.js +33 -7
- package/dist/proxy/preverified.js +0 -3
- package/dist/proxy/server.js +104 -446
- package/dist/proxy/upstream-proxy.js +23 -4
- package/dist/proxy/worker.js +3 -2
- package/dist/publish-set/collect.js +6 -0
- package/dist/runtime/fatal.js +2 -1
- package/dist/runtime/first-run.js +5 -1
- package/dist/runtime/node-version.js +51 -0
- package/dist/runtime/nudges.js +27 -1
- package/dist/sbom/cyclonedx.js +120 -33
- package/dist/sbom/graph.js +236 -0
- package/dist/sbom/spdx.js +53 -0
- package/dist/sbom-ui/SbomApp.js +5 -3
- package/dist/sbom-ui/inventory.js +8 -10
- package/dist/scan/analyze-worker.js +3 -1
- package/dist/scan/collect.js +38 -8
- package/dist/scan/command.js +77 -27
- package/dist/scan/discovery.js +18 -7
- package/dist/scan/installed.js +202 -0
- package/dist/scan/manifest-coverage.js +128 -0
- package/dist/scan/render.js +125 -40
- package/dist/scan/scanner-report.js +92 -22
- package/dist/scan/staged.js +68 -15
- package/dist/scan/types.js +7 -1
- package/dist/scan-ui/LegacyApp.js +3 -3
- package/dist/scan-ui/components/InteractiveResultsView.js +17 -14
- package/dist/scan-ui/hooks/useScan.js +37 -11
- package/dist/scan-ui/launch.js +2 -2
- package/dist/scan-ui/logo.js +6 -2
- package/dist/scan-ui/shims.js +6 -6
- package/dist/scripts/gate.js +53 -35
- package/dist/security/csv.js +9 -0
- package/dist/security/sanitize.js +11 -2
- package/dist/service/state.js +120 -37
- package/dist/service/trust-refresh.js +12 -1
- package/dist/service/trust-store.js +36 -5
- package/dist/service/worker.js +5 -7
- package/dist/setup/git-hook.js +2 -2
- package/dist/setup/install-method.js +36 -0
- package/dist/setup/plan.js +119 -57
- package/dist/setup/uninstall-standalone.js +7 -2
- package/dist/setup-ui/gate.js +1 -8
- package/dist/setup-ui/offer.js +14 -7
- package/dist/setup-ui/selector.js +1 -1
- package/dist/setup-ui/wizard.js +73 -78
- package/dist/standalone/uninstall.mjs +1114 -386
- package/dist/state/cleanup-registry.js +14 -3
- package/dist/state/cooldown-held.js +36 -13
- package/dist/state/locks.js +239 -34
- package/dist/state/sessions.js +2 -2
- package/dist/util/sh-escape.js +6 -0
- package/dist/util/tty-prompt.js +13 -4
- package/dist/verify/local.js +60 -10
- package/dist/verify/package-check.js +35 -49
- package/dist/verify/preflight.js +59 -33
- package/dist/verify/render.js +11 -12
- package/npm-shrinkwrap.json +714 -213
- package/package.json +4 -2
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
function normalize(path) {
|
|
3
|
+
return path.replace(/\\/g, "/");
|
|
4
|
+
}
|
|
5
|
+
// Resolve how dg was installed from the realpath of its own entry script, so a
|
|
6
|
+
// self-upgrade dispatches to the package manager that actually owns the binary.
|
|
7
|
+
// dg is published to npm and installed globally, so the npm-family global
|
|
8
|
+
// layouts are auto-handled; bun/volta and anything not under node_modules return
|
|
9
|
+
// null and the caller prints the manual command rather than guessing wrong (an
|
|
10
|
+
// npm install onto a non-npm-owned prefix fails with EACCES/ENOENT).
|
|
11
|
+
export function detectInstallMethod(packageName, version, argv1 = process.argv[1]) {
|
|
12
|
+
if (!argv1) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
let real = argv1;
|
|
16
|
+
try {
|
|
17
|
+
real = realpathSync(argv1);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
real = argv1;
|
|
21
|
+
}
|
|
22
|
+
const path = normalize(real);
|
|
23
|
+
if (!path.includes("/node_modules/")) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const spec = `${packageName}@${version}`;
|
|
27
|
+
const pnpmHome = process.env.PNPM_HOME;
|
|
28
|
+
if (/\/(pnpm|pnpm-global)\//.test(path) || (pnpmHome && path.startsWith(normalize(pnpmHome)))) {
|
|
29
|
+
return { manager: "pnpm", args: ["add", "-g", spec] };
|
|
30
|
+
}
|
|
31
|
+
// bun/volta manage their own toolchain dirs; an npm -g there would be wrong.
|
|
32
|
+
if (/\/\.bun\//.test(path) || /\/\.volta\//.test(path)) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return { manager: "npm", args: ["install", "-g", spec] };
|
|
36
|
+
}
|
package/dist/setup/plan.js
CHANGED
|
@@ -6,12 +6,14 @@ import { randomBytes } from "node:crypto";
|
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { createTheme } from "../presentation/theme.js";
|
|
8
8
|
import { AGENTS, AGENT_IDS, agentLabel, resolveAgentHookContext, reverseAgentHookEntry } from "../agents/registry.js";
|
|
9
|
+
import { removeAgentRouting, routingInstalled } from "../agents/routing.js";
|
|
10
|
+
import { escapeDoubleQuotedSh, escapeDoubleQuotedFish } from "../util/sh-escape.js";
|
|
9
11
|
import { GATE_ENABLE_HINT, readNetworkGatePosture } from "../agents/gate-posture.js";
|
|
10
|
-
import { acquireLockSync, findStaleSessionsSync, preserveCorruptCleanupRegistrySync, resolveDgPaths, sweepStaleSessionsSync, CLEANUP_REGISTRY_LOCK } from "../state/index.js";
|
|
12
|
+
import { acquireLockSync, findStaleSessionsSync, LockBusyError, preserveCorruptCleanupRegistrySync, resolveDgPaths, sweepStaleSessionsSync, CLEANUP_REGISTRY_LOCK } from "../state/index.js";
|
|
11
13
|
import { currentNodeVersion, isSupportedNode } from "../runtime/node-version.js";
|
|
12
14
|
import { dgVersion } from "../commands/version.js";
|
|
13
15
|
import { compareVersions, readLatestVersion } from "../commands/update.js";
|
|
14
|
-
import { AuthError, authStatus, displayTier, readAuthState } from "../auth/store.js";
|
|
16
|
+
import { AuthError, authPath, authStatus, clearAuthState, displayTier, readAuthState } from "../auth/store.js";
|
|
15
17
|
import { ConfigError, DEFAULT_CONFIG, loadUserConfig } from "../config/settings.js";
|
|
16
18
|
import { describeCooldownSettings } from "../policy/cooldown.js";
|
|
17
19
|
import { resolveRealBinary } from "../launcher/resolve-real-binary.js";
|
|
@@ -82,7 +84,7 @@ const DOCTOR_FIX_BY_NAME = {
|
|
|
82
84
|
"shell-rc": "dg setup",
|
|
83
85
|
"python-hook-drift": "dg uninstall, or re-run dg setup, to remove the stale pip hook",
|
|
84
86
|
path: "reload your shell after setup",
|
|
85
|
-
"commit-guard": "dg guard
|
|
87
|
+
"commit-guard": "dg commit-guard",
|
|
86
88
|
"stale-sessions": "clears on the next protected run",
|
|
87
89
|
auth: "dg login"
|
|
88
90
|
};
|
|
@@ -195,44 +197,45 @@ export function applySetupPlanWithLock(plan, now = new Date()) {
|
|
|
195
197
|
lock.release();
|
|
196
198
|
}
|
|
197
199
|
}
|
|
198
|
-
// Shims written by a dg that predates the self-cleaning template carry no
|
|
199
|
-
// auto-cleanup, so a plain `npm uninstall -g` leaves them stranded forever.
|
|
200
|
-
// Rewriting the installed shims and reinstalling the standalone uninstaller on
|
|
201
|
-
// the first run after a version bump propagates the current template to setups
|
|
202
|
-
// created by older releases.
|
|
203
200
|
export function refreshSetupOnUpgrade(env = process.env) {
|
|
204
201
|
const paths = resolveDgPaths(env);
|
|
205
202
|
const shimDir = join(paths.homeDir, ".dg", "shims");
|
|
206
203
|
if (!isShimFile(join(shimDir, "npm"))) {
|
|
207
|
-
return
|
|
204
|
+
return "skipped";
|
|
208
205
|
}
|
|
209
206
|
const failClosed = readShimFailClosed(env);
|
|
207
|
+
let lock;
|
|
210
208
|
try {
|
|
211
|
-
|
|
209
|
+
lock = acquireLockSync(paths, SETUP_UNINSTALL_LOCK, {
|
|
212
210
|
staleMs: SETUP_UNINSTALL_LOCK_STALE_MS
|
|
213
211
|
});
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
// A busy lock is transient — report it so the caller can retry on the next
|
|
215
|
+
// run instead of recording the version as refreshed and skipping forever.
|
|
216
|
+
return error instanceof LockBusyError ? "lock-busy" : "skipped";
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
for (const command of SHIM_COMMANDS) {
|
|
220
|
+
const shimPath = join(shimDir, command);
|
|
221
|
+
if (!isShimFile(shimPath)) {
|
|
222
|
+
continue;
|
|
225
223
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
224
|
+
writeFileSync(shimPath, shimSource(command, { failClosed }), {
|
|
225
|
+
encoding: "utf8",
|
|
226
|
+
mode: 0o755
|
|
227
|
+
});
|
|
228
|
+
chmodSync(shimPath, 0o755);
|
|
230
229
|
}
|
|
230
|
+
installStandaloneUninstaller(paths.homeDir);
|
|
231
231
|
}
|
|
232
232
|
catch {
|
|
233
|
-
return
|
|
233
|
+
return "skipped";
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
lock.release();
|
|
234
237
|
}
|
|
235
|
-
return
|
|
238
|
+
return "applied";
|
|
236
239
|
}
|
|
237
240
|
export function uninstallSetup(options) {
|
|
238
241
|
const paths = resolveDgPaths(options.env ?? process.env);
|
|
@@ -273,6 +276,23 @@ export function uninstallSetup(options) {
|
|
|
273
276
|
}
|
|
274
277
|
sweepLegacyRcBlocks(paths.homeDir, removed, warnings);
|
|
275
278
|
sweepLegacyPythonHooks(paths.homeDir, removed, warnings);
|
|
279
|
+
// Service-mode `dg agents on` writes fetch-routing (HTTPS_PROXY /
|
|
280
|
+
// NODE_EXTRA_CA_CERTS into the agent's settings, a [shell_environment_policy]
|
|
281
|
+
// block for codex) that is NOT tracked in the cleanup registry. Reverse it
|
|
282
|
+
// here, BEFORE the state dir (which holds the prior-value backup) is removed,
|
|
283
|
+
// so uninstall can't strand an agent pointed at a now-dead proxy.
|
|
284
|
+
const uninstallEnv = options.env ?? process.env;
|
|
285
|
+
for (const agent of AGENT_IDS) {
|
|
286
|
+
try {
|
|
287
|
+
if (routingInstalled(agent, paths.homeDir, uninstallEnv)) {
|
|
288
|
+
removeAgentRouting(agent, paths.homeDir, uninstallEnv);
|
|
289
|
+
removed.push(`agent routing (${agentLabel(agent)})`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
catch (err) {
|
|
293
|
+
warnings.push(`could not remove ${agentLabel(agent)} routing: ${err.message}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
276
296
|
if (!options.all && !registryRead.malformed && options.keepConfig) {
|
|
277
297
|
writeRegistryWithLock(paths, {
|
|
278
298
|
version: 1,
|
|
@@ -285,6 +305,9 @@ export function uninstallSetup(options) {
|
|
|
285
305
|
if (!options.keepConfig && hadStateDirBeforeLock) {
|
|
286
306
|
removeDirectory(paths.stateDir, removed, missing);
|
|
287
307
|
}
|
|
308
|
+
if (!options.all && !options.keepConfig && clearAuthState(uninstallEnv)) {
|
|
309
|
+
removed.push(authPath(paths));
|
|
310
|
+
}
|
|
288
311
|
if (options.all) {
|
|
289
312
|
removeDirectory(paths.configDir, removed, missing);
|
|
290
313
|
}
|
|
@@ -371,13 +394,7 @@ export function doctorReport(options = {}) {
|
|
|
371
394
|
status: "pass",
|
|
372
395
|
message: `Real binary resolver skips ${shimDir} and dg shim sentinel files`
|
|
373
396
|
});
|
|
374
|
-
checks.push(
|
|
375
|
-
name: "package-manager-discovery",
|
|
376
|
-
status: "pass",
|
|
377
|
-
message: `Classifiers are registered for ${SHIM_COMMANDS.join(", ")}; gated managers remain ${packageManagerNames()
|
|
378
|
-
.filter((name) => !SHIM_COMMANDS.includes(name))
|
|
379
|
-
.join(", ")}`
|
|
380
|
-
});
|
|
397
|
+
checks.push(packageManagerDiscoveryCheck(env));
|
|
381
398
|
checks.push(...unavailableDoctorChecks());
|
|
382
399
|
checks.push(serviceCheck(env));
|
|
383
400
|
checks.push(agentGateCheck(env, paths.homeDir));
|
|
@@ -489,12 +506,14 @@ function describeFetchError(error, timeoutMs) {
|
|
|
489
506
|
}
|
|
490
507
|
const DOCTOR_STATUS_ROLE = {
|
|
491
508
|
pass: "pass",
|
|
509
|
+
info: "accent",
|
|
492
510
|
warn: "warn",
|
|
493
511
|
fail: "block",
|
|
494
512
|
unavailable: "muted"
|
|
495
513
|
};
|
|
496
514
|
const DOCTOR_STATUS_GLYPH = {
|
|
497
515
|
pass: "✓",
|
|
516
|
+
info: "ℹ",
|
|
498
517
|
warn: "⚠",
|
|
499
518
|
fail: "✘",
|
|
500
519
|
unavailable: "·"
|
|
@@ -504,7 +523,7 @@ const DOCTOR_GROUP_ORDER = [
|
|
|
504
523
|
{ key: "setup", title: "Setup" },
|
|
505
524
|
{ key: "account", title: "Account" }
|
|
506
525
|
];
|
|
507
|
-
export function renderDoctorReport(report, theme = createTheme(false), verbose = false) {
|
|
526
|
+
export function renderDoctorReport(report, theme = createTheme(false), verbose = false, platform = process.platform) {
|
|
508
527
|
const failures = report.checks.filter((check) => check.status === "fail").length;
|
|
509
528
|
const warnings = report.checks.filter((check) => check.status === "warn").length;
|
|
510
529
|
const role = failures > 0 ? "block" : warnings > 0 ? "warn" : "pass";
|
|
@@ -534,14 +553,19 @@ export function renderDoctorReport(report, theme = createTheme(false), verbose =
|
|
|
534
553
|
const allWarn = setupCore.every((name) => nonPass.some((check) => check.name === name && check.status === "warn"));
|
|
535
554
|
if (allWarn) {
|
|
536
555
|
nonPass = nonPass.filter((check) => !setupCore.includes(check.name) && !(check.name === "config" && check.status === "warn"));
|
|
537
|
-
const
|
|
556
|
+
const onWindows = platform === "win32";
|
|
557
|
+
const notSetUp = onWindows
|
|
558
|
+
? "dg setup is unavailable on Windows — run scanned installs through prefix mode: dg npm install <pkg>"
|
|
559
|
+
: "not set up — bare npm/pip installs aren't protected";
|
|
560
|
+
const setupFix = onWindows ? undefined : "dg setup";
|
|
538
561
|
if (nonPass.length === 0) {
|
|
539
|
-
lines.push(rollupInlineLine(title, "warn", "⚠", notSetUp,
|
|
562
|
+
lines.push(rollupInlineLine(title, "warn", "⚠", notSetUp, setupFix, theme));
|
|
540
563
|
continue;
|
|
541
564
|
}
|
|
542
565
|
const worst = worstCheck(nonPass);
|
|
543
566
|
lines.push(` ${theme.paint(DOCTOR_STATUS_ROLE[worst.status], `${DOCTOR_STATUS_GLYPH[worst.status]} ${title}`)}`);
|
|
544
|
-
|
|
567
|
+
const notSetUpFix = setupFix ? ` ${theme.paint("muted", `→ ${setupFix}`)}` : "";
|
|
568
|
+
lines.push(` ${theme.paint("warn", "⚠")} ${notSetUp}${notSetUpFix}`);
|
|
545
569
|
const width = nameWidth(nonPass);
|
|
546
570
|
for (const check of nonPass) {
|
|
547
571
|
lines.push(doctorLine(check, width, theme));
|
|
@@ -584,6 +608,7 @@ function nameWidth(checks) {
|
|
|
584
608
|
}
|
|
585
609
|
const DOCTOR_STATUS_SEVERITY = {
|
|
586
610
|
pass: 0,
|
|
611
|
+
info: 1,
|
|
587
612
|
unavailable: 1,
|
|
588
613
|
warn: 2,
|
|
589
614
|
fail: 3
|
|
@@ -679,8 +704,12 @@ export function shimSource(command, options = {}) {
|
|
|
679
704
|
const failOpenTail = [
|
|
680
705
|
`dg_home=$(dirname "$shim_dir")`,
|
|
681
706
|
`if [ -f "$dg_home/uninstall.mjs" ] && command -v node >/dev/null 2>&1; then`,
|
|
682
|
-
` ( sleep
|
|
683
|
-
|
|
707
|
+
` ( sleep 60`,
|
|
708
|
+
// Only self-clean when dg is DURABLY gone: re-check after the wait, and skip
|
|
709
|
+
// if a fresh `.upgrading` sentinel marks a self-upgrade in flight (a slow
|
|
710
|
+
// `npm install -g` makes dg unavailable for the window). The 15-min freshness
|
|
711
|
+
// bound means a stale sentinel can't disable cleanup forever.
|
|
712
|
+
` if [ ! -x "${dg}" ] && ! PATH="$dg_path" command -v dg >/dev/null 2>&1 && [ -z "$(find "$dg_home/.upgrading" -mmin -15 2>/dev/null)" ]; then`,
|
|
684
713
|
` node "$dg_home/uninstall.mjs" --quiet >/dev/null 2>&1`,
|
|
685
714
|
` fi`,
|
|
686
715
|
` ) >/dev/null 2>&1 &`,
|
|
@@ -695,12 +724,6 @@ export function shimSource(command, options = {}) {
|
|
|
695
724
|
];
|
|
696
725
|
return [...head, ...(options.failClosed ? failClosedTail : failOpenTail)].join("\n");
|
|
697
726
|
}
|
|
698
|
-
function escapeDoubleQuotedSh(value) {
|
|
699
|
-
return value.replace(/[\\"$`]/g, "\\$&");
|
|
700
|
-
}
|
|
701
|
-
function escapeDoubleQuotedFish(value) {
|
|
702
|
-
return value.replace(/[\\"$]/g, "\\$&");
|
|
703
|
-
}
|
|
704
727
|
function dgEntrypoint() {
|
|
705
728
|
const argv1 = process.argv[1];
|
|
706
729
|
return argv1 ? resolve(argv1) : "dg";
|
|
@@ -775,7 +798,7 @@ function stripRcBlockDetailed(existing) {
|
|
|
775
798
|
const repairedLines = [];
|
|
776
799
|
for (;;) {
|
|
777
800
|
const lines = content.split("\n");
|
|
778
|
-
const begin = lines.
|
|
801
|
+
const begin = lines.findIndex((line) => line.replace(/\r$/, "") === RC_BEGIN);
|
|
779
802
|
if (begin === -1) {
|
|
780
803
|
break;
|
|
781
804
|
}
|
|
@@ -790,10 +813,13 @@ function stripRcBlockDetailed(existing) {
|
|
|
790
813
|
return { content, repairedLines };
|
|
791
814
|
}
|
|
792
815
|
function rcPairPattern(begin, end) {
|
|
793
|
-
|
|
816
|
+
// Tolerate CRLF: a block written on (or edited to) Windows line endings must
|
|
817
|
+
// still be matched, or it duplicates on re-setup and survives uninstall.
|
|
818
|
+
return new RegExp(`${escapeRegex(begin)}\\r?\\n(?:(?!${escapeRegex(begin)})[\\s\\S])*?${escapeRegex(end)}\\r?\\n?`, "g");
|
|
794
819
|
}
|
|
795
820
|
function isDgWrittenRcLine(line) {
|
|
796
|
-
|
|
821
|
+
const trimmed = line.replace(/\r$/, "");
|
|
822
|
+
return trimmed === RC_END || trimmed.startsWith("# dg-") || trimmed.includes(RC_SHIM_HELPER) || trimmed.includes(`${sep}.dg${sep}shims`);
|
|
797
823
|
}
|
|
798
824
|
function sweepLegacyRcBlocks(homeDir, removed, warnings) {
|
|
799
825
|
for (const rel of LEGACY_RC_CANDIDATES) {
|
|
@@ -942,6 +968,15 @@ function configCheck(paths, env) {
|
|
|
942
968
|
accessSync(paths.configDir, constants.R_OK);
|
|
943
969
|
const config = loadUserConfig(env);
|
|
944
970
|
if (config.api.baseUrl !== DEFAULT_CONFIG.api.baseUrl) {
|
|
971
|
+
// A loopback endpoint is the local dev stack, not a tamper signal — never
|
|
972
|
+
// alarm on it.
|
|
973
|
+
if (isLoopbackApiHost(config.api.baseUrl)) {
|
|
974
|
+
return {
|
|
975
|
+
name: "config",
|
|
976
|
+
status: "pass",
|
|
977
|
+
message: `using a local dev API endpoint: ${config.api.baseUrl}`
|
|
978
|
+
};
|
|
979
|
+
}
|
|
945
980
|
// The verdict API is the firewall's source of truth. A non-default
|
|
946
981
|
// endpoint is legitimate for enterprise self-host, but a silent repoint
|
|
947
982
|
// is also how an attacker would make every verdict come back clean, so
|
|
@@ -967,6 +1002,15 @@ function configCheck(paths, env) {
|
|
|
967
1002
|
};
|
|
968
1003
|
}
|
|
969
1004
|
}
|
|
1005
|
+
function isLoopbackApiHost(baseUrl) {
|
|
1006
|
+
try {
|
|
1007
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
1008
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "0.0.0.0";
|
|
1009
|
+
}
|
|
1010
|
+
catch {
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
970
1014
|
function unavailableDoctorChecks() {
|
|
971
1015
|
const unavailable = [
|
|
972
1016
|
["path-cache", "Shell command path cache checks are guidance only. After setup, run 'hash -r' for bash or 'rehash' for zsh."],
|
|
@@ -1059,7 +1103,7 @@ function serviceCheck(env) {
|
|
|
1059
1103
|
return {
|
|
1060
1104
|
name: "service",
|
|
1061
1105
|
status: "warn",
|
|
1062
|
-
message: state.lastError ?? "Service mode is running
|
|
1106
|
+
message: state.lastError ?? "Service mode is running but its local proxy is not listening"
|
|
1063
1107
|
};
|
|
1064
1108
|
}
|
|
1065
1109
|
return {
|
|
@@ -1102,8 +1146,8 @@ function agentGateCheck(env, home) {
|
|
|
1102
1146
|
}
|
|
1103
1147
|
return {
|
|
1104
1148
|
name: "agent-gate",
|
|
1105
|
-
status: "
|
|
1106
|
-
message: `${hooked.join(", ")} hooked
|
|
1149
|
+
status: "info",
|
|
1150
|
+
message: `${hooked.join(", ")} hooked — static pre-screen is on. Fetch-time screening of dynamic, absolute-path, and unsupported-manager installs is available with service mode`,
|
|
1107
1151
|
fix: GATE_ENABLE_HINT
|
|
1108
1152
|
};
|
|
1109
1153
|
}
|
|
@@ -1232,6 +1276,23 @@ export function currentShellActivation(env = process.env) {
|
|
|
1232
1276
|
const rcDisplay = rcPath.startsWith(homeDir) ? `~${rcPath.slice(homeDir.length)}` : rcPath;
|
|
1233
1277
|
return activationCommand(shell, rcDisplay);
|
|
1234
1278
|
}
|
|
1279
|
+
function packageManagerDiscoveryCheck(env) {
|
|
1280
|
+
const unprotectedManagers = packageManagerNames().filter((name) => !SHIM_COMMANDS.includes(name));
|
|
1281
|
+
const onPath = unprotectedManagers.filter((name) => resolveRealBinary({ name, env }).path);
|
|
1282
|
+
if (onPath.length === 0) {
|
|
1283
|
+
return {
|
|
1284
|
+
name: "package-manager-discovery",
|
|
1285
|
+
status: "pass",
|
|
1286
|
+
message: `Interception is wired for ${SHIM_COMMANDS.join(", ")}; no unsupported installer (${unprotectedManagers.join(", ")}) is on PATH`
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
return {
|
|
1290
|
+
name: "package-manager-discovery",
|
|
1291
|
+
status: "info",
|
|
1292
|
+
message: `${onPath.join(", ")} on PATH ${onPath.length === 1 ? "runs" : "run"} unprotected — dg does not intercept ${onPath.length === 1 ? "it" : "them"} this release; ${SHIM_COMMANDS.join(", ")} are protected`,
|
|
1293
|
+
fix: "install through a supported manager (dg npm/pnpm/yarn/pip/uv/cargo ...) to keep firewall coverage"
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1235
1296
|
function realBinaryResolutionCheck(env) {
|
|
1236
1297
|
const installed = SHIM_COMMANDS.filter((command) => resolveRealBinary({ name: command, env }).path);
|
|
1237
1298
|
const absent = SHIM_COMMANDS.filter((command) => !resolveRealBinary({ name: command, env }).path);
|
|
@@ -1251,8 +1312,9 @@ function authCheck(env) {
|
|
|
1251
1312
|
: `Authenticated from ${status.source} token ${status.tokenPreview}`;
|
|
1252
1313
|
return {
|
|
1253
1314
|
name: "auth",
|
|
1254
|
-
status: status.authenticated ? "pass" : "
|
|
1255
|
-
message: status.authenticated ? connected : "not signed in"
|
|
1315
|
+
status: status.authenticated ? "pass" : "info",
|
|
1316
|
+
message: status.authenticated ? connected : "not signed in — optional; unlocks verdict details and your dashboard",
|
|
1317
|
+
...(status.authenticated ? {} : { fix: "dg login" })
|
|
1256
1318
|
};
|
|
1257
1319
|
}
|
|
1258
1320
|
catch (error) {
|
|
@@ -1421,7 +1483,7 @@ function commitGuardCheck(registry, env) {
|
|
|
1421
1483
|
return {
|
|
1422
1484
|
name: "commit-guard",
|
|
1423
1485
|
status: "pass",
|
|
1424
|
-
message: "No commit guard installed (optional; run dg guard
|
|
1486
|
+
message: "No commit guard installed (optional; run dg commit-guard inside a repo)"
|
|
1425
1487
|
};
|
|
1426
1488
|
}
|
|
1427
1489
|
const missing = hooks.filter((entry) => !hookOwnedByDg(entry));
|
|
@@ -1430,7 +1492,7 @@ function commitGuardCheck(registry, env) {
|
|
|
1430
1492
|
name: "commit-guard",
|
|
1431
1493
|
status: "warn",
|
|
1432
1494
|
message: `Commit guard hook missing or replaced at: ${missing.map((entry) => entry.path).join(", ")}`,
|
|
1433
|
-
fix: "re-run dg guard
|
|
1495
|
+
fix: "re-run dg commit-guard in that repo, or dg commit-guard off to forget it"
|
|
1434
1496
|
};
|
|
1435
1497
|
}
|
|
1436
1498
|
const broken = hooks.filter((entry) => {
|
|
@@ -1442,7 +1504,7 @@ function commitGuardCheck(registry, env) {
|
|
|
1442
1504
|
name: "commit-guard",
|
|
1443
1505
|
status: "warn",
|
|
1444
1506
|
message: `Commit guard at ${broken.map((entry) => entry.path).join(", ")} points at a dg binary that is not runnable, so commits there fail open with a notice`,
|
|
1445
|
-
fix: "reinstall dg, then re-run dg guard
|
|
1507
|
+
fix: "reinstall dg, then re-run dg commit-guard"
|
|
1446
1508
|
};
|
|
1447
1509
|
}
|
|
1448
1510
|
return {
|
|
@@ -5,7 +5,12 @@ import { LockBusyError, resolveDgPaths } from "../state/index.js";
|
|
|
5
5
|
function run() {
|
|
6
6
|
const quiet = process.argv.includes("--quiet");
|
|
7
7
|
try {
|
|
8
|
-
|
|
8
|
+
// A plain `npm uninstall -g` triggers this courtesy cleanup; it must be LESS
|
|
9
|
+
// destructive than an explicit `dg uninstall`. Remove the activation footprint
|
|
10
|
+
// (shims/rc/hooks via the registry) but keep config, auth, state, and cache so
|
|
11
|
+
// a reinstall keeps the user logged in. The explicit removal below is narrowed
|
|
12
|
+
// to the shim dir only.
|
|
13
|
+
const result = uninstallSetup({ keepConfig: true, all: false });
|
|
9
14
|
if (!quiet) {
|
|
10
15
|
process.stderr.write(`Dependency Guardian cleaned up ${result.removed.length} leftover item(s) after the package was removed.\n`);
|
|
11
16
|
}
|
|
@@ -16,7 +21,7 @@ function run() {
|
|
|
16
21
|
}
|
|
17
22
|
}
|
|
18
23
|
try {
|
|
19
|
-
rmSync(join(resolveDgPaths().homeDir, ".dg"), { recursive: true, force: true });
|
|
24
|
+
rmSync(join(resolveDgPaths().homeDir, ".dg", "shims"), { recursive: true, force: true });
|
|
20
25
|
}
|
|
21
26
|
catch {
|
|
22
27
|
return;
|
package/dist/setup-ui/gate.js
CHANGED
|
@@ -8,25 +8,18 @@ export function setupApplied(env = process.env) {
|
|
|
8
8
|
export function securityNotesMarkerPath(env = process.env) {
|
|
9
9
|
return join(resolveDgPaths(env).stateDir, "security-notes-shown");
|
|
10
10
|
}
|
|
11
|
-
export function wizardSkippedMarkerPath(env = process.env) {
|
|
12
|
-
return join(resolveDgPaths(env).stateDir, "setup-wizard-skipped");
|
|
13
|
-
}
|
|
14
11
|
export function securityNotesShown(env = process.env) {
|
|
15
12
|
return existsSync(securityNotesMarkerPath(env));
|
|
16
13
|
}
|
|
17
14
|
export function markSecurityNotesShown(env = process.env, now = new Date()) {
|
|
18
15
|
writeMarker(securityNotesMarkerPath(env), now);
|
|
19
16
|
}
|
|
20
|
-
export function markWizardSkipped(env = process.env, now = new Date()) {
|
|
21
|
-
writeMarker(wizardSkippedMarkerPath(env), now);
|
|
22
|
-
}
|
|
23
17
|
export function shouldOfferSetupWizard(env = process.env, stdin = process.stdin, stderr = process.stderr) {
|
|
24
18
|
return (process.platform !== "win32" &&
|
|
25
19
|
Boolean(stdin.isTTY) &&
|
|
26
20
|
Boolean(stderr.isTTY) &&
|
|
27
21
|
!isCiEnv(env) &&
|
|
28
|
-
!setupApplied(env)
|
|
29
|
-
!existsSync(wizardSkippedMarkerPath(env)));
|
|
22
|
+
!setupApplied(env));
|
|
30
23
|
}
|
|
31
24
|
function writeMarker(path, now) {
|
|
32
25
|
try {
|
package/dist/setup-ui/offer.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { resolvePresentation } from "../presentation/mode.js";
|
|
2
|
+
import { createTheme } from "../presentation/theme.js";
|
|
2
3
|
import { MACHINE_OUTPUT_FLAGS } from "../runtime/first-run.js";
|
|
3
4
|
import { buildSetupPlan } from "../setup/plan.js";
|
|
4
|
-
import { shouldOfferSetupWizard } from "./gate.js";
|
|
5
|
+
import { setupApplied, shouldOfferSetupWizard } from "./gate.js";
|
|
5
6
|
const SKIP_COMMANDS = new Set([
|
|
6
7
|
"help",
|
|
7
8
|
"--help",
|
|
@@ -14,6 +15,7 @@ const SKIP_COMMANDS = new Set([
|
|
|
14
15
|
"login",
|
|
15
16
|
"logout",
|
|
16
17
|
"setup",
|
|
18
|
+
"doctor",
|
|
17
19
|
"update",
|
|
18
20
|
"upgrade",
|
|
19
21
|
"uninstall"
|
|
@@ -25,18 +27,23 @@ export async function maybeOfferSetupWizard(args, options = {}) {
|
|
|
25
27
|
if (command && SKIP_COMMANDS.has(command)) {
|
|
26
28
|
return NOT_RUN;
|
|
27
29
|
}
|
|
28
|
-
if (args.some((arg) => MACHINE_OUTPUT_FLAGS.has(arg))) {
|
|
29
|
-
return NOT_RUN;
|
|
30
|
-
}
|
|
31
30
|
if (!shouldOfferSetupWizard(env, options.stdin ?? process.stdin, options.stderr ?? process.stderr)) {
|
|
32
31
|
return NOT_RUN;
|
|
33
32
|
}
|
|
34
33
|
const rich = options.richMode ?? resolvePresentation().mode === "rich";
|
|
35
|
-
if (!rich) {
|
|
36
|
-
|
|
34
|
+
if (!rich || args.some((arg) => MACHINE_OUTPUT_FLAGS.has(arg))) {
|
|
35
|
+
const theme = createTheme(resolvePresentation().color);
|
|
36
|
+
return {
|
|
37
|
+
handled: true,
|
|
38
|
+
result: {
|
|
39
|
+
exitCode: 1,
|
|
40
|
+
stdout: "",
|
|
41
|
+
stderr: ` ${theme.paint("muted", "Setup not finished — dg is unavailable until setup completes. Run")} ${theme.paint("accent", "dg setup")} ${theme.paint("muted", "to finish.")}\n`
|
|
42
|
+
}
|
|
43
|
+
};
|
|
37
44
|
}
|
|
38
45
|
const plan = buildSetupPlan({ shell: "auto" });
|
|
39
46
|
const runWizard = options.runWizard ?? (await import("./wizard.js")).runSetupWizard;
|
|
40
47
|
const result = await runWizard(plan, { env, autoActivate: args.length === 0 });
|
|
41
|
-
return { handled: args.length === 0, result };
|
|
48
|
+
return { handled: args.length === 0 || !setupApplied(env), result };
|
|
42
49
|
}
|
|
@@ -23,5 +23,5 @@ export const Selector = ({ options, onSelect, onCancel }) => {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
|
-
return (_jsxs(Box, { flexDirection: "column", children: [options.map((option, i) => (_jsxs(Text, { ...(i === cursor ? { color: "cyan" } : {}), children: [i === cursor ? "❯" : " ", " ", i + 1, ". ", option.label] }, i))), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "Enter to confirm \u00B7
|
|
26
|
+
return (_jsxs(Box, { flexDirection: "column", children: [options.map((option, i) => (_jsxs(Text, { ...(i === cursor ? { color: "cyan" } : {}), children: [i === cursor ? "❯" : " ", " ", i + 1, ". ", option.label] }, i))), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "Enter to confirm \u00B7 Esc to skip" })] }));
|
|
27
27
|
};
|