@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 +21 -0
- package/dist/lib/api.d.ts +17 -0
- package/dist/lib/api.js +36 -0
- package/package.json +1 -1
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.
|
|
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",
|