@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,42 @@
|
|
|
1
|
+
import { resolvePresentation } from "../presentation/mode.js";
|
|
2
|
+
import { MACHINE_OUTPUT_FLAGS } from "../runtime/first-run.js";
|
|
3
|
+
import { buildSetupPlan } from "../setup/plan.js";
|
|
4
|
+
import { shouldOfferSetupWizard } from "./gate.js";
|
|
5
|
+
const SKIP_COMMANDS = new Set([
|
|
6
|
+
"help",
|
|
7
|
+
"--help",
|
|
8
|
+
"-h",
|
|
9
|
+
"--help-all",
|
|
10
|
+
"help-all",
|
|
11
|
+
"version",
|
|
12
|
+
"--version",
|
|
13
|
+
"-v",
|
|
14
|
+
"login",
|
|
15
|
+
"logout",
|
|
16
|
+
"setup",
|
|
17
|
+
"update",
|
|
18
|
+
"upgrade",
|
|
19
|
+
"uninstall"
|
|
20
|
+
]);
|
|
21
|
+
const NOT_RUN = { handled: false, result: { exitCode: 0, stdout: "", stderr: "" } };
|
|
22
|
+
export async function maybeOfferSetupWizard(args, options = {}) {
|
|
23
|
+
const env = options.env ?? process.env;
|
|
24
|
+
const command = args[0] ?? "";
|
|
25
|
+
if (command && SKIP_COMMANDS.has(command)) {
|
|
26
|
+
return NOT_RUN;
|
|
27
|
+
}
|
|
28
|
+
if (args.some((arg) => MACHINE_OUTPUT_FLAGS.has(arg))) {
|
|
29
|
+
return NOT_RUN;
|
|
30
|
+
}
|
|
31
|
+
if (!shouldOfferSetupWizard(env, options.stdin ?? process.stdin, options.stderr ?? process.stderr)) {
|
|
32
|
+
return NOT_RUN;
|
|
33
|
+
}
|
|
34
|
+
const rich = options.richMode ?? resolvePresentation().mode === "rich";
|
|
35
|
+
if (!rich) {
|
|
36
|
+
return NOT_RUN;
|
|
37
|
+
}
|
|
38
|
+
const plan = buildSetupPlan({ shell: "auto" });
|
|
39
|
+
const runWizard = options.runWizard ?? (await import("./wizard.js")).runSetupWizard;
|
|
40
|
+
const result = await runWizard(plan, { env, autoActivate: args.length === 0 });
|
|
41
|
+
return { handled: args.length === 0, result };
|
|
42
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
export const Selector = ({ options, onSelect, onCancel }) => {
|
|
5
|
+
const [cursor, setCursor] = useState(0);
|
|
6
|
+
useInput((input, key) => {
|
|
7
|
+
if (key.upArrow || input === "k") {
|
|
8
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
9
|
+
}
|
|
10
|
+
else if (key.downArrow || input === "j") {
|
|
11
|
+
setCursor((c) => Math.min(options.length - 1, c + 1));
|
|
12
|
+
}
|
|
13
|
+
else if (key.return) {
|
|
14
|
+
onSelect(cursor);
|
|
15
|
+
}
|
|
16
|
+
else if (key.escape) {
|
|
17
|
+
onCancel();
|
|
18
|
+
}
|
|
19
|
+
else if (/^[1-9]$/.test(input)) {
|
|
20
|
+
const index = Number(input) - 1;
|
|
21
|
+
if (index < options.length) {
|
|
22
|
+
onSelect(index);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
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 arrows or number keys to choose \u00B7 Esc to skip" })] }));
|
|
27
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { LockBusyError } from "../state/index.js";
|
|
2
|
+
import { applyGitHook } from "../setup/git-hook.js";
|
|
3
|
+
import { applySetupPlanWithLock } from "../setup/plan.js";
|
|
4
|
+
import { applyAgentHook } from "../agents/registry.js";
|
|
5
|
+
export function buildWizardTasks(plan, agents) {
|
|
6
|
+
const tasks = [
|
|
7
|
+
{
|
|
8
|
+
label: "shell installs route through dg",
|
|
9
|
+
run: async () => {
|
|
10
|
+
try {
|
|
11
|
+
applySetupPlanWithLock(plan);
|
|
12
|
+
return { ok: true };
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (error instanceof LockBusyError) {
|
|
16
|
+
return {
|
|
17
|
+
ok: false,
|
|
18
|
+
fatal: true,
|
|
19
|
+
detail: `dg setup cannot apply while another setup or uninstall is running: ${error.path}`
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
];
|
|
27
|
+
for (const offer of agents) {
|
|
28
|
+
tasks.push({
|
|
29
|
+
label: `${offer.label} installs route through dg`,
|
|
30
|
+
run: async () => {
|
|
31
|
+
try {
|
|
32
|
+
await applyAgentHook(offer.ctx);
|
|
33
|
+
return { ok: true };
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return { ok: false, detail: error instanceof Error ? error.message : "unknown error" };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return tasks;
|
|
42
|
+
}
|
|
43
|
+
export function buildCommitGuardTask(repo) {
|
|
44
|
+
return {
|
|
45
|
+
label: "commits in this repo are scanned",
|
|
46
|
+
run: async () => {
|
|
47
|
+
try {
|
|
48
|
+
applyGitHook(repo);
|
|
49
|
+
return { ok: true };
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return { ok: false, detail: error instanceof Error ? error.message : "unknown error" };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
4
|
+
import { Spinner } from "../scan-ui/components/Spinner.js";
|
|
5
|
+
import { renderLogo } from "../scan-ui/logo.js";
|
|
6
|
+
import { enterTui, leaveTui } from "../scan-ui/alt-screen.js";
|
|
7
|
+
import { dgVersion } from "../commands/version.js";
|
|
8
|
+
import { authStatus, displayTier } from "../auth/store.js";
|
|
9
|
+
import { resolveWebBase } from "../auth/device-login.js";
|
|
10
|
+
import { useLogin } from "../auth/login-app.js";
|
|
11
|
+
import { collectAgentOffers, collectAgentSkips } from "../agents/registry.js";
|
|
12
|
+
import { markFirstRunShown } from "../runtime/first-run.js";
|
|
13
|
+
import { recordLoginNudge } from "../runtime/nudges.js";
|
|
14
|
+
import { resolvePresentation } from "../presentation/mode.js";
|
|
15
|
+
import { createTheme } from "../presentation/theme.js";
|
|
16
|
+
import { activateShell, activationOffer } from "../setup/activate-shell.js";
|
|
17
|
+
import { commitGuardOffer } from "../setup/git-hook.js";
|
|
18
|
+
import { activationCommand, tildifyPath } from "../setup/plan.js";
|
|
19
|
+
import { markSecurityNotesShown, markWizardSkipped, securityNotesShown } from "./gate.js";
|
|
20
|
+
import { Selector } from "./selector.js";
|
|
21
|
+
import { buildCommitGuardTask, buildWizardTasks } from "./tasks.js";
|
|
22
|
+
export function protectsSentence(agentLabels) {
|
|
23
|
+
if (agentLabels.length === 0) {
|
|
24
|
+
return "Protects npm and pip installs in your shell.";
|
|
25
|
+
}
|
|
26
|
+
if (agentLabels.length === 1) {
|
|
27
|
+
return `Protects npm and pip installs in your shell and in ${agentLabels[0]}.`;
|
|
28
|
+
}
|
|
29
|
+
return `Protects npm and pip installs in your shell and in ${agentLabels.slice(0, -1).join(", ")} and ${agentLabels[agentLabels.length - 1]}.`;
|
|
30
|
+
}
|
|
31
|
+
const Header = ({ version }) => (_jsxs(Box, { flexDirection: "column", children: [renderLogo("pass").map((line, i) => (_jsx(Text, { children: line }, i))), _jsx(Text, { children: " " }), _jsxs(Text, { bold: true, children: ["Dependency Guardian v", version] }), _jsx(Text, { children: " " })] }));
|
|
32
|
+
const LoginFlow = ({ webBase, env, onDone }) => {
|
|
33
|
+
const { state, openAndPoll } = useLogin(webBase, env);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (state.phase === "ready") {
|
|
36
|
+
openAndPoll();
|
|
37
|
+
}
|
|
38
|
+
}, [state.phase, openAndPoll]);
|
|
39
|
+
useInput((_input, key) => {
|
|
40
|
+
if (key.return && (state.phase === "success" || state.phase === "expired" || state.phase === "error")) {
|
|
41
|
+
onDone(state.phase === "success" ? state.email : "");
|
|
42
|
+
}
|
|
43
|
+
else if (key.escape) {
|
|
44
|
+
onDone("");
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
switch (state.phase) {
|
|
48
|
+
case "creating":
|
|
49
|
+
return _jsx(Spinner, { label: "Creating login session\u2026" });
|
|
50
|
+
case "ready":
|
|
51
|
+
case "waiting":
|
|
52
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Sign in at:" }), _jsx(Text, { color: "cyan", children: state.verifyUrl }), _jsx(Text, { children: " " }), _jsx(Spinner, { label: "Waiting for you to approve in the browser\u2026" }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "Esc to skip" })] }));
|
|
53
|
+
case "success":
|
|
54
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "green", bold: true, children: ["\u2713 Logged in", state.email ? ` as ${state.email}` : "", state.plan ? ` (${displayTier(state.plan)} plan)` : ""] }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "Login successful. Press Enter to continue\u2026" })] }));
|
|
55
|
+
case "expired":
|
|
56
|
+
case "error":
|
|
57
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: state.phase === "expired" ? "That login link expired." : `Login failed: ${state.message}.` }), _jsx(Text, { dimColor: true, children: "You can run dg login later. Press Enter to continue\u2026" })] }));
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
function hyperlink(label, url) {
|
|
61
|
+
return `\u001B]8;;${url}\u0007${label}\u001B]8;;\u0007`;
|
|
62
|
+
}
|
|
63
|
+
const NotesStep = ({ termsUrl, privacyUrl, onAgree }) => {
|
|
64
|
+
useInput((_input, key) => {
|
|
65
|
+
if (key.return) {
|
|
66
|
+
onAgree();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Security notes" }), _jsx(Text, { children: " " }), _jsx(Text, { children: "1. dg can make mistakes." }), _jsx(Text, { children: " A PASS verdict does not guarantee a package is safe. You are" }), _jsx(Text, { children: " responsible for what you install and should review new" }), _jsx(Text, { children: " dependencies." }), _jsx(Text, { children: " " }), _jsx(Text, { children: "2. By continuing you confirm you have read and understand the" }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: hyperlink("Terms of Service", termsUrl) }), " and ", _jsx(Text, { color: "cyan", children: hyperlink("Privacy Policy", privacyUrl) }), "."] }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "Press Enter to agree" })] }));
|
|
70
|
+
};
|
|
71
|
+
export const SetupWizardApp = (props) => {
|
|
72
|
+
const { exit } = useApp();
|
|
73
|
+
const [step, setStep] = useState(props.loginNeeded ? "login" : props.notesNeeded ? "notes" : "choose");
|
|
74
|
+
const [results, setResults] = useState([]);
|
|
75
|
+
const [running, setRunning] = useState(null);
|
|
76
|
+
const [fatalDetail, setFatalDetail] = useState("");
|
|
77
|
+
const [email, setEmail] = useState(props.authedEmail);
|
|
78
|
+
const emailRef = useRef(props.authedEmail);
|
|
79
|
+
const applyStarted = useRef(false);
|
|
80
|
+
const Login = props.LoginFlowComponent ?? LoginFlow;
|
|
81
|
+
const afterLogin = () => {
|
|
82
|
+
setStep(props.notesNeeded ? "notes" : "choose");
|
|
83
|
+
};
|
|
84
|
+
useInput((_input, key) => {
|
|
85
|
+
if (key.return) {
|
|
86
|
+
exit();
|
|
87
|
+
}
|
|
88
|
+
}, { isActive: step === "done" });
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (step !== "declined" && step !== "fatal") {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
const timer = setTimeout(() => exit(), 30);
|
|
94
|
+
return () => clearTimeout(timer);
|
|
95
|
+
}, [step, exit]);
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (step !== "applying" || applyStarted.current) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
applyStarted.current = true;
|
|
101
|
+
let cancelled = false;
|
|
102
|
+
void (async () => {
|
|
103
|
+
const completed = [];
|
|
104
|
+
for (const task of props.tasks) {
|
|
105
|
+
if (cancelled) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
setRunning(task.label);
|
|
109
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
110
|
+
let result;
|
|
111
|
+
try {
|
|
112
|
+
result = await task.run();
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
result = { ok: false, fatal: true, detail: error instanceof Error ? error.message : "unknown error" };
|
|
116
|
+
}
|
|
117
|
+
if (cancelled) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const line = { label: task.label, ok: result.ok, detail: result.detail };
|
|
121
|
+
completed.push(line);
|
|
122
|
+
setResults((prev) => [...prev, line]);
|
|
123
|
+
if (result.fatal) {
|
|
124
|
+
setRunning(null);
|
|
125
|
+
setFatalDetail(result.detail ?? "");
|
|
126
|
+
props.onOutcome({ kind: "fatal", detail: result.detail ?? "" });
|
|
127
|
+
setStep("fatal");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
setRunning(null);
|
|
132
|
+
props.onOutcome({ kind: "done", email: emailRef.current, results: completed });
|
|
133
|
+
setStep("done");
|
|
134
|
+
})();
|
|
135
|
+
return () => {
|
|
136
|
+
cancelled = true;
|
|
137
|
+
};
|
|
138
|
+
}, [step, props]);
|
|
139
|
+
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, paddingTop: 1, children: [_jsx(Header, { version: props.version }), step === "login" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Connect your account" }), _jsx(Text, { children: " " }), _jsx(Selector, { options: [{ label: "Connect in your browser" }, { label: "Skip for now" }], onSelect: (index) => {
|
|
140
|
+
if (index === 0) {
|
|
141
|
+
setStep("login-flow");
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
props.onLoginSkipped();
|
|
145
|
+
afterLogin();
|
|
146
|
+
}
|
|
147
|
+
}, onCancel: () => {
|
|
148
|
+
props.onLoginSkipped();
|
|
149
|
+
afterLogin();
|
|
150
|
+
} })] })), step === "login-flow" && (_jsx(Login, { webBase: props.webBase, env: props.env, onDone: (loggedInEmail) => {
|
|
151
|
+
if (loggedInEmail) {
|
|
152
|
+
setEmail(loggedInEmail);
|
|
153
|
+
emailRef.current = loggedInEmail;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
props.onLoginSkipped();
|
|
157
|
+
}
|
|
158
|
+
afterLogin();
|
|
159
|
+
} })), step === "notes" && (_jsx(NotesStep, { termsUrl: `${props.webBase}/terms`, privacyUrl: `${props.webBase}/privacy`, onAgree: () => {
|
|
160
|
+
props.onNotesAgreed();
|
|
161
|
+
setStep("choose");
|
|
162
|
+
} })), step === "choose" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Use recommended settings?" }), _jsx(Text, { children: " " }), _jsx(Text, { children: props.sentence }), props.guardsRepo && _jsx(Text, { children: "Also scans this repo's commits before they land." }), props.skips.map((skip) => (_jsxs(Text, { dimColor: true, children: [skip.label, " found but skipped \u2014 ", skip.detail] }, skip.label))), _jsx(Text, { children: " " }), _jsx(Selector, { options: [{ label: "Yes, use recommended settings" }, { label: "No, maybe later with dg setup" }], onSelect: (index) => {
|
|
163
|
+
if (index === 0) {
|
|
164
|
+
setStep("applying");
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
props.onOutcome({ kind: "declined" });
|
|
168
|
+
setStep("declined");
|
|
169
|
+
}
|
|
170
|
+
}, onCancel: () => {
|
|
171
|
+
props.onOutcome({ kind: "declined" });
|
|
172
|
+
setStep("declined");
|
|
173
|
+
} })] })), (step === "applying" || step === "done" || step === "fatal") && (_jsxs(Box, { flexDirection: "column", children: [results.map((line) => (_jsxs(Text, { color: line.ok ? "green" : "yellow", children: [line.ok ? "✓" : "✗", " ", line.label, !line.ok && line.detail ? ` — ${line.detail}` : ""] }, line.label))), running !== null && _jsx(Spinner, { label: running }), step === "fatal" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: " " }), _jsx(Text, { color: "red", children: fatalDetail })] })), step === "done" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "\u2713 Setup complete." }), " ", "Activate now: ", _jsx(Text, { bold: true, children: props.activateHint })] }), email ? _jsxs(Text, { dimColor: true, children: ["Logged in as ", email] }) : null, _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "Press Enter to continue\u2026" })] }))] })), step === "declined" && _jsx(Text, { dimColor: true, children: "Run dg setup any time to turn this on." })] }));
|
|
174
|
+
};
|
|
175
|
+
export async function runSetupWizard(plan, options = {}) {
|
|
176
|
+
const env = options.env ?? process.env;
|
|
177
|
+
const ci = process.env.CI;
|
|
178
|
+
const restoreCi = ci === "" || ci === "0" || ci === "false";
|
|
179
|
+
if (restoreCi) {
|
|
180
|
+
delete process.env.CI;
|
|
181
|
+
}
|
|
182
|
+
const agents = collectAgentOffers();
|
|
183
|
+
const repo = commitGuardOffer();
|
|
184
|
+
const auth = authStatus(env);
|
|
185
|
+
const state = { outcome: null };
|
|
186
|
+
const tasks = buildWizardTasks(plan, agents);
|
|
187
|
+
if (repo) {
|
|
188
|
+
tasks.push(buildCommitGuardTask(repo));
|
|
189
|
+
}
|
|
190
|
+
const { render } = await import("ink");
|
|
191
|
+
enterTui();
|
|
192
|
+
const instance = render(_jsx(SetupWizardApp, { version: dgVersion(), notesNeeded: !securityNotesShown(env), loginNeeded: !auth.authenticated, authedEmail: auth.email ?? "", sentence: protectsSentence(agents.map((offer) => offer.label)), guardsRepo: repo !== null, skips: collectAgentSkips(), tasks: tasks, webBase: resolveWebBase(env), env: env, activateHint: activationCommand(plan.shell, tildifyPath(plan.rcPath)), onNotesAgreed: () => markSecurityNotesShown(env), onLoginSkipped: () => recordLoginNudge(env), onOutcome: (outcome) => {
|
|
193
|
+
state.outcome = outcome;
|
|
194
|
+
} }), { exitOnCtrlC: true });
|
|
195
|
+
await instance.waitUntilExit();
|
|
196
|
+
leaveTui();
|
|
197
|
+
if (restoreCi) {
|
|
198
|
+
process.env.CI = ci;
|
|
199
|
+
}
|
|
200
|
+
const theme = createTheme(resolvePresentation().color);
|
|
201
|
+
const accent = (text) => theme.paint("accent", text);
|
|
202
|
+
const muted = (text) => theme.paint("muted", text);
|
|
203
|
+
const outcome = state.outcome;
|
|
204
|
+
if (outcome === null) {
|
|
205
|
+
// Aborted (e.g. Ctrl-C). Record the skip so the wizard does not re-intercept
|
|
206
|
+
// every subsequent dg command until the user explicitly declines or applies it.
|
|
207
|
+
markWizardSkipped(env);
|
|
208
|
+
markFirstRunShown(env);
|
|
209
|
+
return { exitCode: 0, stdout: "", stderr: "" };
|
|
210
|
+
}
|
|
211
|
+
if (outcome.kind === "declined") {
|
|
212
|
+
markWizardSkipped(env);
|
|
213
|
+
markFirstRunShown(env);
|
|
214
|
+
return { exitCode: 0, stdout: "", stderr: ` ${muted("Run")} ${accent("dg setup")} ${muted("any time to turn this on.")}\n` };
|
|
215
|
+
}
|
|
216
|
+
if (outcome.kind === "fatal") {
|
|
217
|
+
return { exitCode: 1, stdout: "", stderr: ` ${outcome.detail}\n` };
|
|
218
|
+
}
|
|
219
|
+
markFirstRunShown(env);
|
|
220
|
+
const lines = outcome.results.map((line) => line.ok
|
|
221
|
+
? ` ${theme.paint("pass", `✓ ${line.label}`)}`
|
|
222
|
+
: ` ${theme.paint("warn", `✗ ${line.label}`)}${line.detail ? ` ${muted(`— ${line.detail}`)}` : ""}`);
|
|
223
|
+
lines.push(` ${theme.paint("pass", "✓ dg setup complete — installs are protected in new terminals.")}`);
|
|
224
|
+
if (outcome.email) {
|
|
225
|
+
lines.push(` ${muted(`Logged in as ${outcome.email}`)}`);
|
|
226
|
+
}
|
|
227
|
+
if (options.autoActivate && activationOffer() === "prompt") {
|
|
228
|
+
process.stderr.write(`${lines.join("\n")}\n ${muted("Starting a protected shell — type")} ${accent("exit")} ${muted("to return to your previous one.")}\n`);
|
|
229
|
+
return { exitCode: activateShell(), stdout: "", stderr: "" };
|
|
230
|
+
}
|
|
231
|
+
lines.push(` ${muted("Activate now:")} ${accent(activationCommand(plan.shell, tildifyPath(plan.rcPath)))}`);
|
|
232
|
+
return { exitCode: 0, stdout: "", stderr: `${lines.join("\n")}\n` };
|
|
233
|
+
}
|