@westbayberry/dg 1.0.6 → 1.0.7

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 (3) hide show
  1. package/README.md +59 -44
  2. package/dist/index.mjs +300 -280
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @westbayberry/dg
2
2
 
3
- Supply chain security scanner for npm dependencies. Detects malicious packages, typosquatting, dependency confusion, and 20+ attack patterns before they reach production.
3
+ Supply chain security scanner for npm and Python dependencies. Detects malicious packages, typosquatting, dependency confusion, and 20+ attack patterns before they reach production.
4
4
 
5
5
  ## Install
6
6
 
@@ -8,44 +8,42 @@ Supply chain security scanner for npm dependencies. Detects malicious packages,
8
8
  npm install -g @westbayberry/dg
9
9
  ```
10
10
 
11
- Or run without installing:
12
-
13
- ```bash
14
- npx @westbayberry/dg scan
15
- ```
16
-
17
11
  ## Quick Start
18
12
 
19
- 1. Get your API key at [westbayberry.com/dashboard](https://westbayberry.com/dashboard)
20
- 2. Run a scan:
21
-
22
13
  ```bash
23
- export DG_API_KEY=dg_live_your_key_here
14
+ dg login
24
15
  dg scan
25
16
  ```
26
17
 
27
- That's it. The CLI auto-detects changed packages by diffing your lockfile against the base branch.
18
+ The CLI auto-discovers projects in your directory tree npm lockfiles (package-lock.json, yarn.lock, pnpm-lock.yaml) and Python dependency files (requirements.txt, Pipfile.lock, poetry.lock). If multiple projects are found, you pick which ones to scan.
28
19
 
29
- ## Usage
20
+ ## Commands
30
21
 
31
22
  ```
32
- dg scan [options]
23
+ dg scan [options] Scan dependencies (auto-discovers npm + Python projects)
24
+ dg npm install <pkg> Scan packages before installing
25
+ dg login Authenticate with your WestBayBerry account
26
+ dg logout Remove saved credentials
27
+ dg hook install Install git pre-commit hook to scan lockfile changes
28
+ dg hook uninstall Remove the pre-commit hook
29
+ dg update Check for and install the latest version
30
+ dg wrap Show instructions to alias npm to dg
33
31
  ```
34
32
 
35
- ### Options
36
-
37
- | Flag | Env Var | Default | Description |
38
- |------|---------|---------|-------------|
39
- | `--api-key <key>` | `DG_API_KEY` | *required* | Your API key |
40
- | `--mode <mode>` | `DG_MODE` | `warn` | `block` / `warn` / `off` |
41
- | `--block-threshold <n>` | | `70` | Score threshold for blocking |
42
- | `--warn-threshold <n>` | | `60` | Score threshold for warnings |
43
- | `--max-packages <n>` | | `200` | Max packages per scan |
44
- | `--allowlist <pkgs>` | `DG_ALLOWLIST` | | Comma-separated packages to skip |
45
- | `--json` | | | Output JSON for CI parsing |
46
- | `--scan-all` | | | Scan all packages, not just changed |
47
- | `--base-lockfile <path>` | | | Explicit base lockfile for diff |
48
- | `--api-url <url>` | `DG_API_URL` | `https://api.westbayberry.com` | API endpoint |
33
+ ### Scan Options
34
+
35
+ | Flag | Default | Description |
36
+ |------|---------|-------------|
37
+ | `--mode <mode>` | `warn` | `block` / `warn` / `off` |
38
+ | `--block-threshold <n>` | `70` | Score threshold for blocking |
39
+ | `--warn-threshold <n>` | `60` | Score threshold for warnings |
40
+ | `--max-packages <n>` | `200` | Max packages per scan |
41
+ | `--allowlist <pkgs>` | | Comma-separated packages to skip |
42
+ | `--json` | | Output JSON for CI parsing |
43
+ | `--scan-all` | | Scan all packages, not just changed |
44
+ | `--base-lockfile <path>` | | Explicit base lockfile for diff |
45
+ | `--workspace <dir>` | | Scan a specific workspace subdirectory |
46
+ | `--debug` | | Show diagnostic output |
49
47
 
50
48
  ### Exit Codes
51
49
 
@@ -54,37 +52,54 @@ dg scan [options]
54
52
  | `0` | Pass | Continue |
55
53
  | `1` | Warning | Advisory — review recommended |
56
54
  | `2` | Block | Fail the pipeline |
55
+ | `3` | Error | Internal error |
56
+
57
+ ## CI Setup
57
58
 
58
- ## CI Examples
59
+ Authenticate first, then add the scan to your pipeline:
59
60
 
60
- ### GitHub Actions CI
61
+ ### GitHub Actions
61
62
 
62
63
  ```yaml
63
64
  - name: Scan dependencies
64
- run: npx @westbayberry/dg scan --mode block
65
- env:
66
- DG_API_KEY: ${{ secrets.DG_API_KEY }}
65
+ run: |
66
+ npx @westbayberry/dg login
67
+ npx @westbayberry/dg scan --mode block --json
67
68
  ```
68
69
 
69
- ### GitLab CI
70
+ ### Any CI
70
71
 
71
- ```yaml
72
- dependency-scan:
73
- script:
74
- - npx @westbayberry/dg scan --mode block
75
- variables:
76
- DG_API_KEY: $DG_API_KEY
72
+ ```bash
73
+ npx @westbayberry/dg login
74
+ npx @westbayberry/dg scan --mode block --json
77
75
  ```
78
76
 
79
- ### Any CI
77
+ ## Git Hook
78
+
79
+ Block commits that introduce risky dependencies:
80
80
 
81
81
  ```bash
82
- export DG_API_KEY="$DG_API_KEY"
83
- npx @westbayberry/dg scan --mode block --json
82
+ dg hook install
83
+ ```
84
+
85
+ This installs a pre-commit hook that runs `dg scan --mode block` whenever a lockfile change is staged. Use `dg hook uninstall` to remove it.
86
+
87
+ ## npm Wrapper
88
+
89
+ Scan packages before installing:
90
+
91
+ ```bash
92
+ dg npm install express lodash
93
+ ```
94
+
95
+ Use `--dg-force` to bypass a block. Or alias npm globally:
96
+
97
+ ```bash
98
+ echo 'alias npm="dg npm"' >> ~/.zshrc
84
99
  ```
85
100
 
86
101
  ## Links
87
102
 
88
- - [Dashboard & API Keys](https://westbayberry.com/dashboard)
103
+ - [Dashboard](https://westbayberry.com/dashboard)
89
104
  - [Documentation](https://westbayberry.com/docs)
90
105
  - [Pricing](https://westbayberry.com/pricing)
package/dist/index.mjs CHANGED
@@ -39,6 +39,194 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
39
39
  mod
40
40
  ));
41
41
 
42
+ // src/config.ts
43
+ var config_exports = {};
44
+ __export(config_exports, {
45
+ USAGE: () => USAGE,
46
+ getVersion: () => getVersion,
47
+ parseConfig: () => parseConfig
48
+ });
49
+ import { parseArgs } from "node:util";
50
+ import { readFileSync, existsSync } from "node:fs";
51
+ import { join } from "node:path";
52
+ import { homedir } from "node:os";
53
+ function loadDgrc() {
54
+ const candidates = [
55
+ join(process.cwd(), ".dgrc.json"),
56
+ join(homedir(), ".dgrc.json")
57
+ ];
58
+ for (const filepath of candidates) {
59
+ if (existsSync(filepath)) {
60
+ try {
61
+ return JSON.parse(readFileSync(filepath, "utf-8"));
62
+ } catch {
63
+ process.stderr.write(`Warning: Failed to parse ${filepath}, ignoring.
64
+ `);
65
+ }
66
+ }
67
+ }
68
+ return {};
69
+ }
70
+ function getVersion() {
71
+ try {
72
+ const pkg = JSON.parse(
73
+ readFileSync(join(__dirname, "..", "package.json"), "utf-8")
74
+ );
75
+ return pkg.version ?? "1.0.0";
76
+ } catch {
77
+ return "1.0.0";
78
+ }
79
+ }
80
+ function parseConfig(argv) {
81
+ const { values, positionals } = parseArgs({
82
+ args: argv.slice(2),
83
+ options: {
84
+ "api-url": { type: "string" },
85
+ mode: { type: "string" },
86
+ "block-threshold": { type: "string" },
87
+ "warn-threshold": { type: "string" },
88
+ "max-packages": { type: "string" },
89
+ allowlist: { type: "string" },
90
+ json: { type: "boolean", default: false },
91
+ "scan-all": { type: "boolean", default: false },
92
+ "base-lockfile": { type: "string" },
93
+ workspace: { type: "string", short: "w" },
94
+ debug: { type: "boolean", default: false },
95
+ "no-config": { type: "boolean", default: false },
96
+ help: { type: "boolean", default: false },
97
+ version: { type: "boolean", default: false }
98
+ },
99
+ allowPositionals: true,
100
+ strict: false
101
+ });
102
+ if (values.help) {
103
+ process.stdout.write(USAGE);
104
+ process.exit(0);
105
+ }
106
+ if (values.version) {
107
+ process.stdout.write(`dependency-guardian v${getVersion()}
108
+ `);
109
+ process.exit(0);
110
+ }
111
+ const command = positionals[0] ?? "scan";
112
+ const noConfig = values["no-config"];
113
+ const dgrc = noConfig ? {} : loadDgrc();
114
+ const apiKey = dgrc.apiKey ?? "";
115
+ if (!apiKey) {
116
+ process.stderr.write(
117
+ "Error: Not logged in. Run `dg login` to authenticate.\n"
118
+ );
119
+ process.exit(1);
120
+ }
121
+ const modeRaw = values.mode ?? process.env.DG_MODE ?? dgrc.mode ?? "warn";
122
+ if (!["block", "warn", "off"].includes(modeRaw)) {
123
+ process.stderr.write(
124
+ `Error: Invalid mode "${modeRaw}". Must be block, warn, or off.
125
+ `
126
+ );
127
+ process.exit(1);
128
+ }
129
+ const allowlistRaw = values.allowlist ?? process.env.DG_ALLOWLIST ?? "";
130
+ const blockThreshold = Number(values["block-threshold"] ?? dgrc.blockThreshold ?? "70");
131
+ const warnThreshold = Number(values["warn-threshold"] ?? dgrc.warnThreshold ?? "60");
132
+ const maxPackages = Number(values["max-packages"] ?? dgrc.maxPackages ?? "200");
133
+ const debug = values.debug || process.env.DG_DEBUG === "1";
134
+ if (isNaN(blockThreshold) || blockThreshold < 0 || blockThreshold > 100) {
135
+ process.stderr.write("Error: --block-threshold must be a number between 0 and 100\n");
136
+ process.exit(1);
137
+ }
138
+ if (isNaN(warnThreshold) || warnThreshold < 0 || warnThreshold > 100) {
139
+ process.stderr.write("Error: --warn-threshold must be a number between 0 and 100\n");
140
+ process.exit(1);
141
+ }
142
+ if (isNaN(maxPackages) || maxPackages < 1 || maxPackages > 1e4) {
143
+ process.stderr.write("Error: --max-packages must be a number between 1 and 10000\n");
144
+ process.exit(1);
145
+ }
146
+ return {
147
+ apiKey,
148
+ apiUrl: values["api-url"] ?? process.env.DG_API_URL ?? dgrc.apiUrl ?? "https://api.westbayberry.com",
149
+ mode: modeRaw,
150
+ blockThreshold,
151
+ warnThreshold,
152
+ maxPackages,
153
+ allowlist: allowlistRaw ? allowlistRaw.split(",").map((s) => s.trim()).filter(Boolean) : dgrc.allowlist ?? [],
154
+ json: values.json,
155
+ scanAll: values["scan-all"],
156
+ baseLockfile: values["base-lockfile"] ?? null,
157
+ workspace: values.workspace ?? process.env.DG_WORKSPACE ?? null,
158
+ command,
159
+ debug
160
+ };
161
+ }
162
+ var USAGE;
163
+ var init_config = __esm({
164
+ "src/config.ts"() {
165
+ "use strict";
166
+ USAGE = `
167
+ Dependency Guardian \u2014 Supply chain security scanner
168
+
169
+ Usage:
170
+ dependency-guardian scan [options]
171
+ dg scan [options]
172
+ dg npm install <pkg> [npm-flags]
173
+ dg wrap
174
+
175
+ Commands:
176
+ scan Scan dependencies (auto-discovers npm + Python projects)
177
+ npm Wrap npm commands \u2014 scans packages before installing
178
+ hook install Install git pre-commit hook to scan lockfile changes
179
+ hook uninstall Remove the pre-commit hook
180
+ update Check for and install the latest version
181
+ login Authenticate with your WestBayBerry account
182
+ logout Remove saved credentials
183
+ wrap Show instructions to alias npm to dg
184
+
185
+ Options:
186
+ --api-url <url> API base URL (default: https://api.westbayberry.com)
187
+ --mode <mode> block | warn | off (default: warn)
188
+ --block-threshold <n> Score threshold for blocking (default: 70)
189
+ --warn-threshold <n> Score threshold for warnings (default: 60)
190
+ --max-packages <n> Max packages per scan (default: 200)
191
+ --allowlist <pkgs> Comma-separated package names to skip
192
+ --json Output JSON for CI parsing
193
+ --scan-all Scan all packages, not just changed
194
+ --base-lockfile <path> Path to base lockfile for explicit diff
195
+ --workspace <dir> Scan a specific workspace subdirectory
196
+ --debug Show diagnostic output (discovery, batches, timing)
197
+ --no-config Skip loading .dgrc.json config file
198
+ --help Show this help message
199
+ --version Show version number
200
+
201
+ Config File:
202
+ Place a .dgrc.json in your project root or home directory.
203
+ Precedence: CLI flags > env vars > .dgrc.json > defaults
204
+
205
+ Environment Variables:
206
+ DG_API_URL API base URL
207
+ DG_MODE Mode (block/warn/off)
208
+ DG_ALLOWLIST Comma-separated allowlist
209
+ DG_DEBUG Enable debug output (set to 1)
210
+ DG_WORKSPACE Workspace subdirectory to scan
211
+
212
+ Exit Codes:
213
+ 0 pass \u2014 No risks detected
214
+ 1 warn \u2014 Risks detected (advisory)
215
+ 2 block \u2014 High-risk packages detected
216
+ 3 error \u2014 Internal error (API failure, config error)
217
+
218
+ Examples:
219
+ dg scan
220
+ dg scan --json
221
+ dg scan --scan-all --mode block
222
+ dg scan --base-lockfile ./main-lockfile.json
223
+ dg npm install express lodash
224
+ dg npm install @scope/pkg@^2.0.0
225
+ dg npm install risky-pkg --dg-force
226
+ `.trimStart();
227
+ }
228
+ });
229
+
42
230
  // src/npm-wrapper.ts
43
231
  import { spawn } from "node:child_process";
44
232
  import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
@@ -3106,11 +3294,11 @@ var require_react_development = __commonJS({
3106
3294
  }
3107
3295
  return dispatcher.useContext(Context);
3108
3296
  }
3109
- function useState6(initialState) {
3297
+ function useState5(initialState) {
3110
3298
  var dispatcher = resolveDispatcher();
3111
3299
  return dispatcher.useState(initialState);
3112
3300
  }
3113
- function useReducer4(reducer4, initialArg, init) {
3301
+ function useReducer5(reducer4, initialArg, init) {
3114
3302
  var dispatcher = resolveDispatcher();
3115
3303
  return dispatcher.useReducer(reducer4, initialArg, init);
3116
3304
  }
@@ -3907,9 +4095,9 @@ var require_react_development = __commonJS({
3907
4095
  exports.useInsertionEffect = useInsertionEffect;
3908
4096
  exports.useLayoutEffect = useLayoutEffect2;
3909
4097
  exports.useMemo = useMemo4;
3910
- exports.useReducer = useReducer4;
4098
+ exports.useReducer = useReducer5;
3911
4099
  exports.useRef = useRef6;
3912
- exports.useState = useState6;
4100
+ exports.useState = useState5;
3913
4101
  exports.useSyncExternalStore = useSyncExternalStore;
3914
4102
  exports.useTransition = useTransition;
3915
4103
  exports.version = ReactVersion;
@@ -35768,9 +35956,6 @@ var init_build2 = __esm({
35768
35956
  // src/auth.ts
35769
35957
  var auth_exports = {};
35770
35958
  __export(auth_exports, {
35771
- MAX_POLL_ATTEMPTS: () => MAX_POLL_ATTEMPTS,
35772
- POLL_INTERVAL_MS: () => POLL_INTERVAL_MS,
35773
- WEB_BASE: () => WEB_BASE,
35774
35959
  clearCredentials: () => clearCredentials,
35775
35960
  createAuthSession: () => createAuthSession,
35776
35961
  getStoredApiKey: () => getStoredApiKey,
@@ -35801,7 +35986,6 @@ async function createAuthSession() {
35801
35986
  const json = await res.json();
35802
35987
  return {
35803
35988
  sessionId: json.session_id,
35804
- userCode: json.user_code,
35805
35989
  verifyUrl: json.verify_url,
35806
35990
  expiresIn: json.expires_in
35807
35991
  };
@@ -35889,13 +36073,11 @@ function openBrowser(url) {
35889
36073
  exec2(cmd, () => {
35890
36074
  });
35891
36075
  }
35892
- var WEB_BASE, POLL_INTERVAL_MS, MAX_POLL_ATTEMPTS, CONFIG_FILE;
36076
+ var WEB_BASE, CONFIG_FILE;
35893
36077
  var init_auth = __esm({
35894
36078
  "src/auth.ts"() {
35895
36079
  "use strict";
35896
36080
  WEB_BASE = "https://westbayberry.com";
35897
- POLL_INTERVAL_MS = 2e3;
35898
- MAX_POLL_ATTEMPTS = 150;
35899
36081
  CONFIG_FILE = ".dgrc.json";
35900
36082
  }
35901
36083
  });
@@ -35906,7 +36088,7 @@ function reducer(_state, action) {
35906
36088
  case "ALREADY_LOGGED_IN":
35907
36089
  return { phase: "already_logged_in", apiKey: action.apiKey };
35908
36090
  case "SESSION_CREATED":
35909
- return { phase: "waiting", userCode: action.userCode, verifyUrl: action.verifyUrl };
36091
+ return { phase: "waiting", verifyUrl: action.verifyUrl };
35910
36092
  case "AUTH_COMPLETE":
35911
36093
  return { phase: "success", email: action.email };
35912
36094
  case "AUTH_EXPIRED":
@@ -35941,13 +36123,12 @@ function useLogin() {
35941
36123
  }
35942
36124
  dispatch({
35943
36125
  type: "SESSION_CREATED",
35944
- userCode: session.userCode,
35945
36126
  verifyUrl: session.verifyUrl
35946
36127
  });
35947
36128
  openBrowser(session.verifyUrl);
35948
- for (let i = 0; i < MAX_POLL_ATTEMPTS2; i++) {
36129
+ for (let i = 0; i < MAX_POLL_ATTEMPTS; i++) {
35949
36130
  if (cancelled) return;
35950
- await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS2));
36131
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
35951
36132
  if (cancelled) return;
35952
36133
  try {
35953
36134
  const result = await pollAuthSession(session.sessionId);
@@ -35977,14 +36158,14 @@ function useLogin() {
35977
36158
  }, []);
35978
36159
  return state;
35979
36160
  }
35980
- var import_react22, POLL_INTERVAL_MS2, MAX_POLL_ATTEMPTS2;
36161
+ var import_react22, POLL_INTERVAL_MS, MAX_POLL_ATTEMPTS;
35981
36162
  var init_useLogin = __esm({
35982
36163
  "src/ui/hooks/useLogin.ts"() {
35983
36164
  "use strict";
35984
36165
  import_react22 = __toESM(require_react());
35985
36166
  init_auth();
35986
- POLL_INTERVAL_MS2 = 2e3;
35987
- MAX_POLL_ATTEMPTS2 = 150;
36167
+ POLL_INTERVAL_MS = 2e3;
36168
+ MAX_POLL_ATTEMPTS = 150;
35988
36169
  }
35989
36170
  });
35990
36171
 
@@ -38656,13 +38837,6 @@ var init_LoginApp = __esm({
38656
38837
  ] });
38657
38838
  case "waiting":
38658
38839
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 1, children: [
38659
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
38660
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { bold: true, children: "Your verification code:" }),
38661
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
38662
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { bold: true, color: "green", children: [
38663
- " ",
38664
- state.userCode
38665
- ] }),
38666
38840
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
38667
38841
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: "Opening browser to authenticate..." }),
38668
38842
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: "If it didn't open, visit:" }),
@@ -39758,7 +39932,7 @@ async function runStaticLogin() {
39758
39932
  process.exit(1);
39759
39933
  }
39760
39934
  process.stderr.write(`
39761
- Your verification code: ${import_chalk4.default.bold.green(session.userCode)}
39935
+ Opening browser to authenticate...
39762
39936
  `);
39763
39937
  process.stderr.write(` Visit: ${import_chalk4.default.cyan(session.verifyUrl)}
39764
39938
 
@@ -40115,13 +40289,10 @@ function useNpmWrapper(npmArgs, config) {
40115
40289
  }
40116
40290
  } catch (error) {
40117
40291
  const message = error instanceof Error ? error.message : String(error);
40118
- const proceed = true;
40119
- dispatch({ type: "ERROR", message, proceed });
40120
- if (proceed) {
40121
- dispatch({ type: "INSTALLING" });
40122
- const code = await runNpm(parsedRef.current.rawArgs);
40123
- dispatch({ type: "DONE", exitCode: code });
40124
- }
40292
+ dispatch({ type: "ERROR", message, proceed: true });
40293
+ dispatch({ type: "INSTALLING" });
40294
+ const code = await runNpm(parsedRef.current.rawArgs);
40295
+ dispatch({ type: "DONE", exitCode: code });
40125
40296
  }
40126
40297
  })();
40127
40298
  }, [npmArgs, config]);
@@ -40427,7 +40598,7 @@ function getHint(error) {
40427
40598
  if (typeof statusCode !== "number") return null;
40428
40599
  switch (statusCode) {
40429
40600
  case 401:
40430
- return "Check your --api-key";
40601
+ return "Not authenticated. Run `dg login` to sign in.";
40431
40602
  case 429:
40432
40603
  return "Rate limit exceeded. Upgrade at westbayberry.com/pricing";
40433
40604
  case 504:
@@ -40734,6 +40905,8 @@ function reducer3(_state, action) {
40734
40905
  switch (action.type) {
40735
40906
  case "PROJECTS_FOUND":
40736
40907
  return { phase: "selecting", projects: action.projects };
40908
+ case "RESTART_SELECTION":
40909
+ return { phase: "selecting", projects: action.projects };
40737
40910
  case "DISCOVERY_COMPLETE":
40738
40911
  return { phase: "scanning", done: 0, total: action.packages.length, currentBatch: [] };
40739
40912
  case "DISCOVERY_EMPTY":
@@ -40749,6 +40922,7 @@ function reducer3(_state, action) {
40749
40922
  function useScan(config) {
40750
40923
  const [state, dispatch] = (0, import_react27.useReducer)(reducer3, { phase: "discovering" });
40751
40924
  const started = (0, import_react27.useRef)(false);
40925
+ const discoveredProjects = (0, import_react27.useRef)(null);
40752
40926
  (0, import_react27.useEffect)(() => {
40753
40927
  if (started.current) return;
40754
40928
  started.current = true;
@@ -40764,6 +40938,7 @@ function useScan(config) {
40764
40938
  runNpmScan(packages, discovery.skipped.length, config, dispatch);
40765
40939
  } catch {
40766
40940
  const projects = discoverProjects(process.cwd());
40941
+ discoveredProjects.current = projects.length > 1 ? projects : null;
40767
40942
  if (projects.length === 0) {
40768
40943
  dispatch({ type: "DISCOVERY_EMPTY", message: "No dependency files found." });
40769
40944
  return;
@@ -40780,7 +40955,15 @@ function useScan(config) {
40780
40955
  dispatch({ type: "DISCOVERY_COMPLETE", packages: [], skippedCount: 0 });
40781
40956
  scanProjects(projects, config, dispatch);
40782
40957
  }, [config]);
40783
- return { state, scanSelectedProjects };
40958
+ const restartSelection = (0, import_react27.useCallback)(() => {
40959
+ if (!discoveredProjects.current) return;
40960
+ dispatch({ type: "RESTART_SELECTION", projects: discoveredProjects.current });
40961
+ }, []);
40962
+ return {
40963
+ state,
40964
+ scanSelectedProjects,
40965
+ restartSelection: discoveredProjects.current ? restartSelection : null
40966
+ };
40784
40967
  }
40785
40968
  async function runNpmScan(packages, skippedCount, config, dispatch) {
40786
40969
  try {
@@ -41016,7 +41199,17 @@ function affectsLine(group) {
41016
41199
  if (names.length <= 5) return names.join(", ");
41017
41200
  return names.slice(0, 5).join(", ") + ` + ${names.length - 5} more`;
41018
41201
  }
41019
- var import_react29, import_chalk10, import_jsx_runtime10, SEVERITY_LABELS3, SEVERITY_COLORS, EVIDENCE_LIMIT2, FIXED_CHROME, MOUSE_ON, MOUSE_OFF, InteractiveResultsView, T, FindingsSummary, FindingsDetail;
41202
+ function viewReducer(_state, action) {
41203
+ switch (action.type) {
41204
+ case "MOVE":
41205
+ return { ..._state, cursor: action.cursor, viewport: action.viewport };
41206
+ case "EXPAND":
41207
+ return { ..._state, expandedIndex: action.expandedIndex, expandLevel: action.expandLevel, viewport: action.viewport };
41208
+ case "MOVE_EXPAND":
41209
+ return { cursor: action.cursor, expandedIndex: action.expandedIndex, expandLevel: action.expandLevel, viewport: action.viewport };
41210
+ }
41211
+ }
41212
+ var import_react29, import_chalk10, import_jsx_runtime10, SEVERITY_LABELS3, SEVERITY_COLORS, EVIDENCE_LIMIT2, FIXED_CHROME, InteractiveResultsView, T, FindingsSummary, FindingsDetail;
41020
41213
  var init_InteractiveResultsView = __esm({
41021
41214
  async "src/ui/components/InteractiveResultsView.tsx"() {
41022
41215
  "use strict";
@@ -41042,19 +41235,18 @@ var init_InteractiveResultsView = __esm({
41042
41235
  };
41043
41236
  EVIDENCE_LIMIT2 = 2;
41044
41237
  FIXED_CHROME = 16;
41045
- MOUSE_ON = "\x1B[?1000h\x1B[?1003h\x1B[?1006h";
41046
- MOUSE_OFF = "\x1B[?1006l\x1B[?1003l\x1B[?1000l";
41047
41238
  InteractiveResultsView = ({
41048
41239
  result,
41049
41240
  config,
41050
41241
  durationMs,
41051
- onExit
41242
+ onExit,
41243
+ onBack
41052
41244
  }) => {
41053
41245
  (0, import_react29.useEffect)(() => {
41054
41246
  if (!process.stdout.isTTY) return;
41055
- process.stdout.write("\x1B[?1049h" + MOUSE_ON);
41247
+ process.stdout.write("\x1B[?1049h");
41056
41248
  return () => {
41057
- process.stdout.write(MOUSE_OFF + "\x1B[?1049l");
41249
+ process.stdout.write("\x1B[?1049l");
41058
41250
  };
41059
41251
  }, []);
41060
41252
  const flagged = (0, import_react29.useMemo)(
@@ -41067,45 +41259,41 @@ var init_InteractiveResultsView = __esm({
41067
41259
  );
41068
41260
  const total = result.packages.length;
41069
41261
  const groups = (0, import_react29.useMemo)(() => groupPackages3(flagged), [flagged]);
41070
- const [cursorIndex, setCursorIndex] = (0, import_react29.useState)(0);
41071
- const [expandLevel, setExpandLevel] = (0, import_react29.useState)(null);
41072
- const [expandedIndex, setExpandedIndex] = (0, import_react29.useState)(null);
41073
- const [viewportStart, setViewportStart] = (0, import_react29.useState)(0);
41074
- const cursorRef = (0, import_react29.useRef)(cursorIndex);
41075
- const expandLevelRef = (0, import_react29.useRef)(expandLevel);
41076
- const expandedIdxRef = (0, import_react29.useRef)(expandedIndex);
41077
- const viewportRef = (0, import_react29.useRef)(viewportStart);
41078
- cursorRef.current = cursorIndex;
41079
- expandLevelRef.current = expandLevel;
41080
- expandedIdxRef.current = expandedIndex;
41081
- viewportRef.current = viewportStart;
41262
+ const [view, dispatchView] = (0, import_react29.useReducer)(viewReducer, {
41263
+ cursor: 0,
41264
+ expandLevel: null,
41265
+ expandedIndex: null,
41266
+ viewport: 0
41267
+ });
41268
+ const viewRef = (0, import_react29.useRef)(view);
41269
+ viewRef.current = view;
41082
41270
  const { stdout } = use_stdout_default();
41083
41271
  const termCols = stdout?.columns ?? process.stdout.columns ?? 80;
41084
41272
  const termRows = stdout?.rows ?? process.stdout.rows ?? 24;
41085
41273
  const availableRows = Math.max(5, termRows - FIXED_CHROME);
41086
41274
  const innerWidth = Math.max(40, termCols - 6);
41087
41275
  const getLevel = (idx) => {
41088
- return expandedIndex === idx ? expandLevel : null;
41276
+ return view.expandedIndex === idx ? view.expandLevel : null;
41089
41277
  };
41090
41278
  const expandTargetHeight = (0, import_react29.useMemo)(() => {
41091
- if (expandedIndex === null || expandLevel === null) return 0;
41092
- const group = groups[expandedIndex];
41279
+ if (view.expandedIndex === null || view.expandLevel === null) return 0;
41280
+ const group = groups[view.expandedIndex];
41093
41281
  if (!group) return 0;
41094
- if (expandLevel === "summary") return findingsSummaryHeight(group);
41282
+ if (view.expandLevel === "summary") return findingsSummaryHeight(group);
41095
41283
  return findingsDetailHeight(group, result.safeVersions);
41096
- }, [expandedIndex, expandLevel, groups, result.safeVersions]);
41284
+ }, [view.expandedIndex, view.expandLevel, groups, result.safeVersions]);
41097
41285
  const { visibleLines: animVisibleLines } = useExpandAnimation(
41098
41286
  expandTargetHeight,
41099
- expandedIndex !== null
41287
+ view.expandedIndex !== null
41100
41288
  );
41101
41289
  const animatedGroupHeight = (group, level, idx) => {
41102
41290
  if (level === null) return 1;
41103
- if (idx === expandedIndex) return 1 + animVisibleLines;
41291
+ if (idx === view.expandedIndex) return 1 + animVisibleLines;
41104
41292
  return groupRowHeight(group, level, result.safeVersions);
41105
41293
  };
41106
41294
  const visibleEnd = (0, import_react29.useMemo)(() => {
41107
41295
  let consumed = 0;
41108
- let end = viewportStart;
41296
+ let end = view.viewport;
41109
41297
  while (end < groups.length) {
41110
41298
  const level = getLevel(end);
41111
41299
  const h = animatedGroupHeight(groups[end], level, end);
@@ -41113,9 +41301,9 @@ var init_InteractiveResultsView = __esm({
41113
41301
  consumed += h;
41114
41302
  end++;
41115
41303
  }
41116
- if (end === viewportStart && groups.length > 0) end = viewportStart + 1;
41304
+ if (end === view.viewport && groups.length > 0) end = view.viewport + 1;
41117
41305
  return end;
41118
- }, [viewportStart, groups, expandedIndex, expandLevel, animVisibleLines, availableRows, result.safeVersions]);
41306
+ }, [view.viewport, groups, view.expandedIndex, view.expandLevel, animVisibleLines, availableRows, result.safeVersions]);
41119
41307
  const adjustViewport = (cursor, expIdx, expLvl, currentStart) => {
41120
41308
  if (cursor < currentStart) return cursor;
41121
41309
  const getLvl = (i) => expIdx === i ? expLvl : null;
@@ -41140,43 +41328,39 @@ var init_InteractiveResultsView = __esm({
41140
41328
  if (input === "q" || key.return) onExit();
41141
41329
  return;
41142
41330
  }
41143
- const cursor = cursorRef.current;
41144
- const expLvl = expandLevelRef.current;
41145
- const expIdx = expandedIdxRef.current;
41146
- const vpStart = viewportRef.current;
41331
+ const { cursor, expandLevel: expLvl, expandedIndex: expIdx, viewport: vpStart } = viewRef.current;
41147
41332
  if (key.upArrow) {
41148
41333
  const next = Math.max(0, cursor - 1);
41149
41334
  const newVp = adjustViewport(next, expIdx, expLvl, vpStart < next ? vpStart : next);
41150
- setCursorIndex(next);
41151
- setViewportStart(newVp);
41335
+ dispatchView({ type: "MOVE", cursor: next, viewport: newVp });
41152
41336
  } else if (key.downArrow) {
41153
41337
  const next = Math.min(groups.length - 1, cursor + 1);
41154
41338
  const newVp = adjustViewport(next, expIdx, expLvl, vpStart);
41155
- setCursorIndex(next);
41156
- setViewportStart(newVp);
41339
+ dispatchView({ type: "MOVE", cursor: next, viewport: newVp });
41157
41340
  } else if (key.return) {
41158
41341
  let newExpIdx;
41159
41342
  let newExpLvl;
41160
- if (expIdx !== cursor) {
41161
- newExpIdx = cursor;
41162
- newExpLvl = "summary";
41163
- } else if (expLvl === "summary") {
41164
- newExpIdx = cursor;
41165
- newExpLvl = "detail";
41166
- } else {
41343
+ if (expIdx === cursor && expLvl !== null) {
41167
41344
  newExpIdx = null;
41168
41345
  newExpLvl = null;
41346
+ } else {
41347
+ newExpIdx = cursor;
41348
+ newExpLvl = "summary";
41169
41349
  }
41170
- setExpandedIndex(newExpIdx);
41171
- setExpandLevel(newExpLvl);
41172
41350
  const newVp = adjustViewport(cursor, newExpIdx, newExpLvl, vpStart);
41173
- setViewportStart(newVp);
41351
+ dispatchView({ type: "EXPAND", expandedIndex: newExpIdx, expandLevel: newExpLvl, viewport: newVp });
41352
+ } else if (input === "e") {
41353
+ if (expIdx === cursor && expLvl === "detail") return;
41354
+ const newVp = adjustViewport(cursor, cursor, "detail", vpStart);
41355
+ dispatchView({ type: "EXPAND", expandedIndex: cursor, expandLevel: "detail", viewport: newVp });
41356
+ } else if (input === "b" && onBack) {
41357
+ onBack();
41174
41358
  } else if (input === "q") {
41175
41359
  onExit();
41176
41360
  }
41177
41361
  });
41178
- const visibleGroups = groups.slice(viewportStart, visibleEnd);
41179
- const aboveCount = viewportStart;
41362
+ const visibleGroups = groups.slice(view.viewport, visibleEnd);
41363
+ const aboveCount = view.viewport;
41180
41364
  const belowCount = groups.length - visibleEnd;
41181
41365
  const nameCol = Math.max(20, innerWidth - 22);
41182
41366
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", children: [
@@ -41215,8 +41399,8 @@ var init_InteractiveResultsView = __esm({
41215
41399
  " more above"
41216
41400
  ] }),
41217
41401
  visibleGroups.map((group, visIdx) => {
41218
- const globalIdx = viewportStart + visIdx;
41219
- const isCursor = globalIdx === cursorIndex;
41402
+ const globalIdx = view.viewport + visIdx;
41403
+ const isCursor = globalIdx === view.cursor;
41220
41404
  const level = getLevel(globalIdx);
41221
41405
  const rep = group.packages[0];
41222
41406
  const { label, color } = actionBadge4(rep.score, config);
@@ -41235,7 +41419,7 @@ var init_InteractiveResultsView = __esm({
41235
41419
  {
41236
41420
  group,
41237
41421
  maxWidth: innerWidth - 8,
41238
- maxLines: globalIdx === expandedIndex ? animVisibleLines : void 0
41422
+ maxLines: globalIdx === view.expandedIndex ? animVisibleLines : void 0
41239
41423
  }
41240
41424
  ),
41241
41425
  level === "detail" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -41244,7 +41428,7 @@ var init_InteractiveResultsView = __esm({
41244
41428
  group,
41245
41429
  safeVersion: result.safeVersions[rep.name],
41246
41430
  maxWidth: innerWidth - 8,
41247
- maxLines: globalIdx === expandedIndex ? animVisibleLines : void 0
41431
+ maxLines: globalIdx === view.expandedIndex ? animVisibleLines : void 0
41248
41432
  }
41249
41433
  )
41250
41434
  ] }, group.key);
@@ -41291,13 +41475,23 @@ var init_InteractiveResultsView = __esm({
41291
41475
  " navigate",
41292
41476
  " ",
41293
41477
  import_chalk10.default.cyan("\u23CE"),
41294
- " expand/collapse",
41478
+ " toggle",
41295
41479
  " ",
41480
+ import_chalk10.default.cyan("e"),
41481
+ " detail",
41482
+ " ",
41483
+ onBack && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
41484
+ import_chalk10.default.cyan("b"),
41485
+ " back",
41486
+ " "
41487
+ ] }),
41296
41488
  import_chalk10.default.cyan("q"),
41297
41489
  " quit"
41298
41490
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
41299
41491
  "Press ",
41300
41492
  import_chalk10.default.cyan("q"),
41493
+ " or ",
41494
+ import_chalk10.default.cyan("Enter"),
41301
41495
  " to exit"
41302
41496
  ] })
41303
41497
  ] })
@@ -41305,13 +41499,9 @@ var init_InteractiveResultsView = __esm({
41305
41499
  };
41306
41500
  T = {
41307
41501
  branch: import_chalk10.default.dim("\u251C\u2500\u2500"),
41308
- // ├──
41309
41502
  last: import_chalk10.default.dim("\u2514\u2500\u2500"),
41310
- // └──
41311
41503
  pipe: import_chalk10.default.dim("\u2502"),
41312
- // │
41313
41504
  blank: " "
41314
- //
41315
41505
  };
41316
41506
  FindingsSummary = ({ group, maxWidth, maxLines }) => {
41317
41507
  const rep = group.packages[0];
@@ -41477,12 +41667,7 @@ var init_ProjectSelector = __esm({
41477
41667
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { bold: isCursor, inverse: isCursor, children: line }, i);
41478
41668
  }),
41479
41669
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
41480
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { dimColor: true, children: [
41481
- selected.size,
41482
- " of ",
41483
- projects.length,
41484
- " selected"
41485
- ] })
41670
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { dimColor: true, children: selected.size === 0 ? "Select at least 1 project to scan" : `${selected.size} of ${projects.length} selected` })
41486
41671
  ] });
41487
41672
  };
41488
41673
  }
@@ -41507,7 +41692,7 @@ var init_App2 = __esm({
41507
41692
  await init_ProjectSelector();
41508
41693
  import_jsx_runtime12 = __toESM(require_jsx_runtime());
41509
41694
  App2 = ({ config }) => {
41510
- const { state, scanSelectedProjects } = useScan(config);
41695
+ const { state, scanSelectedProjects, restartSelection } = useScan(config);
41511
41696
  const { exit } = use_app_default();
41512
41697
  const handleResultsExit = (0, import_react31.useCallback)(() => {
41513
41698
  if (state.phase === "results") {
@@ -41574,7 +41759,8 @@ var init_App2 = __esm({
41574
41759
  result: state.result,
41575
41760
  config,
41576
41761
  durationMs: state.durationMs,
41577
- onExit: handleResultsExit
41762
+ onExit: handleResultsExit,
41763
+ onBack: restartSelection ?? void 0
41578
41764
  }
41579
41765
  );
41580
41766
  case "empty":
@@ -41586,183 +41772,8 @@ var init_App2 = __esm({
41586
41772
  }
41587
41773
  });
41588
41774
 
41589
- // src/config.ts
41590
- import { parseArgs } from "node:util";
41591
- import { readFileSync, existsSync } from "node:fs";
41592
- import { join } from "node:path";
41593
- import { homedir } from "node:os";
41594
- function loadDgrc() {
41595
- const candidates = [
41596
- join(process.cwd(), ".dgrc.json"),
41597
- join(homedir(), ".dgrc.json")
41598
- ];
41599
- for (const filepath of candidates) {
41600
- if (existsSync(filepath)) {
41601
- try {
41602
- return JSON.parse(readFileSync(filepath, "utf-8"));
41603
- } catch {
41604
- process.stderr.write(`Warning: Failed to parse ${filepath}, ignoring.
41605
- `);
41606
- }
41607
- }
41608
- }
41609
- return {};
41610
- }
41611
- var USAGE = `
41612
- Dependency Guardian \u2014 Supply chain security scanner
41613
-
41614
- Usage:
41615
- dependency-guardian scan [options]
41616
- dg scan [options]
41617
- dg npm install <pkg> [npm-flags]
41618
- dg wrap
41619
-
41620
- Commands:
41621
- scan Scan dependencies (auto-discovers npm + Python projects)
41622
- npm Wrap npm commands \u2014 scans packages before installing
41623
- hook install Install git pre-commit hook to scan lockfile changes
41624
- hook uninstall Remove the pre-commit hook
41625
- update Check for and install the latest version
41626
- login Authenticate with your WestBayBerry account
41627
- logout Remove saved credentials
41628
- wrap Show instructions to alias npm to dg
41629
-
41630
- Options:
41631
- --api-url <url> API base URL (default: https://api.westbayberry.com)
41632
- --mode <mode> block | warn | off (default: warn)
41633
- --block-threshold <n> Score threshold for blocking (default: 70)
41634
- --warn-threshold <n> Score threshold for warnings (default: 60)
41635
- --max-packages <n> Max packages per scan (default: 200)
41636
- --allowlist <pkgs> Comma-separated package names to skip
41637
- --json Output JSON for CI parsing
41638
- --scan-all Scan all packages, not just changed
41639
- --base-lockfile <path> Path to base lockfile for explicit diff
41640
- --workspace <dir> Scan a specific workspace subdirectory
41641
- --debug Show diagnostic output (discovery, batches, timing)
41642
- --no-config Skip loading .dgrc.json config file
41643
- --help Show this help message
41644
- --version Show version number
41645
-
41646
- Config File:
41647
- Place a .dgrc.json in your project root or home directory.
41648
- Precedence: CLI flags > env vars > .dgrc.json > defaults
41649
-
41650
- Environment Variables:
41651
- DG_API_URL API base URL
41652
- DG_MODE Mode (block/warn/off)
41653
- DG_ALLOWLIST Comma-separated allowlist
41654
- DG_DEBUG Enable debug output (set to 1)
41655
- DG_WORKSPACE Workspace subdirectory to scan
41656
-
41657
- Exit Codes:
41658
- 0 pass \u2014 No risks detected
41659
- 1 warn \u2014 Risks detected (advisory)
41660
- 2 block \u2014 High-risk packages detected
41661
- 3 error \u2014 Internal error (API failure, config error)
41662
-
41663
- Examples:
41664
- dg scan
41665
- dg scan --json
41666
- dg scan --scan-all --mode block
41667
- dg scan --base-lockfile ./main-lockfile.json
41668
- dg npm install express lodash
41669
- dg npm install @scope/pkg@^2.0.0
41670
- dg npm install risky-pkg --dg-force
41671
- `.trimStart();
41672
- function getVersion() {
41673
- try {
41674
- const pkg = JSON.parse(
41675
- readFileSync(join(__dirname, "..", "package.json"), "utf-8")
41676
- );
41677
- return pkg.version ?? "1.0.0";
41678
- } catch {
41679
- return "1.0.0";
41680
- }
41681
- }
41682
- function parseConfig(argv) {
41683
- const { values, positionals } = parseArgs({
41684
- args: argv.slice(2),
41685
- options: {
41686
- "api-url": { type: "string" },
41687
- mode: { type: "string" },
41688
- "block-threshold": { type: "string" },
41689
- "warn-threshold": { type: "string" },
41690
- "max-packages": { type: "string" },
41691
- allowlist: { type: "string" },
41692
- json: { type: "boolean", default: false },
41693
- "scan-all": { type: "boolean", default: false },
41694
- "base-lockfile": { type: "string" },
41695
- workspace: { type: "string", short: "w" },
41696
- debug: { type: "boolean", default: false },
41697
- "no-config": { type: "boolean", default: false },
41698
- help: { type: "boolean", default: false },
41699
- version: { type: "boolean", default: false }
41700
- },
41701
- allowPositionals: true,
41702
- strict: false
41703
- });
41704
- if (values.help) {
41705
- process.stdout.write(USAGE);
41706
- process.exit(0);
41707
- }
41708
- if (values.version) {
41709
- process.stdout.write(`dependency-guardian v${getVersion()}
41710
- `);
41711
- process.exit(0);
41712
- }
41713
- const command = positionals[0] ?? "scan";
41714
- const noConfig = values["no-config"];
41715
- const dgrc = noConfig ? {} : loadDgrc();
41716
- const apiKey = dgrc.apiKey ?? "";
41717
- if (!apiKey) {
41718
- process.stderr.write(
41719
- "Error: Not logged in. Run `dg login` to authenticate.\n"
41720
- );
41721
- process.exit(1);
41722
- }
41723
- const modeRaw = values.mode ?? process.env.DG_MODE ?? dgrc.mode ?? "warn";
41724
- if (!["block", "warn", "off"].includes(modeRaw)) {
41725
- process.stderr.write(
41726
- `Error: Invalid mode "${modeRaw}". Must be block, warn, or off.
41727
- `
41728
- );
41729
- process.exit(1);
41730
- }
41731
- const allowlistRaw = values.allowlist ?? process.env.DG_ALLOWLIST ?? "";
41732
- const blockThreshold = Number(values["block-threshold"] ?? dgrc.blockThreshold ?? "70");
41733
- const warnThreshold = Number(values["warn-threshold"] ?? dgrc.warnThreshold ?? "60");
41734
- const maxPackages = Number(values["max-packages"] ?? dgrc.maxPackages ?? "200");
41735
- const debug = values.debug || process.env.DG_DEBUG === "1";
41736
- if (isNaN(blockThreshold) || blockThreshold < 0 || blockThreshold > 100) {
41737
- process.stderr.write("Error: --block-threshold must be a number between 0 and 100\n");
41738
- process.exit(1);
41739
- }
41740
- if (isNaN(warnThreshold) || warnThreshold < 0 || warnThreshold > 100) {
41741
- process.stderr.write("Error: --warn-threshold must be a number between 0 and 100\n");
41742
- process.exit(1);
41743
- }
41744
- if (isNaN(maxPackages) || maxPackages < 1 || maxPackages > 1e4) {
41745
- process.stderr.write("Error: --max-packages must be a number between 1 and 10000\n");
41746
- process.exit(1);
41747
- }
41748
- return {
41749
- apiKey,
41750
- apiUrl: values["api-url"] ?? process.env.DG_API_URL ?? dgrc.apiUrl ?? "https://api.westbayberry.com",
41751
- mode: modeRaw,
41752
- blockThreshold,
41753
- warnThreshold,
41754
- maxPackages,
41755
- allowlist: allowlistRaw ? allowlistRaw.split(",").map((s) => s.trim()).filter(Boolean) : dgrc.allowlist ?? [],
41756
- json: values.json,
41757
- scanAll: values["scan-all"],
41758
- baseLockfile: values["base-lockfile"] ?? null,
41759
- workspace: values.workspace ?? process.env.DG_WORKSPACE ?? null,
41760
- command,
41761
- debug
41762
- };
41763
- }
41764
-
41765
41775
  // src/bin.ts
41776
+ init_config();
41766
41777
  init_npm_wrapper();
41767
41778
 
41768
41779
  // src/update-check.ts
@@ -41824,8 +41835,7 @@ function spawnBackgroundUpdate(version) {
41824
41835
  try {
41825
41836
  const child = spawn2("npm", ["install", "-g", `${PKG_NAME}@${version}`], {
41826
41837
  detached: true,
41827
- stdio: "ignore",
41828
- shell: true
41838
+ stdio: "ignore"
41829
41839
  });
41830
41840
  child.unref();
41831
41841
  } catch {
@@ -41887,10 +41897,20 @@ async function runUpdate(currentVersion) {
41887
41897
  }
41888
41898
 
41889
41899
  // src/bin.ts
41890
- var CLI_VERSION = "1.0.4";
41900
+ var CLI_VERSION = getVersion();
41891
41901
  var isInteractive = process.stdout.isTTY === true && !process.env.CI && !process.env.NO_COLOR;
41892
41902
  async function main() {
41893
41903
  const rawCommand = process.argv[2];
41904
+ if (!rawCommand || rawCommand === "--help" || rawCommand === "-h") {
41905
+ const { USAGE: USAGE3 } = await Promise.resolve().then(() => (init_config(), config_exports));
41906
+ process.stdout.write(USAGE3);
41907
+ return;
41908
+ }
41909
+ if (rawCommand === "--version" || rawCommand === "-v") {
41910
+ process.stdout.write(`dependency-guardian v${CLI_VERSION}
41911
+ `);
41912
+ return;
41913
+ }
41894
41914
  if (rawCommand === "wrap") {
41895
41915
  handleWrapCommand();
41896
41916
  return;
@@ -41921,7 +41941,7 @@ async function main() {
41921
41941
  const { clearCredentials: clearCredentials2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
41922
41942
  clearCredentials2();
41923
41943
  const chalk9 = (await Promise.resolve().then(() => __toESM(require_source()))).default;
41924
- process.stderr.write(chalk9.green(" Logged out.") + chalk9.dim(" API key removed from ~/.dgrc.json\n"));
41944
+ process.stderr.write(chalk9.green(" Logged out.\n"));
41925
41945
  return;
41926
41946
  }
41927
41947
  const config = parseConfig(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@westbayberry/dg",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "Supply chain security scanner — scan npm dependencies in any CI or terminal",
5
5
  "bin": {
6
6
  "dependency-guardian": "dist/index.mjs",