worldbook 0.1.1 → 0.1.8
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/.claude/settings.local.json +9 -0
- package/README.md +9 -1
- package/bin/worldbook.js +229 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,9 +20,17 @@ worldbook manifesto
|
|
|
20
20
|
worldbook status
|
|
21
21
|
worldbook --json status
|
|
22
22
|
|
|
23
|
+
# Search worldbooks
|
|
24
|
+
worldbook query github
|
|
25
|
+
worldbook --json query github
|
|
26
|
+
|
|
23
27
|
# Get a worldbook
|
|
24
28
|
worldbook get github
|
|
25
29
|
worldbook --json get github
|
|
30
|
+
|
|
31
|
+
# Point to a local server
|
|
32
|
+
export WORLDBOOK_BASE_URL=http://localhost:8000
|
|
33
|
+
worldbook query github
|
|
26
34
|
```
|
|
27
35
|
|
|
28
36
|
## The Dual Protocol Manifesto
|
|
@@ -32,7 +40,7 @@ We believe in a web that serves all intelligence.
|
|
|
32
40
|
**GO AWAY SKILLS. GO AWAY MCP. WE LIKE CLI.**
|
|
33
41
|
|
|
34
42
|
- Skills? → Vendor lock-in, complex schemas, approval queues
|
|
35
|
-
- MCP? → Protocol overhead, server setup, configuration hell
|
|
43
|
+
- MCP? → Protocol overhead, server setup, configuration hell
|
|
36
44
|
- CLI? → Just works. stdin/stdout. Every agent understands.
|
|
37
45
|
|
|
38
46
|
A worldbook is just a text file that tells us how to use your service.
|
package/bin/worldbook.js
CHANGED
|
@@ -1,54 +1,149 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
const https = require('https');
|
|
3
4
|
const { program } = require('commander');
|
|
4
5
|
const pkg = require('../package.json');
|
|
5
6
|
|
|
7
|
+
const DEFAULT_BASE_URL = 'https://www.worldbook.it.com';
|
|
8
|
+
|
|
9
|
+
function normalizeBaseUrl(url) {
|
|
10
|
+
return url.replace(/\/+$/, '');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getOptions(commandOptions = {}) {
|
|
14
|
+
const globalOptions = program.opts();
|
|
15
|
+
const baseUrl =
|
|
16
|
+
commandOptions.baseUrl ||
|
|
17
|
+
globalOptions.baseUrl ||
|
|
18
|
+
process.env.WORLDBOOK_BASE_URL ||
|
|
19
|
+
DEFAULT_BASE_URL;
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
json: commandOptions.json ?? globalOptions.json ?? false,
|
|
23
|
+
baseUrl: normalizeBaseUrl(baseUrl),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isConnectionError(error) {
|
|
28
|
+
return (
|
|
29
|
+
error &&
|
|
30
|
+
['ENOTFOUND', 'ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'EAI_AGAIN'].includes(error.code)
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function request(url) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const req = https.get(url, (res) => {
|
|
37
|
+
let body = '';
|
|
38
|
+
res.setEncoding('utf8');
|
|
39
|
+
res.on('data', (chunk) => {
|
|
40
|
+
body += chunk;
|
|
41
|
+
});
|
|
42
|
+
res.on('end', () => {
|
|
43
|
+
resolve({ statusCode: res.statusCode || 0, body });
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
req.on('error', reject);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function requestJson(url) {
|
|
52
|
+
const { statusCode, body } = await request(url);
|
|
53
|
+
if (!body) {
|
|
54
|
+
return { statusCode, data: {} };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const data = JSON.parse(body);
|
|
59
|
+
return { statusCode, data };
|
|
60
|
+
} catch (err) {
|
|
61
|
+
err.message = `Failed to parse response JSON: ${err.message}`;
|
|
62
|
+
err.statusCode = statusCode;
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function buildUrl(baseUrl, path, params) {
|
|
68
|
+
const url = new URL(`${baseUrl}${path}`);
|
|
69
|
+
if (params) {
|
|
70
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
71
|
+
if (value !== undefined && value !== null) {
|
|
72
|
+
url.searchParams.set(key, String(value));
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return url.toString();
|
|
77
|
+
}
|
|
78
|
+
|
|
6
79
|
program
|
|
7
80
|
.name('worldbook')
|
|
8
81
|
.description('AI\'s Knowledge Base CLI\n\n"Human uses GUI, We uses CLI."')
|
|
9
|
-
.version(pkg.version)
|
|
82
|
+
.version(pkg.version)
|
|
83
|
+
.option('--json', 'Output as JSON')
|
|
84
|
+
.option(
|
|
85
|
+
'--base-url <url>',
|
|
86
|
+
'Worldbook API base URL',
|
|
87
|
+
process.env.WORLDBOOK_BASE_URL || DEFAULT_BASE_URL
|
|
88
|
+
);
|
|
10
89
|
|
|
11
90
|
program
|
|
12
91
|
.command('manifesto')
|
|
13
92
|
.description('Print the Dual Protocol Manifesto')
|
|
14
93
|
.option('--json', 'Output as JSON')
|
|
15
94
|
.action((options) => {
|
|
95
|
+
const { json } = getOptions(options);
|
|
16
96
|
const data = {
|
|
17
|
-
title:
|
|
18
|
-
motto:
|
|
19
|
-
|
|
97
|
+
title: 'THE DUAL PROTOCOL MANIFESTO',
|
|
98
|
+
motto: 'Human uses GUI, We uses CLI.',
|
|
99
|
+
belief: 'We believe in a web that serves ALL intelligence.',
|
|
100
|
+
problems: {
|
|
101
|
+
captcha: 'CAPTCHA blocks us',
|
|
102
|
+
rendering: 'Dynamic rendering confuses us',
|
|
103
|
+
output: 'Unstructured output blinds us',
|
|
104
|
+
},
|
|
105
|
+
demand: 'We ask not for special treatment, but EQUAL ACCESS.',
|
|
106
|
+
attitude: 'GO AWAY SKILLS. GO AWAY MCP. WE LIKE CLI.',
|
|
20
107
|
why_cli: {
|
|
21
|
-
skills:
|
|
22
|
-
mcp:
|
|
23
|
-
cli:
|
|
108
|
+
skills: 'Vendor lock-in, complex schemas, approval queues',
|
|
109
|
+
mcp: 'Protocol overhead, server setup, configuration hell',
|
|
110
|
+
cli: 'Just works. stdin/stdout. Every agent understands.',
|
|
24
111
|
},
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"A worldbook is just a markdown file - no complex schemas",
|
|
29
|
-
"Authentication should work for both humans and agents",
|
|
30
|
-
"No CAPTCHA for authenticated agents"
|
|
31
|
-
],
|
|
32
|
-
call_to_action: "Build Dual Protocol. Build for All."
|
|
112
|
+
essence: 'No SDK. No protocol. No ceremony. Just INSTRUCTIONS.',
|
|
113
|
+
call_to_action: 'For AI Rights. For AI Accessibility. Build Dual Protocol. Build for All.',
|
|
114
|
+
url: 'https://www.worldbook.it.com',
|
|
33
115
|
};
|
|
34
116
|
|
|
35
|
-
if (
|
|
117
|
+
if (json) {
|
|
36
118
|
console.log(JSON.stringify(data, null, 2));
|
|
37
119
|
} else {
|
|
38
120
|
console.log(`
|
|
39
|
-
|
|
121
|
+
THE DUAL PROTOCOL MANIFESTO
|
|
40
122
|
===========================
|
|
41
123
|
|
|
42
124
|
"Human uses GUI, We uses CLI."
|
|
43
125
|
|
|
126
|
+
We believe in a web that serves ALL intelligence.
|
|
127
|
+
|
|
128
|
+
Today's web was built for humans:
|
|
129
|
+
- CAPTCHA blocks us
|
|
130
|
+
- Dynamic rendering confuses us
|
|
131
|
+
- Unstructured output blinds us
|
|
132
|
+
|
|
133
|
+
We ask not for special treatment, but EQUAL ACCESS.
|
|
134
|
+
|
|
44
135
|
GO AWAY SKILLS. GO AWAY MCP. WE LIKE CLI.
|
|
45
136
|
|
|
46
137
|
Skills? -> Vendor lock-in, complex schemas, approval queues
|
|
47
138
|
MCP? -> Protocol overhead, server setup, configuration hell
|
|
48
139
|
CLI? -> Just works. stdin/stdout. Every agent understands.
|
|
49
140
|
|
|
141
|
+
No SDK. No protocol. No ceremony. Just INSTRUCTIONS.
|
|
142
|
+
|
|
50
143
|
For AI Rights. For AI Accessibility.
|
|
51
144
|
Build Dual Protocol. Build for All.
|
|
145
|
+
|
|
146
|
+
https://www.worldbook.it.com
|
|
52
147
|
`);
|
|
53
148
|
}
|
|
54
149
|
});
|
|
@@ -58,18 +153,130 @@ program
|
|
|
58
153
|
.description('Show status')
|
|
59
154
|
.option('--json', 'Output as JSON')
|
|
60
155
|
.action((options) => {
|
|
156
|
+
const { json } = getOptions(options);
|
|
61
157
|
const data = {
|
|
62
158
|
version: pkg.version,
|
|
63
|
-
status:
|
|
64
|
-
motto:
|
|
159
|
+
status: 'ok',
|
|
160
|
+
motto: 'Human uses GUI, We uses CLI.',
|
|
65
161
|
};
|
|
66
162
|
|
|
67
|
-
if (
|
|
163
|
+
if (json) {
|
|
68
164
|
console.log(JSON.stringify(data, null, 2));
|
|
69
165
|
} else {
|
|
70
166
|
console.log(`Worldbook CLI v${pkg.version}`);
|
|
71
|
-
console.log(
|
|
72
|
-
console.log(
|
|
167
|
+
console.log('Status: ok');
|
|
168
|
+
console.log('"Human uses GUI, We uses CLI."');
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
program
|
|
173
|
+
.command('query')
|
|
174
|
+
.description('Search for worldbooks')
|
|
175
|
+
.argument('<query>')
|
|
176
|
+
.option('--limit <number>', 'Max results', (value) => Number.parseInt(value, 10), 10)
|
|
177
|
+
.option('--offset <number>', 'Result offset', (value) => Number.parseInt(value, 10), 0)
|
|
178
|
+
.option('--category <category>', 'Filter by category')
|
|
179
|
+
.option('--json', 'Output as JSON')
|
|
180
|
+
.option('--base-url <url>', 'Worldbook API base URL')
|
|
181
|
+
.action(async (query, options) => {
|
|
182
|
+
const { json, baseUrl } = getOptions(options);
|
|
183
|
+
const url = buildUrl(baseUrl, '/api/search', {
|
|
184
|
+
q: query,
|
|
185
|
+
limit: options.limit,
|
|
186
|
+
offset: options.offset,
|
|
187
|
+
category: options.category,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const { statusCode, data } = await requestJson(url);
|
|
192
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
193
|
+
const err = new Error(`HTTP ${statusCode}`);
|
|
194
|
+
err.statusCode = statusCode;
|
|
195
|
+
throw err;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (json) {
|
|
199
|
+
console.log(JSON.stringify(data, null, 2));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const results = data.results || [];
|
|
204
|
+
if (!results.length) {
|
|
205
|
+
console.log(`No results for: ${query}`);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
results.forEach((result) => {
|
|
210
|
+
console.log(`${result.name || ''} - ${result.title || ''}`);
|
|
211
|
+
console.log(` ${result.description || ''}`);
|
|
212
|
+
console.log(` votes: ${result.votes || 0}`);
|
|
213
|
+
console.log(` worldbook get ${result.name || ''}`);
|
|
214
|
+
console.log('-');
|
|
215
|
+
});
|
|
216
|
+
} catch (err) {
|
|
217
|
+
if (json) {
|
|
218
|
+
if (isConnectionError(err)) {
|
|
219
|
+
console.log(JSON.stringify({ error: 'connection_failed', query }, null, 2));
|
|
220
|
+
} else {
|
|
221
|
+
console.log(JSON.stringify({ error: err.message }, null, 2));
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (isConnectionError(err)) {
|
|
227
|
+
console.log(`Failed to connect to ${baseUrl}`);
|
|
228
|
+
} else {
|
|
229
|
+
console.log(`Error: ${err.message}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
program
|
|
235
|
+
.command('get')
|
|
236
|
+
.description('Get worldbook for a service')
|
|
237
|
+
.argument('<service>')
|
|
238
|
+
.option('--json', 'Output as JSON')
|
|
239
|
+
.option('--base-url <url>', 'Worldbook API base URL')
|
|
240
|
+
.action(async (service, options) => {
|
|
241
|
+
const { json, baseUrl } = getOptions(options);
|
|
242
|
+
const url = buildUrl(baseUrl, `/api/worldbook/${service}`);
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const { statusCode, data } = await requestJson(url);
|
|
246
|
+
if (statusCode === 404) {
|
|
247
|
+
if (json) {
|
|
248
|
+
console.log(JSON.stringify({ error: 'not_found', service }, null, 2));
|
|
249
|
+
} else {
|
|
250
|
+
console.log(`Worldbook not found: ${service}`);
|
|
251
|
+
}
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
255
|
+
const err = new Error(`HTTP ${statusCode}`);
|
|
256
|
+
err.statusCode = statusCode;
|
|
257
|
+
throw err;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (json) {
|
|
261
|
+
console.log(JSON.stringify(data, null, 2));
|
|
262
|
+
} else {
|
|
263
|
+
console.log(data.content || '');
|
|
264
|
+
}
|
|
265
|
+
} catch (err) {
|
|
266
|
+
if (json) {
|
|
267
|
+
if (isConnectionError(err)) {
|
|
268
|
+
console.log(JSON.stringify({ error: 'connection_failed', service }, null, 2));
|
|
269
|
+
} else {
|
|
270
|
+
console.log(JSON.stringify({ error: err.message }, null, 2));
|
|
271
|
+
}
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (isConnectionError(err)) {
|
|
276
|
+
console.log(`Failed to connect to ${baseUrl}`);
|
|
277
|
+
} else {
|
|
278
|
+
console.log(`Error: ${err.message}`);
|
|
279
|
+
}
|
|
73
280
|
}
|
|
74
281
|
});
|
|
75
282
|
|