@westbayberry/dg 1.3.3 → 2.0.1

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.
Files changed (126) hide show
  1. package/LICENSE +1 -201
  2. package/NOTICE +1 -4
  3. package/README.md +293 -0
  4. package/dist/api/analyze.js +210 -0
  5. package/dist/audit/deep.js +180 -0
  6. package/dist/audit/detectors.js +247 -0
  7. package/dist/audit/events.js +41 -0
  8. package/dist/audit/rules.js +426 -0
  9. package/dist/audit-ui/AuditApp.js +39 -0
  10. package/dist/audit-ui/components/AuditHeader.js +24 -0
  11. package/dist/audit-ui/components/AuditResultsView.js +307 -0
  12. package/dist/audit-ui/components/DeepStatusRow.js +11 -0
  13. package/dist/audit-ui/export.js +85 -0
  14. package/dist/audit-ui/format.js +34 -0
  15. package/dist/audit-ui/launch.js +34 -0
  16. package/dist/auth/device-login.js +271 -0
  17. package/dist/auth/env-token.js +6 -0
  18. package/dist/auth/login-app.js +156 -0
  19. package/dist/auth/store.js +147 -0
  20. package/dist/bin/dg.js +71 -0
  21. package/dist/commands/audit.js +362 -0
  22. package/dist/commands/completion.js +116 -0
  23. package/dist/commands/config.js +99 -0
  24. package/dist/commands/doctor.js +39 -0
  25. package/dist/commands/explain.js +100 -0
  26. package/dist/commands/guard-commit.js +158 -0
  27. package/dist/commands/help.js +74 -0
  28. package/dist/commands/licenses.js +435 -0
  29. package/dist/commands/login.js +81 -0
  30. package/dist/commands/logout.js +37 -0
  31. package/dist/commands/router.js +98 -0
  32. package/dist/commands/scan.js +18 -0
  33. package/dist/commands/service.js +475 -0
  34. package/dist/commands/setup.js +302 -0
  35. package/dist/commands/status.js +115 -0
  36. package/dist/commands/suggest.js +35 -0
  37. package/dist/commands/types.js +4 -0
  38. package/dist/commands/unavailable.js +11 -0
  39. package/dist/commands/uninstall.js +111 -0
  40. package/dist/commands/update.js +210 -0
  41. package/dist/commands/verify.js +151 -0
  42. package/dist/commands/version.js +22 -0
  43. package/dist/commands/wrap.js +55 -0
  44. package/dist/config/settings.js +302 -0
  45. package/dist/install-ui/LiveInstall.js +24 -0
  46. package/dist/install-ui/block-render.js +85 -0
  47. package/dist/install-ui/live-install-app.js +48 -0
  48. package/dist/install-ui/prompt.js +24 -0
  49. package/dist/launcher/classify.js +116 -0
  50. package/dist/launcher/env.js +53 -0
  51. package/dist/launcher/live-install.js +50 -0
  52. package/dist/launcher/output-redaction.js +77 -0
  53. package/dist/launcher/preflight-prompt.js +139 -0
  54. package/dist/launcher/resolve-real-binary.js +73 -0
  55. package/dist/launcher/run.js +417 -0
  56. package/dist/policy/evaluate.js +128 -0
  57. package/dist/presentation/mode.js +52 -0
  58. package/dist/presentation/theme.js +29 -0
  59. package/dist/proxy/buffer-budget.js +64 -0
  60. package/dist/proxy/ca.js +126 -0
  61. package/dist/proxy/classify-host.js +26 -0
  62. package/dist/proxy/enforcement.js +102 -0
  63. package/dist/proxy/metadata-map.js +336 -0
  64. package/dist/proxy/server.js +919 -0
  65. package/dist/proxy/upstream-proxy.js +102 -0
  66. package/dist/proxy/worker.js +39 -0
  67. package/dist/publish-set/collect.js +51 -0
  68. package/dist/publish-set/no-exec-shell.js +19 -0
  69. package/dist/publish-set/npm.js +109 -0
  70. package/dist/publish-set/pack.js +36 -0
  71. package/dist/publish-set/pypi.js +59 -0
  72. package/dist/runtime/cli.js +17 -0
  73. package/dist/runtime/first-run.js +60 -0
  74. package/dist/runtime/node-version.js +58 -0
  75. package/dist/runtime/nudges.js +105 -0
  76. package/dist/scan/analyze-worker.js +21 -0
  77. package/dist/scan/collect.js +153 -0
  78. package/dist/scan/command.js +159 -0
  79. package/dist/scan/discovery.js +209 -0
  80. package/dist/scan/render.js +240 -0
  81. package/dist/scan/scanner-report.js +82 -0
  82. package/dist/scan/staged.js +173 -0
  83. package/dist/scan/types.js +1 -0
  84. package/dist/scan-ui/LegacyApp.js +156 -0
  85. package/dist/scan-ui/alt-screen.js +84 -0
  86. package/dist/scan-ui/api-aliases.js +1 -0
  87. package/dist/scan-ui/components/ErrorView.js +23 -0
  88. package/dist/scan-ui/components/InteractiveResultsView.js +1179 -0
  89. package/dist/scan-ui/components/ProgressBar.js +89 -0
  90. package/dist/scan-ui/components/ProjectSelector.js +62 -0
  91. package/dist/scan-ui/components/ScoreHeader.js +20 -0
  92. package/dist/scan-ui/components/SetupBanner.js +13 -0
  93. package/dist/scan-ui/components/Spinner.js +4 -0
  94. package/dist/scan-ui/format-helpers.js +40 -0
  95. package/dist/scan-ui/hooks/useExpandAnimation.js +40 -0
  96. package/dist/scan-ui/hooks/useScan.js +113 -0
  97. package/dist/scan-ui/hooks/useTerminalSize.js +24 -0
  98. package/dist/scan-ui/launch.js +27 -0
  99. package/dist/scan-ui/logo.js +91 -0
  100. package/dist/scan-ui/shims.js +30 -0
  101. package/dist/security/sanitize.js +28 -0
  102. package/dist/service/state.js +837 -0
  103. package/dist/service/trust-store.js +234 -0
  104. package/dist/service/worker.js +88 -0
  105. package/dist/setup/git-hook.js +244 -0
  106. package/dist/setup/optional-support.js +58 -0
  107. package/dist/setup/plan.js +935 -0
  108. package/dist/state/cleanup-registry.js +60 -0
  109. package/dist/state/index.js +5 -0
  110. package/dist/state/locks.js +161 -0
  111. package/dist/state/paths.js +24 -0
  112. package/dist/state/sessions.js +170 -0
  113. package/dist/state/store.js +50 -0
  114. package/dist/telemetry/events.js +40 -0
  115. package/dist/util/git.js +20 -0
  116. package/dist/util/tty-prompt.js +43 -0
  117. package/dist/verify/local.js +400 -0
  118. package/dist/verify/package-check.js +240 -0
  119. package/dist/verify/preflight.js +698 -0
  120. package/dist/verify/render.js +184 -0
  121. package/dist/verify/types.js +1 -0
  122. package/package.json +33 -50
  123. package/dist/index.mjs +0 -54116
  124. package/dist/postinstall.mjs +0 -731
  125. package/dist/python-hook/dg_pip_hook.pth +0 -1
  126. package/dist/python-hook/dg_pip_hook.py +0 -130
@@ -0,0 +1,307 @@
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useMemo, useRef, useState } from "react";
3
+ import { Box, Text, useInput } from "ink";
4
+ import chalk from "chalk";
5
+ import { writeFileSync } from "node:fs";
6
+ import { resolve as resolvePath } from "node:path";
7
+ import { findingLocation } from "../../audit/detectors.js";
8
+ import { isLoggedIn } from "../../scan-ui/shims.js";
9
+ import { clearScreen } from "../../scan-ui/alt-screen.js";
10
+ import { useTerminalSize } from "../../scan-ui/hooks/useTerminalSize.js";
11
+ import { pad, truncate } from "../../scan-ui/format-helpers.js";
12
+ import { AuditHeader } from "./AuditHeader.js";
13
+ import { DeepStatusRow } from "./DeepStatusRow.js";
14
+ import { countSummary, severityGlyph, severityRole } from "../format.js";
15
+ import { buildExport, exportFilename } from "../export.js";
16
+ const ROLE_PAINT = {
17
+ block: chalk.red,
18
+ warn: chalk.yellow,
19
+ muted: chalk.dim
20
+ };
21
+ function glyphPaint(severity) {
22
+ return ROLE_PAINT[severityRole(severity)] ?? chalk.dim;
23
+ }
24
+ const FIXED_CHROME = 12;
25
+ const EXPAND_CHROME = 10;
26
+ export const AuditResultsView = ({ findings, action, artifact, ecosystem, target, fileCount, publishSetSource, deep, onExit }) => {
27
+ const { rows: termRows, cols: termCols } = useTerminalSize();
28
+ const [cursor, setCursor] = useState(0);
29
+ const [expanded, setExpanded] = useState(false);
30
+ const [expandScroll, setExpandScroll] = useState(0);
31
+ const [viewport, setViewport] = useState(0);
32
+ const [searchMode, setSearchMode] = useState(false);
33
+ const [searchQuery, setSearchQuery] = useState("");
34
+ const searchModeRef = useRef(searchMode);
35
+ searchModeRef.current = searchMode;
36
+ const [showHelp, setShowHelp] = useState(false);
37
+ const showHelpRef = useRef(showHelp);
38
+ showHelpRef.current = showHelp;
39
+ const [exportMenu, setExportMenu] = useState(null);
40
+ const exportMenuRef = useRef(exportMenu);
41
+ exportMenuRef.current = exportMenu;
42
+ const [exportMsg, setExportMsg] = useState(null);
43
+ const exportTimer = useRef(null);
44
+ const showExportMsg = (message) => {
45
+ setExportMsg(message);
46
+ if (exportTimer.current)
47
+ clearTimeout(exportTimer.current);
48
+ exportTimer.current = setTimeout(() => setExportMsg(null), 4000);
49
+ };
50
+ const filtered = useMemo(() => {
51
+ if (!searchQuery)
52
+ return findings;
53
+ const q = searchQuery.toLowerCase();
54
+ return findings.filter((f) => f.location.toLowerCase().includes(q) || f.title.toLowerCase().includes(q));
55
+ }, [findings, searchQuery]);
56
+ const expandedRef = useRef(expanded);
57
+ expandedRef.current = expanded;
58
+ const cursorRef = useRef(cursor);
59
+ cursorRef.current = cursor;
60
+ const expandScrollRef = useRef(expandScroll);
61
+ expandScrollRef.current = expandScroll;
62
+ const viewportRef = useRef(viewport);
63
+ viewportRef.current = viewport;
64
+ const innerWidth = Math.max(40, termCols - 8);
65
+ const listRows = Math.max(3, termRows - FIXED_CHROME);
66
+ const clampedCursor = filtered.length > 0 ? Math.min(cursor, filtered.length - 1) : 0;
67
+ const expandLines = useMemo(() => {
68
+ const finding = filtered[clampedCursor];
69
+ if (!finding)
70
+ return [];
71
+ const lines = [];
72
+ if (finding.evidence && finding.evidence !== `path: ${finding.location}` && finding.evidence !== finding.location) {
73
+ lines.push(chalk.dim(finding.evidence));
74
+ }
75
+ lines.push(`${chalk.cyan("→")} ${finding.recommendation}`);
76
+ return lines;
77
+ }, [filtered, clampedCursor]);
78
+ const expandViewportRows = Math.max(2, termRows - EXPAND_CHROME);
79
+ const moveCursor = (next, keepExpanded = false) => {
80
+ const clamped = Math.max(0, Math.min(filtered.length - 1, next));
81
+ setCursor(clamped);
82
+ setExpanded(keepExpanded);
83
+ setExpandScroll(0);
84
+ if (clamped < viewportRef.current) {
85
+ setViewport(clamped);
86
+ }
87
+ else if (clamped >= viewportRef.current + listRows) {
88
+ setViewport(clamped - listRows + 1);
89
+ }
90
+ };
91
+ const runExport = (format) => {
92
+ if (!isLoggedIn()) {
93
+ showExportMsg("Export requires `dg login` (free account)");
94
+ return;
95
+ }
96
+ try {
97
+ const input = {
98
+ target,
99
+ artifact,
100
+ ecosystem,
101
+ action,
102
+ fileCount,
103
+ publishSetSource,
104
+ findings,
105
+ deep: deep ?? { ran: false, reason: "deep behavioral scan did not run" }
106
+ };
107
+ const { body, ext } = buildExport(input, format);
108
+ const filename = exportFilename(ext);
109
+ writeFileSync(resolvePath(process.cwd(), filename), body, "utf-8");
110
+ showExportMsg(`✓ Exported to ${filename}`);
111
+ }
112
+ catch (error) {
113
+ showExportMsg(`Export failed: ${error.message}`);
114
+ }
115
+ };
116
+ useInput((input, key) => {
117
+ if (exportMenuRef.current) {
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
+ }
153
+ return;
154
+ }
155
+ if (showHelpRef.current) {
156
+ if (input === "?" || key.escape)
157
+ setShowHelp(false);
158
+ else if (input === "q")
159
+ onExit();
160
+ return;
161
+ }
162
+ if (searchModeRef.current) {
163
+ if (key.escape) {
164
+ setSearchMode(false);
165
+ setSearchQuery("");
166
+ setCursor(0);
167
+ setViewport(0);
168
+ setExpanded(false);
169
+ }
170
+ else if (key.return) {
171
+ setSearchMode(false);
172
+ }
173
+ else if (key.backspace || key.delete) {
174
+ setSearchQuery((q) => q.slice(0, -1));
175
+ setCursor(0);
176
+ setViewport(0);
177
+ }
178
+ else if (input && !key.upArrow && !key.downArrow && /^[\x20-\x7e]+$/.test(input)) {
179
+ setSearchQuery((q) => q + input);
180
+ setCursor(0);
181
+ setViewport(0);
182
+ }
183
+ return;
184
+ }
185
+ if (expandedRef.current) {
186
+ if (key.escape || key.return) {
187
+ setExpanded(false);
188
+ setExpandScroll(0);
189
+ return;
190
+ }
191
+ if (input === "q") {
192
+ onExit();
193
+ return;
194
+ }
195
+ if (key.upArrow || input === "k") {
196
+ moveCursor(cursorRef.current - 1, true);
197
+ return;
198
+ }
199
+ if (key.downArrow || input === "j") {
200
+ moveCursor(cursorRef.current + 1, true);
201
+ return;
202
+ }
203
+ if (input === "g") {
204
+ moveCursor(0, true);
205
+ return;
206
+ }
207
+ if (input === "G") {
208
+ moveCursor(filtered.length - 1, true);
209
+ return;
210
+ }
211
+ if (input === "/") {
212
+ setSearchMode(true);
213
+ setExpanded(false);
214
+ setExpandScroll(0);
215
+ return;
216
+ }
217
+ if (input === "?") {
218
+ setShowHelp(true);
219
+ return;
220
+ }
221
+ if (input === "e") {
222
+ if (!isLoggedIn()) {
223
+ showExportMsg("Export requires `dg login` (free account)");
224
+ return;
225
+ }
226
+ setExportMenu({ format: "json", activeRow: "scope" });
227
+ return;
228
+ }
229
+ return;
230
+ }
231
+ if (input === "?") {
232
+ setShowHelp(true);
233
+ return;
234
+ }
235
+ if (input === "e") {
236
+ if (!isLoggedIn()) {
237
+ showExportMsg("Export requires `dg login` (free account)");
238
+ return;
239
+ }
240
+ setExportMenu({ format: "json", activeRow: "scope" });
241
+ return;
242
+ }
243
+ if (input === "q") {
244
+ onExit();
245
+ return;
246
+ }
247
+ if (filtered.length === 0) {
248
+ if (input === "/")
249
+ setSearchMode(true);
250
+ else if (key.return)
251
+ onExit();
252
+ return;
253
+ }
254
+ if (key.upArrow || input === "k")
255
+ moveCursor(cursorRef.current - 1);
256
+ else if (key.downArrow || input === "j")
257
+ moveCursor(cursorRef.current + 1);
258
+ else if (input === "g")
259
+ moveCursor(0);
260
+ else if (input === "G")
261
+ moveCursor(filtered.length - 1);
262
+ else if (key.pageDown)
263
+ moveCursor(cursorRef.current + listRows);
264
+ else if (key.pageUp)
265
+ moveCursor(cursorRef.current - listRows);
266
+ else if (key.return) {
267
+ setExpanded(true);
268
+ setExpandScroll(0);
269
+ }
270
+ else if (input === "/")
271
+ setSearchMode(true);
272
+ });
273
+ if (exportMenu) {
274
+ const FORMATS_RENDER = [
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")] })] }));
285
+ }
286
+ if (showHelp) {
287
+ 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"] })] })] }));
288
+ }
289
+ const visible = filtered.slice(viewport, viewport + listRows);
290
+ const aboveCount = viewport;
291
+ const belowCount = Math.max(0, filtered.length - viewport - listRows);
292
+ const locCol = Math.max(20, Math.floor(innerWidth * 0.45));
293
+ const titleCol = Math.max(16, innerWidth - locCol - 4);
294
+ const focused = filtered[clampedCursor];
295
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(AuditHeader, { action: action, artifact: artifact, ecosystem: ecosystem, countSummary: countSummary(findings), fileCount: fileCount, fallback: publishSetSource === "fallback" }), _jsx(Text, { dimColor: true, children: chalk.dim("─".repeat(Math.max(20, termCols - 4))) }), _jsxs(Box, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, width: "100%", children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { bold: true, children: "Findings" }), _jsx(Text, { dimColor: true, children: searchQuery ? `${filtered.length} of ${findings.length}` : filtered.length > 0 ? `${clampedCursor + 1}/${filtered.length}` : "0" })] }), aboveCount > 0 && _jsxs(Text, { dimColor: true, children: [chalk.cyan(" ↑"), " ", aboveCount, " more above"] }), filtered.length === 0 && (_jsx(Text, { dimColor: true, children: searchQuery ? ` (no findings match "${searchQuery}")` : " No findings — the publish set is clean." })), visible.map((finding, visIdx) => {
296
+ const globalIdx = viewport + visIdx;
297
+ const isCursor = globalIdx === clampedCursor;
298
+ const paint = glyphPaint(finding.severity);
299
+ const glyph = severityGlyph(finding.severity);
300
+ const loc = pad(truncate(findingLocation(finding), locCol - 1), locCol);
301
+ const title = truncate(finding.title, titleCol);
302
+ if (isCursor) {
303
+ return (_jsxs(Text, { backgroundColor: "#1a1a2e", children: [chalk.cyan("▌"), " ", paint(glyph), " ", chalk.bold(loc), chalk.dim(title)] }, `${finding.id}|${finding.location}`));
304
+ }
305
+ 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)] })] }))] }));
307
+ };
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import chalk from "chalk";
4
+ import { Spinner } from "../../scan-ui/components/Spinner.js";
5
+ import { deepSummary } from "../../audit/deep.js";
6
+ export const DeepStatusRow = ({ deep }) => {
7
+ if (deep === null) {
8
+ return (_jsx(Box, { paddingLeft: 1, children: _jsx(Spinner, { label: "uploading to behavioral scanner\u2026" }) }));
9
+ }
10
+ return (_jsx(Box, { paddingLeft: 1, children: _jsx(Text, { children: chalk.dim(`Deep behavioral scan · ${deepSummary(deep)}`) }) }));
11
+ };
@@ -0,0 +1,85 @@
1
+ import { findingLocation } from "../audit/detectors.js";
2
+ import { deepSummary } from "../audit/deep.js";
3
+ import { severityKind, WARN_GLYPH } from "./format.js";
4
+ function countSummaryLine(findings) {
5
+ const blocking = findings.filter((finding) => finding.severity >= 4).length;
6
+ const warnings = findings.filter((finding) => finding.severity === 3).length;
7
+ const notes = findings.filter((finding) => finding.severity < 3).length;
8
+ return ([
9
+ blocking ? `${blocking} blocking` : "",
10
+ warnings ? `${warnings} warning${warnings === 1 ? "" : "s"}` : "",
11
+ notes ? `${notes} note${notes === 1 ? "" : "s"}` : ""
12
+ ]
13
+ .filter(Boolean)
14
+ .join(" · ") || "no issues");
15
+ }
16
+ function buildJson(input) {
17
+ const report = {
18
+ target: input.target,
19
+ artifact: input.artifact,
20
+ ecosystem: input.ecosystem,
21
+ action: input.action,
22
+ fileCount: input.fileCount,
23
+ publishSetSource: input.publishSetSource,
24
+ findings: input.findings,
25
+ deep: input.deep
26
+ };
27
+ return `${JSON.stringify(report, null, 2)}\n`;
28
+ }
29
+ function buildMd(input) {
30
+ const lines = [
31
+ `# Dependency Guardian — audit of ${input.artifact}`,
32
+ "",
33
+ `**Verdict:** ${input.action.toUpperCase()} · ${input.ecosystem}`,
34
+ `**Files:** ${input.fileCount} · ${countSummaryLine(input.findings)}`,
35
+ input.publishSetSource === "fallback" ? "**Publish set approximated**" : "",
36
+ `**Deep behavioral scan:** ${deepSummary(input.deep)}`,
37
+ ""
38
+ ].filter((line, index) => line !== "" || index === 1);
39
+ if (input.findings.length === 0) {
40
+ lines.push("> No findings — the publish set is clean.", "");
41
+ return `${lines.join("\n")}\n`;
42
+ }
43
+ lines.push("| Severity | Location | Title | Evidence | Recommendation |", "|---|---|---|---|---|");
44
+ for (const finding of input.findings) {
45
+ const kind = severityKind(finding.severity);
46
+ const sev = kind === "block" ? "BLOCK" : kind === "warn" ? "WARN" : "NOTE";
47
+ const cell = (value) => value.replace(/\|/g, "\\|").replace(/\n/g, " ");
48
+ lines.push(`| ${sev} | ${cell(findingLocation(finding))} | ${cell(finding.title)} | ${cell(finding.evidence)} | ${cell(finding.recommendation)} |`);
49
+ }
50
+ lines.push("");
51
+ return `${lines.join("\n")}\n`;
52
+ }
53
+ function buildTxt(input) {
54
+ const glyph = input.action === "block" ? "✘" : input.action === "warn" ? WARN_GLYPH : "✓";
55
+ const fallback = input.publishSetSource === "fallback" ? " · publish set approximated" : "";
56
+ const lines = [
57
+ `${glyph} ${input.action.toUpperCase()} ${input.artifact} · ${input.ecosystem}`,
58
+ `${countSummaryLine(input.findings)} in ${input.fileCount} file${input.fileCount === 1 ? "" : "s"}${fallback}`,
59
+ ""
60
+ ];
61
+ for (const finding of input.findings) {
62
+ const kind = severityKind(finding.severity);
63
+ const tag = kind === "block" ? "✘" : kind === "warn" ? WARN_GLYPH : "·";
64
+ lines.push(` ${tag} ${findingLocation(finding)}`);
65
+ lines.push(` ${finding.title}`);
66
+ if (finding.evidence && finding.evidence !== `path: ${finding.location}` && finding.evidence !== finding.location) {
67
+ lines.push(` ${finding.evidence}`);
68
+ }
69
+ lines.push(` → ${finding.recommendation}`);
70
+ lines.push("");
71
+ }
72
+ lines.push(` Deep behavioral scan · ${deepSummary(input.deep)}`);
73
+ return `${lines.join("\n")}\n`;
74
+ }
75
+ export function buildExport(input, format) {
76
+ if (format === "json")
77
+ return { body: buildJson(input), ext: "json" };
78
+ if (format === "md")
79
+ return { body: buildMd(input), ext: "md" };
80
+ return { body: buildTxt(input), ext: "txt" };
81
+ }
82
+ export function exportFilename(format, now = new Date()) {
83
+ const ts = now.toISOString().replace(/[:T]/g, "-").replace(/\..*$/, "");
84
+ return `dg-audit-${ts}-findings.${format}`;
85
+ }
@@ -0,0 +1,34 @@
1
+ export const WARN_GLYPH = "⚠︎";
2
+ export function severityKind(severity) {
3
+ if (severity >= 4)
4
+ return "block";
5
+ if (severity === 3)
6
+ return "warn";
7
+ return "note";
8
+ }
9
+ export function severityGlyph(severity) {
10
+ const kind = severityKind(severity);
11
+ return kind === "block" ? "✘" : kind === "warn" ? WARN_GLYPH : "·";
12
+ }
13
+ export function severityRole(severity) {
14
+ const kind = severityKind(severity);
15
+ return kind === "block" ? "block" : kind === "warn" ? "warn" : "muted";
16
+ }
17
+ export function verdictGlyph(action) {
18
+ return action === "block" ? "✘" : action === "warn" ? WARN_GLYPH : "✓";
19
+ }
20
+ export function verdictRole(action) {
21
+ return action;
22
+ }
23
+ export function countSummary(findings) {
24
+ const blocking = findings.filter((finding) => finding.severity >= 4).length;
25
+ const warnings = findings.filter((finding) => finding.severity === 3).length;
26
+ const notes = findings.filter((finding) => finding.severity < 3).length;
27
+ return ([
28
+ blocking ? `${blocking} blocking` : "",
29
+ warnings ? `${warnings} warning${warnings === 1 ? "" : "s"}` : "",
30
+ notes ? `${notes} note${notes === 1 ? "" : "s"}` : ""
31
+ ]
32
+ .filter(Boolean)
33
+ .join(" · ") || "no issues");
34
+ }
@@ -0,0 +1,34 @@
1
+ import { resolvePresentation } from "../presentation/mode.js";
2
+ export function shouldLaunchAuditTui(options) {
3
+ if (options.format !== "text" || options.outputPath) {
4
+ return false;
5
+ }
6
+ return resolvePresentation().mode === "rich";
7
+ }
8
+ export async function launchAuditTui(props) {
9
+ const ci = process.env.CI;
10
+ if (ci === "" || ci === "0" || ci === "false") {
11
+ delete process.env.CI;
12
+ }
13
+ const [{ render }, react, app, altScreen] = await Promise.all([
14
+ import("ink"),
15
+ import("react"),
16
+ import("./AuditApp.js"),
17
+ import("../scan-ui/alt-screen.js")
18
+ ]);
19
+ let exitCode = 0;
20
+ altScreen.enterTui();
21
+ try {
22
+ const instance = render(react.default.createElement(app.AuditApp, {
23
+ gathered: props.gathered,
24
+ initialDeep: props.initialDeep,
25
+ deepPromise: props.deepPromise,
26
+ onExitCode: (code) => { exitCode = code; }
27
+ }), { exitOnCtrlC: true });
28
+ await instance.waitUntilExit();
29
+ }
30
+ finally {
31
+ altScreen.leaveTui();
32
+ }
33
+ return exitCode;
34
+ }