@westbayberry/dg 2.1.0 → 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 +1 -1
- 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 +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 +197 -27
- 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 +14 -44
- package/dist/decisions/remember-prompt.js +5 -10
- package/dist/export-ui/ExportDialog.js +198 -0
- package/dist/install-ui/LiveInstall.js +2 -2
- package/dist/install-ui/block-render.js +1 -1
- package/dist/install-ui/prep-spinner.js +32 -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 +51 -14
- package/dist/launcher/live-install.js +25 -5
- package/dist/launcher/output-redaction.js +7 -4
- package/dist/launcher/preflight-prompt.js +14 -1
- package/dist/launcher/run.js +89 -86
- 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 +145 -6
- 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 +41 -3
- package/dist/proxy/preverified.js +55 -0
- package/dist/proxy/server.js +448 -49
- 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 +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 +2 -1
- 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 +16 -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/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 +73 -39
- 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/report-writer.js +57 -0
- package/dist/verify/local.js +240 -42
- package/dist/verify/package-check.js +84 -28
- package/dist/verify/preflight.js +242 -49
- package/dist/verify/render.js +15 -1
- package/npm-shrinkwrap.json +2383 -0
- package/package.json +6 -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,127 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { resolveDgPaths } from "../state/index.js";
|
|
4
|
+
import { claudeCodeIntegration } from "./claude-code.js";
|
|
5
|
+
import { codexIntegration } from "./codex.js";
|
|
6
|
+
import { copilotCliIntegration } from "./copilot-cli.js";
|
|
7
|
+
import { cursorIntegration } from "./cursor.js";
|
|
8
|
+
import { geminiIntegration } from "./gemini.js";
|
|
9
|
+
import { LEGACY_AGENT_HOOK_SENTINEL } from "./persistence.js";
|
|
10
|
+
import { windsurfIntegration } from "./windsurf.js";
|
|
11
|
+
export const AGENTS = {
|
|
12
|
+
"claude-code": claudeCodeIntegration,
|
|
13
|
+
codex: codexIntegration,
|
|
14
|
+
cursor: cursorIntegration,
|
|
15
|
+
"copilot-cli": copilotCliIntegration,
|
|
16
|
+
gemini: geminiIntegration,
|
|
17
|
+
windsurf: windsurfIntegration,
|
|
18
|
+
};
|
|
19
|
+
export const AGENT_IDS = Object.keys(AGENTS);
|
|
20
|
+
export function isAgentId(value) {
|
|
21
|
+
return value in AGENTS;
|
|
22
|
+
}
|
|
23
|
+
export function getAgent(id) {
|
|
24
|
+
return AGENTS[id];
|
|
25
|
+
}
|
|
26
|
+
export function agentLabel(id) {
|
|
27
|
+
return isAgentId(id) ? AGENTS[id].label : id;
|
|
28
|
+
}
|
|
29
|
+
export function agentHookSignature(agent) {
|
|
30
|
+
return `hook-exec ${agent}`;
|
|
31
|
+
}
|
|
32
|
+
export function defaultDgCommand(agent) {
|
|
33
|
+
let bin = process.argv[1] ?? "dg";
|
|
34
|
+
try {
|
|
35
|
+
bin = realpathSync(bin);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// keep argv[1]; the hook still resolves via PATH as a fallback
|
|
39
|
+
}
|
|
40
|
+
// Absolute node + script path so the agent's non-interactive subprocess
|
|
41
|
+
// can't lose it to a PATH change (the whole point of the hook).
|
|
42
|
+
return `${process.execPath} ${bin} ${agentHookSignature(agent)}`;
|
|
43
|
+
}
|
|
44
|
+
export function resolveAgentHookContext(agent, options = {}) {
|
|
45
|
+
const env = options.env ?? process.env;
|
|
46
|
+
const home = options.home ?? env.HOME ?? homedir();
|
|
47
|
+
return {
|
|
48
|
+
agent,
|
|
49
|
+
settingsPath: AGENTS[agent].configPath(home),
|
|
50
|
+
dgCommand: options.dgCommand ?? defaultDgCommand(agent),
|
|
51
|
+
paths: resolveDgPaths(env),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function applyAgentHook(ctx) {
|
|
55
|
+
return AGENTS[ctx.agent].apply(ctx);
|
|
56
|
+
}
|
|
57
|
+
export function removeAgentHookForAgent(ctx) {
|
|
58
|
+
return AGENTS[ctx.agent].remove(ctx);
|
|
59
|
+
}
|
|
60
|
+
export function verifyAgentHook(ctx) {
|
|
61
|
+
return AGENTS[ctx.agent].verify(ctx);
|
|
62
|
+
}
|
|
63
|
+
function agentFromSentinel(sentinel) {
|
|
64
|
+
if (!sentinel || sentinel === LEGACY_AGENT_HOOK_SENTINEL) {
|
|
65
|
+
return "claude-code";
|
|
66
|
+
}
|
|
67
|
+
if (!sentinel.startsWith(`${LEGACY_AGENT_HOOK_SENTINEL}:`)) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const id = sentinel.slice(LEGACY_AGENT_HOOK_SENTINEL.length + 1);
|
|
71
|
+
return isAgentId(id) ? id : null;
|
|
72
|
+
}
|
|
73
|
+
export function reverseAgentHookEntry(entry, removed, missing, warnings) {
|
|
74
|
+
const agent = agentFromSentinel(entry.sentinel);
|
|
75
|
+
if (!agent) {
|
|
76
|
+
warnings.push(`${entry.path}: unrecognized agent hook entry (${entry.sentinel ?? "no sentinel"}); left untouched`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
AGENTS[agent].reverseEntry(entry, removed, missing, warnings);
|
|
80
|
+
}
|
|
81
|
+
export function collectAgentSkips(options) {
|
|
82
|
+
const skips = [];
|
|
83
|
+
const env = options?.env ?? process.env;
|
|
84
|
+
const home = options?.home ?? env.HOME ?? homedir();
|
|
85
|
+
for (const agent of AGENT_IDS) {
|
|
86
|
+
try {
|
|
87
|
+
const integration = AGENTS[agent];
|
|
88
|
+
if (!integration.detect(home)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const probe = integration.probeHookSupport(home);
|
|
92
|
+
if (!probe.supported) {
|
|
93
|
+
skips.push({ agent, label: integration.label, detail: probe.detail });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return skips;
|
|
101
|
+
}
|
|
102
|
+
export function collectAgentOffers(options) {
|
|
103
|
+
const offers = [];
|
|
104
|
+
const env = options?.env ?? process.env;
|
|
105
|
+
const home = options?.home ?? env.HOME ?? homedir();
|
|
106
|
+
for (const agent of AGENT_IDS) {
|
|
107
|
+
try {
|
|
108
|
+
const integration = AGENTS[agent];
|
|
109
|
+
if (!integration.detect(home)) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const probe = integration.probeHookSupport(home);
|
|
113
|
+
if (!probe.supported) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const ctx = resolveAgentHookContext(agent, options ?? {});
|
|
117
|
+
const hooked = integration.verify(ctx).find((check) => check.name === integration.isInstalledCheckName)?.ok ?? false;
|
|
118
|
+
if (!hooked) {
|
|
119
|
+
offers.push({ agent, label: integration.label, ctx, probe });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return offers;
|
|
127
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { dirExists, mergedJsonHook } from "./persistence.js";
|
|
3
|
+
const SIGNATURE = "hook-exec windsurf";
|
|
4
|
+
function configPath(home) {
|
|
5
|
+
return join(home, ".codeium", "windsurf", "hooks.json");
|
|
6
|
+
}
|
|
7
|
+
function isDgEntry(entry) {
|
|
8
|
+
return (typeof entry === "object" &&
|
|
9
|
+
entry !== null &&
|
|
10
|
+
typeof entry.command === "string" &&
|
|
11
|
+
entry.command.includes(SIGNATURE));
|
|
12
|
+
}
|
|
13
|
+
function insertHook(settings, dgCommand) {
|
|
14
|
+
const next = { ...settings };
|
|
15
|
+
const hooks = typeof next.hooks === "object" && next.hooks !== null ? { ...next.hooks } : {};
|
|
16
|
+
const list = Array.isArray(hooks.pre_run_command) ? hooks.pre_run_command : [];
|
|
17
|
+
hooks.pre_run_command = [...list.filter((entry) => !isDgEntry(entry)), { command: dgCommand }];
|
|
18
|
+
next.hooks = hooks;
|
|
19
|
+
return next;
|
|
20
|
+
}
|
|
21
|
+
function removeHook(settings) {
|
|
22
|
+
const next = { ...settings };
|
|
23
|
+
if (typeof next.hooks !== "object" || next.hooks === null) {
|
|
24
|
+
return { settings: next, changed: false, empty: Object.keys(next).length === 0 };
|
|
25
|
+
}
|
|
26
|
+
const hooks = { ...next.hooks };
|
|
27
|
+
const list = Array.isArray(hooks.pre_run_command) ? hooks.pre_run_command : [];
|
|
28
|
+
const withoutDg = list.filter((entry) => !isDgEntry(entry));
|
|
29
|
+
const changed = withoutDg.length !== list.length;
|
|
30
|
+
if (withoutDg.length > 0) {
|
|
31
|
+
hooks.pre_run_command = withoutDg;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
delete hooks.pre_run_command;
|
|
35
|
+
}
|
|
36
|
+
if (Object.keys(hooks).length > 0) {
|
|
37
|
+
next.hooks = hooks;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
delete next.hooks;
|
|
41
|
+
}
|
|
42
|
+
return { settings: next, changed, empty: Object.keys(next).length === 0 };
|
|
43
|
+
}
|
|
44
|
+
function hasDgHook(settings) {
|
|
45
|
+
const hooks = settings.hooks?.pre_run_command;
|
|
46
|
+
return Array.isArray(hooks) && hooks.some((entry) => isDgEntry(entry));
|
|
47
|
+
}
|
|
48
|
+
function parseInput(stdin) {
|
|
49
|
+
try {
|
|
50
|
+
const obj = JSON.parse(stdin);
|
|
51
|
+
const command = obj.tool_info?.command_line;
|
|
52
|
+
if (typeof command !== "string") {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const cwd = obj.tool_info?.cwd;
|
|
56
|
+
return typeof cwd === "string" ? { command, cwd } : { command };
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function emitDecision(verdict) {
|
|
63
|
+
if (verdict.decision === "allow") {
|
|
64
|
+
return { stdout: "", exitCode: 0 };
|
|
65
|
+
}
|
|
66
|
+
const reason = verdict.reason ?? "Dependency Guardian firewall";
|
|
67
|
+
const suffix = verdict.decision === "ask" ? " (flagged for review — run the install in a terminal to decide)" : "";
|
|
68
|
+
return { stdout: `${reason}${suffix}\n`, exitCode: 2 };
|
|
69
|
+
}
|
|
70
|
+
function probeHookSupport(home) {
|
|
71
|
+
if (!dirExists(join(home, ".codeium", "windsurf"))) {
|
|
72
|
+
return { supported: false, detail: "~/.codeium/windsurf not found (is Windsurf installed?)" };
|
|
73
|
+
}
|
|
74
|
+
return { supported: true, detail: "Windsurf config directory found; dg cannot read the app version from disk" };
|
|
75
|
+
}
|
|
76
|
+
export const windsurfIntegration = {
|
|
77
|
+
id: "windsurf",
|
|
78
|
+
label: "Windsurf",
|
|
79
|
+
kind: "merged-json",
|
|
80
|
+
maturity: "unverified",
|
|
81
|
+
minVersion: null,
|
|
82
|
+
configPath,
|
|
83
|
+
detect: (home) => dirExists(join(home, ".codeium", "windsurf")),
|
|
84
|
+
probeHookSupport,
|
|
85
|
+
parseInput,
|
|
86
|
+
emitDecision,
|
|
87
|
+
...mergedJsonHook({
|
|
88
|
+
checkName: "dg pre_run_command hook",
|
|
89
|
+
insert: insertHook,
|
|
90
|
+
remove: removeHook,
|
|
91
|
+
isInstalled: hasDgHook,
|
|
92
|
+
}),
|
|
93
|
+
};
|
package/dist/api/analyze.js
CHANGED
|
@@ -328,7 +328,7 @@ function normalizeAction(action) {
|
|
|
328
328
|
if (action === "block" || action === "warn" || action === "analysis_incomplete" || action === "pass") {
|
|
329
329
|
return action;
|
|
330
330
|
}
|
|
331
|
-
return "
|
|
331
|
+
return "analysis_incomplete";
|
|
332
332
|
}
|
|
333
333
|
export function mergeAnalyzeResponses(responses) {
|
|
334
334
|
const first = responses[0];
|
package/dist/audit/detectors.js
CHANGED
|
@@ -133,17 +133,6 @@ function contentChecks(file, push) {
|
|
|
133
133
|
}
|
|
134
134
|
function structuralProjectChecks(context, push) {
|
|
135
135
|
if (context.ecosystem === "npm" && context.packageJson) {
|
|
136
|
-
if (context.packageJson.private === true) {
|
|
137
|
-
push({
|
|
138
|
-
id: "publishing-private",
|
|
139
|
-
category: "structural",
|
|
140
|
-
severity: 4,
|
|
141
|
-
title: "Package is marked private but is being audited for publish",
|
|
142
|
-
recommendation: "A private package being published is almost always a mistake — remove \"private\" or do not publish.",
|
|
143
|
-
location: "package.json",
|
|
144
|
-
evidence: "\"private\": true"
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
136
|
if (!context.hasFilesAllowlist) {
|
|
148
137
|
push({
|
|
149
138
|
id: "no-files-allowlist",
|
package/dist/audit/events.js
CHANGED
|
@@ -1,33 +1,17 @@
|
|
|
1
1
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import { authStatus } from "../auth/store.js";
|
|
4
|
-
import { loadUserConfig } from "../config/settings.js";
|
|
5
3
|
import { resolveDgPaths } from "../state/index.js";
|
|
6
4
|
export function auditLogPath(paths) {
|
|
7
5
|
return join(paths.stateDir, "audit.jsonl");
|
|
8
6
|
}
|
|
9
|
-
export function webhookOutboxPath(paths) {
|
|
10
|
-
return join(paths.stateDir, "webhooks.jsonl");
|
|
11
|
-
}
|
|
12
7
|
export function recordAuditEvent(event, env = process.env) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const status = authStatus(env);
|
|
19
|
-
if (!config.webhooks.enabled || !status.authenticated) {
|
|
8
|
+
try {
|
|
9
|
+
appendJsonLine(auditLogPath(resolveDgPaths(env)), event);
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
20
13
|
return false;
|
|
21
14
|
}
|
|
22
|
-
const paths = resolveDgPaths(env);
|
|
23
|
-
appendJsonLine(webhookOutboxPath(paths), {
|
|
24
|
-
...event,
|
|
25
|
-
auth: {
|
|
26
|
-
source: status.source,
|
|
27
|
-
token: status.tokenPreview
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
return true;
|
|
31
15
|
}
|
|
32
16
|
function appendJsonLine(path, value) {
|
|
33
17
|
mkdirSync(dirname(path), {
|
|
@@ -3,8 +3,10 @@ import { useCallback, useEffect, useState } from "react";
|
|
|
3
3
|
import { Box, useApp } from "ink";
|
|
4
4
|
import { combineAction, auditExitCode, displayTarget } from "../commands/audit.js";
|
|
5
5
|
import { AuditResultsView } from "./components/AuditResultsView.js";
|
|
6
|
+
import { useResizeRepaint } from "../scan-ui/hooks/useResizeRepaint.js";
|
|
6
7
|
export const AuditApp = ({ gathered, initialDeep, deepPromise, onExitCode }) => {
|
|
7
8
|
const { exit } = useApp();
|
|
9
|
+
useResizeRepaint();
|
|
8
10
|
const [deep, setDeep] = useState(initialDeep);
|
|
9
11
|
useEffect(() => {
|
|
10
12
|
if (!deepPromise)
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useMemo, useRef, useState } from "react";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { Box, Text, useInput } from "ink";
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
-
import { writeFileSync } from "node:fs";
|
|
6
|
-
import { resolve as resolvePath } from "node:path";
|
|
7
5
|
import { findingLocation } from "../../audit/detectors.js";
|
|
8
6
|
import { isLoggedIn } from "../../scan-ui/shims.js";
|
|
9
7
|
import { clearScreen } from "../../scan-ui/alt-screen.js";
|
|
@@ -12,7 +10,10 @@ import { pad, truncate } from "../../scan-ui/format-helpers.js";
|
|
|
12
10
|
import { AuditHeader } from "./AuditHeader.js";
|
|
13
11
|
import { DeepStatusRow } from "./DeepStatusRow.js";
|
|
14
12
|
import { countSummary, severityGlyph, severityRole } from "../format.js";
|
|
15
|
-
import { buildExport
|
|
13
|
+
import { buildExport } from "../export.js";
|
|
14
|
+
import { ExportDialog, loginRequiredToast } from "../../export-ui/ExportDialog.js";
|
|
15
|
+
import { resolvePresentation } from "../../presentation/mode.js";
|
|
16
|
+
import { createTheme } from "../../presentation/theme.js";
|
|
16
17
|
const ROLE_PAINT = {
|
|
17
18
|
block: chalk.red,
|
|
18
19
|
warn: chalk.yellow,
|
|
@@ -23,6 +24,11 @@ function glyphPaint(severity) {
|
|
|
23
24
|
}
|
|
24
25
|
const FIXED_CHROME = 12;
|
|
25
26
|
const EXPAND_CHROME = 10;
|
|
27
|
+
const EXPORT_FORMATS = [
|
|
28
|
+
{ format: "json", label: "JSON" },
|
|
29
|
+
{ format: "md", label: "Markdown" },
|
|
30
|
+
{ format: "txt", label: "Plain text" }
|
|
31
|
+
];
|
|
26
32
|
export const AuditResultsView = ({ findings, action, artifact, ecosystem, target, fileCount, publishSetSource, deep, onExit }) => {
|
|
27
33
|
const { rows: termRows, cols: termCols } = useTerminalSize();
|
|
28
34
|
const [cursor, setCursor] = useState(0);
|
|
@@ -36,17 +42,22 @@ export const AuditResultsView = ({ findings, action, artifact, ecosystem, target
|
|
|
36
42
|
const [showHelp, setShowHelp] = useState(false);
|
|
37
43
|
const showHelpRef = useRef(showHelp);
|
|
38
44
|
showHelpRef.current = showHelp;
|
|
39
|
-
const [
|
|
40
|
-
const
|
|
41
|
-
|
|
45
|
+
const [exportDialog, setExportDialog] = useState(null);
|
|
46
|
+
const exportDialogRef = useRef(exportDialog);
|
|
47
|
+
exportDialogRef.current = exportDialog;
|
|
48
|
+
const theme = useMemo(() => createTheme(resolvePresentation().color), []);
|
|
42
49
|
const [exportMsg, setExportMsg] = useState(null);
|
|
43
50
|
const exportTimer = useRef(null);
|
|
44
|
-
const showExportMsg = (
|
|
45
|
-
setExportMsg(
|
|
51
|
+
const showExportMsg = (text, tone = "ok") => {
|
|
52
|
+
setExportMsg({ text, tone });
|
|
46
53
|
if (exportTimer.current)
|
|
47
54
|
clearTimeout(exportTimer.current);
|
|
48
55
|
exportTimer.current = setTimeout(() => setExportMsg(null), 4000);
|
|
49
56
|
};
|
|
57
|
+
useEffect(() => () => {
|
|
58
|
+
if (exportTimer.current)
|
|
59
|
+
clearTimeout(exportTimer.current);
|
|
60
|
+
}, []);
|
|
50
61
|
const filtered = useMemo(() => {
|
|
51
62
|
if (!searchQuery)
|
|
52
63
|
return findings;
|
|
@@ -88,70 +99,40 @@ export const AuditResultsView = ({ findings, action, artifact, ecosystem, target
|
|
|
88
99
|
setViewport(clamped - listRows + 1);
|
|
89
100
|
}
|
|
90
101
|
};
|
|
91
|
-
const
|
|
102
|
+
const openExport = () => {
|
|
92
103
|
if (!isLoggedIn()) {
|
|
93
|
-
showExportMsg(
|
|
104
|
+
showExportMsg(loginRequiredToast(), "nudge");
|
|
94
105
|
return;
|
|
95
106
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
107
|
+
const input = {
|
|
108
|
+
target,
|
|
109
|
+
artifact,
|
|
110
|
+
ecosystem,
|
|
111
|
+
action,
|
|
112
|
+
fileCount,
|
|
113
|
+
publishSetSource,
|
|
114
|
+
findings,
|
|
115
|
+
deep: deep ?? { ran: false, reason: "deep behavioral scan did not run" }
|
|
116
|
+
};
|
|
117
|
+
setExportDialog(EXPORT_FORMATS.map(({ format, label }) => ({
|
|
118
|
+
label,
|
|
119
|
+
defaultName: `dg-audit.${format}`,
|
|
120
|
+
render: () => buildExport(input, format).body
|
|
121
|
+
})));
|
|
122
|
+
};
|
|
123
|
+
const handleExportDone = (result) => {
|
|
124
|
+
setExportDialog(null);
|
|
125
|
+
clearScreen();
|
|
126
|
+
if (result === null)
|
|
127
|
+
return;
|
|
128
|
+
if ("path" in result)
|
|
129
|
+
showExportMsg(`✓ Exported to ${result.path}`);
|
|
130
|
+
else
|
|
131
|
+
showExportMsg(`Export failed: ${result.error}`, "error");
|
|
115
132
|
};
|
|
116
133
|
useInput((input, key) => {
|
|
117
|
-
if (
|
|
118
|
-
const menu = exportMenuRef.current;
|
|
119
|
-
const FORMATS = ["json", "md", "txt"];
|
|
120
|
-
const move = (cur, dir) => {
|
|
121
|
-
const i = FORMATS.indexOf(cur);
|
|
122
|
-
return FORMATS[Math.max(0, Math.min(FORMATS.length - 1, i + dir))] ?? cur;
|
|
123
|
-
};
|
|
124
|
-
if (key.escape) {
|
|
125
|
-
setExportMenu(null);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
if (input === "q") {
|
|
129
|
-
setExportMenu(null);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
if (key.return) {
|
|
133
|
-
const format = menu.format;
|
|
134
|
-
setExportMenu(null);
|
|
135
|
-
clearScreen();
|
|
136
|
-
runExport(format);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
if (key.tab || key.leftArrow || key.rightArrow) {
|
|
140
|
-
setExportMenu({ ...menu, activeRow: menu.activeRow === "scope" ? "format" : "scope" });
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
if (key.upArrow || input === "k") {
|
|
144
|
-
if (menu.activeRow === "format")
|
|
145
|
-
setExportMenu({ ...menu, format: move(menu.format, -1) });
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
if (key.downArrow || input === "j") {
|
|
149
|
-
if (menu.activeRow === "format")
|
|
150
|
-
setExportMenu({ ...menu, format: move(menu.format, 1) });
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
134
|
+
if (exportDialogRef.current)
|
|
153
135
|
return;
|
|
154
|
-
}
|
|
155
136
|
if (showHelpRef.current) {
|
|
156
137
|
if (input === "?" || key.escape)
|
|
157
138
|
setShowHelp(false);
|
|
@@ -219,11 +200,7 @@ export const AuditResultsView = ({ findings, action, artifact, ecosystem, target
|
|
|
219
200
|
return;
|
|
220
201
|
}
|
|
221
202
|
if (input === "e") {
|
|
222
|
-
|
|
223
|
-
showExportMsg("Export requires `dg login` (free account)");
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
setExportMenu({ format: "json", activeRow: "scope" });
|
|
203
|
+
openExport();
|
|
227
204
|
return;
|
|
228
205
|
}
|
|
229
206
|
return;
|
|
@@ -233,11 +210,7 @@ export const AuditResultsView = ({ findings, action, artifact, ecosystem, target
|
|
|
233
210
|
return;
|
|
234
211
|
}
|
|
235
212
|
if (input === "e") {
|
|
236
|
-
|
|
237
|
-
showExportMsg("Export requires `dg login` (free account)");
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
setExportMenu({ format: "json", activeRow: "scope" });
|
|
213
|
+
openExport();
|
|
241
214
|
return;
|
|
242
215
|
}
|
|
243
216
|
if (input === "q") {
|
|
@@ -269,19 +242,9 @@ export const AuditResultsView = ({ findings, action, artifact, ecosystem, target
|
|
|
269
242
|
}
|
|
270
243
|
else if (input === "/")
|
|
271
244
|
setSearchMode(true);
|
|
272
|
-
});
|
|
273
|
-
if (
|
|
274
|
-
|
|
275
|
-
{ value: "json", label: "JSON" },
|
|
276
|
-
{ value: "md", label: "Markdown" },
|
|
277
|
-
{ value: "txt", label: "Plain text" }
|
|
278
|
-
];
|
|
279
|
-
const renderColumn = (title, isActive, rows) => (_jsxs(Box, { flexDirection: "column", marginRight: 4, children: [_jsxs(Text, { children: [isActive ? chalk.cyan("▌ ") : " ", isActive ? chalk.bold(title) : chalk.dim(title)] }), rows] }));
|
|
280
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(AuditHeader, { action: action, artifact: artifact, ecosystem: ecosystem, countSummary: countSummary(findings), fileCount: fileCount, fallback: publishSetSource === "fallback" }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingLeft: 2, paddingRight: 2, children: [_jsx(Text, { bold: true, children: "Export" }), _jsx(Text, { children: "" }), _jsxs(Box, { flexDirection: "row", children: [renderColumn("What", exportMenu.activeRow === "scope", (_jsxs(Box, { children: [_jsx(Box, { width: 5, flexShrink: 0, children: _jsxs(Text, { children: [" ", exportMenu.activeRow === "scope" ? chalk.cyan("●") : chalk.green("●")] }) }), _jsx(Text, { children: chalk.bold("Findings") })] }))), renderColumn("Format", exportMenu.activeRow === "format", (_jsx(_Fragment, { children: FORMATS_RENDER.map((r) => {
|
|
281
|
-
const selected = r.value === exportMenu.format;
|
|
282
|
-
const bullet = selected ? (exportMenu.activeRow === "format" ? chalk.cyan("●") : chalk.green("●")) : chalk.dim("○");
|
|
283
|
-
return (_jsxs(Box, { children: [_jsx(Box, { width: 5, flexShrink: 0, children: _jsxs(Text, { children: [" ", bullet] }) }), _jsx(Text, { children: selected ? chalk.bold(r.label) : r.label })] }, r.value));
|
|
284
|
-
}) })))] })] }), _jsx(Text, { dimColor: true, children: chalk.dim("─".repeat(Math.max(20, termCols - 4))) }), _jsxs(Text, { children: [" ", chalk.bold.cyan("↑↓"), " ", chalk.dim("scroll"), " ", chalk.bold.cyan("←→/Tab"), " ", chalk.dim("switch row"), " ", chalk.bold.cyan("⏎"), " ", chalk.dim("export"), " ", chalk.bold.cyan("Esc"), " ", chalk.dim("cancel")] })] }));
|
|
245
|
+
}, { isActive: exportDialog === null });
|
|
246
|
+
if (exportDialog) {
|
|
247
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(AuditHeader, { action: action, artifact: artifact, ecosystem: ecosystem, countSummary: countSummary(findings), fileCount: fileCount, fallback: publishSetSource === "fallback" }), _jsx(ExportDialog, { options: exportDialog, theme: theme, cwd: process.cwd(), onDone: handleExportDone })] }));
|
|
285
248
|
}
|
|
286
249
|
if (showHelp) {
|
|
287
250
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(AuditHeader, { action: action, artifact: artifact, ecosystem: ecosystem, countSummary: countSummary(findings), fileCount: fileCount, fallback: publishSetSource === "fallback" }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingLeft: 2, paddingRight: 2, width: "100%", children: [_jsx(Text, { bold: true, children: "Keyboard Shortcuts" }), _jsx(Text, { children: "" }), _jsx(Text, { bold: true, children: " Navigation" }), _jsxs(Text, { children: [" ", chalk.cyan("↑ k"), " ", chalk.dim("Move up")] }), _jsxs(Text, { children: [" ", chalk.cyan("↓ j"), " ", chalk.dim("Move down")] }), _jsxs(Text, { children: [" ", chalk.cyan("g"), " ", chalk.dim("Jump to top")] }), _jsxs(Text, { children: [" ", chalk.cyan("G"), " ", chalk.dim("Jump to bottom")] }), _jsxs(Text, { children: [" ", chalk.cyan("PgUp"), " ", chalk.dim("Page up")] }), _jsxs(Text, { children: [" ", chalk.cyan("PgDn"), " ", chalk.dim("Page down")] }), _jsx(Text, { children: "" }), _jsx(Text, { bold: true, children: " Actions" }), _jsxs(Text, { children: [" ", chalk.cyan("⏎"), " ", chalk.dim("Expand / collapse finding (evidence + recommendation)")] }), _jsxs(Text, { children: [" ", chalk.cyan("/"), " ", chalk.dim("Search findings")] }), _jsxs(Text, { children: [" ", chalk.cyan("e"), " ", chalk.dim("Export menu — Findings as JSON / Markdown / text")] }), _jsxs(Text, { children: [" ", chalk.cyan("q"), " ", chalk.dim("Quit")] }), _jsx(Text, { children: "" }), _jsxs(Text, { dimColor: true, children: [" Press ", chalk.bold.cyan("?"), " or ", chalk.bold.cyan("Esc"), " to close"] })] })] }));
|
|
@@ -303,5 +266,5 @@ export const AuditResultsView = ({ findings, action, artifact, ecosystem, target
|
|
|
303
266
|
return (_jsxs(Text, { backgroundColor: "#1a1a2e", children: [chalk.cyan("▌"), " ", paint(glyph), " ", chalk.bold(loc), chalk.dim(title)] }, `${finding.id}|${finding.location}`));
|
|
304
267
|
}
|
|
305
268
|
return (_jsxs(Text, { children: [" ", paint(glyph), " ", loc, chalk.dim(title)] }, `${finding.id}|${finding.location}`));
|
|
306
|
-
}), belowCount > 0 && _jsxs(Text, { dimColor: true, children: [chalk.cyan(" ↓"), " ", belowCount, " more below"] }), expanded && focused && (_jsxs(Box, { flexDirection: "column", marginTop: 1, marginLeft: 4, children: [_jsxs(Text, { bold: true, children: [findingLocation(focused), chalk.dim(" — "), focused.title] }), expandLines.slice(expandScroll, expandScroll + expandViewportRows).map((line, i) => (_jsx(Text, { children: line }, i))), expandLines.length > expandViewportRows && (_jsx(Text, { dimColor: true, children: ` ${expandScroll + 1}-${Math.min(expandScroll + expandViewportRows, expandLines.length)} / ${expandLines.length} (Esc collapse)` }))] }))] }), _jsx(Text, { dimColor: true, children: chalk.dim("─".repeat(Math.max(20, termCols - 4))) }), _jsx(DeepStatusRow, { deep: deep }), _jsx(Text, { dimColor: true, children: chalk.dim("─".repeat(Math.max(20, termCols - 4))) }), searchMode ? (_jsxs(Text, { children: [" ", chalk.bold.cyan("/"), " ", searchQuery, chalk.cyan("█"), " ", chalk.dim("Esc clear · Enter confirm")] })) : (_jsxs(Text, { children: [" ", filtered.length > 0 && (_jsxs(_Fragment, { children: [chalk.bold.cyan("↑↓"), " ", chalk.dim("navigate"), " ", chalk.bold.cyan("⏎"), " ", chalk.dim(expanded ? "collapse" : "expand"), " ", chalk.bold.cyan("/"), " ", chalk.dim("search"), " "] })), chalk.bold.cyan("e"), " ", chalk.dim("export"), " ", chalk.bold.cyan("?"), " ", chalk.dim("help"), " ", chalk.bold.cyan("q"), " ", chalk.dim("quit"), exportMsg && _jsxs(_Fragment, { children: [" ", chalk.green(exportMsg)] })] }))] }));
|
|
269
|
+
}), belowCount > 0 && _jsxs(Text, { dimColor: true, children: [chalk.cyan(" ↓"), " ", belowCount, " more below"] }), expanded && focused && (_jsxs(Box, { flexDirection: "column", marginTop: 1, marginLeft: 4, children: [_jsxs(Text, { bold: true, children: [findingLocation(focused), chalk.dim(" — "), focused.title] }), expandLines.slice(expandScroll, expandScroll + expandViewportRows).map((line, i) => (_jsx(Text, { children: line }, i))), expandLines.length > expandViewportRows && (_jsx(Text, { dimColor: true, children: ` ${expandScroll + 1}-${Math.min(expandScroll + expandViewportRows, expandLines.length)} / ${expandLines.length} (Esc collapse)` }))] }))] }), _jsx(Text, { dimColor: true, children: chalk.dim("─".repeat(Math.max(20, termCols - 4))) }), _jsx(DeepStatusRow, { deep: deep }), _jsx(Text, { dimColor: true, children: chalk.dim("─".repeat(Math.max(20, termCols - 4))) }), searchMode ? (_jsxs(Text, { children: [" ", chalk.bold.cyan("/"), " ", searchQuery, chalk.cyan("█"), " ", chalk.dim("Esc clear · Enter confirm")] })) : (_jsxs(Text, { children: [" ", filtered.length > 0 && (_jsxs(_Fragment, { children: [chalk.bold.cyan("↑↓"), " ", chalk.dim("navigate"), " ", chalk.bold.cyan("⏎"), " ", chalk.dim(expanded ? "collapse" : "expand"), " ", chalk.bold.cyan("/"), " ", chalk.dim("search"), " "] })), chalk.bold.cyan("e"), " ", chalk.dim("export"), " ", chalk.bold.cyan("?"), " ", chalk.dim("help"), " ", chalk.bold.cyan("q"), " ", chalk.dim("quit"), exportMsg && _jsxs(_Fragment, { children: [" ", exportMsg.tone === "nudge" ? exportMsg.text : (exportMsg.tone === "error" ? chalk.red : chalk.green)(exportMsg.text)] })] }))] }));
|
|
307
270
|
};
|
package/dist/audit-ui/export.js
CHANGED
|
@@ -78,7 +78,3 @@ export function buildExport(input, format) {
|
|
|
78
78
|
return { body: buildMd(input), ext: "md" };
|
|
79
79
|
return { body: buildTxt(input), ext: "txt" };
|
|
80
80
|
}
|
|
81
|
-
export function exportFilename(format, now = new Date()) {
|
|
82
|
-
const ts = now.toISOString().replace(/[:T]/g, "-").replace(/\..*$/, "");
|
|
83
|
-
return `dg-audit-${ts}-findings.${format}`;
|
|
84
|
-
}
|
package/dist/audit-ui/format.js
CHANGED
|
@@ -17,9 +17,6 @@ export function severityRole(severity) {
|
|
|
17
17
|
export function verdictGlyph(action) {
|
|
18
18
|
return action === "block" ? "✘" : action === "warn" ? WARN_GLYPH : "✓";
|
|
19
19
|
}
|
|
20
|
-
export function verdictRole(action) {
|
|
21
|
-
return action;
|
|
22
|
-
}
|
|
23
20
|
export function countSummary(findings) {
|
|
24
21
|
const blocking = findings.filter((finding) => finding.severity >= 4).length;
|
|
25
22
|
const warnings = findings.filter((finding) => finding.severity === 3).length;
|
package/dist/audit-ui/launch.js
CHANGED
|
@@ -3,11 +3,18 @@ export function shouldLaunchAuditTui(options) {
|
|
|
3
3
|
if (options.format !== "text" || options.outputPath) {
|
|
4
4
|
return false;
|
|
5
5
|
}
|
|
6
|
+
if (process.env.TERM === "dumb") {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
if (!process.stdin.isTTY) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
6
12
|
return resolvePresentation().mode === "rich";
|
|
7
13
|
}
|
|
8
14
|
export async function launchAuditTui(props) {
|
|
9
15
|
const ci = process.env.CI;
|
|
10
|
-
|
|
16
|
+
const ciCleared = ci === "" || ci === "0" || ci === "false";
|
|
17
|
+
if (ciCleared) {
|
|
11
18
|
delete process.env.CI;
|
|
12
19
|
}
|
|
13
20
|
const [{ render }, react, app, altScreen] = await Promise.all([
|
|
@@ -28,6 +35,8 @@ export async function launchAuditTui(props) {
|
|
|
28
35
|
await instance.waitUntilExit();
|
|
29
36
|
}
|
|
30
37
|
finally {
|
|
38
|
+
if (ciCleared)
|
|
39
|
+
process.env.CI = ci;
|
|
31
40
|
altScreen.leaveTui();
|
|
32
41
|
}
|
|
33
42
|
return exitCode;
|
|
@@ -191,6 +191,7 @@ export async function fetchAccountStatus(token, env, fetchImpl, timeoutMs = 5_00
|
|
|
191
191
|
const body = (await response.json());
|
|
192
192
|
return {
|
|
193
193
|
tier: typeof body.tier === "string" && body.tier.length > 0 ? body.tier.toLowerCase() : null,
|
|
194
|
+
name: typeof body.name === "string" && body.name.trim().length > 0 ? body.name.trim() : null,
|
|
194
195
|
scansUsed: typeof body.scansUsed === "number" && Number.isFinite(body.scansUsed) ? body.scansUsed : null,
|
|
195
196
|
scansLimit: typeof body.scansLimit === "number" && Number.isFinite(body.scansLimit) ? body.scansLimit : null
|
|
196
197
|
};
|
|
@@ -202,9 +203,6 @@ export async function fetchAccountStatus(token, env, fetchImpl, timeoutMs = 5_00
|
|
|
202
203
|
clearTimeout(timer);
|
|
203
204
|
}
|
|
204
205
|
}
|
|
205
|
-
export async function fetchAccountTier(token, env, fetchImpl) {
|
|
206
|
-
return (await fetchAccountStatus(token, env, fetchImpl))?.tier ?? null;
|
|
207
|
-
}
|
|
208
206
|
export async function runDeviceLogin(io = {}) {
|
|
209
207
|
const env = io.env ?? process.env;
|
|
210
208
|
const fetchImpl = io.fetchImpl ?? fetch;
|
|
@@ -236,8 +234,9 @@ export async function runDeviceLogin(io = {}) {
|
|
|
236
234
|
for (;;) {
|
|
237
235
|
const result = await pollAuthSession(webBase, session.sessionId, fetchImpl);
|
|
238
236
|
if (result.status === "complete" && result.apiKey) {
|
|
239
|
-
const
|
|
240
|
-
|
|
237
|
+
const account = await fetchAccountStatus(result.apiKey, env, fetchImpl);
|
|
238
|
+
const tier = account?.tier ?? null;
|
|
239
|
+
writeAuthState({ token: result.apiKey, email: result.email, tier: tier ?? undefined, name: account?.name ?? undefined });
|
|
241
240
|
const who = result.email ? ` as ${result.email}` : "";
|
|
242
241
|
return {
|
|
243
242
|
exitCode: 0,
|
package/dist/auth/login-app.js
CHANGED
|
@@ -3,10 +3,10 @@ import { useEffect, useRef, useState } from "react";
|
|
|
3
3
|
import { Box, Text, useApp, useInput } from "ink";
|
|
4
4
|
import { Spinner } from "../scan-ui/components/Spinner.js";
|
|
5
5
|
import { displayTier, writeAuthState } from "./store.js";
|
|
6
|
-
import { POLL_INTERVAL_MS, POLL_TIMEOUT_MS, createAuthSession,
|
|
6
|
+
import { POLL_INTERVAL_MS, POLL_TIMEOUT_MS, createAuthSession, fetchAccountStatus, openBrowser, pollAuthSession, resolveWebBase } from "./device-login.js";
|
|
7
7
|
const SUCCESS_MIN_MS = 600;
|
|
8
8
|
const SUCCESS_MAX_MS = 2500;
|
|
9
|
-
function useLogin(webBase, env) {
|
|
9
|
+
export function useLogin(webBase, env) {
|
|
10
10
|
const [state, setState] = useState({ phase: "creating", verifyUrl: "", email: "", plan: "", message: "" });
|
|
11
11
|
const sessionId = useRef("");
|
|
12
12
|
const started = useRef(false);
|
|
@@ -58,12 +58,12 @@ function useLogin(webBase, env) {
|
|
|
58
58
|
const email = result.email ?? "";
|
|
59
59
|
writeAuthState({ token: apiKey, email: result.email });
|
|
60
60
|
setState((prev) => ({ ...prev, phase: "success", email }));
|
|
61
|
-
void
|
|
62
|
-
.then((
|
|
63
|
-
if (tier) {
|
|
64
|
-
writeAuthState({ token: apiKey, email: result.email, tier });
|
|
61
|
+
void fetchAccountStatus(apiKey, env, fetch)
|
|
62
|
+
.then((account) => {
|
|
63
|
+
if (account?.tier) {
|
|
64
|
+
writeAuthState({ token: apiKey, email: result.email, tier: account.tier, name: account.name ?? undefined });
|
|
65
65
|
if (!cancelled.current) {
|
|
66
|
-
setState((prev) => ({ ...prev, plan: tier }));
|
|
66
|
+
setState((prev) => ({ ...prev, plan: account.tier ?? "" }));
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
})
|