@vigilbase/cli 0.2.0 → 0.2.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 (235) hide show
  1. package/CLOUDFLARE.md +9 -12
  2. package/COMMANDS.md +192 -111
  3. package/LOCAL.md +77 -84
  4. package/ONE.md +5 -6
  5. package/README.md +349 -173
  6. package/SECURITY.md +6 -4
  7. package/dist/auth/device-auth.d.ts +0 -1
  8. package/dist/auth/device-auth.js +6 -5
  9. package/dist/auth/index.d.ts +1 -2
  10. package/dist/auth/index.js +1 -2
  11. package/dist/auth/provider.d.ts +1 -1
  12. package/dist/auth/provider.js +16 -7
  13. package/dist/auth/refresh.d.ts +23 -4
  14. package/dist/auth/refresh.js +78 -22
  15. package/dist/auth/store.d.ts +78 -4
  16. package/dist/auth/store.js +385 -74
  17. package/dist/auth/token-metadata.d.ts +0 -1
  18. package/dist/auth/token-metadata.js +0 -1
  19. package/dist/auth/types.d.ts +0 -1
  20. package/dist/auth/types.js +0 -1
  21. package/dist/bin.d.ts +0 -1
  22. package/dist/bin.js +0 -1
  23. package/dist/cli.d.ts +4 -2
  24. package/dist/cli.js +13 -11
  25. package/dist/commands/account.d.ts +0 -1
  26. package/dist/commands/account.js +1 -2
  27. package/dist/commands/auth.d.ts +0 -1
  28. package/dist/commands/auth.js +71 -22
  29. package/dist/commands/billing.d.ts +0 -1
  30. package/dist/commands/billing.js +0 -1
  31. package/dist/commands/cloudflare.d.ts +1 -2
  32. package/dist/commands/cloudflare.js +14 -9
  33. package/dist/commands/helpers.d.ts +0 -1
  34. package/dist/commands/helpers.js +0 -1
  35. package/dist/commands/index.d.ts +0 -1
  36. package/dist/commands/index.js +0 -1
  37. package/dist/commands/legacy-validation.d.ts +7 -2
  38. package/dist/commands/legacy-validation.js +15 -3
  39. package/dist/commands/local.d.ts +6 -1
  40. package/dist/commands/local.js +36 -12
  41. package/dist/commands/one.d.ts +0 -1
  42. package/dist/commands/one.js +39 -13
  43. package/dist/commands/public-tools.d.ts +0 -1
  44. package/dist/commands/public-tools.js +63 -29
  45. package/dist/commands/runs.d.ts +0 -1
  46. package/dist/commands/runs.js +1 -2
  47. package/dist/commands/security.d.ts +1 -2
  48. package/dist/commands/security.js +34 -23
  49. package/dist/commands/watchdog.d.ts +0 -1
  50. package/dist/commands/watchdog.js +0 -1
  51. package/dist/index.d.ts +2 -3
  52. package/dist/index.js +1 -2
  53. package/dist/lib/banner.d.ts +0 -1
  54. package/dist/lib/banner.js +0 -1
  55. package/dist/lib/client.d.ts +9 -2
  56. package/dist/lib/client.js +49 -20
  57. package/dist/lib/cloudflare-client.d.ts +0 -1
  58. package/dist/lib/cloudflare-client.js +1 -2
  59. package/dist/lib/config.d.ts +16 -3
  60. package/dist/lib/config.js +41 -14
  61. package/dist/lib/format.d.ts +2 -1
  62. package/dist/lib/format.js +19 -8
  63. package/dist/lib/help.d.ts +4 -2
  64. package/dist/lib/help.js +72 -23
  65. package/dist/lib/network.d.ts +10 -1
  66. package/dist/lib/network.js +31 -4
  67. package/dist/lib/one-client.d.ts +4 -1
  68. package/dist/lib/one-client.js +9 -3
  69. package/dist/lib/one-error-hints.d.ts +19 -0
  70. package/dist/lib/one-error-hints.js +47 -0
  71. package/dist/lib/one-operations.d.ts +0 -1
  72. package/dist/lib/one-operations.js +0 -1
  73. package/dist/lib/open-url.d.ts +0 -1
  74. package/dist/lib/open-url.js +0 -1
  75. package/dist/lib/platform.d.ts +2 -0
  76. package/dist/lib/platform.js +4 -0
  77. package/dist/lib/progress.d.ts +0 -1
  78. package/dist/lib/progress.js +0 -1
  79. package/dist/lib/security-client.d.ts +0 -1
  80. package/dist/lib/security-client.js +0 -1
  81. package/dist/lib/skill-fetch.d.ts +0 -1
  82. package/dist/lib/skill-fetch.js +0 -1
  83. package/dist/lib/system-trust.d.ts +0 -1
  84. package/dist/lib/system-trust.js +0 -1
  85. package/dist/lib/tui.d.ts +0 -1
  86. package/dist/lib/tui.js +0 -1
  87. package/dist/local/cloudflare-checkup/cf/allowlist.d.ts +0 -1
  88. package/dist/local/cloudflare-checkup/cf/allowlist.js +0 -1
  89. package/dist/local/cloudflare-checkup/cf/auth-lifecycle.d.ts +0 -1
  90. package/dist/local/cloudflare-checkup/cf/auth-lifecycle.js +0 -1
  91. package/dist/local/cloudflare-checkup/cf/child-env.d.ts +0 -1
  92. package/dist/local/cloudflare-checkup/cf/child-env.js +0 -1
  93. package/dist/local/cloudflare-checkup/cf/install.d.ts +0 -1
  94. package/dist/local/cloudflare-checkup/cf/install.js +0 -1
  95. package/dist/local/cloudflare-checkup/cf/locate.d.ts +0 -1
  96. package/dist/local/cloudflare-checkup/cf/locate.js +0 -1
  97. package/dist/local/cloudflare-checkup/cf/run-cf.d.ts +0 -1
  98. package/dist/local/cloudflare-checkup/cf/run-cf.js +0 -1
  99. package/dist/local/cloudflare-checkup/collector.d.ts +0 -1
  100. package/dist/local/cloudflare-checkup/collector.js +0 -1
  101. package/dist/local/cloudflare-checkup/contract.d.ts +14 -3
  102. package/dist/local/cloudflare-checkup/contract.js +9 -3
  103. package/dist/local/cloudflare-checkup/email-verification.d.ts +0 -1
  104. package/dist/local/cloudflare-checkup/email-verification.js +0 -1
  105. package/dist/local/cloudflare-checkup/errors.d.ts +4 -1
  106. package/dist/local/cloudflare-checkup/errors.js +8 -2
  107. package/dist/local/cloudflare-checkup/output.d.ts +0 -1
  108. package/dist/local/cloudflare-checkup/output.js +0 -1
  109. package/dist/local/cloudflare-checkup/prompts.d.ts +0 -1
  110. package/dist/local/cloudflare-checkup/prompts.js +0 -1
  111. package/dist/local/cloudflare-checkup/rate-budget.d.ts +0 -1
  112. package/dist/local/cloudflare-checkup/rate-budget.js +0 -1
  113. package/dist/local/cloudflare-checkup/snapshot-file.d.ts +0 -1
  114. package/dist/local/cloudflare-checkup/snapshot-file.js +5 -4
  115. package/dist/local/cloudflare-checkup/submit-client.d.ts +19 -5
  116. package/dist/local/cloudflare-checkup/submit-client.js +63 -17
  117. package/dist/local/cloudflare-checkup/terminal-ui.d.ts +0 -1
  118. package/dist/local/cloudflare-checkup/terminal-ui.js +0 -1
  119. package/dist/types.d.ts +25 -3
  120. package/dist/types.js +16 -1
  121. package/dist/version.d.ts +0 -1
  122. package/dist/version.js +0 -1
  123. package/package.json +5 -9
  124. package/dist/auth/device-auth.d.ts.map +0 -1
  125. package/dist/auth/device-auth.js.map +0 -1
  126. package/dist/auth/index.d.ts.map +0 -1
  127. package/dist/auth/index.js.map +0 -1
  128. package/dist/auth/provider.d.ts.map +0 -1
  129. package/dist/auth/provider.js.map +0 -1
  130. package/dist/auth/refresh.d.ts.map +0 -1
  131. package/dist/auth/refresh.js.map +0 -1
  132. package/dist/auth/store.d.ts.map +0 -1
  133. package/dist/auth/store.js.map +0 -1
  134. package/dist/auth/token-metadata.d.ts.map +0 -1
  135. package/dist/auth/token-metadata.js.map +0 -1
  136. package/dist/auth/types.d.ts.map +0 -1
  137. package/dist/auth/types.js.map +0 -1
  138. package/dist/bin.d.ts.map +0 -1
  139. package/dist/bin.js.map +0 -1
  140. package/dist/cli.d.ts.map +0 -1
  141. package/dist/cli.js.map +0 -1
  142. package/dist/commands/account.d.ts.map +0 -1
  143. package/dist/commands/account.js.map +0 -1
  144. package/dist/commands/auth.d.ts.map +0 -1
  145. package/dist/commands/auth.js.map +0 -1
  146. package/dist/commands/billing.d.ts.map +0 -1
  147. package/dist/commands/billing.js.map +0 -1
  148. package/dist/commands/cloudflare.d.ts.map +0 -1
  149. package/dist/commands/cloudflare.js.map +0 -1
  150. package/dist/commands/helpers.d.ts.map +0 -1
  151. package/dist/commands/helpers.js.map +0 -1
  152. package/dist/commands/index.d.ts.map +0 -1
  153. package/dist/commands/index.js.map +0 -1
  154. package/dist/commands/legacy-validation.d.ts.map +0 -1
  155. package/dist/commands/legacy-validation.js.map +0 -1
  156. package/dist/commands/local.d.ts.map +0 -1
  157. package/dist/commands/local.js.map +0 -1
  158. package/dist/commands/one.d.ts.map +0 -1
  159. package/dist/commands/one.js.map +0 -1
  160. package/dist/commands/public-tools.d.ts.map +0 -1
  161. package/dist/commands/public-tools.js.map +0 -1
  162. package/dist/commands/runs.d.ts.map +0 -1
  163. package/dist/commands/runs.js.map +0 -1
  164. package/dist/commands/security.d.ts.map +0 -1
  165. package/dist/commands/security.js.map +0 -1
  166. package/dist/commands/watchdog.d.ts.map +0 -1
  167. package/dist/commands/watchdog.js.map +0 -1
  168. package/dist/index.d.ts.map +0 -1
  169. package/dist/index.js.map +0 -1
  170. package/dist/lib/banner.d.ts.map +0 -1
  171. package/dist/lib/banner.js.map +0 -1
  172. package/dist/lib/client.d.ts.map +0 -1
  173. package/dist/lib/client.js.map +0 -1
  174. package/dist/lib/cloudflare-client.d.ts.map +0 -1
  175. package/dist/lib/cloudflare-client.js.map +0 -1
  176. package/dist/lib/config.d.ts.map +0 -1
  177. package/dist/lib/config.js.map +0 -1
  178. package/dist/lib/format.d.ts.map +0 -1
  179. package/dist/lib/format.js.map +0 -1
  180. package/dist/lib/help.d.ts.map +0 -1
  181. package/dist/lib/help.js.map +0 -1
  182. package/dist/lib/network.d.ts.map +0 -1
  183. package/dist/lib/network.js.map +0 -1
  184. package/dist/lib/one-client.d.ts.map +0 -1
  185. package/dist/lib/one-client.js.map +0 -1
  186. package/dist/lib/one-operations.d.ts.map +0 -1
  187. package/dist/lib/one-operations.js.map +0 -1
  188. package/dist/lib/open-url.d.ts.map +0 -1
  189. package/dist/lib/open-url.js.map +0 -1
  190. package/dist/lib/progress.d.ts.map +0 -1
  191. package/dist/lib/progress.js.map +0 -1
  192. package/dist/lib/security-client.d.ts.map +0 -1
  193. package/dist/lib/security-client.js.map +0 -1
  194. package/dist/lib/skill-fetch.d.ts.map +0 -1
  195. package/dist/lib/skill-fetch.js.map +0 -1
  196. package/dist/lib/system-trust.d.ts.map +0 -1
  197. package/dist/lib/system-trust.js.map +0 -1
  198. package/dist/lib/tui.d.ts.map +0 -1
  199. package/dist/lib/tui.js.map +0 -1
  200. package/dist/local/cloudflare-checkup/cf/allowlist.d.ts.map +0 -1
  201. package/dist/local/cloudflare-checkup/cf/allowlist.js.map +0 -1
  202. package/dist/local/cloudflare-checkup/cf/auth-lifecycle.d.ts.map +0 -1
  203. package/dist/local/cloudflare-checkup/cf/auth-lifecycle.js.map +0 -1
  204. package/dist/local/cloudflare-checkup/cf/child-env.d.ts.map +0 -1
  205. package/dist/local/cloudflare-checkup/cf/child-env.js.map +0 -1
  206. package/dist/local/cloudflare-checkup/cf/install.d.ts.map +0 -1
  207. package/dist/local/cloudflare-checkup/cf/install.js.map +0 -1
  208. package/dist/local/cloudflare-checkup/cf/locate.d.ts.map +0 -1
  209. package/dist/local/cloudflare-checkup/cf/locate.js.map +0 -1
  210. package/dist/local/cloudflare-checkup/cf/run-cf.d.ts.map +0 -1
  211. package/dist/local/cloudflare-checkup/cf/run-cf.js.map +0 -1
  212. package/dist/local/cloudflare-checkup/collector.d.ts.map +0 -1
  213. package/dist/local/cloudflare-checkup/collector.js.map +0 -1
  214. package/dist/local/cloudflare-checkup/contract.d.ts.map +0 -1
  215. package/dist/local/cloudflare-checkup/contract.js.map +0 -1
  216. package/dist/local/cloudflare-checkup/email-verification.d.ts.map +0 -1
  217. package/dist/local/cloudflare-checkup/email-verification.js.map +0 -1
  218. package/dist/local/cloudflare-checkup/errors.d.ts.map +0 -1
  219. package/dist/local/cloudflare-checkup/errors.js.map +0 -1
  220. package/dist/local/cloudflare-checkup/output.d.ts.map +0 -1
  221. package/dist/local/cloudflare-checkup/output.js.map +0 -1
  222. package/dist/local/cloudflare-checkup/prompts.d.ts.map +0 -1
  223. package/dist/local/cloudflare-checkup/prompts.js.map +0 -1
  224. package/dist/local/cloudflare-checkup/rate-budget.d.ts.map +0 -1
  225. package/dist/local/cloudflare-checkup/rate-budget.js.map +0 -1
  226. package/dist/local/cloudflare-checkup/snapshot-file.d.ts.map +0 -1
  227. package/dist/local/cloudflare-checkup/snapshot-file.js.map +0 -1
  228. package/dist/local/cloudflare-checkup/submit-client.d.ts.map +0 -1
  229. package/dist/local/cloudflare-checkup/submit-client.js.map +0 -1
  230. package/dist/local/cloudflare-checkup/terminal-ui.d.ts.map +0 -1
  231. package/dist/local/cloudflare-checkup/terminal-ui.js.map +0 -1
  232. package/dist/types.d.ts.map +0 -1
  233. package/dist/types.js.map +0 -1
  234. package/dist/version.d.ts.map +0 -1
  235. package/dist/version.js.map +0 -1
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import { VigilbaseApiError, VigilbaseClient } from './lib/client.js';
4
4
  import { createConfig, createLocalCommandConfig } from './lib/config.js';
5
5
  import { renderBanner, shouldShowBanner } from './lib/banner.js';
6
6
  import { createWriter, formatError } from './lib/format.js';
7
- import { HELP_TAGLINE, HELP_TEXT, helpForCommand } from './lib/help.js';
7
+ import { HELP_TAGLINE, HELP_TEXT, HELP_USAGE, helpForCommand, isCatalogTool } from './lib/help.js';
8
8
  import { wasRendered } from './lib/tui.js';
9
9
  import { getCommandHandler } from './commands/index.js';
10
10
  import { LocalCheckupError } from './local/cloudflare-checkup/errors.js';
@@ -163,7 +163,8 @@ function resolveMode(jsonFlag) {
163
163
  }
164
164
  export async function runCli(options = {}) {
165
165
  const argv = options.argv ?? process.argv.slice(2);
166
- const writer = createWriter(options.stdout, options.stderr);
166
+ const stdoutStream = options.stdout ?? process.stdout;
167
+ const writer = createWriter(stdoutStream, options.stderr);
167
168
  const previousEnv = process.env;
168
169
  if (options.env) {
169
170
  process.env = options.env;
@@ -182,12 +183,16 @@ export async function runCli(options = {}) {
182
183
  if (parsed.values.isVersion && parsed.positionals.length)
183
184
  throw new Error('Usage: vigilbase --version');
184
185
  if (parsed.positionals[0] === 'help' && parsed.positionals.length > 2)
185
- throw new Error('Usage: vigilbase help [command]');
186
+ throw new Error(HELP_USAGE);
186
187
  }
187
188
  if (parsed.values.isVersion) {
188
189
  if (mode === 'json') {
189
190
  writer.stdout(JSON.stringify({ name: '@vigilbase/cli', version: CLI_VERSION }, null, 2));
190
191
  }
192
+ else if (!stdoutStream.isTTY) {
193
+ // Pipes and files get only the version, for scripts.
194
+ writer.stdout(CLI_VERSION);
195
+ }
191
196
  else if (shouldShowBanner(mode)) {
192
197
  writer.stdout(renderBanner({ version: CLI_VERSION, tagline: HELP_TAGLINE }));
193
198
  }
@@ -238,7 +243,10 @@ export async function runCli(options = {}) {
238
243
  const publicClient = new VigilbaseClient(config);
239
244
  const handler = getCommandHandler(command);
240
245
  if (!handler) {
241
- throw new Error(`Unknown command "${command}". Run \`vigilbase help\` for usage.`);
246
+ // Tools are not commands of their own (no aliases): point to how they run.
247
+ throw new Error(isCatalogTool(command)
248
+ ? `Unknown command "${command}". Run the tool with \`vigilbase run ${command} <target>\`.`
249
+ : `Unknown command "${command}". Run \`vigilbase help\` for usage.`);
242
250
  }
243
251
  return await handler({
244
252
  command,
@@ -250,7 +258,7 @@ export async function runCli(options = {}) {
250
258
  publicClient,
251
259
  env: process.env,
252
260
  stdin: options.stdin,
253
- stdoutStream: options.stdout ?? process.stdout,
261
+ stdoutStream,
254
262
  stderrStream: options.stderr ?? process.stderr,
255
263
  loginHandler: options.loginHandler,
256
264
  });
@@ -266,11 +274,6 @@ export async function runCli(options = {}) {
266
274
  if (error instanceof LocalCheckupError && error.isRateLimited) {
267
275
  return EXIT.RATE_LIMITED;
268
276
  }
269
- if (error instanceof DeviceAuthError && error.code === 'not_enabled') {
270
- if (mode !== 'json') {
271
- writer.stderr('Public tools remain available without login: `vigilbase list`, `vigilbase run …`');
272
- }
273
- }
274
277
  return EXIT.ERROR;
275
278
  }
276
279
  finally {
@@ -279,4 +282,3 @@ export async function runCli(options = {}) {
279
282
  }
280
283
  }
281
284
  }
282
- //# sourceMappingURL=cli.js.map
@@ -1,3 +1,2 @@
1
1
  import { type CommandHandler } from './helpers.js';
2
2
  export declare const handleAccount: CommandHandler;
3
- //# sourceMappingURL=account.d.ts.map
@@ -18,7 +18,7 @@ export const handleAccount = async (ctx) => {
18
18
  else if (action === 'create' || action === 'add') {
19
19
  if (ctx.values.email !== undefined && ctx.rest[2] !== undefined)
20
20
  throw new Error('Specify the invite email either positionally or with --email, not both.');
21
- validateArguments(ctx, ctx.values.email === undefined ? 3 : 2, ['email', 'role', 'isConfirm']);
21
+ validateArguments(ctx, ctx.values.email === undefined ? 3 : 2, ['email', 'role', 'isConfirm'], 'vigilbase account invites create <email> --confirm');
22
22
  invitation = invitationSchema.parse({ email: ctx.values.email ?? ctx.rest[2], role: ctx.values.role });
23
23
  if (!ctx.values.isConfirm)
24
24
  throw new Error('Sending an organization invitation requires --confirm.');
@@ -184,4 +184,3 @@ export const handleAccount = async (ctx) => {
184
184
  }
185
185
  throw new Error('Usage: vigilbase account [services|org|endpoints|sessions|invites|billing] [--json]');
186
186
  };
187
- //# sourceMappingURL=account.js.map
@@ -3,4 +3,3 @@ export declare function readTokenFromStdin(input?: NodeJS.ReadableStream): Promi
3
3
  export declare const handleLogin: CommandHandler;
4
4
  export declare const handleLogout: CommandHandler;
5
5
  export declare const handleWhoami: CommandHandler;
6
- //# sourceMappingURL=auth.d.ts.map
@@ -1,6 +1,7 @@
1
1
  import { readIdentityTokenMetadata } from '../auth/token-metadata.js';
2
2
  import { discoverAuthProvider } from '../auth/provider.js';
3
- import { revokeSavedCredentials, withCredentialsLock } from '../auth/refresh.js';
3
+ import { revokeIdentityCredentials, revokeIdentityGrant, revokeSavedCredentials, withCredentialsLock } from '../auth/refresh.js';
4
+ import { checkCredentialsLocation, clearIdentityCredentials, clearOneCredentials, hasIgnoredIdentityCredentials, readReplacedIdentityGrant, readStoredCredentials } from '../auth/store.js';
4
5
  import { getOneAccessToken, readCredentials, requestDeviceCode, waitForDeviceAuthorization, writeOneCredentials, } from '../auth/index.js';
5
6
  import { VigilbaseOneClient, ONE_AUTH_REQUIRED_TOOLS } from '../lib/one-client.js';
6
7
  import { VigilbaseApiError } from '../lib/client.js';
@@ -22,6 +23,30 @@ export async function readTokenFromStdin(input = process.stdin) {
22
23
  throw new Error('Token input must contain exactly one nonempty token.');
23
24
  return token;
24
25
  }
26
+ /**
27
+ * Revoke the Identity grant a new login replaces, before the new grant exists. Identity treats a revoked refresh
28
+ * token presented again as reuse and ends every CLI grant of the user, so revoking after the new grant was issued
29
+ * could end it too. The lock is held from the read to the removal of the old file, so no concurrent command can
30
+ * refresh with the token in between, and no later command presents it again.
31
+ */
32
+ async function retirePreviousIdentityGrant(ctx) {
33
+ await withCredentialsLock(ctx.config, async () => {
34
+ const previous = await readReplacedIdentityGrant(ctx.config);
35
+ // After a refresh with an uncertain result the token may be rotated already; presenting it trips the same check.
36
+ if (!previous || previous.refreshPending)
37
+ return;
38
+ try {
39
+ await revokeIdentityGrant(ctx.config, previous);
40
+ }
41
+ catch {
42
+ ctx.writer.stderr("Warning: Couldn't revoke your previous sign-in; it will expire on its own.");
43
+ return;
44
+ }
45
+ // The grant came from identity-credentials.json, or from a CLI 0.2.0 credentials.json when that file is absent.
46
+ const { identity } = await readStoredCredentials(ctx.config, () => { });
47
+ await (identity ? clearIdentityCredentials(ctx.config) : clearOneCredentials(ctx.config));
48
+ }, true);
49
+ }
25
50
  export const handleLogin = async (ctx) => {
26
51
  if (ctx.rest.length)
27
52
  throw new Error('Usage: vigilbase login [--token-stdin | --token <bearer>] [--no-open]');
@@ -29,6 +54,9 @@ export const handleLogin = async (ctx) => {
29
54
  if (ctx.loginHandler) {
30
55
  return ctx.loginHandler(ctx.config);
31
56
  }
57
+ // Fail on Windows or an unsafe credentials directory or file before validating a token or starting device
58
+ // authorization: once a device grant is approved, a failed save would leave it unsaved and unrevoked.
59
+ await checkCredentialsLocation(ctx.config);
32
60
  if (ctx.values.isTokenStdin && (ctx.values.token !== undefined || ctx.env.VIGILBASE_ONE_TOKEN !== undefined)) {
33
61
  throw new Error('Use --token-stdin without --token or VIGILBASE_ONE_TOKEN.');
34
62
  }
@@ -50,15 +78,19 @@ export const handleLogin = async (ctx) => {
50
78
  email: session.user.email ?? undefined,
51
79
  name: session.user.name ?? undefined,
52
80
  };
53
- await withCredentialsLock(ctx.config, () => writeOneCredentials(ctx.config, {
54
- accessToken: explicitToken,
55
- tokenType: 'Bearer',
56
- expiresAt: typeof session.session?.expiresAt === 'string'
57
- ? session.session.expiresAt
58
- : undefined,
59
- user,
60
- obtainedAt: new Date().toISOString(),
61
- }), true);
81
+ await withCredentialsLock(ctx.config, async () => {
82
+ // Identity credentials win when tokens are read, so revoke them before saving this session.
83
+ await revokeIdentityCredentials(ctx.config);
84
+ await writeOneCredentials(ctx.config, {
85
+ accessToken: explicitToken,
86
+ tokenType: 'Bearer',
87
+ expiresAt: typeof session.session?.expiresAt === 'string'
88
+ ? session.session.expiresAt
89
+ : undefined,
90
+ user,
91
+ obtainedAt: new Date().toISOString(),
92
+ });
93
+ }, true);
62
94
  if (ctx.mode === 'json') {
63
95
  outputJson(ctx, {
64
96
  success: true,
@@ -76,6 +108,8 @@ export const handleLogin = async (ctx) => {
76
108
  const preferNoOpen = Boolean(ctx.values.isNoOpen) || isHeadlessEnvironment(ctx.env);
77
109
  ctx.writer.stderr(`login ${ctx.config.oneBaseUrl}`);
78
110
  ctx.config.authProvider = await discoverAuthProvider(ctx.config);
111
+ if (ctx.config.authProvider)
112
+ await retirePreviousIdentityGrant(ctx);
79
113
  const device = await withSpinner(ctx, 'Requesting device code', () => requestDeviceCode(ctx.config));
80
114
  const verifyUrl = resolveDeviceVerificationUrl(ctx.config.authProvider?.issuer ?? ctx.config.oneBaseUrl, device);
81
115
  const prettyCode = formatUserCode(device.user_code);
@@ -131,15 +165,20 @@ export const handleLogin = async (ctx) => {
131
165
  if (ctx.config.authProvider && (!token.refresh_token || !expiresAt || !token.expires_in || token.expires_in > 300))
132
166
  throw new Error('Identity returned an invalid token grant.');
133
167
  const identityMetadata = ctx.config.authProvider ? readIdentityTokenMetadata(token.access_token, { issuer: ctx.config.authProvider.issuer, clientId: ctx.config.clientId, resource: ctx.config.authProvider.resource }) : undefined;
134
- await withCredentialsLock(ctx.config, () => writeOneCredentials(ctx.config, {
135
- ...(ctx.config.authProvider ? { issuer: ctx.config.authProvider.issuer, clientId: ctx.config.clientId, resource: ctx.config.authProvider.resource, refreshToken: token.refresh_token, orgId: identityMetadata?.organizationId } : {}),
136
- accessToken: token.access_token,
137
- tokenType: token.token_type,
138
- scope: token.scope,
139
- expiresAt,
140
- user,
141
- obtainedAt: new Date().toISOString(),
142
- }), true);
168
+ await withCredentialsLock(ctx.config, async () => {
169
+ // A legacy session is saved in credentials.json, which saved Identity credentials would override.
170
+ if (!ctx.config.authProvider)
171
+ await revokeIdentityCredentials(ctx.config);
172
+ await writeOneCredentials(ctx.config, {
173
+ ...(ctx.config.authProvider ? { issuer: ctx.config.authProvider.issuer, clientId: ctx.config.clientId, resource: ctx.config.authProvider.resource, refreshToken: token.refresh_token, orgId: identityMetadata?.organizationId } : {}),
174
+ accessToken: token.access_token,
175
+ tokenType: token.token_type,
176
+ scope: token.scope,
177
+ expiresAt,
178
+ user,
179
+ obtainedAt: new Date().toISOString(),
180
+ });
181
+ }, true);
143
182
  if (ctx.mode === 'json') {
144
183
  outputJson(ctx, {
145
184
  success: true,
@@ -160,7 +199,10 @@ export const handleLogout = async (ctx) => {
160
199
  if (ctx.rest.length)
161
200
  throw new Error('Usage: vigilbase logout');
162
201
  assertCommandFlags(ctx, []);
163
- const cleared = await revokeSavedCredentials(ctx.config);
202
+ const cleared = await revokeSavedCredentials(ctx.config, (warning) => {
203
+ if (ctx.mode !== 'json')
204
+ ctx.writer.stderr(`Warning: ${warning}`);
205
+ });
164
206
  if (ctx.mode === 'json') {
165
207
  outputJson(ctx, { success: true, cleared });
166
208
  }
@@ -176,7 +218,7 @@ export const handleWhoami = async (ctx) => {
176
218
  if (ctx.rest.length)
177
219
  throw new Error('Usage: vigilbase whoami');
178
220
  assertCommandFlags(ctx, []);
179
- const credentials = await readCredentials(ctx.config);
221
+ const legacyIdentityFileIgnored = await hasIgnoredIdentityCredentials(ctx.config);
180
222
  const accessToken = await getOneAccessToken(ctx.config);
181
223
  let liveUser = null;
182
224
  if (accessToken) {
@@ -199,6 +241,8 @@ export const handleWhoami = async (ctx) => {
199
241
  }
200
242
  }
201
243
  const signedIn = Boolean(liveUser);
244
+ // Read after the token and session calls: either may have refreshed the sign-in, which saves a new expiry and scope.
245
+ const credentials = await readCredentials(ctx.config);
202
246
  const payload = {
203
247
  authenticated: signedIn,
204
248
  oneBaseUrl: ctx.config.oneBaseUrl,
@@ -211,7 +255,10 @@ export const handleWhoami = async (ctx) => {
211
255
  scope: signedIn ? credentials.one?.scope ?? null : null,
212
256
  obtainedAt: signedIn ? credentials.one?.obtainedAt ?? null : null,
213
257
  authRequiredTools: signedIn ? [...ONE_AUTH_REQUIRED_TOOLS] : [],
258
+ legacyIdentityFileIgnored,
214
259
  };
260
+ // CLI 0.2.0 can save a second Identity grant in credentials.json; logout revokes it.
261
+ const legacyNote = 'Note: found a credentials file from CLI 0.2.0. `vigilbase logout` will revoke and remove it.';
215
262
  if (ctx.mode === 'json') {
216
263
  outputJson(ctx, payload);
217
264
  }
@@ -219,6 +266,7 @@ export const handleWhoami = async (ctx) => {
219
266
  outputLines(ctx, [
220
267
  'Not signed in. Public tools still work.',
221
268
  'Run `vigilbase login` to unlock auth-required tools (website-security, react2shell, fortibleed-check).',
269
+ ...(legacyIdentityFileIgnored ? [legacyNote] : []),
222
270
  ]);
223
271
  }
224
272
  else {
@@ -229,8 +277,9 @@ export const handleWhoami = async (ctx) => {
229
277
  lines.push(`Expires: ${credentials.one.expiresAt}`);
230
278
  }
231
279
  lines.push(`Auth-required tools: ${ONE_AUTH_REQUIRED_TOOLS.join(', ')}`);
280
+ if (legacyIdentityFileIgnored)
281
+ lines.push(legacyNote);
232
282
  outputLines(ctx, lines);
233
283
  }
234
284
  return ok();
235
285
  };
236
- //# sourceMappingURL=auth.js.map
@@ -1,3 +1,2 @@
1
1
  import { type CommandHandler } from './helpers.js';
2
2
  export declare const handleBilling: CommandHandler;
3
- //# sourceMappingURL=billing.d.ts.map
@@ -48,4 +48,3 @@ export const handleBilling = async (ctx) => {
48
48
  }
49
49
  throw new Error('Usage: vigilbase billing [summary|portal] [--json] [--no-open]');
50
50
  };
51
- //# sourceMappingURL=billing.js.map
@@ -1,4 +1,3 @@
1
1
  import { type CommandHandler } from './helpers.js';
2
- export declare const CLOUDFLARE_HELP = "Usage: vigilbase cf <command> [options]\nAlias: vigilbase cloudflare\n\nInventory and workspace:\n zones List zones (default); --customer-id, --account-id, --zone-id\n zone <zone-id> Zone detail; --customer-id selects live details, optional --account-id\n accounts List accessible accounts; --customer-id, --account-id\n account <account-id> Stored account detail; --customer-id\n workspace [section] Workspace bootstrap or section; --customer-id, --account-id, --zone-id\n Sections: overview, reports, zones, rule-analyzer, accounts, checkups, settings\n\nZone reads (optional --customer-id; IDs can use --zone-id):\n dns <zone-id> DNS records, TTL and proxy state\n waf <zone-id> WAF rules, expressions, analytics and suggestions\n speed-settings <zone-id> Performance settings (internal users only)\n\nAccount reads (optional --customer-id; IDs can use --account-id):\n audit-logs <account-id> Latest provider audit events (up to 100; service-read permission)\n zero-trust <account-id> Users, Access applications, tunnels and authentication analytics\n developer-platform <account-id> Workers, Workers analytics and Pages\n magic-transit <account-id> Prefixes, routes and traffic analytics\n Zero Trust, Developer Platform and Magic Transit require deployment-read permission.\n\nAnalytics and checkups:\n analytics --customer-id <uuid> [--account-id <id> | --zone-id <id>] [--days 1..30]\n Default 7 days; live zone analytics uses the most recent 23 hours.\n checkup [status] Latest completed checkup; --customer-id, --account-id\n checkup start Start and persist a scan; --customer-id, --account-id\n checkup progress <run-uuid> Read saved progress once\n checkup result <run-uuid> Read the stored Cloudflare checkup result\n checkups aliases checkup; list/show alias status; run aliases start.\n\nCommon options: --json, --one-url <origin>, --timeout <milliseconds>, --help\nCustomer IDs are UUIDs; account and zone IDs are 32 hexadecimal characters.\nOne login, tenant bindings and permissions apply to every command.\nCheckup start creates a scan run. These commands do not edit provider configuration.\nSee CLOUDFLARE.md for full grammar, API mapping, data limits and examples.";
2
+ export declare const CLOUDFLARE_HELP = "Usage: vigilbase cf <command> [options]\nAlias: vigilbase cloudflare\n\nInventory and workspace:\n zones List zones (default); --customer-id, --account-id, --zone-id\n zone <zone-id> Zone detail; --customer-id selects live details, optional --account-id\n accounts List accessible accounts; --customer-id, --account-id\n account <account-id> Stored account detail; --customer-id\n workspace [section] Workspace bootstrap or section; --customer-id, --account-id, --zone-id\n Sections: overview, reports, zones, rule-analyzer, accounts, checkups, settings\n\nZone reads (--customer-id <uuid> required; IDs can use --zone-id):\n dns <zone-id> DNS records, TTL and proxy state\n waf <zone-id> WAF rules, expressions, analytics and suggestions\n speed-settings <zone-id> Performance settings (internal users only)\n\nAccount reads (optional --customer-id; IDs can use --account-id):\n audit-logs <account-id> Latest provider audit events (up to 100; service-read permission)\n zero-trust <account-id> Users, Access applications, tunnels and authentication analytics\n developer-platform <account-id>\n Workers, Workers analytics and Pages\n magic-transit <account-id> Prefixes, routes and traffic analytics\n Zero Trust, Developer Platform and Magic Transit require deployment-read permission.\n\nAnalytics and checkups:\n analytics --customer-id <uuid> [--account-id <id> | --zone-id <id>] [--days 1..30]\n Default 7 days; live zone analytics uses the most recent 23 hours.\n checkup [status] Latest completed checkup; --customer-id, --account-id\n checkup start Start and persist a scan; --customer-id, --account-id\n checkup progress <run-uuid> Read saved progress once\n checkup result <run-uuid> Read the stored Cloudflare checkup result\n checkups aliases checkup; list/show alias status; run aliases start.\n\nCommon options: --json, --one-url <origin>, --timeout <milliseconds>, --help\nCustomer IDs are UUIDs; account and zone IDs are 32 hexadecimal characters.\nOne login, tenant bindings and permissions apply to every command.\nCheckup start creates a scan run. These commands do not edit provider configuration.\nSee CLOUDFLARE.md for full grammar, API mapping, data limits and examples.";
3
3
  export declare const handleCloudflare: CommandHandler;
4
- //# sourceMappingURL=cloudflare.d.ts.map
@@ -9,12 +9,12 @@ Alias: vigilbase cloudflare
9
9
  Inventory and workspace:
10
10
  zones List zones (default); --customer-id, --account-id, --zone-id
11
11
  zone <zone-id> Zone detail; --customer-id selects live details, optional --account-id
12
- accounts List accessible accounts; --customer-id, --account-id
12
+ accounts List accessible accounts; --customer-id, --account-id
13
13
  account <account-id> Stored account detail; --customer-id
14
- workspace [section] Workspace bootstrap or section; --customer-id, --account-id, --zone-id
14
+ workspace [section] Workspace bootstrap or section; --customer-id, --account-id, --zone-id
15
15
  Sections: overview, reports, zones, rule-analyzer, accounts, checkups, settings
16
16
 
17
- Zone reads (optional --customer-id; IDs can use --zone-id):
17
+ Zone reads (--customer-id <uuid> required; IDs can use --zone-id):
18
18
  dns <zone-id> DNS records, TTL and proxy state
19
19
  waf <zone-id> WAF rules, expressions, analytics and suggestions
20
20
  speed-settings <zone-id> Performance settings (internal users only)
@@ -22,15 +22,16 @@ Zone reads (optional --customer-id; IDs can use --zone-id):
22
22
  Account reads (optional --customer-id; IDs can use --account-id):
23
23
  audit-logs <account-id> Latest provider audit events (up to 100; service-read permission)
24
24
  zero-trust <account-id> Users, Access applications, tunnels and authentication analytics
25
- developer-platform <account-id> Workers, Workers analytics and Pages
25
+ developer-platform <account-id>
26
+ Workers, Workers analytics and Pages
26
27
  magic-transit <account-id> Prefixes, routes and traffic analytics
27
28
  Zero Trust, Developer Platform and Magic Transit require deployment-read permission.
28
29
 
29
30
  Analytics and checkups:
30
31
  analytics --customer-id <uuid> [--account-id <id> | --zone-id <id>] [--days 1..30]
31
32
  Default 7 days; live zone analytics uses the most recent 23 hours.
32
- checkup [status] Latest completed checkup; --customer-id, --account-id
33
- checkup start Start and persist a scan; --customer-id, --account-id
33
+ checkup [status] Latest completed checkup; --customer-id, --account-id
34
+ checkup start Start and persist a scan; --customer-id, --account-id
34
35
  checkup progress <run-uuid> Read saved progress once
35
36
  checkup result <run-uuid> Read the stored Cloudflare checkup result
36
37
  checkups aliases checkup; list/show alias status; run aliases start.
@@ -110,7 +111,7 @@ export const handleCloudflare = async (ctx) => {
110
111
  throw new Error('The positional account ID and --account-id must match.');
111
112
  accountId = args[0] ?? scope.accountId;
112
113
  if (!accountId)
113
- throw new Error(`Usage: vigilbase cf ${subcommand} <account-id> [--customer-id …] [--json]`);
114
+ throw new Error(`Missing <account-id>. Usage: vigilbase cf ${subcommand} <account-id> [--customer-id <uuid>] [--json]`);
114
115
  validate(cloudflareScopeSchema, { accountId });
115
116
  }
116
117
  else if (['dns', 'waf', 'zone', 'speed-settings'].includes(subcommand)) {
@@ -120,9 +121,12 @@ export const handleCloudflare = async (ctx) => {
120
121
  zoneId = args[0] ?? scope.zoneId;
121
122
  // Preserve the historical `cf zone` alias for the zone inventory.
122
123
  if (subcommand !== 'zone' && !zoneId)
123
- throw new Error(`Usage: vigilbase cf ${subcommand} <zone-id> [--customer-id …] [--json]`);
124
+ throw new Error(`Missing <zone-id>. Usage: vigilbase cf ${subcommand} <zone-id> --customer-id <uuid> [--json]`);
124
125
  if (zoneId)
125
126
  validate(cloudflareScopeSchema, { zoneId });
127
+ // One answers 400 to every caller that leaves the customer out of a zone read.
128
+ if (subcommand !== 'zone' && !scope.customerId)
129
+ throw new Error(`cf ${subcommand} requires --customer-id <uuid>. Find it with \`vigilbase cf accounts --json\`.`);
126
130
  }
127
131
  else if (!['workspace', 'checkup', 'checkups'].includes(subcommand)) {
128
132
  noExtraArgs(0);
@@ -143,6 +147,8 @@ export const handleCloudflare = async (ctx) => {
143
147
  if (subcommand === 'checkup' || subcommand === 'checkups') {
144
148
  if (['progress', 'result'].includes(action)) {
145
149
  noExtraArgs(2, checkupUsage);
150
+ if (args[1] === undefined)
151
+ throw new Error(`Missing <run-uuid>. Usage: vigilbase cf checkup ${action} <run-uuid> [--json]`);
146
152
  runId = validate(z.string().uuid('A checkup run UUID is required.'), args[1]);
147
153
  if (scope.customerId || scope.accountId)
148
154
  throw new Error(`cf checkup ${action} is scoped by run ID and does not accept --customer-id or --account-id.`);
@@ -338,4 +344,3 @@ export const handleCloudflare = async (ctx) => {
338
344
  fields(ctx, report);
339
345
  });
340
346
  };
341
- //# sourceMappingURL=cloudflare.js.map
@@ -96,4 +96,3 @@ export declare function requireOneClient(ctx: CommandContext): Promise<Vigilbase
96
96
  export declare function withSpinner<T>(ctx: CommandContext, label: string, work: () => Promise<T>): Promise<T>;
97
97
  export declare function ok(): ExitCode;
98
98
  export { EXIT };
99
- //# sourceMappingURL=helpers.d.ts.map
@@ -45,4 +45,3 @@ export function ok() {
45
45
  return EXIT.OK;
46
46
  }
47
47
  export { EXIT };
48
- //# sourceMappingURL=helpers.js.map
@@ -8,4 +8,3 @@ import type { CommandHandler } from './helpers.js';
8
8
  */
9
9
  export declare const COMMAND_HANDLERS: Record<string, CommandHandler>;
10
10
  export declare function getCommandHandler(command: string): CommandHandler | null;
11
- //# sourceMappingURL=index.d.ts.map
@@ -46,4 +46,3 @@ export const COMMAND_HANDLERS = {
46
46
  export function getCommandHandler(command) {
47
47
  return COMMAND_HANDLERS[command] ?? null;
48
48
  }
49
- //# sourceMappingURL=index.js.map
@@ -1,6 +1,12 @@
1
1
  import { z } from 'zod';
2
2
  import { type CommandContext } from './helpers.js';
3
- export declare function validateArguments(ctx: CommandContext, count: number, flags?: readonly string[]): void;
3
+ /**
4
+ * Exactly `count` positional arguments. When some are missing, they are named from the trailing `<placeholders>`
5
+ * of `usage` (for example `vigilbase run <tool> <target>`), so `usage` lists only positional placeholders.
6
+ */
7
+ export declare function validateArguments(ctx: CommandContext, count: number, flags?: readonly string[], usage?: string): void;
8
+ /** "Missing <target>. Usage: vigilbase headers <url>", or a pointer to --help without a usage. */
9
+ export declare function missingArguments(command: string, missingCount: number, usage?: string): string;
4
10
  export declare function limitValue(raw: string | undefined, fallback: number, max: number): number;
5
11
  export declare const invitationSchema: z.ZodObject<{
6
12
  email: z.ZodString;
@@ -18,4 +24,3 @@ export declare const invitationSchema: z.ZodObject<{
18
24
  }>>;
19
25
  }, z.core.$strip>;
20
26
  export declare const toolNameSchema: z.ZodString;
21
- //# sourceMappingURL=legacy-validation.d.ts.map
@@ -1,10 +1,23 @@
1
1
  import { z } from 'zod';
2
2
  import { assertCommandFlags } from './helpers.js';
3
- export function validateArguments(ctx, count, flags = []) {
4
- if (ctx.rest.length !== count)
3
+ /**
4
+ * Exactly `count` positional arguments. When some are missing, they are named from the trailing `<placeholders>`
5
+ * of `usage` (for example `vigilbase run <tool> <target>`), so `usage` lists only positional placeholders.
6
+ */
7
+ export function validateArguments(ctx, count, flags = [], usage) {
8
+ if (ctx.rest.length < count)
9
+ throw new Error(missingArguments(ctx.command, count - ctx.rest.length, usage));
10
+ if (ctx.rest.length > count)
5
11
  throw new Error(`Unexpected arguments. Run \`vigilbase ${ctx.command} --help\` for usage.`);
6
12
  assertCommandFlags(ctx, flags);
7
13
  }
14
+ /** "Missing <target>. Usage: vigilbase headers <url>", or a pointer to --help without a usage. */
15
+ export function missingArguments(command, missingCount, usage) {
16
+ const missing = usage?.match(/<[^>]+>/g)?.slice(-missingCount) ?? [];
17
+ if (!usage || missing.length !== missingCount)
18
+ return `Missing arguments. Run \`vigilbase ${command} --help\` for usage.`;
19
+ return `Missing ${missing.join(' and ')}. Usage: ${usage}`;
20
+ }
8
21
  export function limitValue(raw, fallback, max) {
9
22
  if (raw === undefined)
10
23
  return fallback;
@@ -17,4 +30,3 @@ export const invitationSchema = z.object({
17
30
  role: z.enum(['admin', 'member', 'readonly', 'partner_owner', 'partner_sales', 'partner_operations', 'partner_finance', 'partner_readonly', 'billing_only', 'sales_only']).default('member'),
18
31
  });
19
32
  export const toolNameSchema = z.string().trim().min(1).max(128).regex(/^[a-z0-9-]+$/, 'Tool names must contain lowercase letters, digits, or hyphens.');
20
- //# sourceMappingURL=legacy-validation.js.map
@@ -27,4 +27,9 @@ export declare function displayPath(path: string, env?: NodeJS.ProcessEnv, platf
27
27
  * `-2`, `-3`, … only when a run in the same second already used the name.
28
28
  */
29
29
  export declare function defaultSnapshotName(accountId: string, at: Date, attempt?: number): string;
30
- //# sourceMappingURL=local.d.ts.map
30
+ /**
31
+ * `submit` for a saved file. A loopback origin is named with `--base-url`, so a
32
+ * development run never suggests an upload to production; production is the
33
+ * CLI's default and is not named.
34
+ */
35
+ export declare function submitCommand(path: string, origin: string): string;
@@ -33,7 +33,7 @@ import { buildSnapshot, collectAccount, estimateRequests, listAccounts } from '.
33
33
  import { accountName, countEntries, countEntriesByKind, dataCategories, loadSnapshotFile, writeSnapshotFile, zoneCount, } from '../local/cloudflare-checkup/snapshot-file.js';
34
34
  import { confirmDefaultYes, confirmUpload, hasInteractiveInput, promptLine } from '../local/cloudflare-checkup/prompts.js';
35
35
  import { SAME_ADDRESS_WAIT, emailLocale, parseEmailFlag, verifyEmail } from '../local/cloudflare-checkup/email-verification.js';
36
- import { SubmitClient, VerificationClient, assertSessionAccepts, displayRequester, resolveSubmitOrigin, } from '../local/cloudflare-checkup/submit-client.js';
36
+ import { SubmitClient, VerificationClient, assertServerAccepts, displayRequester, isLoopbackOrigin, resolveSubmitOrigin, } from '../local/cloudflare-checkup/submit-client.js';
37
37
  import { displayText, formatBytes, formatClock, formatDuration, maskId, readsSummary, shortSha, startCountsProgress } from '../local/cloudflare-checkup/output.js';
38
38
  import { CheckupTui } from '../local/cloudflare-checkup/terminal-ui.js';
39
39
  import { EXIT, assertCommandFlags, ok, outputJson } from './helpers.js';
@@ -134,7 +134,8 @@ function parse(schema, value) {
134
134
  function parseLocalOptions(ctx, subcommand, args) {
135
135
  const takesFile = subcommand === 'inspect' || subcommand === 'submit';
136
136
  if (takesFile ? args.length !== 1 : args.length !== 0) {
137
- throw new Error(takesFile ? `Usage: vigilbase cloudflare-checkup ${subcommand} <file>` : usage);
137
+ const fileUsage = `Usage: vigilbase cloudflare-checkup ${subcommand} <file>`;
138
+ throw new Error(!takesFile ? usage : args.length === 0 ? `Missing <file>. ${fileUsage}` : fileUsage);
138
139
  }
139
140
  const values = ctx.values;
140
141
  const requested = parse(z.enum(['auto', 'profile', 'env', 'default'], { message: '--cf-auth must be auto, profile, env or default.' }), values['cf-auth'] ?? (subcommand === 'run' ? 'auto' : 'profile'));
@@ -1159,10 +1160,23 @@ function clientOptions(ctx, origin) {
1159
1160
  ...(ctx.values.timeout !== undefined ? { timeoutMs: ctx.config.timeoutMs } : {}),
1160
1161
  };
1161
1162
  }
1163
+ /**
1164
+ * Before the email is asked for (or `--email` is used): vigilbase.com says
1165
+ * whether local checkups are on and which CLI and snapshot schema it accepts,
1166
+ * so a switched-off local mode (LOCAL_MODE_DISABLED) or an old CLI
1167
+ * (CLI_UPGRADE_REQUIRED) never costs an emailed code. Without a usable answer
1168
+ * the run goes on, and the session check after the email decides as before.
1169
+ */
1170
+ async function checkAvailability(ctx, origin, schemaVersion) {
1171
+ const client = new VerificationClient(clientOptions(ctx, origin));
1172
+ const availability = await busy(ctx, 'Checking vigilbase.com', () => client.availability());
1173
+ if (availability)
1174
+ assertServerAccepts(availability, CLI_VERSION, schemaVersion);
1175
+ }
1162
1176
  /**
1163
1177
  * The email verification (prompts on stderr, answers from piped stdin
1164
- * without a terminal), then `✓ Verified j***@acme.com`. Nothing is sent
1165
- * before an address is entered, and cf is not touched before it is verified.
1178
+ * without a terminal), then `✓ Verified j***@acme.com`. No address is sent
1179
+ * before one is entered, and cf is not touched before it is verified.
1166
1180
  */
1167
1181
  async function verifyRequester(ctx, options, origin) {
1168
1182
  const verified = await verifyEmail({
@@ -1230,16 +1244,25 @@ class CheckupSession {
1230
1244
  /** Failures after the snapshot is saved that a later `submit <file>` can fix without collecting again. */
1231
1245
  const UPLOAD_LATER_CODES = new Set(['SESSION_INVALID', 'SESSION_EXPIRED', 'SESSION_CLOSED', 'VERIFICATION_EXPIRED', 'VERIFICATION_INVALID',
1232
1246
  'SUBMIT_FAILED', 'VIGILBASE_RATE_LIMITED', 'VIGILBASE_CHALLENGED', 'LOCAL_MODE_DISABLED']);
1247
+ /**
1248
+ * `submit` for a saved file. A loopback origin is named with `--base-url`, so a
1249
+ * development run never suggests an upload to production; production is the
1250
+ * CLI's default and is not named.
1251
+ */
1252
+ export function submitCommand(path, origin) {
1253
+ const baseUrl = isLoopbackOrigin(origin) ? ` --base-url ${shellArg(origin)}` : '';
1254
+ return `${NPX_COMMAND} cloudflare-checkup submit ${shellArg(path)}${baseUrl}`;
1255
+ }
1233
1256
  /**
1234
1257
  * Right before such an error is reported (stderr, both modes): the `submit`
1235
1258
  * command for the saved file. A line of its own, because hints are capped at
1236
1259
  * 300 characters and a path has no fixed length; in the terminal UI, the
1237
1260
  * command has a line of its own too, never wrapped, so it can be copied.
1238
1261
  */
1239
- function noteSavedSnapshot(ctx, error, path) {
1262
+ function noteSavedSnapshot(ctx, error, path, origin) {
1240
1263
  if (!(error instanceof LocalCheckupError) || !UPLOAD_LATER_CODES.has(error.code))
1241
1264
  return;
1242
- const submit = `${NPX_COMMAND} cloudflare-checkup submit ${shellArg(path)}`;
1265
+ const submit = submitCommand(path, origin);
1243
1266
  if (ctx.tui)
1244
1267
  ctx.tui.uploadLater(submit);
1245
1268
  else
@@ -1247,7 +1270,7 @@ function noteSavedSnapshot(ctx, error, path) {
1247
1270
  }
1248
1271
  /** Session checks that need no upload: CLI version, single use, and the bound account. */
1249
1272
  function assertSessionUsable(session, accountId) {
1250
- assertSessionAccepts(session, CLI_VERSION, SNAPSHOT_SCHEMA_VERSION);
1273
+ assertServerAccepts(session, CLI_VERSION, SNAPSHOT_SCHEMA_VERSION);
1251
1274
  if (session.state === 'completed')
1252
1275
  throw new LocalCheckupError('SESSION_USED', 'This checkup already has results, so it cannot accept a new snapshot.');
1253
1276
  // A checkup the email verification just started is never processing; this guards any other state.
@@ -1288,7 +1311,8 @@ async function runOneCommand(ctx, options) {
1288
1311
  // A bad --out never costs an emailed code (collectSnapshot checks it again).
1289
1312
  if (options.out !== undefined)
1290
1313
  await assertOutWritable(options.out, Boolean(ctx.values.isOverwrite));
1291
- // 1-3. The email is verified, and the checkup it started is checked, before cf is touched.
1314
+ // 1-3. Availability, then the email, then the checkup it started are checked before cf is touched.
1315
+ await checkAvailability(ctx, origin, SNAPSHOT_SCHEMA_VERSION);
1292
1316
  const checkup = new CheckupSession(ctx, origin, await verifyRequester(ctx, options, origin));
1293
1317
  const early = await busy(ctx, 'Checking the checkup', () => checkup.getSession());
1294
1318
  assertSessionUsable(early, options.accountId);
@@ -1312,7 +1336,7 @@ async function runOneCommand(ctx, options) {
1312
1336
  result = await uploadLoaded(ctx, { checkup, session, origin, loaded });
1313
1337
  }
1314
1338
  catch (error) {
1315
- noteSavedSnapshot(ctx, error, outcome.path);
1339
+ noteSavedSnapshot(ctx, error, outcome.path, origin);
1316
1340
  throw error;
1317
1341
  }
1318
1342
  if (ctx.mode === 'json') {
@@ -1410,7 +1434,8 @@ async function submitFile(ctx, options) {
1410
1434
  ctx.writer.stderr(head);
1411
1435
  else
1412
1436
  ctx.writer.stdout(head);
1413
- // The file is checked before the email is verified, so a bad file never sends a code.
1437
+ // The file, then vigilbase.com's availability, are checked before the email is verified, so neither costs a code.
1438
+ await checkAvailability(ctx, origin, loaded.snapshot.schemaVersion);
1414
1439
  const checkup = new CheckupSession(ctx, origin, await verifyRequester(ctx, options, origin));
1415
1440
  const session = await busy(ctx, 'Checking the checkup', () => checkup.getSession());
1416
1441
  const result = await uploadLoaded(ctx, { checkup, session, origin, loaded });
@@ -1455,7 +1480,7 @@ async function uploadLoaded(ctx, input) {
1455
1480
  const { checkup, session, origin, loaded } = input;
1456
1481
  const { snapshot } = loaded;
1457
1482
  assertFresh(snapshot);
1458
- assertSessionAccepts(session, CLI_VERSION, snapshot.schemaVersion);
1483
+ assertServerAccepts(session, CLI_VERSION, snapshot.schemaVersion);
1459
1484
  if (session.accountId !== null && session.accountId !== snapshot.accountId) {
1460
1485
  throw new LocalCheckupError('ACCOUNT_MISMATCH', `This checkup is bound to Cloudflare account ${maskId(session.accountId)}, but the snapshot is for ${maskId(snapshot.accountId)}. Nothing was uploaded.`);
1461
1486
  }
@@ -1517,4 +1542,3 @@ async function uploadLoaded(ctx, input) {
1517
1542
  ...(result.resultsUrl !== undefined ? { resultsUrl: result.resultsUrl } : {}),
1518
1543
  };
1519
1544
  }
1520
- //# sourceMappingURL=local.js.map
@@ -3,4 +3,3 @@ import { type CommandContext, type CommandHandler } from './helpers.js';
3
3
  export declare function buildOneOperation(ctx: CommandContext): OneOperation;
4
4
  export declare const handleOne: CommandHandler;
5
5
  export declare const ONE_HELP: Record<string, string>;
6
- //# sourceMappingURL=one.d.ts.map