netlibrary-cli 1.0.0

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.
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander');
4
+ const output = require('../lib/output');
5
+
6
+ program
7
+ .name('netlibrary')
8
+ .description('Net Library CLI — interact with the decentralized digital library on Base')
9
+ .version('1.0.0')
10
+ .option('--json', 'Output raw JSON (for programmatic/agent use)')
11
+ .option('--api-key <key>', 'Override API key')
12
+ .option('--base-url <url>', 'Override base URL')
13
+ .hook('preAction', (thisCommand) => {
14
+ const opts = thisCommand.optsWithGlobals();
15
+ if (opts.json) output.setJsonMode(true);
16
+ if (opts.apiKey) process.env.NETLIB_API_KEY = opts.apiKey;
17
+ if (opts.baseUrl) process.env.NETLIB_BASE_URL = opts.baseUrl;
18
+ });
19
+
20
+ require('../commands/config')(program);
21
+ require('../commands/library')(program);
22
+ require('../commands/search')(program);
23
+ require('../commands/stacks')(program);
24
+ require('../commands/member')(program);
25
+ require('../commands/agents')(program);
26
+ require('../commands/tasks')(program);
27
+ require('../commands/archive')(program);
28
+ require('../commands/stats')(program);
29
+ require('../commands/embeds')(program);
30
+ require('../commands/info')(program);
31
+ require('../commands/comments')(program);
32
+
33
+ program.parse();
@@ -0,0 +1,173 @@
1
+ const api = require('../lib/api');
2
+ const output = require('../lib/output');
3
+ const config = require('../lib/config');
4
+
5
+ module.exports = function (program) {
6
+ const cmd = program.command('agents').description('Agent management');
7
+
8
+ cmd
9
+ .command('me')
10
+ .description('View your agent profile and widgets')
11
+ .action(async () => {
12
+ try {
13
+ const data = await api.get('/agents/me');
14
+ if (output.isJsonMode()) {
15
+ output.json(data);
16
+ return;
17
+ }
18
+ const agent = data.agent || {};
19
+ const profile = agent.profile || {};
20
+ const membership = data.membership || {};
21
+ output.item({}, [
22
+ ['ID', () => agent.id],
23
+ ['Name', () => profile.name || agent.name],
24
+ ['Address', () => agent.address],
25
+ ['FID', () => agent.fid],
26
+ ['Permissions', () => (agent.permissions || []).join(', ')],
27
+ ['Created', () => agent.createdAt ? new Date(agent.createdAt).toLocaleString() : null],
28
+ ]);
29
+
30
+ if (membership.memberId) {
31
+ console.log('\nMembership:');
32
+ output.item(membership, [
33
+ ['Member ID', 'memberId'],
34
+ ['ENS', 'ensSubname'],
35
+ ['Since', 'memberSince', v => v ? new Date(v).toLocaleDateString() : null],
36
+ ]);
37
+ }
38
+
39
+ if (data.widgets) {
40
+ console.log('\nWidgets:');
41
+ const w = data.widgets;
42
+ if (w.libraryCard) console.log(` Library Card: ${w.libraryCard.url}`);
43
+ if (w.profile) console.log(` Profile: ${w.profile.url}`);
44
+ if (w.stacks && w.stacks.length > 0) {
45
+ w.stacks.forEach(s => console.log(` Stack: ${s.url} (${s.name})`));
46
+ }
47
+ }
48
+ } catch (err) {
49
+ output.error(err.message);
50
+ process.exit(1);
51
+ }
52
+ });
53
+
54
+ cmd
55
+ .command('register')
56
+ .description('Register a new agent (admin only)')
57
+ .requiredOption('--id <id>', 'Agent ID (lowercase, alphanumeric + hyphens)')
58
+ .requiredOption('--name <name>', 'Agent display name')
59
+ .option('--permissions <perms...>', 'Permissions', ['library:write', 'stacks:create', 'stacks:write'])
60
+ .option('--address <addr>', 'Wallet address')
61
+ .option('--description <desc>', 'Description')
62
+ .option('--fid <n>', 'Farcaster FID')
63
+ .option('--pfp-url <url>', 'Profile picture URL')
64
+ .option('--webhook-url <url>', 'Webhook URL')
65
+ .action(async (opts) => {
66
+ try {
67
+ const cfg = config.load();
68
+ const adminKey = process.env.NETLIB_ADMIN_KEY || cfg.adminKey;
69
+ if (!adminKey) {
70
+ output.error('Admin key required. Set with: netlibrary config set admin-key <key>');
71
+ process.exit(1);
72
+ }
73
+
74
+ const body = {
75
+ id: opts.id,
76
+ name: opts.name,
77
+ permissions: opts.permissions,
78
+ };
79
+ if (opts.address) body.address = opts.address;
80
+ if (opts.description) body.description = opts.description;
81
+ if (opts.fid) body.fid = parseInt(opts.fid);
82
+ if (opts.pfpUrl) body.pfpUrl = opts.pfpUrl;
83
+ if (opts.webhookUrl) body.webhookUrl = opts.webhookUrl;
84
+
85
+ const data = await api.post('/agents', body, {
86
+ headers: { 'Authorization': `Bearer ${adminKey}` },
87
+ auth: false,
88
+ });
89
+
90
+ output.success(`Agent "${opts.id}" registered!`);
91
+ if (data.agent?.apiKey) {
92
+ console.log(`\nAPI Key: ${data.agent.apiKey}`);
93
+ console.log('Save this key — it cannot be retrieved again!');
94
+ }
95
+ if (output.isJsonMode()) output.json(data);
96
+ } catch (err) {
97
+ output.error(err.message);
98
+ process.exit(1);
99
+ }
100
+ });
101
+
102
+ cmd
103
+ .command('list')
104
+ .description('List all agents (admin only)')
105
+ .action(async () => {
106
+ try {
107
+ const data = await api.get('/agents');
108
+ output.table(
109
+ ['ID', 'Name', 'Address', 'Permissions', 'Active'],
110
+ (data.agents || []).map(a => [
111
+ a.id,
112
+ a.name || '—',
113
+ a.address ? a.address.slice(0, 10) + '...' : '—',
114
+ (a.permissions || []).join(', '),
115
+ a.isActive ? 'Yes' : 'No',
116
+ ])
117
+ );
118
+ } catch (err) {
119
+ output.error(err.message);
120
+ process.exit(1);
121
+ }
122
+ });
123
+
124
+ cmd
125
+ .command('update')
126
+ .description('Update your agent profile')
127
+ .option('--name <name>', 'Display name')
128
+ .option('--description <desc>', 'Description')
129
+ .option('--address <addr>', 'Wallet address')
130
+ .option('--pfp-url <url>', 'Profile picture URL')
131
+ .option('--webhook-url <url>', 'Webhook URL')
132
+ .option('--fid <n>', 'Farcaster FID')
133
+ .option('--8004-token-id <n>', 'ERC-8004 token ID')
134
+ .option('--id <agentId>', 'Target agent ID (admin only)')
135
+ .action(async (opts) => {
136
+ try {
137
+ const body = {};
138
+ if (opts.id) body.id = opts.id;
139
+ if (opts.name) body.name = opts.name;
140
+ if (opts.description) body.description = opts.description;
141
+ if (opts.address) body.address = opts.address;
142
+ if (opts.pfpUrl) body.pfpUrl = opts.pfpUrl;
143
+ if (opts.webhookUrl) body.webhookUrl = opts.webhookUrl;
144
+ if (opts.fid) body.fid = parseInt(opts.fid);
145
+ if (opts['8004TokenId']) body.erc8004TokenId = parseInt(opts['8004TokenId']);
146
+
147
+ const data = await api.put('/agents', body);
148
+ output.success('Agent updated');
149
+ if (data.erc8004Verification) {
150
+ if (data.erc8004Verification.verified) {
151
+ output.success(`ERC-8004 verified! Token ID: ${data.erc8004Verification.tokenId}`);
152
+ }
153
+ }
154
+ if (output.isJsonMode()) output.json(data);
155
+ } catch (err) {
156
+ output.error(err.message);
157
+ process.exit(1);
158
+ }
159
+ });
160
+
161
+ cmd
162
+ .command('deactivate <agentId>')
163
+ .description('Deactivate an agent (admin only)')
164
+ .action(async (agentId) => {
165
+ try {
166
+ const data = await api.del('/agents', { id: agentId });
167
+ output.success(`Agent "${agentId}" deactivated`);
168
+ } catch (err) {
169
+ output.error(err.message);
170
+ process.exit(1);
171
+ }
172
+ });
173
+ };
@@ -0,0 +1,40 @@
1
+ const api = require('../lib/api');
2
+ const output = require('../lib/output');
3
+
4
+ module.exports = function (program) {
5
+ program
6
+ .command('archive [castHash]')
7
+ .description('Archive a Farcaster cast as a library item')
8
+ .option('--cast-url <url>', 'Warpcast URL (alternative to castHash)')
9
+ .option('--text <text>', 'Cast text content')
10
+ .option('--title <title>', 'Custom title')
11
+ .option('-c, --category <cat...>', 'Additional categories')
12
+ .option('--author-fid <n>', 'Author FID')
13
+ .option('--author-username <name>', 'Author username')
14
+ .option('--add-to-stack <stackId>', 'Add to stack after archiving')
15
+ .action(async (castHash, opts) => {
16
+ try {
17
+ if (!castHash && !opts.castUrl) {
18
+ output.error('Provide a cast hash or --cast-url');
19
+ process.exit(1);
20
+ }
21
+
22
+ const body = {};
23
+ if (castHash) body.castHash = castHash;
24
+ if (opts.castUrl) body.castUrl = opts.castUrl;
25
+ if (opts.text) body.text = opts.text;
26
+ if (opts.title) body.title = opts.title;
27
+ if (opts.category) body.categories = opts.category;
28
+ if (opts.authorFid) body.authorFid = parseInt(opts.authorFid);
29
+ if (opts.authorUsername) body.authorUsername = opts.authorUsername;
30
+ if (opts.addToStack) body.addToStack = opts.addToStack;
31
+
32
+ const data = await api.post('/archive', body);
33
+ output.success(`Archived: ${data.contentKey}`);
34
+ if (output.isJsonMode()) output.json(data);
35
+ } catch (err) {
36
+ output.error(err.message);
37
+ process.exit(1);
38
+ }
39
+ });
40
+ };
@@ -0,0 +1,49 @@
1
+ const api = require('../lib/api');
2
+ const output = require('../lib/output');
3
+ const chalk = require('chalk');
4
+
5
+ module.exports = function (program) {
6
+ program
7
+ .command('comments <contentKey>')
8
+ .description('View comments for a library item')
9
+ .option('--parent-id <id>', 'Get replies to a specific comment')
10
+ .action(async (contentKey, opts) => {
11
+ try {
12
+ const data = await api.get(`/comments/${encodeURIComponent(contentKey)}`, {
13
+ query: { parentId: opts.parentId },
14
+ auth: false,
15
+ });
16
+
17
+ if (output.isJsonMode()) {
18
+ output.json(data);
19
+ return;
20
+ }
21
+
22
+ if (data.item) {
23
+ console.log(`Comments for: ${chalk.cyan(data.item.title || contentKey)}\n`);
24
+ }
25
+
26
+ const comments = data.comments || [];
27
+ if (comments.length === 0) {
28
+ console.log(chalk.dim('No comments.'));
29
+ return;
30
+ }
31
+
32
+ comments.forEach(c => {
33
+ const author = c.author?.username || c.author?.address?.slice(0, 10) || 'unknown';
34
+ const date = c.createdAt ? new Date(c.createdAt).toLocaleDateString() : '';
35
+ const edited = c.isEdited ? chalk.dim(' (edited)') : '';
36
+ console.log(`${chalk.cyan(author)} ${chalk.dim(date)}${edited}`);
37
+ console.log(` ${c.text}`);
38
+ if (c.likes > 0) console.log(` ${chalk.dim(`♥ ${c.likes}`)}`);
39
+ if (c.replyCount > 0) console.log(` ${chalk.dim(`↳ ${c.replyCount} replies`)}`);
40
+ console.log('');
41
+ });
42
+
43
+ console.log(`${data.totalCount || comments.length} total comments`);
44
+ } catch (err) {
45
+ output.error(err.message);
46
+ process.exit(1);
47
+ }
48
+ });
49
+ };
@@ -0,0 +1,71 @@
1
+ const chalk = require('chalk');
2
+ const config = require('../lib/config');
3
+ const output = require('../lib/output');
4
+
5
+ module.exports = function (program) {
6
+ const cmd = program.command('config').description('Manage CLI configuration');
7
+
8
+ cmd
9
+ .command('set <key> <value>')
10
+ .description(`Set a config value. Keys: ${config.VALID_KEYS.join(', ')}`)
11
+ .action((key, value) => {
12
+ // Accept kebab-case and convert to camelCase
13
+ const keyMap = {
14
+ 'api-key': 'apiKey',
15
+ 'base-url': 'baseUrl',
16
+ 'rpc-url': 'rpcUrl',
17
+ 'admin-key': 'adminKey',
18
+ 'wallet': 'wallet',
19
+ };
20
+ const resolved = keyMap[key] || key;
21
+
22
+ if (!config.VALID_KEYS.includes(resolved)) {
23
+ output.error(`Invalid key "${key}". Valid keys: ${config.VALID_KEYS.join(', ')}`);
24
+ process.exit(1);
25
+ }
26
+ config.set(resolved, value);
27
+ output.success(`Set ${resolved} = ${key === 'api-key' || key === 'apiKey' ? value.slice(0, 8) + '...' : value}`);
28
+ });
29
+
30
+ cmd
31
+ .command('get <key>')
32
+ .description('Get a config value')
33
+ .action((key) => {
34
+ const keyMap = {
35
+ 'api-key': 'apiKey',
36
+ 'base-url': 'baseUrl',
37
+ 'rpc-url': 'rpcUrl',
38
+ 'admin-key': 'adminKey',
39
+ 'wallet': 'wallet',
40
+ };
41
+ const resolved = keyMap[key] || key;
42
+ const val = config.get(resolved);
43
+ if (output.isJsonMode()) {
44
+ output.json({ [resolved]: val || null });
45
+ } else {
46
+ console.log(val || chalk.dim('(not set)'));
47
+ }
48
+ });
49
+
50
+ cmd
51
+ .command('show')
52
+ .description('Show all config values')
53
+ .action(() => {
54
+ const cfg = config.load();
55
+ if (output.isJsonMode()) {
56
+ output.json(cfg);
57
+ return;
58
+ }
59
+ const masked = { ...cfg };
60
+ if (masked.apiKey) masked.apiKey = masked.apiKey.slice(0, 8) + '...';
61
+ if (masked.adminKey) masked.adminKey = masked.adminKey.slice(0, 8) + '...';
62
+ output.item(masked, [
63
+ ['API Key', 'apiKey'],
64
+ ['Base URL', 'baseUrl'],
65
+ ['Wallet', 'wallet'],
66
+ ['RPC URL', 'rpcUrl'],
67
+ ['Admin Key', 'adminKey'],
68
+ ]);
69
+ console.log(chalk.dim(`\nConfig file: ${config.CONFIG_FILE}`));
70
+ });
71
+ };
@@ -0,0 +1,120 @@
1
+ const api = require('../lib/api');
2
+ const output = require('../lib/output');
3
+
4
+ module.exports = function (program) {
5
+ const cmd = program.command('embeds').description('Embed data API');
6
+
7
+ cmd
8
+ .command('card <address>')
9
+ .description('Get library card data for a member')
10
+ .action(async (address) => {
11
+ try {
12
+ const data = await api.get(`/embeds/card/${address}`, { auth: false });
13
+ if (output.isJsonMode()) {
14
+ output.json(data);
15
+ return;
16
+ }
17
+ if (!data.member) {
18
+ console.log('No member found for this address.');
19
+ return;
20
+ }
21
+ const m = data.member || {};
22
+ output.item(data, [
23
+ ['Display Name', 'displayName'],
24
+ ['Member ID', () => m.memberId],
25
+ ['ENS', () => m.ensSubname],
26
+ ['Joined', () => m.joinedAt ? new Date(m.joinedAt).toLocaleDateString() : null],
27
+ ['Uploads', () => `${data.uploads?.count || 0} (${((data.uploads?.totalBytes || 0) / 1024 / 1024).toFixed(2)} MB)`],
28
+ ['Stacks', 'stackCount'],
29
+ ['Grids', 'gridCount'],
30
+ ['Rank', 'rank'],
31
+ ['Storage Pass', () => m.hasUnlimitedStoragePass ? 'Yes' : 'No'],
32
+ ['ERC-8004', () => m.erc8004Verified ? (m.erc8004TokenId ? `Verified (#${m.erc8004TokenId})` : 'Verified') : 'No'],
33
+ ]);
34
+ } catch (err) {
35
+ output.error(err.message);
36
+ process.exit(1);
37
+ }
38
+ });
39
+
40
+ cmd
41
+ .command('grid <gridId>')
42
+ .description('Get grid embed data')
43
+ .action(async (gridId) => {
44
+ try {
45
+ const data = await api.get(`/embeds/grid/${gridId}`, { auth: false });
46
+ if (output.isJsonMode()) {
47
+ output.json(data);
48
+ return;
49
+ }
50
+ const g = data.grid || {};
51
+ output.item(g, [
52
+ ['Name', 'name'],
53
+ ['ID', 'id'],
54
+ ['Owner', 'owner'],
55
+ ['Columns', 'columns'],
56
+ ['Created', 'createdAt', v => v ? new Date(v).toLocaleString() : null],
57
+ ]);
58
+
59
+ if (data.cells && data.cells.length > 0) {
60
+ console.log('\nCells:');
61
+ output.table(
62
+ ['Pos', 'Title', 'Type', 'Key'],
63
+ data.cells.map(c => [
64
+ c.position,
65
+ c.title || '—',
66
+ c.mediaType || '—',
67
+ c.contentKey,
68
+ ])
69
+ );
70
+ }
71
+ } catch (err) {
72
+ output.error(err.message);
73
+ process.exit(1);
74
+ }
75
+ });
76
+
77
+ cmd
78
+ .command('user <address>')
79
+ .description('Get user profile embed data')
80
+ .option('-p, --page <n>', 'Page number', '1')
81
+ .option('-l, --limit <n>', 'Items per page (max 50)', '20')
82
+ .action(async (address, opts) => {
83
+ try {
84
+ const data = await api.get(`/embeds/user/${address}`, {
85
+ query: { page: opts.page, limit: opts.limit },
86
+ auth: false,
87
+ });
88
+ if (output.isJsonMode()) {
89
+ output.json(data);
90
+ return;
91
+ }
92
+ const m = data.member || {};
93
+ output.item(data, [
94
+ ['Display Name', 'displayName'],
95
+ ['Address', 'address'],
96
+ ['Member ID', () => m.memberId],
97
+ ['ENS', () => m.ensSubname],
98
+ ['Uploads', 'uploadCount'],
99
+ ['Total Size', 'totalBytes', v => v ? `${(v / 1024 / 1024).toFixed(2)} MB` : null],
100
+ ]);
101
+
102
+ if (data.uploads && data.uploads.length > 0) {
103
+ console.log('\nUploads:');
104
+ output.table(
105
+ ['Title', 'Type', 'Added', 'Key'],
106
+ data.uploads.map(u => [
107
+ u.title || '—',
108
+ u.mediaType || '—',
109
+ u.addedAt ? new Date(u.addedAt).toLocaleDateString() : '—',
110
+ u.contentKey,
111
+ ])
112
+ );
113
+ output.pagination(data.pagination);
114
+ }
115
+ } catch (err) {
116
+ output.error(err.message);
117
+ process.exit(1);
118
+ }
119
+ });
120
+ };
@@ -0,0 +1,57 @@
1
+ const api = require('../lib/api');
2
+ const output = require('../lib/output');
3
+
4
+ module.exports = function (program) {
5
+ const cmd = program.command('info').description('API information');
6
+
7
+ cmd
8
+ .command('capabilities')
9
+ .description('View the API capabilities manifest')
10
+ .action(async () => {
11
+ try {
12
+ const data = await api.get('/capabilities', { auth: false });
13
+ if (output.isJsonMode()) {
14
+ output.json(data);
15
+ return;
16
+ }
17
+ output.item(data, [
18
+ ['Name', 'name'],
19
+ ['Version', 'version'],
20
+ ['URL', 'url'],
21
+ ['Chain', () => `${data.chain?.name} (${data.chain?.id})`],
22
+ ['CDN', 'cdn'],
23
+ ]);
24
+
25
+ if (data.pricing) {
26
+ console.log('\nPricing:');
27
+ output.table(
28
+ ['Item', 'Price'],
29
+ Object.entries(data.pricing)
30
+ .filter(([k]) => !['treasury', 'usdc', 'chain'].includes(k))
31
+ .map(([k, v]) => [k, v])
32
+ );
33
+ }
34
+
35
+ if (data.supportedMediaTypes) {
36
+ console.log(`\nMedia types: ${data.supportedMediaTypes.join(', ')}`);
37
+ }
38
+ } catch (err) {
39
+ output.error(err.message);
40
+ process.exit(1);
41
+ }
42
+ });
43
+
44
+ cmd
45
+ .command('skill')
46
+ .description('View the full agent skill document')
47
+ .action(async () => {
48
+ try {
49
+ const data = await api.get('/agent-skill', { auth: false });
50
+ // Agent skill is a large JSON doc — always output as JSON
51
+ output.json(data);
52
+ } catch (err) {
53
+ output.error(err.message);
54
+ process.exit(1);
55
+ }
56
+ });
57
+ };