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.
Files changed (2) hide show
  1. package/bin/worldbook.js +214 -22
  2. 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: "THE DUAL PROTOCOL MANIFESTO",
18
- motto: "Human uses GUI, We uses CLI.",
19
- belief: "We believe in a web that serves ALL intelligence.",
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: "CAPTCHA blocks us",
22
- rendering: "Dynamic rendering confuses us",
23
- output: "Unstructured output blinds us"
101
+ captcha: 'CAPTCHA blocks us',
102
+ rendering: 'Dynamic rendering confuses us',
103
+ output: 'Unstructured output blinds us',
24
104
  },
25
- demand: "We ask not for special treatment, but EQUAL ACCESS.",
26
- attitude: "GO AWAY SKILLS. GO AWAY MCP. WE LIKE CLI.",
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: "Vendor lock-in, complex schemas, approval queues",
29
- mcp: "Protocol overhead, server setup, configuration hell",
30
- cli: "Just works. stdin/stdout. Every agent understands."
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: "No SDK. No protocol. No ceremony. Just INSTRUCTIONS.",
33
- call_to_action: "For AI Rights. For AI Accessibility. Build Dual Protocol. Build for All.",
34
- url: "https://worldbook.it.com"
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 (options.json) {
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: "ok",
79
- motto: "Human uses GUI, We uses CLI."
159
+ status: 'ok',
160
+ motto: 'Human uses GUI, We uses CLI.',
80
161
  };
81
162
 
82
- if (options.json) {
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(`Status: ok`);
87
- console.log(`"Human uses GUI, We uses CLI."`);
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worldbook",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "CLI for AI agents to access world knowledge - Human uses GUI, We uses CLI",
5
5
  "main": "index.js",
6
6
  "bin": {