@vigilbase/cli 0.1.0 → 0.1.2

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 (66) hide show
  1. package/README.md +30 -79
  2. package/dist/bin.js +0 -0
  3. package/dist/cli.d.ts +2 -2
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +36 -376
  6. package/dist/cli.js.map +1 -1
  7. package/dist/commands/account.d.ts +3 -0
  8. package/dist/commands/account.d.ts.map +1 -0
  9. package/dist/commands/account.js +171 -0
  10. package/dist/commands/account.js.map +1 -0
  11. package/dist/commands/auth.d.ts +5 -0
  12. package/dist/commands/auth.d.ts.map +1 -0
  13. package/dist/commands/auth.js +185 -0
  14. package/dist/commands/auth.js.map +1 -0
  15. package/dist/commands/billing.d.ts +3 -0
  16. package/dist/commands/billing.d.ts.map +1 -0
  17. package/dist/commands/billing.js +47 -0
  18. package/dist/commands/billing.js.map +1 -0
  19. package/dist/commands/cloudflare.d.ts +3 -0
  20. package/dist/commands/cloudflare.d.ts.map +1 -0
  21. package/dist/commands/cloudflare.js +53 -0
  22. package/dist/commands/cloudflare.js.map +1 -0
  23. package/dist/commands/helpers.d.ts +49 -0
  24. package/dist/commands/helpers.d.ts.map +1 -0
  25. package/dist/commands/helpers.js +36 -0
  26. package/dist/commands/helpers.js.map +1 -0
  27. package/dist/commands/index.d.ts +9 -0
  28. package/dist/commands/index.d.ts.map +1 -0
  29. package/dist/commands/index.js +37 -0
  30. package/dist/commands/index.js.map +1 -0
  31. package/dist/commands/public-tools.d.ts +9 -0
  32. package/dist/commands/public-tools.d.ts.map +1 -0
  33. package/dist/commands/public-tools.js +171 -0
  34. package/dist/commands/public-tools.js.map +1 -0
  35. package/dist/commands/runs.d.ts +3 -0
  36. package/dist/commands/runs.d.ts.map +1 -0
  37. package/dist/commands/runs.js +69 -0
  38. package/dist/commands/runs.js.map +1 -0
  39. package/dist/commands/watchdog.d.ts +3 -0
  40. package/dist/commands/watchdog.d.ts.map +1 -0
  41. package/dist/commands/watchdog.js +59 -0
  42. package/dist/commands/watchdog.js.map +1 -0
  43. package/dist/index.d.ts +1 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +1 -1
  46. package/dist/index.js.map +1 -1
  47. package/dist/lib/config.d.ts.map +1 -1
  48. package/dist/lib/config.js +2 -1
  49. package/dist/lib/config.js.map +1 -1
  50. package/dist/lib/format.d.ts +3 -0
  51. package/dist/lib/format.d.ts.map +1 -1
  52. package/dist/lib/format.js +4 -3
  53. package/dist/lib/format.js.map +1 -1
  54. package/dist/lib/help.d.ts +1 -1
  55. package/dist/lib/help.d.ts.map +1 -1
  56. package/dist/lib/help.js +34 -9
  57. package/dist/lib/help.js.map +1 -1
  58. package/dist/lib/one-client.d.ts +42 -3
  59. package/dist/lib/one-client.d.ts.map +1 -1
  60. package/dist/lib/one-client.js +121 -28
  61. package/dist/lib/one-client.js.map +1 -1
  62. package/dist/version.d.ts +2 -0
  63. package/dist/version.d.ts.map +1 -0
  64. package/dist/version.js +6 -0
  65. package/dist/version.js.map +1 -0
  66. package/package.json +1 -2
package/README.md CHANGED
@@ -1,12 +1,14 @@
1
1
  # @vigilbase/cli
2
2
 
3
- Terminal client for Vigilbase — public scanners/tools without login, plus Vigilbase One after [device authorization](https://better-auth.com/docs/plugins/device-authorization).
3
+ Terminal client for Vigilbase — public scanners and tools without login, plus Vigilbase One after device authorization.
4
4
 
5
- The binary name is **`vigilbase`** — after a global install you run:
5
+ Binary name: **`vigilbase`**
6
6
 
7
7
  ```bash
8
8
  vigilbase help
9
9
  vigilbase run security-headers https://vigilbase.com
10
+ vigilbase login
11
+ vigilbase account --json
10
12
  ```
11
13
 
12
14
  ## Install
@@ -27,25 +29,11 @@ npx @vigilbase/cli help
27
29
  npx @vigilbase/cli run email-security vigilbase.com --json
28
30
  ```
29
31
 
30
- ### From this monorepo
31
-
32
- ```bash
33
- pnpm install
34
- pnpm link:vigilbase
35
- # builds dist/ and symlinks `vigilbase` into PATH
36
-
37
- vigilbase help
38
- ```
39
-
40
- Unlink later with `pnpm unlink:vigilbase`.
41
-
42
- ## Commands
43
-
44
- ### Public tools (no auth)
32
+ ## Public tools (no auth)
45
33
 
46
34
  | Command | Purpose |
47
35
  | --- | --- |
48
- | `vigilbase list` | Catalog from `GET /api/agent/tools` |
36
+ | `vigilbase list` | Public catalog + auth-required tool names |
49
37
  | `vigilbase run <tool> <target>` | Execute a scan |
50
38
  | `vigilbase headers <url>` | Shortcut for `security-headers` |
51
39
  | `vigilbase email <domain>` | Shortcut for `email-security` |
@@ -53,17 +41,9 @@ Unlink later with `pnpm unlink:vigilbase`.
53
41
  | `vigilbase badge <domain>` | Public badge URL |
54
42
  | `vigilbase doctor` | Connectivity + auth status |
55
43
 
56
- Currently allowlisted for autonomous execution: `security-headers`, `email-security`.
57
-
58
- ### Auth (Vigilbase One)
44
+ Public allowlist: `security-headers`, `email-security`.
59
45
 
60
- | Command | Purpose |
61
- | --- | --- |
62
- | `vigilbase login` | Device-code sign-in (RFC 8628) at `one.vigilbase.com/device` |
63
- | `vigilbase logout` | Clear `~/.config/vigilbase/credentials.json` |
64
- | `vigilbase whoami` | Show local / live One session |
65
-
66
- Sensitive tools unlocked after login:
46
+ ## Auth-required tools (after login)
67
47
 
68
48
  ```bash
69
49
  vigilbase login
@@ -72,14 +52,34 @@ vigilbase run react2shell https://app.example.com
72
52
  vigilbase run fortibleed-check example.com
73
53
  ```
74
54
 
75
- Public tools stay usable while signed out. Sensitive tools route through Vigilbase One after `vigilbase login`.
55
+ ## Account & One management (login required)
56
+
57
+ ```bash
58
+ vigilbase account
59
+ vigilbase account services
60
+ vigilbase account org
61
+ vigilbase account endpoints
62
+ vigilbase account sessions
63
+ vigilbase account invites
64
+ vigilbase account invites create --email user@example.com --confirm
65
+ vigilbase billing summary
66
+ vigilbase billing portal
67
+ vigilbase runs list
68
+ vigilbase runs get <id>
69
+ vigilbase cf zones
70
+ vigilbase cf checkup status
71
+ vigilbase watchdog list
72
+ ```
73
+
74
+ Every command supports `-j` / `--json`. Async work shows a spinner on TTY (disabled for `--json`, CI, and non-TTY).
76
75
 
77
76
  ## Agent-friendly mode
78
77
 
79
78
  ```bash
80
79
  vigilbase list --json
81
80
  vigilbase run security-headers https://vigilbase.com --json
82
- vigilbase email vigilbase.com --json
81
+ vigilbase account --json
82
+ vigilbase runs list --json
83
83
  ```
84
84
 
85
85
  | Exit code | Meaning |
@@ -87,52 +87,3 @@ vigilbase email vigilbase.com --json
87
87
  | `0` | Success |
88
88
  | `1` | Error |
89
89
  | `2` | Rate limited (`HTTP 429`) |
90
-
91
- ## Local development
92
-
93
- For pointing the CLI at local servers (not shown in `vigilbase help`):
94
-
95
- | Env / flag | Description |
96
- | --- | --- |
97
- | `VIGILBASE_BASE_URL` / `--base-url` | Public API (default `https://vigilbase.com`) |
98
- | `VIGILBASE_ONE_URL` / `--one-url` | One API (default `https://one.vigilbase.com`) |
99
- | `VIGILBASE_CLIENT_ID` / `--client-id` | OAuth client id for device auth |
100
- | `VIGILBASE_CONFIG_DIR` | Override `~/.config/vigilbase` |
101
-
102
- ```bash
103
- vigilbase doctor --base-url http://localhost:3001
104
- vigilbase login --one-url http://localhost:3000
105
- ```
106
-
107
- ## Architecture notes
108
-
109
- - **Public surface** (`VigilbaseClient`) talks only to vigilbase.com agent/tool endpoints — no credentials.
110
- - **One surface** stores tokens from Better Auth `POST /api/auth/device/code` + `/device/token` under `~/.config/vigilbase/credentials.json`.
111
- - Headless / CI: `vigilbase login --token "$VIGILBASE_ONE_TOKEN"` or `--no-open` and approve the printed URL elsewhere.
112
-
113
- ## Smoke test
114
-
115
- ```bash
116
- VIGILBASE_BASE_URL=https://vigilbase.com pnpm --filter @vigilbase/cli smoke:e2e
117
- ```
118
-
119
- ## Publishing
120
-
121
- See [PUBLISH.md](./PUBLISH.md) for npm org setup and `npm publish` steps.
122
-
123
- ## Library API
124
-
125
- ```ts
126
- import { runCli, VigilbaseClient, createConfig } from '@vigilbase/cli'
127
-
128
- await runCli({ argv: ['list', '--json'] })
129
-
130
- const client = new VigilbaseClient(createConfig())
131
- await client.listTools()
132
- ```
133
-
134
- ## Related
135
-
136
- - OpenAPI: https://vigilbase.com/openapi.json
137
- - Skills index: https://vigilbase.com/.well-known/agent-skills/index.json
138
- - Device auth docs: https://better-auth.com/docs/plugins/device-authorization
package/dist/bin.js CHANGED
File without changes
package/dist/cli.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { createConfig } from './lib/config.js';
2
2
  import { type ExitCode } from './types.js';
3
- export declare const CLI_VERSION = "0.1.0";
3
+ import { CLI_VERSION } from './version.js';
4
+ export { CLI_VERSION };
4
5
  export type RunCliOptions = {
5
6
  argv?: string[];
6
7
  stdout?: NodeJS.WritableStream;
7
8
  stderr?: NodeJS.WritableStream;
8
9
  env?: NodeJS.ProcessEnv;
9
- /** Injected for tests — skip real device-auth polling. */
10
10
  loginHandler?: (config: ReturnType<typeof createConfig>) => Promise<ExitCode>;
11
11
  };
12
12
  export declare function runCli(options?: RunCliOptions): Promise<ExitCode>;
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAwB9C,OAAO,EAA4B,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAA;AAEpE,eAAO,MAAM,WAAW,UAAU,CAAA;AAsFlC,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAA;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAA;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC9E,CAAA;AAED,wBAAsB,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,QAAQ,CAAC,CA4hB3E"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAM9C,OAAO,EAA4B,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAA;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE1C,OAAO,EAAE,WAAW,EAAE,CAAA;AAmFtB,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAA;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAA;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC9E,CAAA;AAED,wBAAsB,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,QAAQ,CAAC,CA6G3E"}
package/dist/cli.js CHANGED
@@ -1,15 +1,14 @@
1
1
  import { parseArgs } from 'node:util';
2
- import { clearOneCredentials, DeviceAuthError, getOneAccessToken, readCredentials, requestDeviceCode, waitForDeviceAuthorization, writeOneCredentials, } from './auth/index.js';
2
+ import { DeviceAuthError } from './auth/index.js';
3
3
  import { VigilbaseApiError, VigilbaseClient } from './lib/client.js';
4
4
  import { createConfig } from './lib/config.js';
5
- import { isOneAuthRequiredTool, isOneExecutableTool, ONE_AUTH_REQUIRED_TOOLS, resolveToolDefinition, VigilbaseOneClient, } from './lib/one-client.js';
6
5
  import { renderBanner, shouldShowBanner } from './lib/banner.js';
7
- import { createWriter, formatError, formatExecuteResult, formatToolDescription, formatToolsList, } from './lib/format.js';
6
+ import { createWriter, formatError } from './lib/format.js';
8
7
  import { HELP_TAGLINE, HELP_TEXT, helpForCommand } from './lib/help.js';
9
- import { formatUserCode, isHeadlessEnvironment, openUrlInBrowser, resolveDeviceVerificationUrl, } from './lib/open-url.js';
10
- import { shouldShowProgress, startProgress, withProgress } from './lib/progress.js';
8
+ import { getCommandHandler } from './commands/index.js';
11
9
  import { EXIT } from './types.js';
12
- export const CLI_VERSION = '0.1.0';
10
+ import { CLI_VERSION } from './version.js';
11
+ export { CLI_VERSION };
13
12
  function parseCli(argv) {
14
13
  const { values, positionals } = parseArgs({
15
14
  args: argv,
@@ -29,9 +28,15 @@ function parseCli(argv) {
29
28
  public: { type: 'boolean', default: false },
30
29
  token: { type: 'string' },
31
30
  'no-open': { type: 'boolean', default: false },
31
+ email: { type: 'string' },
32
+ role: { type: 'string' },
33
+ confirm: { type: 'boolean', default: false },
34
+ 'account-id': { type: 'string' },
35
+ tool: { type: 'string' },
36
+ limit: { type: 'string' },
37
+ 'target-id': { type: 'string' },
32
38
  },
33
39
  });
34
- // Map parseArgs flag names onto is*/has* props (CLI flags stay --json/--help/…).
35
40
  return {
36
41
  positionals,
37
42
  values: {
@@ -48,6 +53,15 @@ function parseCli(argv) {
48
53
  isPublic: Boolean(values.public),
49
54
  token: typeof values.token === 'string' ? values.token : undefined,
50
55
  isNoOpen: Boolean(values['no-open']),
56
+ email: typeof values.email === 'string' ? values.email : undefined,
57
+ role: typeof values.role === 'string' ? values.role : undefined,
58
+ isConfirm: Boolean(values.confirm),
59
+ 'account-id': typeof values['account-id'] === 'string'
60
+ ? values['account-id']
61
+ : undefined,
62
+ tool: typeof values.tool === 'string' ? values.tool : undefined,
63
+ limit: typeof values.limit === 'string' ? values.limit : undefined,
64
+ 'target-id': typeof values['target-id'] === 'string' ? values['target-id'] : undefined,
51
65
  },
52
66
  };
53
67
  }
@@ -117,376 +131,22 @@ export async function runCli(options = {}) {
117
131
  clientId: parsed.values['client-id'],
118
132
  timeoutMs: parseTimeout(parsed.values.timeout),
119
133
  });
120
- const client = new VigilbaseClient(config);
121
- switch (command) {
122
- case 'list':
123
- case 'tools': {
124
- const catalog = await client.listTools();
125
- const accessToken = await getOneAccessToken(config);
126
- const authenticated = Boolean(accessToken);
127
- if (mode === 'json') {
128
- writer.stdout(JSON.stringify({
129
- ...catalog,
130
- authenticated,
131
- oneAuthRequiredTools: [...ONE_AUTH_REQUIRED_TOOLS],
132
- oneExecutableTools: authenticated
133
- ? [
134
- ...new Set([
135
- ...catalog.tools.map((tool) => tool.toolName),
136
- ...ONE_AUTH_REQUIRED_TOOLS,
137
- ]),
138
- ]
139
- : catalog.tools.map((tool) => tool.toolName),
140
- }, null, 2));
141
- }
142
- else {
143
- formatToolsList(catalog, mode, writer, {
144
- sensitiveTools: ONE_AUTH_REQUIRED_TOOLS,
145
- isAuthenticated: authenticated,
146
- });
147
- }
148
- return EXIT.OK;
149
- }
150
- case 'run':
151
- case 'headers':
152
- case 'email': {
153
- let toolName;
154
- let target;
155
- if (command === 'headers') {
156
- toolName = 'security-headers';
157
- target = rest[0] ?? '';
158
- }
159
- else if (command === 'email') {
160
- toolName = 'email-security';
161
- target = rest[0] ?? '';
162
- }
163
- else {
164
- toolName = rest[0] ?? '';
165
- target = rest[1] ?? '';
166
- }
167
- if (!toolName || !target) {
168
- throw new Error(command === 'run'
169
- ? 'Usage: vigilbase run <tool> <target>'
170
- : `Usage: vigilbase ${command} <target>`);
171
- }
172
- const via = parsed.values.via;
173
- if (via && via !== 'one' && via !== 'public') {
174
- throw new Error('--via must be one of: public, one');
175
- }
176
- const accessToken = await getOneAccessToken(config);
177
- const forcePublic = Boolean(parsed.values.isPublic) || via === 'public';
178
- const forceOne = via === 'one';
179
- // Default routing:
180
- // - sensitive tools → One (requires login)
181
- // - public tools → public agent API unless --via one
182
- const useOne = !forcePublic && (forceOne || isOneAuthRequiredTool(toolName));
183
- if (useOne) {
184
- if (!accessToken) {
185
- throw new Error(`Tool "${toolName}" requires Vigilbase One auth. Run \`vigilbase login\` first.`);
186
- }
187
- if (!isOneExecutableTool(toolName)) {
188
- throw new Error(`Tool "${toolName}" is not executable via Vigilbase One CLI yet.`);
189
- }
190
- const oneClient = new VigilbaseOneClient(config, accessToken);
191
- const oneResult = await withProgress(`Running ${toolName} against ${target}`, mode, () => oneClient.executeTool(toolName, target));
192
- formatExecuteResult({
193
- success: Boolean(oneResult.success),
194
- toolName,
195
- target,
196
- grade: oneResult.data?.grade ?? null,
197
- gradeScore: oneResult.data?.gradeScore ?? null,
198
- result: oneResult.data?.result ?? null,
199
- error: oneResult.error ?? null,
200
- uiUrl: `${config.oneBaseUrl}/dashboard/tools/${toolName}`,
201
- }, mode, writer);
202
- return oneResult.success ? EXIT.OK : EXIT.ERROR;
203
- }
204
- const result = await withProgress(`Running ${toolName} against ${target}`, mode, () => client.executeTool(toolName, target));
205
- formatExecuteResult(result, mode, writer);
206
- return result.success ? EXIT.OK : EXIT.ERROR;
207
- }
208
- case 'describe': {
209
- const toolName = rest[0];
210
- if (!toolName)
211
- throw new Error('Usage: vigilbase describe <tool>');
212
- const catalog = await client.listTools();
213
- const tool = resolveToolDefinition(toolName, catalog.tools, config.oneBaseUrl);
214
- if (!tool) {
215
- throw new Error(`Unknown tool "${toolName}". Run \`vigilbase list\` for the allowlist.`);
216
- }
217
- let skillMarkdown;
218
- if (!parsed.values.isNoSkill) {
219
- try {
220
- skillMarkdown = await client.fetchSkillMarkdown(tool.skillUrl);
221
- }
222
- catch {
223
- // Sensitive tools may not publish public skill docs yet.
224
- }
225
- }
226
- formatToolDescription(tool, mode, writer, skillMarkdown);
227
- return EXIT.OK;
228
- }
229
- case 'skill': {
230
- const toolName = rest[0];
231
- if (!toolName)
232
- throw new Error('Usage: vigilbase skill <tool>');
233
- const catalog = await client.listTools();
234
- const definition = resolveToolDefinition(toolName, catalog.tools, config.oneBaseUrl);
235
- if (!definition) {
236
- throw new Error(`Unknown tool "${toolName}". Run \`vigilbase list\` for the allowlist.`);
237
- }
238
- const skillMarkdown = await client.fetchSkillMarkdown(definition.skillUrl);
239
- if (mode === 'json') {
240
- writer.stdout(JSON.stringify({
241
- toolName: definition.toolName,
242
- skillUrl: definition.skillUrl,
243
- content: skillMarkdown,
244
- }, null, 2));
245
- }
246
- else {
247
- writer.stdout(skillMarkdown.trimEnd());
248
- }
249
- return EXIT.OK;
250
- }
251
- case 'badge': {
252
- const domain = rest[0];
253
- if (!domain)
254
- throw new Error('Usage: vigilbase badge <domain>');
255
- const styleRaw = parsed.values.style ?? 'default';
256
- if (styleRaw !== 'default' && styleRaw !== 'compact' && styleRaw !== 'mini') {
257
- throw new Error('--style must be one of: default, compact, mini');
258
- }
259
- const url = client.badgeUrl(domain, styleRaw);
260
- if (mode === 'json') {
261
- writer.stdout(JSON.stringify({ domain, style: styleRaw, url }, null, 2));
262
- }
263
- else {
264
- writer.stdout(url);
265
- }
266
- return EXIT.OK;
267
- }
268
- case 'doctor': {
269
- try {
270
- const catalog = await client.listTools();
271
- const accessToken = await getOneAccessToken(config);
272
- const payload = {
273
- ok: true,
274
- publicBaseUrl: config.publicBaseUrl,
275
- oneBaseUrl: config.oneBaseUrl,
276
- clientId: config.clientId,
277
- authenticated: Boolean(accessToken),
278
- toolCount: catalog.tools.length,
279
- tools: catalog.tools.map((tool) => tool.toolName),
280
- openapi: catalog.openapi ?? null,
281
- skillsIndex: catalog.skillsIndex ?? null,
282
- rateLimit: catalog.rateLimit ?? null,
283
- };
284
- if (mode === 'json') {
285
- writer.stdout(JSON.stringify(payload, null, 2));
286
- }
287
- else {
288
- writer.stdout(`ok public=${config.publicBaseUrl} one=${config.oneBaseUrl} auth=${payload.authenticated ? 'in' : 'out'} tools=${payload.tools.join(',')}`);
289
- }
290
- return EXIT.OK;
291
- }
292
- catch (error) {
293
- formatError(error, mode, writer);
294
- return error instanceof VigilbaseApiError && error.status === 429
295
- ? EXIT.RATE_LIMITED
296
- : EXIT.ERROR;
297
- }
298
- }
299
- case 'login': {
300
- if (options.loginHandler) {
301
- return options.loginHandler(config);
302
- }
303
- const explicitToken = parsed.values.token?.trim() ||
304
- process.env.VIGILBASE_ONE_TOKEN?.trim() ||
305
- '';
306
- // Headless / CI: accept a bearer session token directly.
307
- if (explicitToken) {
308
- const oneClient = new VigilbaseOneClient(config, explicitToken);
309
- let user;
310
- try {
311
- const session = await oneClient.getSession();
312
- if (!session.user) {
313
- throw new Error('Token did not resolve to a Vigilbase One session.');
314
- }
315
- user = {
316
- id: session.user.id,
317
- email: session.user.email ?? undefined,
318
- name: session.user.name ?? undefined,
319
- };
320
- }
321
- catch (error) {
322
- throw new Error(error instanceof Error
323
- ? `Invalid --token / VIGILBASE_ONE_TOKEN: ${error.message}`
324
- : 'Invalid --token / VIGILBASE_ONE_TOKEN');
325
- }
326
- await writeOneCredentials(config, {
327
- accessToken: explicitToken,
328
- tokenType: 'Bearer',
329
- user,
330
- obtainedAt: new Date().toISOString(),
331
- });
332
- if (mode === 'json') {
333
- writer.stdout(JSON.stringify({
334
- success: true,
335
- mode: 'token',
336
- oneBaseUrl: config.oneBaseUrl,
337
- user: user ?? null,
338
- sensitiveTools: [...ONE_AUTH_REQUIRED_TOOLS],
339
- }, null, 2));
340
- }
341
- else {
342
- writer.stdout(`signed in${user?.email ? ` ${user.email}` : ''} (token)`);
343
- }
344
- return EXIT.OK;
345
- }
346
- const preferNoOpen = Boolean(parsed.values.isNoOpen) || isHeadlessEnvironment(process.env);
347
- writer.stderr(`login ${config.oneBaseUrl}`);
348
- const device = await requestDeviceCode(config);
349
- const verifyUrl = resolveDeviceVerificationUrl(config.oneBaseUrl, device);
350
- const prettyCode = formatUserCode(device.user_code);
351
- writer.stderr(`code ${prettyCode}`);
352
- writer.stderr(`url ${verifyUrl}`);
353
- if (preferNoOpen) {
354
- writer.stderr('headless approve the URL on any signed-in browser, or re-run with --token');
355
- }
356
- else {
357
- const opened = openUrlInBrowser(verifyUrl);
358
- if (opened.isOpened) {
359
- writer.stderr('browser opened — approve the device code');
360
- }
361
- else {
362
- writer.stderr(`browser not opened (${opened.reason ?? 'failed'}) — paste the URL above`);
363
- }
364
- }
365
- const showSpinner = shouldShowProgress(mode);
366
- const progress = startProgress('waiting for approval', {
367
- isEnabled: showSpinner,
368
- expectedMs: Math.min(Math.max(device.expires_in, 30) * 1000, 180_000),
369
- });
370
- if (!showSpinner && mode !== 'json') {
371
- writer.stderr('waiting for approval…');
372
- }
373
- let token;
374
- try {
375
- token = await waitForDeviceAuthorization(config, device);
376
- progress.stop('✓ approved');
377
- }
378
- catch (error) {
379
- progress.stop('✗ login failed');
380
- throw error;
381
- }
382
- const expiresAt = typeof token.expires_in === 'number'
383
- ? new Date(Date.now() + token.expires_in * 1000).toISOString()
384
- : undefined;
385
- let user;
386
- try {
387
- const oneClient = new VigilbaseOneClient(config, token.access_token);
388
- const session = await oneClient.getSession();
389
- if (session.user) {
390
- user = {
391
- id: session.user.id,
392
- email: session.user.email ?? undefined,
393
- name: session.user.name ?? undefined,
394
- };
395
- }
396
- }
397
- catch {
398
- // Session enrichment is best-effort; token storage still succeeds.
399
- }
400
- await writeOneCredentials(config, {
401
- accessToken: token.access_token,
402
- refreshToken: token.refresh_token,
403
- tokenType: token.token_type,
404
- scope: token.scope,
405
- expiresAt,
406
- user,
407
- obtainedAt: new Date().toISOString(),
408
- });
409
- if (mode === 'json') {
410
- writer.stdout(JSON.stringify({
411
- success: true,
412
- mode: 'device',
413
- oneBaseUrl: config.oneBaseUrl,
414
- expiresAt: expiresAt ?? null,
415
- scope: token.scope ?? null,
416
- user: user ?? null,
417
- sensitiveTools: [...ONE_AUTH_REQUIRED_TOOLS],
418
- }, null, 2));
419
- }
420
- else {
421
- writer.stdout(`signed in${user?.email ? ` ${user.email}` : ''}`);
422
- }
423
- return EXIT.OK;
424
- }
425
- case 'logout': {
426
- const cleared = await clearOneCredentials(config);
427
- if (mode === 'json') {
428
- writer.stdout(JSON.stringify({ success: true, cleared }, null, 2));
429
- }
430
- else if (cleared) {
431
- writer.stdout('Signed out. Local One credentials removed.');
432
- }
433
- else {
434
- writer.stdout('Already signed out.');
435
- }
436
- return EXIT.OK;
437
- }
438
- case 'whoami': {
439
- const credentials = await readCredentials(config);
440
- // Honor expiry the same way list/run do (getOneAccessToken).
441
- const accessToken = await getOneAccessToken(config);
442
- let liveUser = accessToken ? (credentials.one?.user ?? null) : null;
443
- if (accessToken) {
444
- try {
445
- const session = await new VigilbaseOneClient(config, accessToken).getSession();
446
- if (session.user) {
447
- liveUser = {
448
- id: session.user.id,
449
- email: session.user.email ?? undefined,
450
- name: session.user.name ?? undefined,
451
- };
452
- }
453
- }
454
- catch {
455
- // Keep stored user if live session probe fails.
456
- }
457
- }
458
- const signedIn = Boolean(accessToken);
459
- const payload = {
460
- authenticated: signedIn,
461
- oneBaseUrl: config.oneBaseUrl,
462
- user: liveUser,
463
- expiresAt: credentials.one?.expiresAt ?? null,
464
- scope: credentials.one?.scope ?? null,
465
- obtainedAt: credentials.one?.obtainedAt ?? null,
466
- sensitiveTools: signedIn ? [...ONE_AUTH_REQUIRED_TOOLS] : [],
467
- };
468
- if (mode === 'json') {
469
- writer.stdout(JSON.stringify(payload, null, 2));
470
- }
471
- else if (!signedIn) {
472
- writer.stdout('Not signed in. Public tools still work.');
473
- writer.stdout('Run `vigilbase login` to unlock website-security, react2shell, and fortibleed-check.');
474
- }
475
- else {
476
- writer.stdout(`Signed in to ${config.oneBaseUrl}`);
477
- if (liveUser?.email) {
478
- writer.stdout(`User: ${liveUser.email}`);
479
- }
480
- if (credentials.one?.expiresAt) {
481
- writer.stdout(`Expires: ${credentials.one.expiresAt}`);
482
- }
483
- writer.stdout(`Sensitive tools: ${ONE_AUTH_REQUIRED_TOOLS.join(', ')}`);
484
- }
485
- return EXIT.OK;
486
- }
487
- default:
488
- throw new Error(`Unknown command "${command}". Run \`vigilbase help\` for usage.`);
134
+ const publicClient = new VigilbaseClient(config);
135
+ const handler = getCommandHandler(command);
136
+ if (!handler) {
137
+ throw new Error(`Unknown command "${command}". Run \`vigilbase help\` for usage.`);
489
138
  }
139
+ return await handler({
140
+ command,
141
+ rest,
142
+ values: parsed.values,
143
+ mode,
144
+ config,
145
+ writer,
146
+ publicClient,
147
+ env: process.env,
148
+ loginHandler: options.loginHandler,
149
+ });
490
150
  }
491
151
  catch (error) {
492
152
  const mode = argv.includes('--json') || argv.includes('-j') ? 'json' : 'text';