@botdocs/cli 0.10.2 → 0.10.3

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.
package/dist/index.js CHANGED
@@ -21,8 +21,29 @@ import { registerTeamCommands } from './commands/team.js';
21
21
  import { registerBackupCommands } from './commands/backups.js';
22
22
  import { undo } from './commands/undo.js';
23
23
  import { createRequire } from 'node:module';
24
+ import { WaitlistError } from './lib/api.js';
24
25
  const require = createRequire(import.meta.url);
25
26
  const pkg = require('../package.json');
27
+ /** Top-level handler for the launch-day waitlist gate. When any command's
28
+ * `apiFetch` returns a 403 with the `waitlisted` body, the CLI exits 0 with
29
+ * a single friendly one-liner instead of a stack trace. Exit 0 because being
30
+ * on the waitlist isn't a malformed command — it's the expected state.
31
+ *
32
+ * We hook `unhandledRejection` rather than wrapping every command because
33
+ * commands use `process.exit(1)` inside their own error paths today; an
34
+ * outer try/catch around `program.parseAsync` wouldn't see throws from
35
+ * actions that commander dispatches as fire-and-forget. */
36
+ process.on('unhandledRejection', (reason) => {
37
+ if (reason instanceof WaitlistError) {
38
+ process.stdout.write("You're on the BotDocs waitlist. We'll email you when there's space.\n" +
39
+ '(Check https://botdocs.ai/waitlist for status.)\n');
40
+ process.exit(0);
41
+ }
42
+ // Anything else: re-raise as a real failure so we don't silently swallow
43
+ // bugs. Mirrors Node's default behavior for un-handled rejections.
44
+ console.error(reason instanceof Error ? reason.stack ?? reason.message : reason);
45
+ process.exit(1);
46
+ });
26
47
  const program = new Command();
27
48
  program
28
49
  .name('botdocs')
package/dist/lib/api.d.ts CHANGED
@@ -13,6 +13,23 @@ export declare class ApiError extends Error {
13
13
  readonly body?: unknown;
14
14
  constructor(status: number, message: string, body?: unknown);
15
15
  }
16
+ /** Thrown by apiFetch when the server replies 403 with `{ error: 'waitlisted',
17
+ * ... }` — meaning the caller is signed in but BotDocs is currently behind the
18
+ * waitlist gate and they haven't been admitted yet.
19
+ *
20
+ * Subclass of ApiError so existing `catch (err) { if (err instanceof ApiError)
21
+ * ... }` paths keep working; the index.ts top-level handler does an explicit
22
+ * `instanceof WaitlistError` check first to print a single friendly one-liner
23
+ * and exit 0 (this is not a user-facing error condition — the user is on the
24
+ * waitlist by design).
25
+ *
26
+ * Telemetry-style callers (`syncLibrary`) that already swallow errors will
27
+ * continue to swallow this one too — they catch every ApiError, so the new
28
+ * class lands inside the same catch arm.
29
+ */
30
+ export declare class WaitlistError extends ApiError {
31
+ constructor(message: string, body?: unknown);
32
+ }
16
33
  interface FetchOptions {
17
34
  method?: string;
18
35
  body?: unknown;
package/dist/lib/api.js CHANGED
@@ -22,6 +22,35 @@ export class ApiError extends Error {
22
22
  this.body = body;
23
23
  }
24
24
  }
25
+ /** Thrown by apiFetch when the server replies 403 with `{ error: 'waitlisted',
26
+ * ... }` — meaning the caller is signed in but BotDocs is currently behind the
27
+ * waitlist gate and they haven't been admitted yet.
28
+ *
29
+ * Subclass of ApiError so existing `catch (err) { if (err instanceof ApiError)
30
+ * ... }` paths keep working; the index.ts top-level handler does an explicit
31
+ * `instanceof WaitlistError` check first to print a single friendly one-liner
32
+ * and exit 0 (this is not a user-facing error condition — the user is on the
33
+ * waitlist by design).
34
+ *
35
+ * Telemetry-style callers (`syncLibrary`) that already swallow errors will
36
+ * continue to swallow this one too — they catch every ApiError, so the new
37
+ * class lands inside the same catch arm.
38
+ */
39
+ export class WaitlistError extends ApiError {
40
+ constructor(message, body) {
41
+ super(403, message, body);
42
+ this.name = 'WaitlistError';
43
+ }
44
+ }
45
+ /** Type guard for the `{ error: 'waitlisted', ... }` JSON shape returned by
46
+ * `requireAdmitted` on the web side. Matches by string discriminant so a
47
+ * server-side rename of the field would break this exactly once, here, rather
48
+ * than silently disabling the friendly path. */
49
+ function isWaitlistedBody(body) {
50
+ return (typeof body === 'object' &&
51
+ body !== null &&
52
+ body.error === 'waitlisted');
53
+ }
25
54
  export async function apiFetch(path, options = {}) {
26
55
  const { method = 'GET', body, auth = false } = options;
27
56
  const baseUrl = getApiUrl();
@@ -67,6 +96,13 @@ export async function apiFetch(path, options = {}) {
67
96
  if (response.status === 401 && auth) {
68
97
  message = 'Authentication failed. Run `botdocs login` to sign in again.';
69
98
  }
99
+ // Distinct error type for the launch-day waitlist. Lets the top-level
100
+ // CLI handler print "You're on the BotDocs waitlist..." instead of a raw
101
+ // 403 trace, and lets background telemetry (syncLibrary) silently swallow
102
+ // it without surfacing anything to the user.
103
+ if (response.status === 403 && isWaitlistedBody(parsedBody)) {
104
+ throw new WaitlistError(parsedBody.message ?? message, parsedBody);
105
+ }
70
106
  throw new ApiError(response.status, message, parsedBody);
71
107
  }
72
108
  const contentType = response.headers.get('content-type') || '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botdocs/cli",
3
- "version": "0.10.2",
3
+ "version": "0.10.3",
4
4
  "description": "CLI for BotDocs — author, publish, install, and sync agent skills across Claude, Claude Code, Cursor, Codex, ChatGPT, Windsurf, Copilot, Gemini, Antigravity, and OpenCode.",
5
5
  "keywords": [
6
6
  "botdocs",