@vigilbase/cli 0.2.0 → 0.2.1

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 (233) hide show
  1. package/CLOUDFLARE.md +2 -5
  2. package/COMMANDS.md +150 -112
  3. package/LOCAL.md +73 -83
  4. package/ONE.md +2 -4
  5. package/README.md +337 -171
  6. package/SECURITY.md +2 -2
  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 +7 -8
  25. package/dist/commands/account.d.ts +0 -1
  26. package/dist/commands/account.js +0 -1
  27. package/dist/commands/auth.d.ts +0 -1
  28. package/dist/commands/auth.js +69 -21
  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 +6 -6
  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 +0 -1
  38. package/dist/commands/legacy-validation.js +0 -1
  39. package/dist/commands/local.d.ts +6 -1
  40. package/dist/commands/local.js +34 -11
  41. package/dist/commands/one.d.ts +0 -1
  42. package/dist/commands/one.js +6 -7
  43. package/dist/commands/public-tools.d.ts +0 -1
  44. package/dist/commands/public-tools.js +52 -18
  45. package/dist/commands/runs.d.ts +0 -1
  46. package/dist/commands/runs.js +0 -1
  47. package/dist/commands/security.d.ts +1 -2
  48. package/dist/commands/security.js +1 -2
  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 +4 -2
  56. package/dist/lib/client.js +42 -18
  57. package/dist/lib/cloudflare-client.d.ts +0 -1
  58. package/dist/lib/cloudflare-client.js +0 -1
  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 +1 -2
  64. package/dist/lib/help.js +29 -18
  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 +2 -1
  68. package/dist/lib/one-client.js +3 -1
  69. package/dist/lib/one-operations.d.ts +0 -1
  70. package/dist/lib/one-operations.js +0 -1
  71. package/dist/lib/open-url.d.ts +0 -1
  72. package/dist/lib/open-url.js +0 -1
  73. package/dist/lib/platform.d.ts +2 -0
  74. package/dist/lib/platform.js +4 -0
  75. package/dist/lib/progress.d.ts +0 -1
  76. package/dist/lib/progress.js +0 -1
  77. package/dist/lib/security-client.d.ts +0 -1
  78. package/dist/lib/security-client.js +0 -1
  79. package/dist/lib/skill-fetch.d.ts +0 -1
  80. package/dist/lib/skill-fetch.js +0 -1
  81. package/dist/lib/system-trust.d.ts +0 -1
  82. package/dist/lib/system-trust.js +0 -1
  83. package/dist/lib/tui.d.ts +0 -1
  84. package/dist/lib/tui.js +0 -1
  85. package/dist/local/cloudflare-checkup/cf/allowlist.d.ts +0 -1
  86. package/dist/local/cloudflare-checkup/cf/allowlist.js +0 -1
  87. package/dist/local/cloudflare-checkup/cf/auth-lifecycle.d.ts +0 -1
  88. package/dist/local/cloudflare-checkup/cf/auth-lifecycle.js +0 -1
  89. package/dist/local/cloudflare-checkup/cf/child-env.d.ts +0 -1
  90. package/dist/local/cloudflare-checkup/cf/child-env.js +0 -1
  91. package/dist/local/cloudflare-checkup/cf/install.d.ts +0 -1
  92. package/dist/local/cloudflare-checkup/cf/install.js +0 -1
  93. package/dist/local/cloudflare-checkup/cf/locate.d.ts +0 -1
  94. package/dist/local/cloudflare-checkup/cf/locate.js +0 -1
  95. package/dist/local/cloudflare-checkup/cf/run-cf.d.ts +0 -1
  96. package/dist/local/cloudflare-checkup/cf/run-cf.js +0 -1
  97. package/dist/local/cloudflare-checkup/collector.d.ts +0 -1
  98. package/dist/local/cloudflare-checkup/collector.js +0 -1
  99. package/dist/local/cloudflare-checkup/contract.d.ts +14 -3
  100. package/dist/local/cloudflare-checkup/contract.js +9 -3
  101. package/dist/local/cloudflare-checkup/email-verification.d.ts +0 -1
  102. package/dist/local/cloudflare-checkup/email-verification.js +0 -1
  103. package/dist/local/cloudflare-checkup/errors.d.ts +2 -1
  104. package/dist/local/cloudflare-checkup/errors.js +6 -2
  105. package/dist/local/cloudflare-checkup/output.d.ts +0 -1
  106. package/dist/local/cloudflare-checkup/output.js +0 -1
  107. package/dist/local/cloudflare-checkup/prompts.d.ts +0 -1
  108. package/dist/local/cloudflare-checkup/prompts.js +0 -1
  109. package/dist/local/cloudflare-checkup/rate-budget.d.ts +0 -1
  110. package/dist/local/cloudflare-checkup/rate-budget.js +0 -1
  111. package/dist/local/cloudflare-checkup/snapshot-file.d.ts +0 -1
  112. package/dist/local/cloudflare-checkup/snapshot-file.js +0 -1
  113. package/dist/local/cloudflare-checkup/submit-client.d.ts +19 -5
  114. package/dist/local/cloudflare-checkup/submit-client.js +63 -17
  115. package/dist/local/cloudflare-checkup/terminal-ui.d.ts +0 -1
  116. package/dist/local/cloudflare-checkup/terminal-ui.js +0 -1
  117. package/dist/types.d.ts +25 -3
  118. package/dist/types.js +16 -1
  119. package/dist/version.d.ts +0 -1
  120. package/dist/version.js +0 -1
  121. package/package.json +5 -9
  122. package/dist/auth/device-auth.d.ts.map +0 -1
  123. package/dist/auth/device-auth.js.map +0 -1
  124. package/dist/auth/index.d.ts.map +0 -1
  125. package/dist/auth/index.js.map +0 -1
  126. package/dist/auth/provider.d.ts.map +0 -1
  127. package/dist/auth/provider.js.map +0 -1
  128. package/dist/auth/refresh.d.ts.map +0 -1
  129. package/dist/auth/refresh.js.map +0 -1
  130. package/dist/auth/store.d.ts.map +0 -1
  131. package/dist/auth/store.js.map +0 -1
  132. package/dist/auth/token-metadata.d.ts.map +0 -1
  133. package/dist/auth/token-metadata.js.map +0 -1
  134. package/dist/auth/types.d.ts.map +0 -1
  135. package/dist/auth/types.js.map +0 -1
  136. package/dist/bin.d.ts.map +0 -1
  137. package/dist/bin.js.map +0 -1
  138. package/dist/cli.d.ts.map +0 -1
  139. package/dist/cli.js.map +0 -1
  140. package/dist/commands/account.d.ts.map +0 -1
  141. package/dist/commands/account.js.map +0 -1
  142. package/dist/commands/auth.d.ts.map +0 -1
  143. package/dist/commands/auth.js.map +0 -1
  144. package/dist/commands/billing.d.ts.map +0 -1
  145. package/dist/commands/billing.js.map +0 -1
  146. package/dist/commands/cloudflare.d.ts.map +0 -1
  147. package/dist/commands/cloudflare.js.map +0 -1
  148. package/dist/commands/helpers.d.ts.map +0 -1
  149. package/dist/commands/helpers.js.map +0 -1
  150. package/dist/commands/index.d.ts.map +0 -1
  151. package/dist/commands/index.js.map +0 -1
  152. package/dist/commands/legacy-validation.d.ts.map +0 -1
  153. package/dist/commands/legacy-validation.js.map +0 -1
  154. package/dist/commands/local.d.ts.map +0 -1
  155. package/dist/commands/local.js.map +0 -1
  156. package/dist/commands/one.d.ts.map +0 -1
  157. package/dist/commands/one.js.map +0 -1
  158. package/dist/commands/public-tools.d.ts.map +0 -1
  159. package/dist/commands/public-tools.js.map +0 -1
  160. package/dist/commands/runs.d.ts.map +0 -1
  161. package/dist/commands/runs.js.map +0 -1
  162. package/dist/commands/security.d.ts.map +0 -1
  163. package/dist/commands/security.js.map +0 -1
  164. package/dist/commands/watchdog.d.ts.map +0 -1
  165. package/dist/commands/watchdog.js.map +0 -1
  166. package/dist/index.d.ts.map +0 -1
  167. package/dist/index.js.map +0 -1
  168. package/dist/lib/banner.d.ts.map +0 -1
  169. package/dist/lib/banner.js.map +0 -1
  170. package/dist/lib/client.d.ts.map +0 -1
  171. package/dist/lib/client.js.map +0 -1
  172. package/dist/lib/cloudflare-client.d.ts.map +0 -1
  173. package/dist/lib/cloudflare-client.js.map +0 -1
  174. package/dist/lib/config.d.ts.map +0 -1
  175. package/dist/lib/config.js.map +0 -1
  176. package/dist/lib/format.d.ts.map +0 -1
  177. package/dist/lib/format.js.map +0 -1
  178. package/dist/lib/help.d.ts.map +0 -1
  179. package/dist/lib/help.js.map +0 -1
  180. package/dist/lib/network.d.ts.map +0 -1
  181. package/dist/lib/network.js.map +0 -1
  182. package/dist/lib/one-client.d.ts.map +0 -1
  183. package/dist/lib/one-client.js.map +0 -1
  184. package/dist/lib/one-operations.d.ts.map +0 -1
  185. package/dist/lib/one-operations.js.map +0 -1
  186. package/dist/lib/open-url.d.ts.map +0 -1
  187. package/dist/lib/open-url.js.map +0 -1
  188. package/dist/lib/progress.d.ts.map +0 -1
  189. package/dist/lib/progress.js.map +0 -1
  190. package/dist/lib/security-client.d.ts.map +0 -1
  191. package/dist/lib/security-client.js.map +0 -1
  192. package/dist/lib/skill-fetch.d.ts.map +0 -1
  193. package/dist/lib/skill-fetch.js.map +0 -1
  194. package/dist/lib/system-trust.d.ts.map +0 -1
  195. package/dist/lib/system-trust.js.map +0 -1
  196. package/dist/lib/tui.d.ts.map +0 -1
  197. package/dist/lib/tui.js.map +0 -1
  198. package/dist/local/cloudflare-checkup/cf/allowlist.d.ts.map +0 -1
  199. package/dist/local/cloudflare-checkup/cf/allowlist.js.map +0 -1
  200. package/dist/local/cloudflare-checkup/cf/auth-lifecycle.d.ts.map +0 -1
  201. package/dist/local/cloudflare-checkup/cf/auth-lifecycle.js.map +0 -1
  202. package/dist/local/cloudflare-checkup/cf/child-env.d.ts.map +0 -1
  203. package/dist/local/cloudflare-checkup/cf/child-env.js.map +0 -1
  204. package/dist/local/cloudflare-checkup/cf/install.d.ts.map +0 -1
  205. package/dist/local/cloudflare-checkup/cf/install.js.map +0 -1
  206. package/dist/local/cloudflare-checkup/cf/locate.d.ts.map +0 -1
  207. package/dist/local/cloudflare-checkup/cf/locate.js.map +0 -1
  208. package/dist/local/cloudflare-checkup/cf/run-cf.d.ts.map +0 -1
  209. package/dist/local/cloudflare-checkup/cf/run-cf.js.map +0 -1
  210. package/dist/local/cloudflare-checkup/collector.d.ts.map +0 -1
  211. package/dist/local/cloudflare-checkup/collector.js.map +0 -1
  212. package/dist/local/cloudflare-checkup/contract.d.ts.map +0 -1
  213. package/dist/local/cloudflare-checkup/contract.js.map +0 -1
  214. package/dist/local/cloudflare-checkup/email-verification.d.ts.map +0 -1
  215. package/dist/local/cloudflare-checkup/email-verification.js.map +0 -1
  216. package/dist/local/cloudflare-checkup/errors.d.ts.map +0 -1
  217. package/dist/local/cloudflare-checkup/errors.js.map +0 -1
  218. package/dist/local/cloudflare-checkup/output.d.ts.map +0 -1
  219. package/dist/local/cloudflare-checkup/output.js.map +0 -1
  220. package/dist/local/cloudflare-checkup/prompts.d.ts.map +0 -1
  221. package/dist/local/cloudflare-checkup/prompts.js.map +0 -1
  222. package/dist/local/cloudflare-checkup/rate-budget.d.ts.map +0 -1
  223. package/dist/local/cloudflare-checkup/rate-budget.js.map +0 -1
  224. package/dist/local/cloudflare-checkup/snapshot-file.d.ts.map +0 -1
  225. package/dist/local/cloudflare-checkup/snapshot-file.js.map +0 -1
  226. package/dist/local/cloudflare-checkup/submit-client.d.ts.map +0 -1
  227. package/dist/local/cloudflare-checkup/submit-client.js.map +0 -1
  228. package/dist/local/cloudflare-checkup/terminal-ui.d.ts.map +0 -1
  229. package/dist/local/cloudflare-checkup/terminal-ui.js.map +0 -1
  230. package/dist/types.d.ts.map +0 -1
  231. package/dist/types.js.map +0 -1
  232. package/dist/version.d.ts.map +0 -1
  233. package/dist/version.js.map +0 -1
@@ -39,17 +39,19 @@ async function postJson(config, path, body) {
39
39
  },
40
40
  body: form ? new URLSearchParams(Object.entries(body).map(([key, value]) => [key, String(value)])) : JSON.stringify(body),
41
41
  });
42
- return { status: response.status, json: parseJsonResponse(response, text) };
42
+ return { status: response.status, json: parseJsonResponse(response, text), origin: new URL(url).origin };
43
43
  }
44
44
  export async function requestDeviceCode(config, options) {
45
- const { status, json } = await postJson(config, '/device/code', {
45
+ const { status, json, origin } = await postJson(config, '/device/code', {
46
46
  client_id: config.clientId,
47
47
  ...(config.authProvider ? { resource: config.authProvider.resource, scope: 'openid profile email offline_access one:api' } : {}),
48
48
  ...(options?.scope ? { scope: options.scope } : {}),
49
49
  });
50
+ // origin is the Identity issuer, or One for legacy sign-in.
50
51
  if (status === 404) {
51
- throw new DeviceAuthError(`Device authorization is not enabled on ${config.oneBaseUrl} yet. ` +
52
- 'Enable Better Auth\'s deviceAuthorization plugin on Vigilbase One, then retry `vigilbase login`.', { status, code: 'not_enabled' });
52
+ throw new DeviceAuthError(`Device sign-in isn't available at ${origin} right now. ` +
53
+ 'If you set --one-url or --auth-url, check that it\'s correct; otherwise try again later or email help@vigilbase.com. ' +
54
+ 'Public tools work without signing in.', { status, code: 'not_enabled' });
53
55
  }
54
56
  if (status < 200 || status >= 300) {
55
57
  throw new DeviceAuthError(`Device code request failed (HTTP ${status})`, { status });
@@ -226,4 +228,3 @@ export async function waitForDeviceAuthorization(config, device, options) {
226
228
  }
227
229
  throw new DeviceAuthError('Timed out waiting for browser authorization. Run `vigilbase login` again.', { status: 0, code: 'timeout' });
228
230
  }
229
- //# sourceMappingURL=device-auth.js.map
@@ -1,7 +1,6 @@
1
- export { clearOneCredentials, credentialsPath, getOneAccessToken, readCredentials, writeOneCredentials, } from './store.js';
1
+ export { clearOneCredentials, credentialsPath, getOneAccessToken, getOptionalOneAccessToken, hasUsableSignIn, identityCredentialsPath, readCredentials, writeOneCredentials, } from './store.js';
2
2
  export { DeviceAuthError, pollDeviceToken, requestDeviceCode, waitForDeviceAuthorization, } from './device-auth.js';
3
3
  export type { CredentialsFile, DeviceCodeResponse, DeviceTokenSuccess, OneCredentials, StoredUser, } from './types.js';
4
4
  export { discoverAuthProvider, identityProvider } from './provider.js';
5
5
  export { managedAccessToken, revokeSavedCredentials } from './refresh.js';
6
6
  export type { AuthProvider } from './provider.js';
7
- //# sourceMappingURL=index.d.ts.map
@@ -1,5 +1,4 @@
1
- export { clearOneCredentials, credentialsPath, getOneAccessToken, readCredentials, writeOneCredentials, } from './store.js';
1
+ export { clearOneCredentials, credentialsPath, getOneAccessToken, getOptionalOneAccessToken, hasUsableSignIn, identityCredentialsPath, readCredentials, writeOneCredentials, } from './store.js';
2
2
  export { DeviceAuthError, pollDeviceToken, requestDeviceCode, waitForDeviceAuthorization, } from './device-auth.js';
3
3
  export { discoverAuthProvider, identityProvider } from './provider.js';
4
4
  export { managedAccessToken, revokeSavedCredentials } from './refresh.js';
5
- //# sourceMappingURL=index.js.map
@@ -7,6 +7,7 @@ export interface AuthProvider {
7
7
  revocationEndpoint: string;
8
8
  resource: string;
9
9
  }
10
+ /** An issuer (discovered from One, or saved with credentials) is trusted only when its origin is exactly trustedIdentityIssuer(config). */
10
11
  export declare function validateIdentityIssuer(raw: string, config: Pick<CliConfig, 'oneBaseUrl' | 'identityBaseUrl'>): string;
11
12
  export declare function identityProvider(config: CliConfig, issuer: string): Promise<AuthProvider>;
12
13
  /**
@@ -15,4 +16,3 @@ export declare function identityProvider(config: CliConfig, issuer: string): Pro
15
16
  * JSON that is present but malformed or names another resource still aborts.
16
17
  */
17
18
  export declare function discoverAuthProvider(config: CliConfig): Promise<AuthProvider | undefined>;
18
- //# sourceMappingURL=provider.d.ts.map
@@ -1,13 +1,23 @@
1
1
  import { DeviceAuthError } from './device-auth.js';
2
2
  import { z } from 'zod';
3
- import { validateOneOrigin } from '../lib/config.js';
3
+ import { DEFAULT_IDENTITY_ISSUER, parseOrigin, STAGING_IDENTITY_ISSUER, trustedIdentityIssuer, validateOneOrigin } from '../lib/config.js';
4
4
  import { requestText, parseJsonResponse } from '../lib/network.js';
5
+ /** An issuer (discovered from One, or saved with credentials) is trusted only when its origin is exactly trustedIdentityIssuer(config). */
5
6
  export function validateIdentityIssuer(raw, config) {
6
- const origin = validateOneOrigin(raw);
7
- const loopback = ['localhost', '127.0.0.1', '[::1]'].includes(new URL(origin).hostname);
8
- if (!['https://auth.vigilbase.com', 'https://identity-staging.vigilbase.com'].includes(origin) && !(loopback && config.identityBaseUrl === origin && ['localhost', '127.0.0.1', '[::1]'].includes(new URL(config.oneBaseUrl).hostname)))
9
- throw new Error('Untrusted Identity issuer. Configure --auth-url explicitly for loopback development.');
10
- return origin;
7
+ const trusted = trustedIdentityIssuer(config);
8
+ let origin;
9
+ // Do not echo a malformed issuer: it can contain credentials.
10
+ try {
11
+ origin = parseOrigin(raw, 'The Identity issuer');
12
+ }
13
+ catch {
14
+ throw new Error('Untrusted Identity issuer: not an HTTP(S) origin.');
15
+ }
16
+ if (origin === trusted)
17
+ return origin;
18
+ throw new Error(config.identityBaseUrl
19
+ ? `Untrusted Identity issuer ${origin}: --auth-url / VIGILBASE_AUTH_URL is ${trusted}. Set it to the issuer for this One, or unset it to use ${DEFAULT_IDENTITY_ISSUER}.`
20
+ : `Untrusted Identity issuer ${origin}: only ${DEFAULT_IDENTITY_ISSUER} is trusted by default. For staging, pass --auth-url ${STAGING_IDENTITY_ISSUER} or set VIGILBASE_AUTH_URL.`);
11
21
  }
12
22
  async function json(config, url) { const { response, text } = await requestText(config, url, { headers: { Accept: 'application/json', 'User-Agent': config.userAgent } }); return { response, body: parseJsonResponse(response, text) }; }
13
23
  export async function identityProvider(config, issuer) {
@@ -59,4 +69,3 @@ export async function discoverAuthProvider(config) {
59
69
  return undefined;
60
70
  return identityProvider(config, issuer);
61
71
  }
62
- //# sourceMappingURL=provider.js.map
@@ -3,8 +3,27 @@ import type { OneCredentials } from './types.js';
3
3
  export declare function boundIdentityCredentials(config: CliConfig, one: OneCredentials): boolean;
4
4
  /** Library callers may omit or mistype timeoutMs; NaN would make the wait unbounded. */
5
5
  export declare function credentialsLockWaitMs(timeoutMs: unknown): number;
6
- /** Never steal a stale lock: a crashed refresh may have consumed the rotating token. */
7
- export declare function withCredentialsLock<T>(config: CliConfig, callback: () => Promise<T>, replaceDeadLock?: boolean): Promise<T>;
6
+ /** Another command holds credentials.json.lock. */
7
+ export declare class CredentialsLockedError extends Error {
8
+ constructor();
9
+ }
10
+ /**
11
+ * credentials.json.lock guards both credentials files; CLI 0.2.0 takes the same lock.
12
+ * Never steal a stale lock: a crashed refresh may have consumed the rotating token.
13
+ * With `wait` false, a held lock fails at once instead of after the lock wait.
14
+ */
15
+ export declare function withCredentialsLock<T>(config: Pick<CliConfig, 'configDir'> & Partial<Pick<CliConfig, 'timeoutMs'>>, callback: () => Promise<T>, replaceDeadLock?: boolean, wait?: boolean): Promise<T>;
8
16
  export declare function managedAccessToken(config: CliConfig, failedToken?: string): Promise<string | null>;
9
- export declare function revokeSavedCredentials(config: CliConfig): Promise<boolean>;
10
- //# sourceMappingURL=refresh.d.ts.map
17
+ /**
18
+ * Revoke one saved refresh grant. Identity answers 400 (`invalid_request` or `invalid_grant`) for a token it no
19
+ * longer knows or has already revoked, for example after a browser sign-out ended the session that approved it,
20
+ * or a refresh rotated it; nothing is left to revoke then. Other failures throw.
21
+ */
22
+ export declare function revokeIdentityGrant(config: CliConfig, one: OneCredentials): Promise<void>;
23
+ /**
24
+ * Revoke every saved Identity grant, then remove its file. A failed revocation keeps every file. A safe file that
25
+ * can't be parsed has no grant to revoke; the login that calls this replaces it. Callers hold the lock.
26
+ */
27
+ export declare function revokeIdentityCredentials(config: CliConfig): Promise<boolean>;
28
+ /** Revoke and remove every credentials file. A failed Identity revocation keeps them all. */
29
+ export declare function revokeSavedCredentials(config: CliConfig, onWarning?: (message: string) => void): Promise<boolean>;
@@ -5,7 +5,7 @@ import { dirname } from 'node:path';
5
5
  import { z } from 'zod';
6
6
  import { DEFAULT_TIMEOUT_MS, validateOneOrigin } from '../lib/config.js';
7
7
  import { requestText, parseJsonResponse } from '../lib/network.js';
8
- import { assertFile, checkDirectory, credentialsPath, readCredentials, writeOneCredentials, clearOneCredentials } from './store.js';
8
+ import { assertFile, checkDirectory, credentialsPath, readCredentials, readLockedCredentials, readStoredCredentials, writeOneCredentials, clearOneCredentials, clearIdentityCredentials, DEFAULT_ONE_ORIGIN, UNCERTAIN_REFRESH } from './store.js';
9
9
  import { identityProvider, validateIdentityIssuer } from './provider.js';
10
10
  export function boundIdentityCredentials(config, one) {
11
11
  try {
@@ -21,8 +21,16 @@ export function credentialsLockWaitMs(timeoutMs) {
21
21
  const requested = typeof timeoutMs === 'number' && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_TIMEOUT_MS;
22
22
  return Math.min(requested, MAX_LOCK_WAIT_MS);
23
23
  }
24
- /** Never steal a stale lock: a crashed refresh may have consumed the rotating token. */
25
- export async function withCredentialsLock(config, callback, replaceDeadLock = false) {
24
+ /** Another command holds credentials.json.lock. */
25
+ export class CredentialsLockedError extends Error {
26
+ constructor() { super('Credentials are locked by another command. A crashed refresh requires a new login; do not reuse its refresh token.'); this.name = 'CredentialsLockedError'; }
27
+ }
28
+ /**
29
+ * credentials.json.lock guards both credentials files; CLI 0.2.0 takes the same lock.
30
+ * Never steal a stale lock: a crashed refresh may have consumed the rotating token.
31
+ * With `wait` false, a held lock fails at once instead of after the lock wait.
32
+ */
33
+ export async function withCredentialsLock(config, callback, replaceDeadLock = false, wait = true) {
26
34
  const path = `${credentialsPath(config)}.lock`;
27
35
  await checkDirectory(dirname(path), true);
28
36
  const deadline = Date.now() + credentialsLockWaitMs(config.timeoutMs);
@@ -36,12 +44,12 @@ export async function withCredentialsLock(config, callback, replaceDeadLock = fa
36
44
  if (!(error instanceof Error && 'code' in error && error.code === 'EEXIST'))
37
45
  throw error;
38
46
  const existing = await lstat(path);
39
- assertFile(existing);
47
+ assertFile(existing, path);
40
48
  if (replaceDeadLock) {
41
49
  const existingHandle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
42
50
  let owner = '';
43
51
  try {
44
- assertFile(await existingHandle.stat());
52
+ assertFile(await existingHandle.stat(), path);
45
53
  owner = await existingHandle.readFile('utf8');
46
54
  }
47
55
  finally {
@@ -65,14 +73,14 @@ export async function withCredentialsLock(config, callback, replaceDeadLock = fa
65
73
  }
66
74
  }
67
75
  }
68
- if (Date.now() >= deadline)
69
- throw new Error('Credentials are locked by another command. A crashed refresh requires a new login; do not reuse its refresh token.');
76
+ if (!wait || Date.now() >= deadline)
77
+ throw new CredentialsLockedError();
70
78
  await new Promise(resolve => setTimeout(resolve, 100));
71
79
  }
72
80
  }
73
81
  await handle.chmod(0o600);
74
82
  const info = await handle.stat();
75
- assertFile(info);
83
+ assertFile(info, path);
76
84
  try {
77
85
  await handle.writeFile(String(process.pid));
78
86
  await handle.sync();
@@ -94,7 +102,7 @@ export async function managedAccessToken(config, failedToken) {
94
102
  return initial.one.accessToken;
95
103
  }
96
104
  return withCredentialsLock(config, async () => {
97
- const current = await readCredentials(config), one = current.one;
105
+ const current = await readLockedCredentials(config), one = current.one;
98
106
  if (current.version !== 2 || !one || !boundIdentityCredentials(config, one))
99
107
  return null;
100
108
  if (failedToken && one.accessToken !== failedToken) {
@@ -106,14 +114,14 @@ export async function managedAccessToken(config, failedToken) {
106
114
  return one.accessToken;
107
115
  }
108
116
  if (one.refreshPending)
109
- throw new Error('A previous refresh has an uncertain result. Run `vigilbase login` again; its token cannot be retried safely.');
117
+ throw new Error(UNCERTAIN_REFRESH);
110
118
  const provider = await identityProvider(config, one.issuer);
111
119
  await writeOneCredentials(config, { ...one, refreshPending: true });
112
120
  const { response, text } = await requestText(config, provider.tokenEndpoint, { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': config.userAgent }, body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: one.refreshToken, client_id: one.clientId, resource: one.resource }) });
113
121
  const body = parseJsonResponse(response, text);
114
122
  if (!response.ok) {
115
123
  if (z.object({ error: z.literal('invalid_grant') }).safeParse(body).success)
116
- await clearOneCredentials(config);
124
+ await clearIdentityCredentials(config);
117
125
  throw new Error('Identity refresh failed. Run `vigilbase login` again.');
118
126
  }
119
127
  const token = z.object({ access_token: z.string().min(1), refresh_token: z.string().min(1), expires_in: z.number().int().positive().max(300), scope: z.string().optional() }).safeParse(body);
@@ -126,18 +134,66 @@ export async function managedAccessToken(config, failedToken) {
126
134
  return token.data.access_token;
127
135
  });
128
136
  }
129
- export async function revokeSavedCredentials(config) {
137
+ const SAVED_FOR_OTHER_CONFIGURATION = 'Saved credentials belong to a different issuer, resource, or client. Use the matching configuration to log out.';
138
+ /**
139
+ * Revoke one saved refresh grant. Identity answers 400 (`invalid_request` or `invalid_grant`) for a token it no
140
+ * longer knows or has already revoked, for example after a browser sign-out ended the session that approved it,
141
+ * or a refresh rotated it; nothing is left to revoke then. Other failures throw.
142
+ */
143
+ export async function revokeIdentityGrant(config, one) {
144
+ if (!boundIdentityCredentials(config, one))
145
+ throw new Error(SAVED_FOR_OTHER_CONFIGURATION);
146
+ const provider = await identityProvider(config, one.issuer);
147
+ const { response, text } = await requestText(config, provider.revocationEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': config.userAgent }, body: new URLSearchParams({ token: one.refreshToken, token_type_hint: 'refresh_token', client_id: one.clientId }) });
148
+ if (response.ok)
149
+ return;
150
+ if (response.status === 400 && z.object({ error: z.enum(['invalid_request', 'invalid_grant']) }).safeParse(parseJsonResponse(response, text)).success)
151
+ return;
152
+ throw new Error('Identity revocation failed, so your saved sign-in was kept. Try again later.');
153
+ }
154
+ async function revokeIdentityGrants(config, { identity, shared }) {
155
+ const grants = [identity, shared].flatMap((saved) => saved?.version === 2 && saved.one ? [saved.one] : []);
156
+ // Check every grant first, so a mismatch can't leave one revoked while its file stays.
157
+ if (grants.some((one) => !boundIdentityCredentials(config, one)))
158
+ throw new Error(SAVED_FOR_OTHER_CONFIGURATION);
159
+ for (const one of grants)
160
+ await revokeIdentityGrant(config, one);
161
+ }
162
+ /**
163
+ * Revoke every saved Identity grant, then remove its file. A failed revocation keeps every file. A safe file that
164
+ * can't be parsed has no grant to revoke; the login that calls this replaces it. Callers hold the lock.
165
+ */
166
+ export async function revokeIdentityCredentials(config) {
167
+ const stored = await readStoredCredentials(config, () => { });
168
+ await revokeIdentityGrants(config, stored);
169
+ // credentials.json goes too only when it held a CLI 0.2.0 grant.
170
+ return stored.shared?.version === 2 ? clearOneCredentials(config) : clearIdentityCredentials(config);
171
+ }
172
+ /** Best effort: legacy One auth retires on 2026-10-31, and its session expires on its own. */
173
+ async function signOutLegacySession(config, one) {
174
+ const origin = validateOneOrigin(one.oneBaseUrl ?? DEFAULT_ONE_ORIGIN);
175
+ const { response } = await requestText(config, `${origin}/api/auth/sign-out`, { method: 'POST', headers: { Accept: 'application/json', Authorization: `Bearer ${one.accessToken}`, 'User-Agent': config.userAgent }, redirect: 'manual', credentials: 'omit' });
176
+ if (!response.ok)
177
+ throw new Error('Legacy One sign-out failed.');
178
+ }
179
+ /** Revoke and remove every credentials file. A failed Identity revocation keeps them all. */
180
+ export async function revokeSavedCredentials(config, onWarning) {
130
181
  return withCredentialsLock(config, async () => {
131
- const saved = await readCredentials(config);
132
- if (saved.version === 2 && saved.one) {
133
- if (!boundIdentityCredentials(config, saved.one))
134
- throw new Error('Saved credentials belong to a different issuer, resource, or client. Use the matching configuration to log out.');
135
- const provider = await identityProvider(config, saved.one.issuer);
136
- const { response } = await requestText(config, provider.revocationEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': config.userAgent }, body: new URLSearchParams({ token: saved.one.refreshToken, token_type_hint: 'refresh_token', client_id: saved.one.clientId }) });
137
- if (!response.ok)
138
- throw new Error('Identity revocation failed. Credentials retained so logout can be retried.');
182
+ // A safe file that cannot be parsed cannot be revoked either; removing it keeps logout from dead-ending.
183
+ const unreadable = [];
184
+ const stored = await readStoredCredentials(config, (path) => unreadable.push(path));
185
+ await revokeIdentityGrants(config, stored);
186
+ if (stored.shared?.version === 1 && stored.shared.one) {
187
+ try {
188
+ await signOutLegacySession(config, stored.shared.one);
189
+ }
190
+ catch {
191
+ onWarning?.('Could not sign out the legacy One session. Local credentials are removed anyway.');
192
+ }
139
193
  }
140
- return clearOneCredentials(config);
194
+ const cleared = await clearOneCredentials(config);
195
+ if (unreadable.length)
196
+ onWarning?.(`Couldn't read the saved sign-in in ${unreadable.join(' and ')}, so it was removed without revoking it. It will expire on its own.`);
197
+ return cleared;
141
198
  }, true);
142
199
  }
143
- //# sourceMappingURL=refresh.js.map
@@ -2,6 +2,8 @@ import { type Stats } from 'node:fs';
2
2
  import { z } from 'zod';
3
3
  import { type CliConfig } from '../lib/config.js';
4
4
  import type { CredentialsFile, OneCredentials } from './types.js';
5
+ export declare const DEFAULT_ONE_ORIGIN = "https://one.vigilbase.com";
6
+ export declare const UNCERTAIN_REFRESH = "A previous refresh has an uncertain result. Run `vigilbase login` again; its token cannot be retried safely.";
5
7
  type CredentialsConfig = Pick<CliConfig, 'configDir'> & Partial<Pick<CliConfig, 'oneBaseUrl' | 'clientId' | 'identityBaseUrl' | 'timeoutMs' | 'userAgent'>>;
6
8
  export declare const OneCredentialsSchema: z.ZodObject<{
7
9
  issuer: z.ZodOptional<z.ZodString>;
@@ -23,13 +25,85 @@ export declare const OneCredentialsSchema: z.ZodObject<{
23
25
  name: z.ZodOptional<z.ZodString>;
24
26
  }, z.core.$strip>>;
25
27
  }, z.core.$strip>;
28
+ /** Version 1 One session credentials, shared with CLI 0.1.x. */
26
29
  export declare function credentialsPath(config: Pick<CliConfig, 'configDir'>): string;
27
- export declare function assertFile(info: Stats): void;
28
- /** Check every ancestor before traversing it; a writable non-sticky parent permits replacement. */
30
+ /** Version 2 Identity credentials with the rotating refresh grant. */
31
+ export declare function identityCredentialsPath(config: Pick<CliConfig, 'configDir'>): string;
32
+ /** A credentials file that passed the safety checks but cannot be parsed or is incomplete. */
33
+ export declare class InvalidCredentialsError extends Error {
34
+ constructor(message: string);
35
+ }
36
+ /** Credentials cannot be protected by modes and owners on Windows yet. */
37
+ export declare class SignInUnsupportedError extends Error {
38
+ readonly code = "SIGN_IN_UNSUPPORTED";
39
+ constructor();
40
+ }
41
+ /**
42
+ * A credentials directory or file the CLI refuses to use. `reason` names the path that failed and `remedy`
43
+ * says how to fix it; `vigilbase login` can't, because it runs the same checks.
44
+ */
45
+ export declare class CredentialsPathError extends Error {
46
+ readonly reason: string;
47
+ readonly remedy: string;
48
+ constructor(reason: string, remedy: string, options?: ErrorOptions);
49
+ }
50
+ export declare function assertFile(info: Stats, path: string): void;
51
+ /**
52
+ * Check every ancestor before traversing it; a writable non-sticky parent permits replacement.
53
+ * Every credentials read or write starts here, so on Windows they fail before touching the filesystem.
54
+ */
29
55
  export declare function checkDirectory(configDir: string, isCreate?: boolean): Promise<boolean>;
30
- export declare function readCredentials(config: Pick<CliConfig, 'configDir'>): Promise<CredentialsFile>;
56
+ /** Both files as saved. credentials.json is version 1, or version 2 when CLI 0.2.0 wrote it. */
57
+ export type StoredCredentials = {
58
+ identity: CredentialsFile | null;
59
+ shared: CredentialsFile | null;
60
+ };
61
+ /**
62
+ * With `onInvalid`, a safe file that cannot be parsed reads as absent and is reported; unsafe files always throw.
63
+ * Without it, an unparseable credentials.json reads as absent when identity-credentials.json holds a sign-in, which wins anyway.
64
+ */
65
+ export declare function readStoredCredentials(config: Pick<CliConfig, 'configDir'>, onInvalid?: (path: string) => void): Promise<StoredCredentials>;
66
+ /** Identity credentials win. The first read moves a CLI 0.2.0 credentials.json aside under the lock. */
67
+ export declare function readCredentials(config: Pick<CliConfig, 'configDir'> & Partial<Pick<CliConfig, 'timeoutMs'>>): Promise<CredentialsFile>;
68
+ /** readCredentials for callers that already hold credentials.json.lock. */
69
+ export declare function readLockedCredentials(config: Pick<CliConfig, 'configDir'>): Promise<CredentialsFile>;
70
+ /**
71
+ * The Identity grant that saving a new one replaces: identity-credentials.json's, or that of a CLI 0.2.0
72
+ * credentials.json which saving moves there. An unparseable file has no grant to revoke. Callers hold the lock.
73
+ */
74
+ export declare function readReplacedIdentityGrant(config: Pick<CliConfig, 'configDir'>): Promise<OneCredentials | null>;
75
+ /**
76
+ * Fail before a login starts, rather than after it was approved, when saving would fail: Windows, or an unsafe
77
+ * credentials directory or file. Unparseable files pass; login replaces them.
78
+ */
79
+ export declare function checkCredentialsLocation(config: Pick<CliConfig, 'configDir'>): Promise<void>;
80
+ /** CLI 0.2.0 can write credentials.json again after migration; that grant is ignored until logout revokes it. */
81
+ export declare function hasIgnoredIdentityCredentials(config: Pick<CliConfig, 'configDir'>): Promise<boolean>;
31
82
  export declare function writeOneCredentials(config: CredentialsConfig, one: OneCredentials): Promise<void>;
83
+ /** Remove every local credentials file without revoking anything. */
32
84
  export declare function clearOneCredentials(config: Pick<CliConfig, 'configDir'>): Promise<boolean>;
85
+ /** Remove identity-credentials.json only; credentials.json stays for CLI 0.1.x. */
86
+ export declare function clearIdentityCredentials(config: Pick<CliConfig, 'configDir'>): Promise<boolean>;
33
87
  export declare function getOneAccessToken(config: CredentialsConfig): Promise<string | null>;
88
+ /** Saved sign-in for doctor, which reports problems instead of failing on them. */
89
+ export type OptionalAccessToken = {
90
+ accessToken: string | null;
91
+ /** Why the saved sign-in could not be used: an unsafe credentials directory or file, unreadable credentials, a held lock, or a failed refresh. */
92
+ problem: string | null;
93
+ };
94
+ /**
95
+ * getOneAccessToken for doctor: any problem with the saved sign-in is returned, not thrown.
96
+ * Lock waits, discovery and refresh use the configured timeout. Windows skips the credential store entirely.
97
+ * With no saved sign-in there is no problem, even under a config path that fails the directory checks.
98
+ */
99
+ export declare function getOptionalOneAccessToken(config: CredentialsConfig): Promise<OptionalAccessToken>;
100
+ /**
101
+ * Whether list can show the saved sign-in as usable, judged from the files alone: no refresh, no network
102
+ * and no wait for the credentials lock. A legacy token must be unexpired; an Identity grant may hold an
103
+ * expired access token, which the next One command refreshes. Windows skips the credential store entirely.
104
+ * Any problem means signed out and one `warn` per process; an expired legacy token is not a problem.
105
+ * A credentials directory that fails the strict checks is never written to or opened, and is only warned
106
+ * about when a credentials file may exist there. Never throws.
107
+ */
108
+ export declare function hasUsableSignIn(config: CliConfig, warn: (message: string) => void): Promise<boolean>;
34
109
  export {};
35
- //# sourceMappingURL=store.d.ts.map