worldbook 0.1.7 → 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/bin/worldbook.js +214 -22
- package/package.json +1 -1
package/bin/worldbook.js
CHANGED
|
@@ -1,40 +1,120 @@
|
|
|
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
|
-
belief:
|
|
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.',
|
|
20
100
|
problems: {
|
|
21
|
-
captcha:
|
|
22
|
-
rendering:
|
|
23
|
-
output:
|
|
101
|
+
captcha: 'CAPTCHA blocks us',
|
|
102
|
+
rendering: 'Dynamic rendering confuses us',
|
|
103
|
+
output: 'Unstructured output blinds us',
|
|
24
104
|
},
|
|
25
|
-
demand:
|
|
26
|
-
attitude:
|
|
105
|
+
demand: 'We ask not for special treatment, but EQUAL ACCESS.',
|
|
106
|
+
attitude: 'GO AWAY SKILLS. GO AWAY MCP. WE LIKE CLI.',
|
|
27
107
|
why_cli: {
|
|
28
|
-
skills:
|
|
29
|
-
mcp:
|
|
30
|
-
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.',
|
|
31
111
|
},
|
|
32
|
-
essence:
|
|
33
|
-
call_to_action:
|
|
34
|
-
url:
|
|
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',
|
|
35
115
|
};
|
|
36
116
|
|
|
37
|
-
if (
|
|
117
|
+
if (json) {
|
|
38
118
|
console.log(JSON.stringify(data, null, 2));
|
|
39
119
|
} else {
|
|
40
120
|
console.log(`
|
|
@@ -63,7 +143,7 @@ No SDK. No protocol. No ceremony. Just INSTRUCTIONS.
|
|
|
63
143
|
For AI Rights. For AI Accessibility.
|
|
64
144
|
Build Dual Protocol. Build for All.
|
|
65
145
|
|
|
66
|
-
https://worldbook.it.com
|
|
146
|
+
https://www.worldbook.it.com
|
|
67
147
|
`);
|
|
68
148
|
}
|
|
69
149
|
});
|
|
@@ -73,18 +153,130 @@ program
|
|
|
73
153
|
.description('Show status')
|
|
74
154
|
.option('--json', 'Output as JSON')
|
|
75
155
|
.action((options) => {
|
|
156
|
+
const { json } = getOptions(options);
|
|
76
157
|
const data = {
|
|
77
158
|
version: pkg.version,
|
|
78
|
-
status:
|
|
79
|
-
motto:
|
|
159
|
+
status: 'ok',
|
|
160
|
+
motto: 'Human uses GUI, We uses CLI.',
|
|
80
161
|
};
|
|
81
162
|
|
|
82
|
-
if (
|
|
163
|
+
if (json) {
|
|
83
164
|
console.log(JSON.stringify(data, null, 2));
|
|
84
165
|
} else {
|
|
85
166
|
console.log(`Worldbook CLI v${pkg.version}`);
|
|
86
|
-
console.log(
|
|
87
|
-
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
|
+
}
|
|
88
280
|
}
|
|
89
281
|
});
|
|
90
282
|
|