@botdocs/cli 0.11.0 → 0.12.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.
- package/dist/commands/ingest.d.ts +5 -0
- package/dist/commands/ingest.js +40 -2
- package/dist/commands/views/ingest-discover-app.js +8 -1
- package/dist/commands/views/login-app.js +6 -5
- package/dist/commands/views/theme.d.ts +3 -4
- package/dist/commands/views/theme.js +3 -4
- package/dist/index.js +11 -4
- package/package.json +1 -2
|
@@ -21,6 +21,11 @@ interface IngestOptions {
|
|
|
21
21
|
pair?: boolean;
|
|
22
22
|
/** Pre-supplied pairing code (BD-XXXXX). Implies `--pair`. */
|
|
23
23
|
pairCode?: string;
|
|
24
|
+
/** Suppress the auto-open of https://botdocs.ai/pair when `--pair` is
|
|
25
|
+
* set. Necessary for headless / SSH / CI use where launching a browser
|
|
26
|
+
* is either pointless or actively harmful. Mirrors the same opt-out we
|
|
27
|
+
* want long-term on `botdocs login`. */
|
|
28
|
+
noBrowser?: boolean;
|
|
24
29
|
}
|
|
25
30
|
/** Per-file size cap. Any file larger than this is skipped with a warning. */
|
|
26
31
|
export declare const PER_FILE_BYTE_CAP: number;
|
package/dist/commands/ingest.js
CHANGED
|
@@ -3,7 +3,8 @@ import fs from 'node:fs';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { render } from 'ink';
|
|
6
|
-
import
|
|
6
|
+
import open from 'open';
|
|
7
|
+
import { ApiError, apiFetch, getApiUrl } from '../lib/api.js';
|
|
7
8
|
import { loadAuth } from '../lib/config.js';
|
|
8
9
|
import { IngestSessionClient, PairingClaimError } from '../lib/ingest-session-client.js';
|
|
9
10
|
import { discoverSkills, ecosystemLabel, formatBytes, STUB_BYTE_THRESHOLD, summaryKey, titleFromContent, } from '../lib/ingest-discover.js';
|
|
@@ -949,7 +950,44 @@ async function setupPairingIfRequested(options) {
|
|
|
949
950
|
}
|
|
950
951
|
let code = options.pairCode;
|
|
951
952
|
if (!code) {
|
|
952
|
-
|
|
953
|
+
// Auto-open the pairing page in the user's default browser unless they
|
|
954
|
+
// opted out via --no-browser. The page mints (or reuses) the BD-XXXXX
|
|
955
|
+
// code server-side, so by the time the browser tab loads, the code is
|
|
956
|
+
// already on screen — no waiting, no second click.
|
|
957
|
+
//
|
|
958
|
+
// We open BEFORE printing the prompt so the order of events feels right
|
|
959
|
+
// ("browser opens, look there, paste here"). If `open()` fails — common
|
|
960
|
+
// in headless / SSH / CI environments — we swallow the error and fall
|
|
961
|
+
// back to the printed URL. The CLI must never become unusable because
|
|
962
|
+
// we couldn't reach a graphical browser.
|
|
963
|
+
const pairUrl = `${getApiUrl()}/pair`;
|
|
964
|
+
const shouldOpen = options.noBrowser !== true;
|
|
965
|
+
let browserOpened = false;
|
|
966
|
+
if (shouldOpen) {
|
|
967
|
+
try {
|
|
968
|
+
await open(pairUrl);
|
|
969
|
+
browserOpened = true;
|
|
970
|
+
}
|
|
971
|
+
catch {
|
|
972
|
+
/* swallow — fall back to the printed URL below */
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
console.log('');
|
|
976
|
+
if (browserOpened) {
|
|
977
|
+
console.log(` ✓ Opened ${pairUrl} in your browser.`);
|
|
978
|
+
console.log(' Copy the BD-XXXXX code shown there and paste it here.');
|
|
979
|
+
}
|
|
980
|
+
else {
|
|
981
|
+
console.log(` Get your pairing code at ${pairUrl}`);
|
|
982
|
+
if (!shouldOpen) {
|
|
983
|
+
console.log(' (--no-browser is set; visit the URL above manually.)');
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
console.log(" (Browser open failed — open the URL above manually.)");
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
console.log('');
|
|
990
|
+
const entered = await readLine(' Pairing code: ');
|
|
953
991
|
if (!entered) {
|
|
954
992
|
console.log(' → Pairing skipped (no code entered).');
|
|
955
993
|
return null;
|
|
@@ -159,7 +159,14 @@ export function IngestDiscoverApp(props) {
|
|
|
159
159
|
if (files.length === 0) {
|
|
160
160
|
return (_jsx(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: _jsx(Text, { children: "No skills found in any known location." }) }));
|
|
161
161
|
}
|
|
162
|
-
|
|
162
|
+
// The header summarizes SKILLS, not raw files. A multi-file skill
|
|
163
|
+
// (e.g. a SKILL.md with helper scripts in scripts/) is one row in the
|
|
164
|
+
// list — and should be one tick in the count too. Previously we used
|
|
165
|
+
// `files.length` here, which counted every adjacent file separately
|
|
166
|
+
// and inflated the total ~3× on typical libraries.
|
|
167
|
+
const skillCount = useMemo(() => files.filter(isRootFile).length, [files]);
|
|
168
|
+
const toolCount = useMemo(() => countEcosystems(files), [files]);
|
|
169
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsx(Text, { bold: true, color: theme.cyan, children: "BotDocs ingest" }), _jsxs(Text, { color: theme.zinc, children: ["Found ", skillCount, " skill", skillCount === 1 ? '' : 's', " across", ' ', toolCount, " tool", toolCount === 1 ? '' : 's', ":"] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: rows.map((row, i) => {
|
|
163
170
|
if (row.kind === 'header') {
|
|
164
171
|
return (_jsx(Box, { marginTop: i === 0 ? 0 : 1, children: _jsxs(Text, { bold: true, color: theme.violet, children: [ecosystemLabel(row.ecosystem), ":"] }) }, `h-${row.ecosystem}`));
|
|
165
172
|
}
|
|
@@ -3,16 +3,17 @@ import { useEffect, useState } from 'react';
|
|
|
3
3
|
import { Box, Text, useApp } from 'ink';
|
|
4
4
|
import Spinner from 'ink-spinner';
|
|
5
5
|
import Gradient from 'ink-gradient';
|
|
6
|
-
import BigText from 'ink-big-text';
|
|
7
6
|
import { theme, BIG_TEXT_MIN_COLS } from './theme.js';
|
|
8
|
-
/** Brand wordmark.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
/** Brand wordmark. A gradient bold "botdocs" on roomy terminals, falling back
|
|
8
|
+
* to a plain bold cyan "botdocs" on narrow ones. (Previously this rendered an
|
|
9
|
+
* ink-big-text ASCII wordmark, but that pulled GPL-3.0 `cfonts` into a package
|
|
10
|
+
* published under MIT — see [G-3]. A styled plain wordmark keeps the brand
|
|
11
|
+
* without the license conflict.) */
|
|
11
12
|
function BrandMark({ columns }) {
|
|
12
13
|
if (columns < BIG_TEXT_MIN_COLS) {
|
|
13
14
|
return (_jsx(Text, { bold: true, color: theme.cyan, children: "botdocs" }));
|
|
14
15
|
}
|
|
15
|
-
return (_jsx(Gradient, { colors: [theme.cyan, theme.violet], children: _jsx(
|
|
16
|
+
return (_jsx(Gradient, { colors: [theme.cyan, theme.violet], children: _jsx(Text, { bold: true, children: "botdocs" }) }));
|
|
16
17
|
}
|
|
17
18
|
/** Single source of truth for active-state lines. Returns a label + spinner
|
|
18
19
|
* pair the renderer can drop into the layout — keeps the state-string mapping
|
|
@@ -9,8 +9,7 @@ export declare const theme: {
|
|
|
9
9
|
readonly red: "#f87171";
|
|
10
10
|
readonly zinc: "#71717a";
|
|
11
11
|
};
|
|
12
|
-
/** Width
|
|
13
|
-
* to a plain bold "botdocs"
|
|
14
|
-
*
|
|
15
|
-
* margins fits comfortably above that. */
|
|
12
|
+
/** Width at/above which the login wordmark renders with the cyan→violet
|
|
13
|
+
* gradient; narrower terminals fall back to a plain bold cyan "botdocs". 60
|
|
14
|
+
* was chosen empirically so the wordmark plus margins fits comfortably. */
|
|
16
15
|
export declare const BIG_TEXT_MIN_COLS = 60;
|
|
@@ -9,8 +9,7 @@ export const theme = {
|
|
|
9
9
|
red: '#f87171', // Tailwind red-400 — error
|
|
10
10
|
zinc: '#71717a', // Tailwind zinc-500 — subtle / hint
|
|
11
11
|
};
|
|
12
|
-
/** Width
|
|
13
|
-
* to a plain bold "botdocs"
|
|
14
|
-
*
|
|
15
|
-
* margins fits comfortably above that. */
|
|
12
|
+
/** Width at/above which the login wordmark renders with the cyan→violet
|
|
13
|
+
* gradient; narrower terminals fall back to a plain bold cyan "botdocs". 60
|
|
14
|
+
* was chosen empirically so the wordmark plus margins fits comfortably. */
|
|
16
15
|
export const BIG_TEXT_MIN_COLS = 60;
|
package/dist/index.js
CHANGED
|
@@ -301,11 +301,18 @@ program
|
|
|
301
301
|
.option('--no-ink', 'Disable the interactive TUI; use plain output (zero-arg mode only)')
|
|
302
302
|
.option('--pair', "Pair with the web onboarding step so it can mirror this ingest live; prompts for a BD-XXXXX code")
|
|
303
303
|
.option('--pair-code <code>', 'Pre-supply the pairing code (e.g. BD-A4F8K) — skips the interactive prompt; implies --pair')
|
|
304
|
+
.option('--no-browser', 'With --pair: do NOT auto-open https://botdocs.ai/pair (for SSH / CI / headless use)')
|
|
304
305
|
.action(async (sourcePath, opts) => {
|
|
305
|
-
// Commander's --no
|
|
306
|
-
// so downstream consumers don't
|
|
307
|
-
|
|
308
|
-
|
|
306
|
+
// Commander's --no-* convention sets opts.<name> = false; flip both
|
|
307
|
+
// negated flags to their `noX` siblings so downstream consumers don't
|
|
308
|
+
// have to handle the inverted boolean each time.
|
|
309
|
+
const { ink, browser, ...rest } = opts;
|
|
310
|
+
await ingest(sourcePath, {
|
|
311
|
+
...rest,
|
|
312
|
+
noInk: ink === false,
|
|
313
|
+
noBrowser: browser === false,
|
|
314
|
+
json: program.opts().json,
|
|
315
|
+
});
|
|
309
316
|
});
|
|
310
317
|
program
|
|
311
318
|
.command('compile <path>')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botdocs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
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",
|
|
@@ -68,7 +68,6 @@
|
|
|
68
68
|
"commander": "^14.0.3",
|
|
69
69
|
"diff": "^9.0.0",
|
|
70
70
|
"ink": "^5.2.1",
|
|
71
|
-
"ink-big-text": "^2.0.0",
|
|
72
71
|
"ink-gradient": "^3.0.0",
|
|
73
72
|
"ink-select-input": "^6.2.0",
|
|
74
73
|
"ink-spinner": "^5.0.0",
|