@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.
Files changed (148) hide show
  1. package/README.md +216 -226
  2. package/dist/agents/claude-code.js +123 -0
  3. package/dist/agents/codex.js +65 -0
  4. package/dist/agents/copilot-cli.js +115 -0
  5. package/dist/agents/cursor.js +118 -0
  6. package/dist/agents/gate-posture.js +21 -0
  7. package/dist/agents/gemini.js +107 -0
  8. package/dist/agents/persistence.js +351 -0
  9. package/dist/agents/registry.js +128 -0
  10. package/dist/agents/routing.js +118 -0
  11. package/dist/agents/types.js +1 -0
  12. package/dist/agents/windsurf.js +93 -0
  13. package/dist/api/analyze.js +1 -1
  14. package/dist/audit/detectors.js +0 -11
  15. package/dist/audit/events.js +5 -21
  16. package/dist/audit/rules.js +6 -2
  17. package/dist/audit-ui/AuditApp.js +2 -0
  18. package/dist/audit-ui/components/AuditResultsView.js +55 -92
  19. package/dist/audit-ui/export.js +0 -4
  20. package/dist/audit-ui/format.js +0 -3
  21. package/dist/audit-ui/launch.js +10 -1
  22. package/dist/auth/device-login.js +4 -5
  23. package/dist/auth/login-app.js +7 -7
  24. package/dist/auth/store.js +17 -5
  25. package/dist/bin/dg.js +59 -52
  26. package/dist/commands/agents.js +275 -0
  27. package/dist/commands/audit.js +28 -3
  28. package/dist/commands/config.js +15 -7
  29. package/dist/commands/cooldown.js +389 -0
  30. package/dist/commands/decisions.js +23 -9
  31. package/dist/commands/doctor.js +1 -1
  32. package/dist/commands/help.js +1 -1
  33. package/dist/commands/licenses.js +10 -22
  34. package/dist/commands/logout.js +4 -11
  35. package/dist/commands/router.js +6 -4
  36. package/dist/commands/sbom.js +206 -0
  37. package/dist/commands/scan.js +1 -1
  38. package/dist/commands/service.js +32 -13
  39. package/dist/commands/setup.js +196 -27
  40. package/dist/commands/types.js +1 -0
  41. package/dist/commands/uninstall.js +2 -1
  42. package/dist/commands/update.js +17 -8
  43. package/dist/commands/verify.js +8 -5
  44. package/dist/config/settings.js +49 -48
  45. package/dist/decisions/remember-prompt.js +5 -10
  46. package/dist/export-ui/ExportDialog.js +198 -0
  47. package/dist/install-ui/LiveInstall.js +5 -4
  48. package/dist/install-ui/block-render.js +18 -14
  49. package/dist/install-ui/prep-spinner.js +32 -0
  50. package/dist/launcher/agent-check.js +896 -0
  51. package/dist/launcher/agent-hook-exec.js +70 -0
  52. package/dist/launcher/agent-hook-io.js +39 -0
  53. package/dist/launcher/cargo-cache.js +40 -0
  54. package/dist/launcher/classify.js +44 -7
  55. package/dist/launcher/env.js +105 -26
  56. package/dist/launcher/install-preflight.js +64 -18
  57. package/dist/launcher/live-install.js +29 -8
  58. package/dist/launcher/manifest-screen.js +171 -0
  59. package/dist/launcher/output-redaction.js +10 -4
  60. package/dist/launcher/preflight-prompt.js +18 -4
  61. package/dist/launcher/run.js +178 -103
  62. package/dist/launcher/spawn-invocation.js +21 -0
  63. package/dist/policy/cooldown.js +18 -5
  64. package/dist/policy/evaluate.js +5 -6
  65. package/dist/policy/pypi-name.js +17 -0
  66. package/dist/presentation/mode.js +3 -2
  67. package/dist/presentation/package-page.js +9 -0
  68. package/dist/presentation/theme.js +7 -7
  69. package/dist/project/dgfile.js +146 -6
  70. package/dist/project/override-trust.js +0 -0
  71. package/dist/proxy/auth.js +42 -0
  72. package/dist/proxy/ca.js +29 -9
  73. package/dist/proxy/cooldown-exemptions-file.js +33 -0
  74. package/dist/proxy/enforcement.js +55 -16
  75. package/dist/proxy/metadata-map.js +66 -12
  76. package/dist/proxy/preverified.js +55 -0
  77. package/dist/proxy/server.js +568 -53
  78. package/dist/proxy/worker.js +16 -1
  79. package/dist/publish-set/collect.js +1 -4
  80. package/dist/publish-set/npm.js +8 -5
  81. package/dist/publish-set/pack.js +9 -3
  82. package/dist/runtime/cli.js +0 -4
  83. package/dist/runtime/fatal.js +31 -0
  84. package/dist/runtime/first-run.js +14 -12
  85. package/dist/runtime/node-version.js +43 -6
  86. package/dist/runtime/nudges.js +35 -2
  87. package/dist/sbom/cyclonedx.js +211 -0
  88. package/dist/sbom-ui/SbomApp.js +158 -0
  89. package/dist/sbom-ui/components/SbomHeader.js +32 -0
  90. package/dist/sbom-ui/components/SbomList.js +52 -0
  91. package/dist/sbom-ui/inventory.js +132 -0
  92. package/dist/sbom-ui/launch.js +51 -0
  93. package/dist/sbom-ui/run.js +55 -0
  94. package/dist/sbom-ui/store.js +26 -0
  95. package/dist/scan/collect.js +10 -6
  96. package/dist/scan/command.js +17 -10
  97. package/dist/scan/discovery.js +11 -2
  98. package/dist/scan/render.js +28 -4
  99. package/dist/scan/scanner-report.js +15 -9
  100. package/dist/scan/staged.js +33 -9
  101. package/dist/scan-ui/LegacyApp.js +9 -13
  102. package/dist/scan-ui/alt-screen.js +5 -8
  103. package/dist/scan-ui/components/InteractiveResultsView.js +133 -120
  104. package/dist/scan-ui/components/ProgressBar.js +3 -14
  105. package/dist/scan-ui/components/ProjectSelector.js +1 -1
  106. package/dist/scan-ui/components/ScoreHeader.js +2 -3
  107. package/dist/scan-ui/components/SetupBanner.js +0 -6
  108. package/dist/scan-ui/format-helpers.js +61 -5
  109. package/dist/scan-ui/hooks/useResizeRepaint.js +25 -0
  110. package/dist/scan-ui/hooks/useScan.js +19 -2
  111. package/dist/scan-ui/launch.js +7 -4
  112. package/dist/scan-ui/shims.js +11 -4
  113. package/dist/scripts/detect.js +7 -2
  114. package/dist/scripts/gate.js +1 -1
  115. package/dist/security/sanitize.js +8 -4
  116. package/dist/service/state.js +28 -9
  117. package/dist/service/trust-refresh.js +92 -0
  118. package/dist/service/trust-store.js +5 -9
  119. package/dist/service/worker.js +23 -1
  120. package/dist/setup/activate-shell.js +28 -0
  121. package/dist/setup/git-hook.js +49 -4
  122. package/dist/setup/plan.js +227 -49
  123. package/dist/setup/uninstall-standalone.js +25 -0
  124. package/dist/setup-ui/gate.js +39 -0
  125. package/dist/setup-ui/offer.js +42 -0
  126. package/dist/setup-ui/selector.js +27 -0
  127. package/dist/setup-ui/tasks.js +56 -0
  128. package/dist/setup-ui/wizard.js +233 -0
  129. package/dist/standalone/uninstall.mjs +2123 -0
  130. package/dist/state/cooldown-held.js +66 -0
  131. package/dist/state/index.js +1 -0
  132. package/dist/state/locks.js +4 -2
  133. package/dist/state/store.js +2 -1
  134. package/dist/util/external-tool.js +25 -0
  135. package/dist/util/git.js +10 -3
  136. package/dist/util/report-writer.js +57 -0
  137. package/dist/verify/local.js +242 -44
  138. package/dist/verify/package-check.js +87 -29
  139. package/dist/verify/preflight.js +242 -49
  140. package/dist/verify/render.js +15 -1
  141. package/npm-shrinkwrap.json +2383 -0
  142. package/package.json +7 -8
  143. package/NOTICE +0 -5
  144. package/dist/commands/completion.js +0 -117
  145. package/dist/commands/explain.js +0 -236
  146. package/dist/commands/unavailable.js +0 -11
  147. package/dist/scripts/rebuild.js +0 -28
  148. package/dist/telemetry/events.js +0 -40
@@ -0,0 +1,65 @@
1
+ import { join } from "node:path";
2
+ import { dirExists, ownedJsonHook, probeMinCliVersion } from "./persistence.js";
3
+ function configPath(home) {
4
+ return join(home, ".codex", "hooks.json");
5
+ }
6
+ function parseInput(stdin) {
7
+ try {
8
+ const obj = JSON.parse(stdin);
9
+ const command = obj.tool_input?.command;
10
+ if (typeof command !== "string") {
11
+ return null;
12
+ }
13
+ return typeof obj.cwd === "string" ? { command, cwd: obj.cwd } : { command };
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ }
19
+ function emitDecision(verdict) {
20
+ if (verdict.decision === "allow") {
21
+ return { stdout: "", exitCode: 0 };
22
+ }
23
+ const reason = verdict.reason ?? "Dependency Guardian firewall";
24
+ const suffix = verdict.decision === "ask" ? " (dg flagged this for human review — not auto-approved)" : "";
25
+ return {
26
+ stdout: JSON.stringify({ decision: "block", reason: `${reason}${suffix}` }),
27
+ exitCode: 0,
28
+ };
29
+ }
30
+ function renderHookFile(dgCommand) {
31
+ return {
32
+ hooks: {
33
+ PreToolUse: [
34
+ {
35
+ matcher: "Bash",
36
+ hooks: [{ type: "command", command: dgCommand, timeout: 60, statusMessage: "Dependency Guardian install check" }],
37
+ },
38
+ ],
39
+ },
40
+ };
41
+ }
42
+ function probeHookSupport(home, deps) {
43
+ if (!dirExists(join(home, ".codex"))) {
44
+ return { supported: false, detail: "~/.codex not found (is Codex CLI installed?)" };
45
+ }
46
+ return probeMinCliVersion("codex", "0.124.0", "Codex CLI", deps);
47
+ }
48
+ export const codexIntegration = {
49
+ id: "codex",
50
+ label: "Codex CLI",
51
+ kind: "owned-json",
52
+ maturity: "verified",
53
+ minVersion: "0.124.0",
54
+ postInstallNote: "Codex asks to trust new hooks: approve the dg hook when Codex prompts on its next start.",
55
+ configPath,
56
+ detect: (home) => dirExists(join(home, ".codex")),
57
+ probeHookSupport,
58
+ parseInput,
59
+ emitDecision,
60
+ ...ownedJsonHook({
61
+ agent: "codex",
62
+ checkName: "dg PreToolUse hook",
63
+ render: renderHookFile,
64
+ }),
65
+ };
@@ -0,0 +1,115 @@
1
+ import { join } from "node:path";
2
+ import { dirExists, mergedJsonHook, probeMinCliVersion } from "./persistence.js";
3
+ const SIGNATURE = "hook-exec copilot-cli";
4
+ function configPath(home) {
5
+ return join(home, ".copilot", "settings.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
+ // Native camelCase event contract (PascalCase keys switch copilot into its
14
+ // VS Code snake_case compat mode, a different payload shape). toolName for
15
+ // the shell tool is "bash" — the UI's "shell" label is display-only.
16
+ function dgEntry(dgCommand) {
17
+ return { type: "command", matcher: "bash", command: dgCommand, timeoutSec: 60 };
18
+ }
19
+ export function insertDgHook(settings, dgCommand) {
20
+ const next = { ...settings };
21
+ const hooks = typeof next.hooks === "object" && next.hooks !== null ? { ...next.hooks } : {};
22
+ const pre = Array.isArray(hooks.preToolUse) ? hooks.preToolUse : [];
23
+ hooks.preToolUse = [...pre.filter((e) => !isDgEntry(e)), dgEntry(dgCommand)];
24
+ next.hooks = hooks;
25
+ return next;
26
+ }
27
+ export function removeDgHook(settings) {
28
+ const next = { ...settings };
29
+ if (typeof next.hooks !== "object" || next.hooks === null) {
30
+ return { settings: next, changed: false, empty: Object.keys(next).length === 0 };
31
+ }
32
+ const hooks = { ...next.hooks };
33
+ const pre = Array.isArray(hooks.preToolUse) ? hooks.preToolUse : [];
34
+ const withoutDg = pre.filter((e) => !isDgEntry(e));
35
+ const changed = withoutDg.length !== pre.length;
36
+ if (withoutDg.length > 0) {
37
+ hooks.preToolUse = withoutDg;
38
+ }
39
+ else {
40
+ delete hooks.preToolUse;
41
+ }
42
+ if (Object.keys(hooks).length > 0) {
43
+ next.hooks = hooks;
44
+ }
45
+ else {
46
+ delete next.hooks;
47
+ }
48
+ return { settings: next, changed, empty: Object.keys(next).length === 0 };
49
+ }
50
+ function hasDgHook(settings) {
51
+ const hooks = settings.hooks?.preToolUse;
52
+ return Array.isArray(hooks) && hooks.some((e) => isDgEntry(e));
53
+ }
54
+ function toolArgsObject(raw) {
55
+ if (typeof raw === "string") {
56
+ try {
57
+ const parsed = JSON.parse(raw);
58
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ return typeof raw === "object" && raw !== null ? raw : null;
65
+ }
66
+ function parseInput(stdin) {
67
+ try {
68
+ const obj = JSON.parse(stdin);
69
+ const args = toolArgsObject(obj.toolArgs);
70
+ const command = args?.command;
71
+ if (typeof command !== "string") {
72
+ return null;
73
+ }
74
+ return typeof obj.cwd === "string" ? { command, cwd: obj.cwd } : { command };
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ }
80
+ function emitDecision(verdict) {
81
+ if (verdict.decision === "allow") {
82
+ return { stdout: "{}", exitCode: 0 };
83
+ }
84
+ return {
85
+ stdout: JSON.stringify({
86
+ permissionDecision: verdict.decision,
87
+ ...(verdict.reason ? { permissionDecisionReason: verdict.reason } : {}),
88
+ }),
89
+ exitCode: 0,
90
+ };
91
+ }
92
+ function probeHookSupport(home, deps) {
93
+ if (!dirExists(join(home, ".copilot"))) {
94
+ return { supported: false, detail: "~/.copilot not found (is GitHub Copilot CLI installed?)" };
95
+ }
96
+ return probeMinCliVersion("copilot", "1.0.61", "GitHub Copilot CLI", deps);
97
+ }
98
+ export const copilotCliIntegration = {
99
+ id: "copilot-cli",
100
+ label: "GitHub Copilot CLI",
101
+ kind: "merged-json",
102
+ maturity: "verified",
103
+ minVersion: "1.0.61",
104
+ configPath,
105
+ detect: (home) => dirExists(join(home, ".copilot")),
106
+ probeHookSupport,
107
+ parseInput,
108
+ emitDecision,
109
+ ...mergedJsonHook({
110
+ checkName: "dg preToolUse hook",
111
+ insert: insertDgHook,
112
+ remove: removeDgHook,
113
+ isInstalled: hasDgHook,
114
+ }),
115
+ };
@@ -0,0 +1,118 @@
1
+ import { join } from "node:path";
2
+ import { formatScreenedNote } from "../launcher/agent-check.js";
3
+ import { dirExists, mergedJsonHook, readSettings } from "./persistence.js";
4
+ const SIGNATURE = "hook-exec cursor";
5
+ function configPath(home) {
6
+ return join(home, ".cursor", "hooks.json");
7
+ }
8
+ function isDgEntry(entry) {
9
+ return (typeof entry === "object" &&
10
+ entry !== null &&
11
+ typeof entry.command === "string" &&
12
+ entry.command.includes(SIGNATURE));
13
+ }
14
+ function hookList(settings) {
15
+ const hooks = settings.hooks;
16
+ if (typeof hooks !== "object" || hooks === null) {
17
+ return [];
18
+ }
19
+ const list = hooks.beforeShellExecution;
20
+ return Array.isArray(list) ? list : [];
21
+ }
22
+ function insertHook(settings, dgCommand) {
23
+ const next = { ...settings };
24
+ const hooks = typeof next.hooks === "object" && next.hooks !== null ? { ...next.hooks } : {};
25
+ const list = Array.isArray(hooks.beforeShellExecution) ? hooks.beforeShellExecution : [];
26
+ hooks.beforeShellExecution = [...list.filter((entry) => !isDgEntry(entry)), { command: dgCommand }];
27
+ next.hooks = hooks;
28
+ if (next.version === undefined) {
29
+ next.version = 1;
30
+ }
31
+ return next;
32
+ }
33
+ function effectivelyEmpty(obj) {
34
+ const keys = Object.keys(obj);
35
+ return keys.length === 0 || (keys.length === 1 && keys[0] === "version");
36
+ }
37
+ function removeHook(settings) {
38
+ const next = { ...settings };
39
+ if (typeof next.hooks !== "object" || next.hooks === null) {
40
+ return { settings: next, changed: false, empty: effectivelyEmpty(next) };
41
+ }
42
+ const hooks = { ...next.hooks };
43
+ const list = Array.isArray(hooks.beforeShellExecution) ? hooks.beforeShellExecution : [];
44
+ const withoutDg = list.filter((entry) => !isDgEntry(entry));
45
+ const changed = withoutDg.length !== list.length;
46
+ if (withoutDg.length > 0) {
47
+ hooks.beforeShellExecution = withoutDg;
48
+ }
49
+ else {
50
+ delete hooks.beforeShellExecution;
51
+ }
52
+ if (Object.keys(hooks).length > 0) {
53
+ next.hooks = hooks;
54
+ }
55
+ else {
56
+ delete next.hooks;
57
+ }
58
+ return { settings: next, changed, empty: effectivelyEmpty(next) };
59
+ }
60
+ function hasDgHook(settings) {
61
+ return hookList(settings).some((entry) => isDgEntry(entry));
62
+ }
63
+ function parseInput(stdin) {
64
+ try {
65
+ const obj = JSON.parse(stdin);
66
+ if (typeof obj.command !== "string") {
67
+ return null;
68
+ }
69
+ return typeof obj.cwd === "string" ? { command: obj.command, cwd: obj.cwd } : { command: obj.command };
70
+ }
71
+ catch {
72
+ return null;
73
+ }
74
+ }
75
+ function emitDecision(verdict) {
76
+ if (verdict.decision === "allow") {
77
+ const note = verdict.screened ? formatScreenedNote(verdict.screened) : "";
78
+ return {
79
+ stdout: JSON.stringify(note ? { permission: "allow", agent_message: note } : { permission: "allow" }),
80
+ exitCode: 0,
81
+ };
82
+ }
83
+ const reason = verdict.reason ?? "Dependency Guardian firewall";
84
+ return {
85
+ stdout: JSON.stringify({ permission: verdict.decision, user_message: reason, agent_message: reason }),
86
+ exitCode: 0,
87
+ };
88
+ }
89
+ function probeHookSupport(home) {
90
+ if (!dirExists(join(home, ".cursor"))) {
91
+ return { supported: false, detail: "~/.cursor not found (is Cursor installed?)" };
92
+ }
93
+ try {
94
+ readSettings(configPath(home));
95
+ }
96
+ catch {
97
+ return { supported: false, detail: `${configPath(home)} exists but is not a JSON object dg can merge into` };
98
+ }
99
+ return { supported: true, detail: "requires Cursor 1.7+; dg cannot read your Cursor version from disk" };
100
+ }
101
+ export const cursorIntegration = {
102
+ id: "cursor",
103
+ label: "Cursor",
104
+ kind: "merged-json",
105
+ maturity: "verified",
106
+ minVersion: "1.7",
107
+ configPath,
108
+ detect: (home) => dirExists(join(home, ".cursor")),
109
+ probeHookSupport,
110
+ parseInput,
111
+ emitDecision,
112
+ ...mergedJsonHook({
113
+ checkName: "dg beforeShellExecution hook",
114
+ insert: insertHook,
115
+ remove: removeHook,
116
+ isInstalled: hasDgHook,
117
+ }),
118
+ };
@@ -0,0 +1,21 @@
1
+ import { readServiceState } from "../service/state.js";
2
+ export function readNetworkGatePosture(env = process.env) {
3
+ try {
4
+ const { state } = readServiceState(env);
5
+ if (state.running && state.proxy) {
6
+ return { live: true, proxyUrl: state.proxy.proxyUrl };
7
+ }
8
+ }
9
+ catch {
10
+ // fall through to the off posture; a missing/unreadable service state means
11
+ // the gate is not live, which is exactly what we report.
12
+ }
13
+ return { live: false };
14
+ }
15
+ export const GATE_OFF_COVERAGE_GAPS = [
16
+ "absolute-path installs (e.g. /usr/local/bin/npm install evil)",
17
+ "manifest-only installs (npm ci, npm install with no package named)",
18
+ "dynamically-built or stdin-fed commands the static parser defers on",
19
+ "recognized-but-unsupported managers (bun, deno, poetry, pdm)",
20
+ ];
21
+ export const GATE_ENABLE_HINT = "dg setup --service --yes && dg service start";
@@ -0,0 +1,107 @@
1
+ import { join } from "node:path";
2
+ import { dirExists, mergedJsonHook, probeMinCliVersion } from "./persistence.js";
3
+ const SIGNATURE = "hook-exec gemini";
4
+ function configPath(home) {
5
+ return join(home, ".gemini", "settings.json");
6
+ }
7
+ function isDgGroup(group) {
8
+ if (typeof group !== "object" || group === null) {
9
+ return false;
10
+ }
11
+ const hooks = group.hooks;
12
+ if (!Array.isArray(hooks)) {
13
+ return false;
14
+ }
15
+ return hooks.some((h) => typeof h === "object" &&
16
+ h !== null &&
17
+ typeof h.command === "string" &&
18
+ h.command.includes(SIGNATURE));
19
+ }
20
+ // No timeout field: gemini reads it in MILLISECONDS (default 60000); the
21
+ // Claude-style seconds value 60 made gemini kill the hook at 60ms and run
22
+ // the command ungated (gemini fails open on hook timeout).
23
+ function dgGroup(dgCommand) {
24
+ return { matcher: "run_shell_command", hooks: [{ type: "command", command: dgCommand }] };
25
+ }
26
+ function insertHook(settings, dgCommand) {
27
+ const next = { ...settings };
28
+ const hooks = typeof next.hooks === "object" && next.hooks !== null ? { ...next.hooks } : {};
29
+ const pre = Array.isArray(hooks.BeforeTool) ? hooks.BeforeTool : [];
30
+ hooks.BeforeTool = [...pre.filter((g) => !isDgGroup(g)), dgGroup(dgCommand)];
31
+ next.hooks = hooks;
32
+ return next;
33
+ }
34
+ function removeHook(settings) {
35
+ const next = { ...settings };
36
+ if (typeof next.hooks !== "object" || next.hooks === null) {
37
+ return { settings: next, changed: false, empty: Object.keys(next).length === 0 };
38
+ }
39
+ const hooks = { ...next.hooks };
40
+ const pre = Array.isArray(hooks.BeforeTool) ? hooks.BeforeTool : [];
41
+ const withoutDg = pre.filter((g) => !isDgGroup(g));
42
+ const changed = withoutDg.length !== pre.length;
43
+ if (withoutDg.length > 0) {
44
+ hooks.BeforeTool = withoutDg;
45
+ }
46
+ else {
47
+ delete hooks.BeforeTool;
48
+ }
49
+ if (Object.keys(hooks).length > 0) {
50
+ next.hooks = hooks;
51
+ }
52
+ else {
53
+ delete next.hooks;
54
+ }
55
+ return { settings: next, changed, empty: Object.keys(next).length === 0 };
56
+ }
57
+ function hasDgHook(settings) {
58
+ const hooks = settings.hooks?.BeforeTool;
59
+ return Array.isArray(hooks) && hooks.some((g) => isDgGroup(g));
60
+ }
61
+ function parseInput(stdin) {
62
+ try {
63
+ const obj = JSON.parse(stdin);
64
+ const command = obj.tool_args?.command ?? obj.tool_input?.command;
65
+ if (typeof command !== "string") {
66
+ return null;
67
+ }
68
+ return typeof obj.cwd === "string" ? { command, cwd: obj.cwd } : { command };
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ function emitDecision(verdict) {
75
+ if (verdict.decision === "allow") {
76
+ return { stdout: "{}", exitCode: 0 };
77
+ }
78
+ const reason = verdict.reason ?? "Dependency Guardian firewall";
79
+ return {
80
+ stdout: JSON.stringify({ decision: "block", reason }),
81
+ exitCode: 0,
82
+ };
83
+ }
84
+ function probeHookSupport(home, deps) {
85
+ if (!dirExists(join(home, ".gemini"))) {
86
+ return { supported: false, detail: "~/.gemini not found (is Gemini CLI installed?)" };
87
+ }
88
+ return probeMinCliVersion("gemini", "0.26.0", "Gemini CLI", deps);
89
+ }
90
+ export const geminiIntegration = {
91
+ id: "gemini",
92
+ label: "Gemini CLI",
93
+ kind: "merged-json",
94
+ maturity: "verified",
95
+ minVersion: "0.26.0",
96
+ configPath,
97
+ detect: (home) => dirExists(join(home, ".gemini")),
98
+ probeHookSupport,
99
+ parseInput,
100
+ emitDecision,
101
+ ...mergedJsonHook({
102
+ checkName: "dg BeforeTool hook",
103
+ insert: insertHook,
104
+ remove: removeHook,
105
+ isInstalled: hasDgHook,
106
+ }),
107
+ };