@vigilbase/cli 0.2.3 → 0.2.5

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 (75) hide show
  1. package/CLOUDFLARE.md +2 -2
  2. package/COMMANDS.md +111 -44
  3. package/LOCAL.md +99 -38
  4. package/ONE.md +2 -2
  5. package/README.md +215 -67
  6. package/dist/auth/acl.d.ts +6 -0
  7. package/dist/auth/acl.js +45 -0
  8. package/dist/auth/device-auth.js +1 -0
  9. package/dist/auth/provider.js +4 -4
  10. package/dist/auth/refresh.d.ts +5 -4
  11. package/dist/auth/refresh.js +82 -41
  12. package/dist/auth/store.d.ts +25 -0
  13. package/dist/auth/store.js +91 -11
  14. package/dist/bin.js +13 -1
  15. package/dist/cli.js +205 -81
  16. package/dist/commands/account.js +11 -7
  17. package/dist/commands/auth.d.ts +6 -0
  18. package/dist/commands/auth.js +75 -24
  19. package/dist/commands/billing.js +2 -2
  20. package/dist/commands/cloudflare.d.ts +1 -1
  21. package/dist/commands/cloudflare.js +31 -17
  22. package/dist/commands/helpers.js +3 -3
  23. package/dist/commands/index.d.ts +1 -0
  24. package/dist/commands/index.js +2 -1
  25. package/dist/commands/legacy-validation.d.ts +20 -0
  26. package/dist/commands/legacy-validation.js +77 -3
  27. package/dist/commands/local.js +186 -65
  28. package/dist/commands/one.js +16 -13
  29. package/dist/commands/public-tools.js +52 -27
  30. package/dist/commands/runs.js +5 -5
  31. package/dist/commands/security.d.ts +1 -1
  32. package/dist/commands/security.js +41 -37
  33. package/dist/commands/watchdog.js +5 -5
  34. package/dist/lib/banner.d.ts +5 -11
  35. package/dist/lib/banner.js +11 -31
  36. package/dist/lib/client.js +19 -1
  37. package/dist/lib/cloudflare-client.d.ts +2 -2
  38. package/dist/lib/config.d.ts +16 -0
  39. package/dist/lib/config.js +77 -19
  40. package/dist/lib/format.d.ts +1 -1
  41. package/dist/lib/format.js +75 -24
  42. package/dist/lib/help.js +57 -6
  43. package/dist/lib/network.d.ts +12 -3
  44. package/dist/lib/network.js +73 -9
  45. package/dist/lib/one-client.js +9 -2
  46. package/dist/lib/one-error-hints.d.ts +1 -0
  47. package/dist/lib/one-error-hints.js +10 -0
  48. package/dist/lib/open-url.js +2 -1
  49. package/dist/lib/platform.d.ts +2 -0
  50. package/dist/lib/platform.js +5 -0
  51. package/dist/lib/progress.d.ts +18 -3
  52. package/dist/lib/progress.js +72 -10
  53. package/dist/lib/system-trust.d.ts +2 -0
  54. package/dist/lib/system-trust.js +6 -0
  55. package/dist/lib/tui.d.ts +1 -1
  56. package/dist/lib/tui.js +5 -7
  57. package/dist/local/cloudflare-checkup/cf/child-env.js +2 -4
  58. package/dist/local/cloudflare-checkup/cf/install.d.ts +4 -2
  59. package/dist/local/cloudflare-checkup/cf/install.js +55 -24
  60. package/dist/local/cloudflare-checkup/cf/locate.d.ts +2 -0
  61. package/dist/local/cloudflare-checkup/cf/locate.js +10 -1
  62. package/dist/local/cloudflare-checkup/cf/run-cf.d.ts +18 -3
  63. package/dist/local/cloudflare-checkup/cf/run-cf.js +75 -17
  64. package/dist/local/cloudflare-checkup/contract.d.ts +16 -9
  65. package/dist/local/cloudflare-checkup/contract.js +80 -14
  66. package/dist/local/cloudflare-checkup/errors.d.ts +34 -1
  67. package/dist/local/cloudflare-checkup/errors.js +36 -5
  68. package/dist/local/cloudflare-checkup/output.d.ts +6 -0
  69. package/dist/local/cloudflare-checkup/output.js +9 -0
  70. package/dist/local/cloudflare-checkup/snapshot-file.d.ts +6 -2
  71. package/dist/local/cloudflare-checkup/snapshot-file.js +25 -11
  72. package/dist/local/cloudflare-checkup/submit-client.js +7 -1
  73. package/dist/types.d.ts +2 -1
  74. package/esm-only.cjs +3 -0
  75. package/package.json +7 -4
@@ -1,37 +1,48 @@
1
1
  import { z } from 'zod';
2
2
  import { CloudflareClient, accountResourceSchema, cloudflareScopeSchema, workspaceSectionSchema, } from '../lib/cloudflare-client.js';
3
3
  import { assertCommandFlags, asArray, asRecord, ok, outputJson, requireOneClient, withSpinner, } from './helpers.js';
4
- const usage = 'Usage: vigilbase cf [zones|zone <id>|accounts|account <id>|workspace [section]|dns <zone-id>|waf <zone-id>|speed-settings <zone-id>|audit-logs <account-id>|zero-trust <account-id>|developer-platform <account-id>|magic-transit <account-id>|analytics|checkup] [--customer-id …] [--account-id …] [--json]';
4
+ import { InvalidArgumentError, inRange, oneOf, unknownSubcommand } from './legacy-validation.js';
5
5
  const checkupUsage = 'Usage: vigilbase cf checkup [status|start|progress <run-id>|result <run-id>] [--customer-id …] [--account-id …] [--json]';
6
6
  export const CLOUDFLARE_HELP = `Usage: vigilbase cf <command> [options]
7
7
  Alias: vigilbase cloudflare
8
8
 
9
9
  Inventory and workspace:
10
- zones List zones (default); --customer-id, --account-id, --zone-id
11
- zone <zone-id> Zone detail; --customer-id selects live details, optional --account-id
12
- accounts List accessible accounts; --customer-id, --account-id
10
+ zones List zones (default)
11
+ --customer-id --account-id --zone-id
12
+ zone <zone-id> Zone detail; --customer-id for live details,
13
+ optional --account-id
14
+ accounts List accessible accounts
15
+ --customer-id --account-id
13
16
  account <account-id> Stored account detail; --customer-id
14
- workspace [section] Workspace bootstrap or section; --customer-id, --account-id, --zone-id
15
- Sections: overview, reports, zones, rule-analyzer, accounts, checkups, settings
17
+ workspace [section] Workspace bootstrap or one section
18
+ --customer-id --account-id --zone-id
19
+ Sections: overview, reports, zones, rule-analyzer,
20
+ accounts, checkups, settings
16
21
 
17
22
  Zone reads (--customer-id <uuid> required; IDs can use --zone-id):
18
23
  dns <zone-id> DNS records, TTL and proxy state
19
- waf <zone-id> WAF rules, expressions, analytics and suggestions
24
+ waf <zone-id> WAF rules, expressions, analytics and
25
+ suggestions
20
26
  speed-settings <zone-id> Performance settings (internal users only)
21
27
 
22
28
  Account reads (optional --customer-id; IDs can use --account-id):
23
- audit-logs <account-id> Latest provider audit events (up to 100; service-read permission)
24
- zero-trust <account-id> Users, Access applications, tunnels and authentication analytics
29
+ audit-logs <account-id> Latest provider audit events (up to 100)
30
+ Needs service-read permission.
31
+ zero-trust <account-id> Users, Access applications, tunnels and
32
+ authentication analytics
25
33
  developer-platform <account-id>
26
34
  Workers, Workers analytics and Pages
27
35
  magic-transit <account-id> Prefixes, routes and traffic analytics
28
36
  Zero Trust, Developer Platform and Magic Transit require deployment-read permission.
29
37
 
30
38
  Analytics and checkups:
31
- analytics --customer-id <uuid> [--account-id <id> | --zone-id <id>] [--days 1..30]
39
+ analytics --customer-id <uuid> [--account-id <id> | --zone-id <id>]
40
+ [--days 1..30]
32
41
  Default 7 days; live zone analytics uses the most recent 23 hours.
33
- checkup [status] Latest completed checkup; --customer-id, --account-id
34
- checkup start Start and persist a scan; --customer-id, --account-id
42
+ checkup [status] Latest completed checkup
43
+ --customer-id --account-id
44
+ checkup start Start and persist a scan
45
+ --customer-id --account-id
35
46
  checkup progress <run-uuid> Read saved progress once
36
47
  checkup result <run-uuid> Read the stored Cloudflare checkup result
37
48
  checkups aliases checkup; list/show alias status; run aliases start.
@@ -44,7 +55,7 @@ See CLOUDFLARE.md for full grammar, API mapping, data limits and examples.`;
44
55
  function validate(schema, input) {
45
56
  const result = schema.safeParse(input);
46
57
  if (!result.success)
47
- throw new Error(result.error.issues.map((issue) => issue.message).join(' '));
58
+ throw new InvalidArgumentError(result.error.issues.map((issue) => issue.message).join(' '));
48
59
  return result.data;
49
60
  }
50
61
  function dataOf(payload) {
@@ -85,7 +96,7 @@ export const handleCloudflare = async (ctx) => {
85
96
  if (scope.zoneId && ['accounts', 'account', ...accountResourceSchema.options, 'checkup', 'checkups'].includes(subcommand))
86
97
  throw new Error(`cf ${subcommand} does not support --zone-id.`);
87
98
  const args = ctx.rest.slice(1);
88
- const noExtraArgs = (count, message = usage) => {
99
+ const noExtraArgs = (count, message = `Unexpected arguments. Run \`vigilbase ${ctx.command} --help\` for usage.`) => {
89
100
  if (args.length > count)
90
101
  throw new Error(message);
91
102
  };
@@ -100,7 +111,7 @@ export const handleCloudflare = async (ctx) => {
100
111
  };
101
112
  // Validate commands before requesting credentials or making API calls.
102
113
  if (!['zones', 'zone', 'accounts', 'workspace', 'dns', 'waf', 'speed-settings', 'account', ...accountResourceSchema.options, 'analytics', 'checkup', 'checkups'].includes(subcommand)) {
103
- throw new Error(usage);
114
+ throw unknownSubcommand(ctx.command, ctx.rest[0] ?? '');
104
115
  }
105
116
  let zoneId;
106
117
  let accountId;
@@ -132,7 +143,7 @@ export const handleCloudflare = async (ctx) => {
132
143
  noExtraArgs(0);
133
144
  }
134
145
  const section = subcommand === 'workspace'
135
- ? (noExtraArgs(1), args[0] ? validate(workspaceSectionSchema, args[0]) : undefined)
146
+ ? (noExtraArgs(1), oneOf('Workspace section', workspaceSectionSchema.options, args[0]))
136
147
  : undefined;
137
148
  let days = 7;
138
149
  if (subcommand === 'analytics') {
@@ -140,7 +151,10 @@ export const handleCloudflare = async (ctx) => {
140
151
  throw new Error('Cloudflare analytics requires --customer-id <uuid>. Find it with `vigilbase cf accounts --json`.');
141
152
  if (scope.zoneId && scope.accountId)
142
153
  throw new Error('Choose either --zone-id or --account-id for analytics.');
143
- days = validate(z.coerce.number().int().min(1).max(30), ctx.values.days ?? 7);
154
+ const raw = ctx.values.days ?? '7';
155
+ if (!/^\d+$/.test(raw))
156
+ throw new InvalidArgumentError('--days must be an integer.');
157
+ days = inRange('--days', Number(raw), 1, 30);
144
158
  }
145
159
  const action = (args[0] ?? 'status').toLowerCase();
146
160
  let runId;
@@ -2,6 +2,7 @@ import { printJson as writeJson } from '../lib/format.js';
2
2
  import { withProgress } from '../lib/progress.js';
3
3
  import { EXIT } from '../types.js';
4
4
  import { getOneAccessToken } from '../auth/index.js';
5
+ import { NotSignedInError } from '../auth/store.js';
5
6
  import { VigilbaseOneClient as OneClient } from '../lib/one-client.js';
6
7
  /** Reject known flags from other command groups instead of silently ignoring filters. */
7
8
  export function assertCommandFlags(ctx, allowed) {
@@ -33,9 +34,8 @@ export function asArray(value) {
33
34
  }
34
35
  export async function requireOneClient(ctx) {
35
36
  const accessToken = await getOneAccessToken(ctx.config);
36
- if (!accessToken) {
37
- throw new Error('This command requires Vigilbase One auth. Run `vigilbase login` first.');
38
- }
37
+ if (!accessToken)
38
+ throw new NotSignedInError();
39
39
  return new OneClient(ctx.config, accessToken, true);
40
40
  }
41
41
  export async function withSpinner(ctx, label, work) {
@@ -7,4 +7,5 @@ import type { CommandHandler } from './helpers.js';
7
7
  * `cloudflare-checkup` is the top-level alias of `local cloudflare-checkup`.
8
8
  */
9
9
  export declare const COMMAND_HANDLERS: Record<string, CommandHandler>;
10
+ /** Own entries only: names such as constructor or __proto__ are not commands. */
10
11
  export declare function getCommandHandler(command: string): CommandHandler | null;
@@ -43,6 +43,7 @@ export const COMMAND_HANDLERS = {
43
43
  local: handleLocal,
44
44
  'cloudflare-checkup': handleCloudflareCheckup,
45
45
  };
46
+ /** Own entries only: names such as constructor or __proto__ are not commands. */
46
47
  export function getCommandHandler(command) {
47
- return COMMAND_HANDLERS[command] ?? null;
48
+ return Object.hasOwn(COMMAND_HANDLERS, command) ? COMMAND_HANDLERS[command] : null;
48
49
  }
@@ -1,12 +1,28 @@
1
1
  import { z } from 'zod';
2
2
  import { type CommandContext } from './helpers.js';
3
+ /** A flag or argument the CLI rejected before any request; `--json` reports it as code INVALID_ARGUMENT. */
4
+ export declare class InvalidArgumentError extends Error {
5
+ readonly code = "INVALID_ARGUMENT";
6
+ constructor(message: string);
7
+ }
3
8
  /**
4
9
  * Exactly `count` positional arguments. When some are missing, they are named from the trailing `<placeholders>`
5
10
  * of `usage` (for example `vigilbase run <tool> <target>`), so `usage` lists only positional placeholders.
11
+ * An empty or whitespace-only argument counts as missing.
6
12
  */
7
13
  export declare function validateArguments(ctx: CommandContext, count: number, flags?: readonly string[], usage?: string): void;
8
14
  /** "Missing <target>. Usage: vigilbase headers <url>", or a pointer to --help without a usage. */
9
15
  export declare function missingArguments(command: string, missingCount: number, usage?: string): string;
16
+ /** 'Unknown security command "x". Run `vigilbase security --help` for usage.' The pointer names the top-level group. */
17
+ export declare function unknownSubcommand(group: string, subcommand: string): Error;
18
+ /** Parses user input with `schema`, throwing `message` instead of Zod's issue list. */
19
+ export declare function parseArgument<T>(schema: z.ZodType<T>, value: unknown, message: string): T;
20
+ /** An integer already checked as digits, within [min, max]. */
21
+ export declare function inRange(label: string, value: number, min: number, max: number): number;
22
+ export declare function oneOf<const T extends readonly string[]>(label: string, values: T, value: string | undefined): T[number] | undefined;
23
+ /** A trimmed, non-empty flag value of at most `max` characters. */
24
+ export declare function textValue(label: string, value: string | undefined, max: number): string | undefined;
25
+ export declare function isoDateTime(label: string, value: string): string;
10
26
  export declare function limitValue(raw: string | undefined, fallback: number, max: number): number;
11
27
  export declare const invitationSchema: z.ZodObject<{
12
28
  email: z.ZodString;
@@ -23,4 +39,8 @@ export declare const invitationSchema: z.ZodObject<{
23
39
  sales_only: "sales_only";
24
40
  }>>;
25
41
  }, z.core.$strip>;
42
+ export declare function invitationValue(email: string | undefined, role: string | undefined): z.infer<typeof invitationSchema>;
26
43
  export declare const toolNameSchema: z.ZodString;
44
+ export declare function toolNameValue(value: string): string;
45
+ /** A scan target: trimmed, 1-2048 characters, no control characters. */
46
+ export declare function targetValue(value: string): string;
@@ -1,14 +1,31 @@
1
1
  import { z } from 'zod';
2
2
  import { assertCommandFlags } from './helpers.js';
3
+ /** A flag or argument the CLI rejected before any request; `--json` reports it as code INVALID_ARGUMENT. */
4
+ export class InvalidArgumentError extends Error {
5
+ code = 'INVALID_ARGUMENT';
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = 'InvalidArgumentError';
9
+ }
10
+ }
3
11
  /**
4
12
  * Exactly `count` positional arguments. When some are missing, they are named from the trailing `<placeholders>`
5
13
  * of `usage` (for example `vigilbase run <tool> <target>`), so `usage` lists only positional placeholders.
14
+ * An empty or whitespace-only argument counts as missing.
6
15
  */
7
16
  export function validateArguments(ctx, count, flags = [], usage) {
8
17
  if (ctx.rest.length < count)
9
18
  throw new Error(missingArguments(ctx.command, count - ctx.rest.length, usage));
10
19
  if (ctx.rest.length > count)
11
20
  throw new Error(`Unexpected arguments. Run \`vigilbase ${ctx.command} --help\` for usage.`);
21
+ const blank = ctx.rest.flatMap((value, index) => value.trim() ? [] : [index]);
22
+ if (blank.length) {
23
+ const placeholders = usage?.match(/<[^>]+>/g) ?? [];
24
+ const missing = blank.map((index) => placeholders[index - (count - placeholders.length)]);
25
+ throw new Error(missing.every(Boolean) && usage
26
+ ? `Missing ${missing.join(' and ')}. Usage: ${usage}`
27
+ : `Missing arguments. Run \`vigilbase ${ctx.command} --help\` for usage.`);
28
+ }
12
29
  assertCommandFlags(ctx, flags);
13
30
  }
14
31
  /** "Missing <target>. Usage: vigilbase headers <url>", or a pointer to --help without a usage. */
@@ -18,15 +35,72 @@ export function missingArguments(command, missingCount, usage) {
18
35
  return `Missing arguments. Run \`vigilbase ${command} --help\` for usage.`;
19
36
  return `Missing ${missing.join(' and ')}. Usage: ${usage}`;
20
37
  }
38
+ /** 'Unknown security command "x". Run `vigilbase security --help` for usage.' The pointer names the top-level group. */
39
+ export function unknownSubcommand(group, subcommand) {
40
+ return new InvalidArgumentError(`Unknown ${group} command "${subcommand.slice(0, 80)}". Run \`vigilbase ${group.split(' ')[0]} --help\` for usage.`);
41
+ }
42
+ /** Parses user input with `schema`, throwing `message` instead of Zod's issue list. */
43
+ export function parseArgument(schema, value, message) {
44
+ const result = schema.safeParse(value);
45
+ if (!result.success)
46
+ throw new InvalidArgumentError(message);
47
+ return result.data;
48
+ }
49
+ /** An integer already checked as digits, within [min, max]. */
50
+ export function inRange(label, value, min, max) {
51
+ if (!Number.isSafeInteger(value) || value < min || value > max)
52
+ throw new InvalidArgumentError(`${label} must be between ${min} and ${max}.`);
53
+ return value;
54
+ }
55
+ export function oneOf(label, values, value) {
56
+ if (value === undefined)
57
+ return undefined;
58
+ if (!values.includes(value))
59
+ throw new InvalidArgumentError(`${label} must be one of: ${values.join(', ')}.`);
60
+ return value;
61
+ }
62
+ /** A trimmed, non-empty flag value of at most `max` characters. */
63
+ export function textValue(label, value, max) {
64
+ if (value === undefined)
65
+ return undefined;
66
+ const trimmed = value.trim();
67
+ if (!trimmed)
68
+ throw new InvalidArgumentError(`${label} must not be empty.`);
69
+ if (trimmed.length > max)
70
+ throw new InvalidArgumentError(`${label} must be at most ${max} characters.`);
71
+ return trimmed;
72
+ }
73
+ export function isoDateTime(label, value) {
74
+ return parseArgument(z.iso.datetime({ offset: true }), value, `${label} must be an ISO 8601 date-time with an offset, for example 2026-10-12T00:00:00Z.`);
75
+ }
21
76
  export function limitValue(raw, fallback, max) {
22
77
  if (raw === undefined)
23
78
  return fallback;
24
79
  if (!/^\d+$/.test(raw))
25
- throw new Error('--limit must be an integer.');
26
- return z.number().int().min(1).max(max).parse(Number(raw));
80
+ throw new InvalidArgumentError('--limit must be an integer.');
81
+ return inRange('--limit', Number(raw), 1, max);
27
82
  }
83
+ const invitationRoles = ['admin', 'member', 'readonly', 'partner_owner', 'partner_sales', 'partner_operations', 'partner_finance', 'partner_readonly', 'billing_only', 'sales_only'];
28
84
  export const invitationSchema = z.object({
29
85
  email: z.string().trim().email(),
30
- role: z.enum(['admin', 'member', 'readonly', 'partner_owner', 'partner_sales', 'partner_operations', 'partner_finance', 'partner_readonly', 'billing_only', 'sales_only']).default('member'),
86
+ role: z.enum(invitationRoles).default('member'),
31
87
  });
88
+ export function invitationValue(email, role) {
89
+ return {
90
+ email: parseArgument(invitationSchema.shape.email, email, 'Enter a valid email address for the invitation.'),
91
+ role: oneOf('--role', invitationRoles, role) ?? 'member',
92
+ };
93
+ }
32
94
  export const toolNameSchema = z.string().trim().min(1).max(128).regex(/^[a-z0-9-]+$/, 'Tool names must contain lowercase letters, digits, or hyphens.');
95
+ export function toolNameValue(value) {
96
+ return parseArgument(toolNameSchema, value, 'Tool names must contain lowercase letters, digits, or hyphens.');
97
+ }
98
+ /** A scan target: trimmed, 1-2048 characters, no control characters. */
99
+ export function targetValue(value) {
100
+ const trimmed = value.trim();
101
+ if (/[\u0000-\u001f\u007f]/.test(trimmed))
102
+ throw new InvalidArgumentError('Target must not contain control characters.');
103
+ if (!trimmed || trimmed.length > 2048)
104
+ throw new InvalidArgumentError('Target must be 1-2048 characters.');
105
+ return trimmed;
106
+ }