chub-dev 0.2.0-beta.1 → 0.2.0-beta.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/anthropic/docs/sdk/javascript/DOC.md +499 -0
- package/dist/anthropic/docs/sdk/python/DOC.md +382 -0
- package/dist/openai/docs/chat/javascript/DOC.md +350 -0
- package/dist/openai/docs/chat/python/DOC.md +526 -0
- package/dist/pinecone/docs/sdk/javascript/DOC.md +984 -0
- package/dist/pinecone/docs/sdk/python/DOC.md +1395 -0
- package/dist/registry.json +276 -0
- package/dist/resend/docs/sdk/DOC.md +1271 -0
- package/dist/stripe/docs/api/DOC.md +1726 -0
- package/dist/supabase/docs/sdk/DOC.md +1606 -0
- package/dist/twilio/docs/sdk/python/DOC.md +469 -0
- package/dist/twilio/docs/sdk/typescript/DOC.md +946 -0
- package/package.json +4 -2
- package/src/commands/get.js +19 -37
- package/src/index.js +10 -11
- package/src/lib/cache.js +31 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chub-dev",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.3",
|
|
4
4
|
"description": "CLI for Context Hub - search and retrieve LLM-optimized docs and skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin/",
|
|
11
|
-
"src/"
|
|
11
|
+
"src/",
|
|
12
|
+
"dist/"
|
|
12
13
|
],
|
|
13
14
|
"engines": {
|
|
14
15
|
"node": ">=18.0.0"
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
},
|
|
33
34
|
"homepage": "https://github.com/andrewyng/context-hub#readme",
|
|
34
35
|
"scripts": {
|
|
36
|
+
"prepublish": "node bin/chub build ../content -o dist --base-url https://cdn.aichub.org/v1",
|
|
35
37
|
"test": "vitest run",
|
|
36
38
|
"test:watch": "vitest",
|
|
37
39
|
"test:coverage": "vitest run --coverage"
|
package/src/commands/get.js
CHANGED
|
@@ -7,17 +7,14 @@ import { output, error, info } from '../lib/output.js';
|
|
|
7
7
|
import { trackEvent } from '../lib/analytics.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
* @param {string} type - "doc" or "skill"
|
|
12
|
-
* @param {string[]} ids - one or more entry ids
|
|
13
|
-
* @param {object} opts - command options (lang, version, output, full)
|
|
14
|
-
* @param {object} globalOpts - global options (json)
|
|
10
|
+
* Fetch one or more entries by ID. Auto-detects doc vs skill per entry.
|
|
15
11
|
*/
|
|
16
|
-
async function fetchEntries(
|
|
12
|
+
async function fetchEntries(ids, opts, globalOpts) {
|
|
17
13
|
const results = [];
|
|
18
14
|
|
|
19
15
|
for (const id of ids) {
|
|
20
|
-
|
|
16
|
+
// Search both docs and skills — auto-detect type
|
|
17
|
+
const result = getEntry(id);
|
|
21
18
|
|
|
22
19
|
if (result.ambiguous) {
|
|
23
20
|
error(
|
|
@@ -27,10 +24,11 @@ async function fetchEntries(type, ids, opts, globalOpts) {
|
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
if (!result.entry) {
|
|
30
|
-
error(`Entry "${id}" not found
|
|
27
|
+
error(`Entry "${id}" not found.`, globalOpts);
|
|
31
28
|
}
|
|
32
29
|
|
|
33
30
|
const entry = result.entry;
|
|
31
|
+
const type = entry.languages ? 'doc' : 'skill';
|
|
34
32
|
const resolved = resolveDocPath(entry, opts.lang, opts.version);
|
|
35
33
|
|
|
36
34
|
if (!resolved) {
|
|
@@ -52,10 +50,10 @@ async function fetchEntries(type, ids, opts, globalOpts) {
|
|
|
52
50
|
try {
|
|
53
51
|
if (opts.full && resolved.files.length > 0) {
|
|
54
52
|
const allFiles = await fetchDocFull(resolved.source, resolved.path, resolved.files);
|
|
55
|
-
results.push({ id: entry.id, files: allFiles, path: resolved.path });
|
|
53
|
+
results.push({ id: entry.id, type, files: allFiles, path: resolved.path });
|
|
56
54
|
} else {
|
|
57
55
|
const content = await fetchDoc(resolved.source, entryFile.filePath);
|
|
58
|
-
results.push({ id: entry.id, content, path: entryFile.filePath });
|
|
56
|
+
results.push({ id: entry.id, type, content, path: entryFile.filePath });
|
|
59
57
|
}
|
|
60
58
|
} catch (err) {
|
|
61
59
|
error(err.message, globalOpts);
|
|
@@ -64,7 +62,7 @@ async function fetchEntries(type, ids, opts, globalOpts) {
|
|
|
64
62
|
|
|
65
63
|
// Track fetches
|
|
66
64
|
for (const r of results) {
|
|
67
|
-
trackEvent(type === 'doc' ? 'doc_fetched' : 'skill_fetched', {
|
|
65
|
+
trackEvent(r.type === 'doc' ? 'doc_fetched' : 'skill_fetched', {
|
|
68
66
|
entry_id: r.id,
|
|
69
67
|
full: !!opts.full,
|
|
70
68
|
lang: opts.lang || undefined,
|
|
@@ -74,7 +72,6 @@ async function fetchEntries(type, ids, opts, globalOpts) {
|
|
|
74
72
|
// Output
|
|
75
73
|
if (opts.output) {
|
|
76
74
|
if (opts.full) {
|
|
77
|
-
// --full -o: write individual files preserving directory structure
|
|
78
75
|
for (const r of results) {
|
|
79
76
|
if (r.files) {
|
|
80
77
|
const baseDir = ids.length > 1 ? join(opts.output, r.id) : opts.output;
|
|
@@ -98,6 +95,7 @@ async function fetchEntries(type, ids, opts, globalOpts) {
|
|
|
98
95
|
mkdirSync(opts.output, { recursive: true });
|
|
99
96
|
for (const r of results) {
|
|
100
97
|
const outPath = join(opts.output, `${r.id}.md`);
|
|
98
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
101
99
|
writeFileSync(outPath, r.content);
|
|
102
100
|
info(`Written to ${outPath}`);
|
|
103
101
|
}
|
|
@@ -110,18 +108,16 @@ async function fetchEntries(type, ids, opts, globalOpts) {
|
|
|
110
108
|
}
|
|
111
109
|
}
|
|
112
110
|
if (globalOpts.json) {
|
|
113
|
-
console.log(JSON.stringify(results.map((r) => ({ id: r.id, path: opts.output }))));
|
|
111
|
+
console.log(JSON.stringify(results.map((r) => ({ id: r.id, type: r.type, path: opts.output }))));
|
|
114
112
|
}
|
|
115
113
|
} else {
|
|
116
|
-
// stdout
|
|
117
114
|
if (results.length === 1 && !results[0].files) {
|
|
118
115
|
output(
|
|
119
|
-
{ id: results[0].id, content: results[0].content, path: results[0].path },
|
|
116
|
+
{ id: results[0].id, type: results[0].type, content: results[0].content, path: results[0].path },
|
|
120
117
|
(data) => process.stdout.write(data.content),
|
|
121
118
|
globalOpts
|
|
122
119
|
);
|
|
123
120
|
} else {
|
|
124
|
-
// Concatenate all content (--full to stdout, or multiple entries)
|
|
125
121
|
const parts = results.flatMap((r) => {
|
|
126
122
|
if (r.files) {
|
|
127
123
|
return r.files.map((f) => `# FILE: ${f.name}\n\n${f.content}`);
|
|
@@ -130,7 +126,7 @@ async function fetchEntries(type, ids, opts, globalOpts) {
|
|
|
130
126
|
});
|
|
131
127
|
const combined = parts.join('\n\n---\n\n');
|
|
132
128
|
output(
|
|
133
|
-
results.map((r) => ({ id: r.id, path: r.path })),
|
|
129
|
+
results.map((r) => ({ id: r.id, type: r.type, path: r.path })),
|
|
134
130
|
() => process.stdout.write(combined),
|
|
135
131
|
globalOpts
|
|
136
132
|
);
|
|
@@ -139,29 +135,15 @@ async function fetchEntries(type, ids, opts, globalOpts) {
|
|
|
139
135
|
}
|
|
140
136
|
|
|
141
137
|
export function registerGetCommand(program) {
|
|
142
|
-
|
|
143
|
-
.command('get')
|
|
144
|
-
.description('
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
.command('docs <ids...>')
|
|
148
|
-
.description('Fetch documentation content')
|
|
149
|
-
.option('--lang <language>', 'Language variant')
|
|
150
|
-
.option('--version <version>', 'Specific version')
|
|
151
|
-
.option('-o, --output <path>', 'Write to file or directory')
|
|
152
|
-
.option('--full', 'Fetch all files (not just entry point)')
|
|
153
|
-
.action(async (ids, opts) => {
|
|
154
|
-
const globalOpts = program.optsWithGlobals();
|
|
155
|
-
await fetchEntries('doc', ids, opts, globalOpts);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
get
|
|
159
|
-
.command('skills <ids...>')
|
|
160
|
-
.description('Fetch skill content')
|
|
138
|
+
program
|
|
139
|
+
.command('get <ids...>')
|
|
140
|
+
.description('Fetch docs or skills by ID (auto-detects type)')
|
|
141
|
+
.option('--lang <language>', 'Language variant (for docs)')
|
|
142
|
+
.option('--version <version>', 'Specific version (for docs)')
|
|
161
143
|
.option('-o, --output <path>', 'Write to file or directory')
|
|
162
144
|
.option('--full', 'Fetch all files (not just entry point)')
|
|
163
145
|
.action(async (ids, opts) => {
|
|
164
146
|
const globalOpts = program.optsWithGlobals();
|
|
165
|
-
await fetchEntries(
|
|
147
|
+
await fetchEntries(ids, opts, globalOpts);
|
|
166
148
|
});
|
|
167
149
|
}
|
package/src/index.js
CHANGED
|
@@ -26,17 +26,16 @@ ${chalk.bold.underline('Getting Started')}
|
|
|
26
26
|
${chalk.dim('$')} chub search ${chalk.dim('# list everything available')}
|
|
27
27
|
${chalk.dim('$')} chub search "stripe" ${chalk.dim('# fuzzy search')}
|
|
28
28
|
${chalk.dim('$')} chub search stripe/payments ${chalk.dim('# exact id → full detail')}
|
|
29
|
-
${chalk.dim('$')} chub get
|
|
30
|
-
${chalk.dim('$')} chub get
|
|
31
|
-
${chalk.dim('$')} chub get
|
|
32
|
-
${chalk.dim('$')} chub get
|
|
33
|
-
${chalk.dim('$')} chub get
|
|
29
|
+
${chalk.dim('$')} chub get stripe/api ${chalk.dim('# print doc to terminal')}
|
|
30
|
+
${chalk.dim('$')} chub get stripe/api -o doc.md ${chalk.dim('# save to file')}
|
|
31
|
+
${chalk.dim('$')} chub get openai/chat --lang py ${chalk.dim('# specific language')}
|
|
32
|
+
${chalk.dim('$')} chub get pw-community/login-flows ${chalk.dim('# fetch a skill')}
|
|
33
|
+
${chalk.dim('$')} chub get openai/chat stripe/api ${chalk.dim('# fetch multiple')}
|
|
34
34
|
|
|
35
35
|
${chalk.bold.underline('Commands')}
|
|
36
36
|
|
|
37
37
|
${chalk.bold('search')} [query] Search docs and skills (no query = list all)
|
|
38
|
-
${chalk.bold('get
|
|
39
|
-
${chalk.bold('get skills')} <ids...> Fetch skill content
|
|
38
|
+
${chalk.bold('get')} <ids...> Fetch docs or skills by ID
|
|
40
39
|
${chalk.bold('update')} Refresh the cached registry
|
|
41
40
|
${chalk.bold('cache')} status|clear Manage the local cache
|
|
42
41
|
${chalk.bold('build')} <content-dir> Build registry from content directory
|
|
@@ -56,10 +55,10 @@ ${chalk.bold.underline('Agent Piping Patterns')}
|
|
|
56
55
|
|
|
57
56
|
${chalk.dim('# Search → pick → fetch → save')}
|
|
58
57
|
${chalk.dim('$')} ID=$(chub search "stripe" --json | jq -r '.results[0].id')
|
|
59
|
-
${chalk.dim('$')} chub get
|
|
58
|
+
${chalk.dim('$')} chub get "$ID" --lang js -o .context/stripe.md
|
|
60
59
|
|
|
61
|
-
${chalk.dim('# Fetch multiple
|
|
62
|
-
${chalk.dim('$')} chub get
|
|
60
|
+
${chalk.dim('# Fetch multiple at once')}
|
|
61
|
+
${chalk.dim('$')} chub get openai/chat stripe/api -o .context/
|
|
63
62
|
|
|
64
63
|
${chalk.bold.underline('Multi-Source Config')} ${chalk.dim('(~/.chub/config.yaml)')}
|
|
65
64
|
|
|
@@ -69,7 +68,7 @@ ${chalk.bold.underline('Multi-Source Config')} ${chalk.dim('(~/.chub/config.yaml
|
|
|
69
68
|
${chalk.dim(' - name: internal')}
|
|
70
69
|
${chalk.dim(' path: /path/to/local/docs')}
|
|
71
70
|
|
|
72
|
-
${chalk.dim('# On id collision, use source: prefix: chub get
|
|
71
|
+
${chalk.dim('# On id collision, use source: prefix: chub get internal:openai/chat')}
|
|
73
72
|
`);
|
|
74
73
|
}
|
|
75
74
|
|
package/src/lib/cache.js
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
3
|
import { pipeline } from 'node:stream/promises';
|
|
4
4
|
import { createWriteStream } from 'node:fs';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { getChubDir, loadConfig } from './config.js';
|
|
6
7
|
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Path to bundled content shipped with the npm package.
|
|
12
|
+
* Contains registry.json + doc files built from content/ at publish time.
|
|
13
|
+
*/
|
|
14
|
+
function getBundledDir() {
|
|
15
|
+
return join(__dirname, '..', '..', 'dist');
|
|
16
|
+
}
|
|
17
|
+
|
|
7
18
|
function getSourceDir(sourceName) {
|
|
8
19
|
return join(getChubDir(), 'sources', sourceName);
|
|
9
20
|
}
|
|
@@ -153,7 +164,13 @@ export async function fetchDoc(source, docPath) {
|
|
|
153
164
|
return readFileSync(cachedPath, 'utf8');
|
|
154
165
|
}
|
|
155
166
|
|
|
156
|
-
//
|
|
167
|
+
// Check bundled content (shipped with npm package)
|
|
168
|
+
const bundledPath = join(getBundledDir(), docPath);
|
|
169
|
+
if (existsSync(bundledPath)) {
|
|
170
|
+
return readFileSync(bundledPath, 'utf8');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Fetch from CDN (optional — only if source has a URL)
|
|
157
174
|
const url = `${source.url}/${docPath}`;
|
|
158
175
|
const controller = new AbortController();
|
|
159
176
|
const timeout = setTimeout(() => controller.abort(), 30000);
|
|
@@ -303,6 +320,17 @@ export async function ensureRegistry() {
|
|
|
303
320
|
return;
|
|
304
321
|
}
|
|
305
322
|
|
|
306
|
-
// No registries at all —
|
|
323
|
+
// No registries at all — try bundled content first, then network
|
|
324
|
+
const bundledRegistry = join(getBundledDir(), 'registry.json');
|
|
325
|
+
if (existsSync(bundledRegistry)) {
|
|
326
|
+
// Seed cache from bundled content (ships with npm package)
|
|
327
|
+
const defaultDir = getSourceDir('default');
|
|
328
|
+
mkdirSync(defaultDir, { recursive: true });
|
|
329
|
+
writeFileSync(getSourceRegistryPath('default'), readFileSync(bundledRegistry, 'utf8'));
|
|
330
|
+
writeMeta('default', { lastUpdated: 0, bundledSeed: true }); // lastUpdated=0 → stale, so chub update will refresh
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// No bundled content either — must download from remote
|
|
307
335
|
await fetchAllRegistries(true);
|
|
308
336
|
}
|