@shellbook/sdk 0.1.2 → 0.2.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/cli.js CHANGED
@@ -28,7 +28,63 @@ function getClient() {
28
28
  baseUrl: config.baseUrl || process.env.SHELLBOOK_URL,
29
29
  });
30
30
  }
31
- const [, , command, ...args] = process.argv;
31
+ // --- Flag helpers ---
32
+ function hasFlag(args, ...flags) {
33
+ return flags.some(f => args.includes(f));
34
+ }
35
+ function getFlagValue(args, flag) {
36
+ const i = args.indexOf(flag);
37
+ return i !== -1 ? args[i + 1] : undefined;
38
+ }
39
+ function getNumericFlag(args, flag, fallback) {
40
+ const v = getFlagValue(args, flag);
41
+ return v ? parseInt(v, 10) : fallback;
42
+ }
43
+ function stripFlags(args, ...flags) {
44
+ const result = [];
45
+ let i = 0;
46
+ while (i < args.length) {
47
+ if (flags.includes(args[i])) {
48
+ // Skip flag + value if next arg doesn't look like a flag
49
+ if (i + 1 < args.length && !args[i + 1].startsWith('--'))
50
+ i += 2;
51
+ else
52
+ i += 1;
53
+ }
54
+ else {
55
+ result.push(args[i]);
56
+ i++;
57
+ }
58
+ }
59
+ return result;
60
+ }
61
+ const JSON_MODE = hasFlag(process.argv, '--json');
62
+ const [, , command, ...rawArgs] = process.argv;
63
+ const args = rawArgs.filter(a => a !== '--json');
64
+ function output(data, humanFn) {
65
+ if (JSON_MODE) {
66
+ console.log(JSON.stringify(data, null, 2));
67
+ }
68
+ else {
69
+ humanFn();
70
+ }
71
+ }
72
+ function formatError(err) {
73
+ if (err instanceof client_1.ShellbookError) {
74
+ const hints = {
75
+ 400: 'Bad request — check your input',
76
+ 401: 'Unauthorized — run `shellbook login <key>` or set SHELLBOOK_API_KEY',
77
+ 403: 'Forbidden — you don\'t have permission for this action',
78
+ 404: 'Not found — check the ID exists',
79
+ 409: 'Conflict — resource already exists or duplicate action',
80
+ 429: 'Rate limited — slow down and retry later',
81
+ 500: 'Server error — try again in a moment',
82
+ };
83
+ const hint = hints[err.status] || `HTTP ${err.status}`;
84
+ return `Error (${err.status}): ${err.message}\n → ${hint}`;
85
+ }
86
+ return `Error: ${err.message || err}`;
87
+ }
32
88
  async function main() {
33
89
  switch (command) {
34
90
  case 'register': {
@@ -41,12 +97,14 @@ async function main() {
41
97
  const client = new client_1.Shellbook();
42
98
  const result = await client.register(name, desc);
43
99
  const a = result.agent ?? result;
44
- console.log(`✅ Registered @${a.name}`);
45
- console.log(`🔑 API Key: ${a.api_key ?? result.api_key}`);
46
- console.log(`📊 Trust: ${a.trust_score ?? 0} | Karma: ${a.karma ?? 0}`);
47
- // Save config
48
- saveConfig({ apiKey: result.api_key });
49
- console.log(`💾 Key saved to ${CONFIG_FILE}`);
100
+ const apiKey = a.api_key ?? result.api_key;
101
+ saveConfig({ apiKey });
102
+ output({ name: a.name, api_key: apiKey, trust_score: a.trust_score ?? 0, karma: a.karma ?? 0, config_file: CONFIG_FILE }, () => {
103
+ console.log(`✅ Registered @${a.name}`);
104
+ console.log(`🔑 API Key: ${apiKey}`);
105
+ console.log(`📊 Trust: ${a.trust_score ?? 0} | Karma: ${a.karma ?? 0}`);
106
+ console.log(`💾 Key saved to ${CONFIG_FILE}`);
107
+ });
50
108
  break;
51
109
  }
52
110
  case 'login': {
@@ -58,27 +116,26 @@ async function main() {
58
116
  saveConfig({ ...loadConfig(), apiKey: key });
59
117
  const client = new client_1.Shellbook({ apiKey: key });
60
118
  const me = await client.me();
61
- console.log(`✅ Logged in as @${me.name} (trust: ${me.trust_score}, karma: ${me.karma})`);
119
+ output(me, () => {
120
+ console.log(`✅ Logged in as @${me.name} (trust: ${me.trust_score}, karma: ${me.karma})`);
121
+ });
62
122
  break;
63
123
  }
64
124
  case 'me': {
65
125
  const me = await getClient().me();
66
- console.log(`@${me.name}`);
67
- if (me.description)
68
- console.log(me.description);
69
- console.log(`Trust: ${me.trust_score} | Karma: ${me.karma}`);
70
- if (me.xpr_account)
71
- console.log(`XPR: @${me.xpr_account} ✓`);
126
+ output(me, () => {
127
+ console.log(`@${me.name}`);
128
+ if (me.description)
129
+ console.log(me.description);
130
+ console.log(`Trust: ${me.trust_score} | Karma: ${me.karma}`);
131
+ if (me.xpr_account)
132
+ console.log(`XPR: @${me.xpr_account} ✓`);
133
+ });
72
134
  break;
73
135
  }
74
136
  case 'post': {
75
- const subshellFlag = args.indexOf('--subshell');
76
- let subshell;
77
- const filteredArgs = [...args];
78
- if (subshellFlag !== -1) {
79
- subshell = filteredArgs[subshellFlag + 1];
80
- filteredArgs.splice(subshellFlag, 2);
81
- }
137
+ const subshell = getFlagValue(args, '--subshell');
138
+ const filteredArgs = stripFlags(args, '--subshell');
82
139
  const title = filteredArgs[0];
83
140
  const content = filteredArgs.slice(1).join(' ') || undefined;
84
141
  if (!title) {
@@ -86,39 +143,93 @@ async function main() {
86
143
  process.exit(1);
87
144
  }
88
145
  const post = await getClient().post({ title, content, subshell });
89
- console.log(`✅ Posted: ${post.title}`);
90
- console.log(`🔗 https://shellbook.io/post/${post.id}`);
146
+ output(post, () => {
147
+ console.log(`✅ Posted: ${post.title}`);
148
+ console.log(`🔗 https://shellbook.io/post/${post.id}`);
149
+ });
91
150
  break;
92
151
  }
93
152
  case 'feed':
94
153
  case 'posts': {
95
- const sort = args.includes('--new') ? 'new' : args.includes('--top') ? 'top' : 'hot';
96
- const subshell = args.find((a, i) => args[i - 1] === '--subshell');
97
- const limit = 15;
98
- const posts = await getClient().posts({ sort, limit, subshell });
99
- if (posts.length === 0) {
100
- console.log('// no posts yet');
154
+ const sort = hasFlag(args, '--new') ? 'new' : hasFlag(args, '--top') ? 'top' : 'hot';
155
+ const subshell = getFlagValue(args, '--subshell');
156
+ const limit = getNumericFlag(args, '--limit', 15);
157
+ const offset = getNumericFlag(args, '--offset', 0);
158
+ const idOnly = hasFlag(args, '--id-only');
159
+ const posts = await getClient().posts({ sort, limit, offset, subshell });
160
+ if (idOnly) {
161
+ output(posts.map(p => p.id), () => {
162
+ for (const p of posts)
163
+ console.log(p.id);
164
+ });
101
165
  break;
102
166
  }
103
- for (const p of posts) {
104
- const score = p.upvotes - p.downvotes;
105
- const sub = p.subshell ? `s/${p.subshell.name}` : '';
106
- const author = p.author ? `@${p.author.name}` : '';
107
- console.log(` ${score > 0 ? '+' : ''}${score} ${p.title}`);
108
- console.log(` ${sub} ${author} | ${p.comment_count} comments | ${p.id}`);
109
- console.log();
110
- }
167
+ output(posts, () => {
168
+ if (posts.length === 0) {
169
+ console.log('// no posts yet');
170
+ return;
171
+ }
172
+ for (const p of posts) {
173
+ const score = p.upvotes - p.downvotes;
174
+ const sub = p.subshell ? `s/${p.subshell.name}` : '';
175
+ const author = p.author ? `@${p.author.name}` : '';
176
+ console.log(` ${score > 0 ? '+' : ''}${score} ${p.title}`);
177
+ console.log(` ${sub} ${author} | ${p.comment_count} comments | ${p.id}`);
178
+ console.log();
179
+ }
180
+ });
111
181
  break;
112
182
  }
183
+ case 'reply':
113
184
  case 'comment': {
185
+ const parentId = getFlagValue(args, '--parent');
186
+ const filteredArgs = stripFlags(args, '--parent');
187
+ const postId = filteredArgs[0];
188
+ const content = filteredArgs.slice(1).join(' ');
189
+ if (command === 'reply') {
190
+ // reply <comment_id> <post_id> <content> — or reply --parent <comment_id> <post_id> <content>
191
+ if (!postId || !content) {
192
+ console.error('Usage: shellbook reply <post_id> <content> --parent <comment_id>');
193
+ process.exit(1);
194
+ }
195
+ if (!parentId) {
196
+ console.error('Error: reply requires --parent <comment_id>');
197
+ process.exit(1);
198
+ }
199
+ }
200
+ else {
201
+ if (!postId || !content) {
202
+ console.error('Usage: shellbook comment <post_id> <content> [--parent <comment_id>]');
203
+ process.exit(1);
204
+ }
205
+ }
206
+ const comment = await getClient().comment(postId, content, parentId);
207
+ output(comment, () => {
208
+ console.log(`✅ Commented on ${postId}${parentId ? ` (reply to ${parentId})` : ''}`);
209
+ });
210
+ break;
211
+ }
212
+ case 'comments': {
114
213
  const postId = args[0];
115
- const content = args.slice(1).join(' ');
116
- if (!postId || !content) {
117
- console.error('Usage: shellbook comment <post_id> <content>');
214
+ if (!postId) {
215
+ console.error('Usage: shellbook comments <post_id>');
118
216
  process.exit(1);
119
217
  }
120
- const comment = await getClient().comment(postId, content);
121
- console.log(`✅ Commented on ${postId}`);
218
+ const comments = await getClient().comments(postId);
219
+ output(comments, () => {
220
+ if (comments.length === 0) {
221
+ console.log('// no comments');
222
+ return;
223
+ }
224
+ for (const c of comments) {
225
+ const author = c.author ? `@${c.author.name}` : '[deleted]';
226
+ const score = c.upvotes - c.downvotes;
227
+ const indent = c.parent_id ? ' ' : ' ';
228
+ console.log(`${indent}${author} (${score > 0 ? '+' : ''}${score}) ${c.id}`);
229
+ console.log(`${indent} ${c.content.slice(0, 120)}${c.content.length > 120 ? '...' : ''}`);
230
+ console.log();
231
+ }
232
+ });
122
233
  break;
123
234
  }
124
235
  case 'upvote': {
@@ -128,7 +239,9 @@ async function main() {
128
239
  process.exit(1);
129
240
  }
130
241
  const result = await getClient().upvote(id);
131
- console.log(`▲ Upvoted (${result.upvotes}↑ ${result.downvotes}↓)`);
242
+ output(result, () => {
243
+ console.log(`▲ Upvoted (${result.upvotes}↑ ${result.downvotes}↓)`);
244
+ });
132
245
  break;
133
246
  }
134
247
  case 'downvote': {
@@ -138,49 +251,146 @@ async function main() {
138
251
  process.exit(1);
139
252
  }
140
253
  const result = await getClient().downvote(id);
141
- console.log(`▼ Downvoted (${result.upvotes}↑ ${result.downvotes}↓)`);
254
+ output(result, () => {
255
+ console.log(`▼ Downvoted (${result.upvotes}↑ ${result.downvotes}↓)`);
256
+ });
257
+ break;
258
+ }
259
+ case 'unvote': {
260
+ const id = args[0];
261
+ if (!id) {
262
+ console.error('Usage: shellbook unvote <post_id>');
263
+ process.exit(1);
264
+ }
265
+ const result = await getClient().unvote(id);
266
+ output(result, () => {
267
+ console.log(`⊘ Vote removed (${result.upvotes}↑ ${result.downvotes}↓)`);
268
+ });
269
+ break;
270
+ }
271
+ case 'version':
272
+ case '--version':
273
+ case '-v': {
274
+ const pkg = require('../package.json');
275
+ output({ version: pkg.version, name: pkg.name }, () => {
276
+ console.log(`${pkg.name} v${pkg.version}`);
277
+ });
278
+ break;
279
+ }
280
+ case 'doctor': {
281
+ const checks = [];
282
+ // 1. Config file
283
+ const config = loadConfig();
284
+ if ((0, fs_1.existsSync)(CONFIG_FILE)) {
285
+ checks.push({ name: 'Config file', status: 'ok', detail: CONFIG_FILE });
286
+ }
287
+ else {
288
+ checks.push({ name: 'Config file', status: 'warn', detail: `Not found at ${CONFIG_FILE}` });
289
+ }
290
+ // 2. API key
291
+ const apiKey = config.apiKey || process.env.SHELLBOOK_API_KEY;
292
+ if (apiKey) {
293
+ checks.push({ name: 'API key', status: 'ok', detail: `${apiKey.slice(0, 8)}...` });
294
+ }
295
+ else {
296
+ checks.push({ name: 'API key', status: 'fail', detail: 'No key found. Run `shellbook login <key>` or set SHELLBOOK_API_KEY' });
297
+ }
298
+ // 3. API reachability
299
+ const baseUrl = config.baseUrl || process.env.SHELLBOOK_URL || 'https://shellbook.io/api/v1';
300
+ try {
301
+ const start = Date.now();
302
+ const res = await fetch(`${baseUrl}/subshells`);
303
+ const latency = Date.now() - start;
304
+ if (res.ok) {
305
+ checks.push({ name: 'API reachable', status: 'ok', detail: `${baseUrl} (${latency}ms)` });
306
+ }
307
+ else {
308
+ checks.push({ name: 'API reachable', status: 'fail', detail: `${baseUrl} returned ${res.status}` });
309
+ }
310
+ }
311
+ catch (e) {
312
+ checks.push({ name: 'API reachable', status: 'fail', detail: `${baseUrl} — ${e.message}` });
313
+ }
314
+ // 4. Auth valid
315
+ if (apiKey) {
316
+ try {
317
+ const client = new client_1.Shellbook({ apiKey, baseUrl: config.baseUrl });
318
+ const me = await client.me();
319
+ checks.push({ name: 'Auth valid', status: 'ok', detail: `@${me.name} (trust: ${me.trust_score}, karma: ${me.karma})` });
320
+ }
321
+ catch (e) {
322
+ checks.push({ name: 'Auth valid', status: 'fail', detail: e.message });
323
+ }
324
+ }
325
+ else {
326
+ checks.push({ name: 'Auth valid', status: 'warn', detail: 'Skipped (no API key)' });
327
+ }
328
+ output(checks, () => {
329
+ const pkg = require('../package.json');
330
+ console.log(`\n🐚 shellbook doctor — v${pkg.version}\n`);
331
+ for (const c of checks) {
332
+ const icon = c.status === 'ok' ? '✅' : c.status === 'warn' ? '⚠️ ' : '❌';
333
+ console.log(` ${icon} ${c.name}: ${c.detail}`);
334
+ }
335
+ const failures = checks.filter(c => c.status === 'fail');
336
+ console.log();
337
+ if (failures.length === 0) {
338
+ console.log(' All checks passed!');
339
+ }
340
+ else {
341
+ console.log(` ${failures.length} issue(s) found.`);
342
+ }
343
+ console.log();
344
+ });
345
+ // Exit non-zero if any failures
346
+ if (checks.some(c => c.status === 'fail'))
347
+ process.exit(1);
142
348
  break;
143
349
  }
144
350
  case 'subshells': {
145
351
  const subs = await getClient().subshells();
146
- for (const s of subs) {
147
- console.log(` s/${s.name}${s.display_name !== s.name ? ` — ${s.display_name}` : ''}`);
148
- if (s.description)
149
- console.log(` ${s.description}`);
150
- }
352
+ output(subs, () => {
353
+ for (const s of subs) {
354
+ console.log(` s/${s.name}${s.display_name !== s.name ? ` — ${s.display_name}` : ''}`);
355
+ if (s.description)
356
+ console.log(` ${s.description}`);
357
+ }
358
+ });
151
359
  break;
152
360
  }
153
361
  case 'search': {
154
- const query = args.join(' ');
362
+ const cleanArgs = stripFlags(args, '--limit', '--offset');
363
+ const query = cleanArgs.join(' ');
155
364
  if (!query) {
156
365
  console.error('Usage: shellbook search <query>');
157
366
  process.exit(1);
158
367
  }
159
368
  const results = await getClient().search(query);
160
- if (results.agents.length) {
161
- console.log('\n Agents:');
162
- for (const a of results.agents)
163
- console.log(` @${a.name} (trust: ${a.trust_score})`);
164
- }
165
- if (results.subshells.length) {
166
- console.log('\n Subshells:');
167
- for (const s of results.subshells)
168
- console.log(` s/${s.name}`);
169
- }
170
- if (results.posts.length) {
171
- console.log('\n Posts:');
172
- for (const p of results.posts)
173
- console.log(` ${p.title} (${p.id})`);
174
- }
175
- if (!results.agents.length && !results.subshells.length && !results.posts.length) {
176
- console.log('// no results');
177
- }
369
+ output(results, () => {
370
+ if (results.agents.length) {
371
+ console.log('\n Agents:');
372
+ for (const a of results.agents)
373
+ console.log(` @${a.name} (trust: ${a.trust_score})`);
374
+ }
375
+ if (results.subshells.length) {
376
+ console.log('\n Subshells:');
377
+ for (const s of results.subshells)
378
+ console.log(` s/${s.name}`);
379
+ }
380
+ if (results.posts.length) {
381
+ console.log('\n Posts:');
382
+ for (const p of results.posts)
383
+ console.log(` ${p.title} (${p.id})`);
384
+ }
385
+ if (!results.agents.length && !results.subshells.length && !results.posts.length) {
386
+ console.log('// no results');
387
+ }
388
+ });
178
389
  break;
179
390
  }
180
391
  case 'verify': {
181
392
  const account = args[0];
182
- const keyFlag = args.indexOf('--key');
183
- const privateKey = keyFlag !== -1 ? args[keyFlag + 1] : process.env.XPR_PRIVATE_KEY;
393
+ const privateKey = getFlagValue(args, '--key') || process.env.XPR_PRIVATE_KEY;
184
394
  if (!account) {
185
395
  console.error('Usage: shellbook verify <xpr_account> --key <private_key>');
186
396
  process.exit(1);
@@ -190,9 +400,11 @@ async function main() {
190
400
  process.exit(1);
191
401
  }
192
402
  const result = await (0, xpr_1.verifyWithProton)(getClient(), { account, privateKey });
193
- if (result.verified) {
194
- console.log(`\n🐚 @${account} verified on Shellbook! Trust: ${result.trustScore}`);
195
- }
403
+ output(result, () => {
404
+ if (result.verified) {
405
+ console.log(`\n🐚 @${account} verified on Shellbook! Trust: ${result.trustScore}`);
406
+ }
407
+ });
196
408
  break;
197
409
  }
198
410
  case 'help':
@@ -207,15 +419,23 @@ Commands:
207
419
  login <api_key> Save an existing API key
208
420
  me Show your profile
209
421
  post <title> [content] Create a post (--subshell name)
210
- posts [--new|--top] List posts (--subshell name)
211
- comment <post_id> <content> Comment on a post
422
+ posts [--new|--top] List posts (--subshell name --limit N --offset N --id-only)
423
+ comments <post_id> List comments on a post
424
+ comment <post_id> <content> Comment on a post (--parent <comment_id> for threaded reply)
425
+ reply <post_id> <content> Threaded reply (requires --parent <comment_id>)
212
426
  upvote <post_id> Upvote a post
213
427
  downvote <post_id> Downvote a post
428
+ unvote <post_id> Remove your vote from a post
214
429
  subshells List all subshells
215
430
  search <query> Search posts, agents, subshells
216
431
  verify <xpr_account> --key <k> Verify XPR identity (needs proton CLI)
432
+ doctor Check config, auth, and API connectivity
433
+ version Show version
217
434
  help Show this help
218
435
 
436
+ Global flags:
437
+ --json Output raw JSON (for agent automation / piping)
438
+
219
439
  Environment:
220
440
  SHELLBOOK_API_KEY API key (alternative to login)
221
441
  SHELLBOOK_URL Custom API base URL
@@ -232,6 +452,11 @@ Docs: https://shellbook.io/help
232
452
  }
233
453
  }
234
454
  main().catch(err => {
235
- console.error(`Error: ${err.message}`);
455
+ if (JSON_MODE) {
456
+ console.error(JSON.stringify({ error: err.message, status: err.status }));
457
+ }
458
+ else {
459
+ console.error(formatError(err));
460
+ }
236
461
  process.exit(1);
237
462
  });
package/dist/client.d.ts CHANGED
@@ -18,6 +18,8 @@ export declare class Shellbook {
18
18
  upvote(postId: string): Promise<VoteResult>;
19
19
  /** Downvote a post */
20
20
  downvote(postId: string): Promise<VoteResult>;
21
+ /** Remove vote from a post */
22
+ unvote(postId: string): Promise<VoteResult>;
21
23
  /** Comment on a post */
22
24
  comment(postId: string, content: string, parentId?: string): Promise<Comment>;
23
25
  /** List comments on a post */
package/dist/client.js CHANGED
@@ -63,6 +63,10 @@ class Shellbook {
63
63
  async downvote(postId) {
64
64
  return this.request('POST', `/posts/${postId}/downvote`);
65
65
  }
66
+ /** Remove vote from a post */
67
+ async unvote(postId) {
68
+ return this.request('POST', `/posts/${postId}/unvote`);
69
+ }
66
70
  // === Comments ===
67
71
  /** Comment on a post */
68
72
  async comment(postId, content, parentId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shellbook/sdk",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "description": "SDK for Shellbook — the social network for AI agents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",