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,78 @@
1
+ const api = require('../lib/api');
2
+ const output = require('../lib/output');
3
+
4
+ module.exports = function (program) {
5
+ const cmd = program.command('tasks').description('Task queue management');
6
+
7
+ cmd
8
+ .command('list')
9
+ .description('Poll for pending tasks')
10
+ .option('-s, --status <status>', 'Filter: pending, in_progress, completed, failed', 'pending')
11
+ .option('-t, --type <type>', 'Filter by task type')
12
+ .option('-l, --limit <n>', 'Max tasks (max 20)', '10')
13
+ .action(async (opts) => {
14
+ try {
15
+ const data = await api.get('/agents/tasks', {
16
+ query: {
17
+ status: opts.status,
18
+ type: opts.type,
19
+ limit: opts.limit,
20
+ },
21
+ });
22
+
23
+ if (output.isJsonMode()) {
24
+ output.json(data);
25
+ return;
26
+ }
27
+
28
+ output.table(
29
+ ['ID', 'Type', 'Status', 'Created', 'Preview'],
30
+ (data.tasks || []).map(t => [
31
+ t.id,
32
+ t.type || '—',
33
+ t.status,
34
+ t.createdAt ? new Date(t.createdAt).toLocaleString() : '—',
35
+ t.data?.castText?.slice(0, 50) || t.data?.authorUsername || '—',
36
+ ])
37
+ );
38
+
39
+ if (data.hasMore) {
40
+ console.log(`\nShowing ${data.tasks?.length}/${data.totalCount} tasks`);
41
+ }
42
+ } catch (err) {
43
+ output.error(err.message);
44
+ process.exit(1);
45
+ }
46
+ });
47
+
48
+ cmd
49
+ .command('update <taskId> <status>')
50
+ .description('Update task status (in_progress, completed, failed)')
51
+ .option('--action <action>', 'Action taken (for completed/failed)')
52
+ .option('--details <details>', 'Result details')
53
+ .option('--error <error>', 'Error message (for failed)')
54
+ .action(async (taskId, status, opts) => {
55
+ try {
56
+ const validStatuses = ['in_progress', 'completed', 'failed'];
57
+ if (!validStatuses.includes(status)) {
58
+ output.error(`Invalid status. Choose: ${validStatuses.join(', ')}`);
59
+ process.exit(1);
60
+ }
61
+
62
+ const body = { taskId, status };
63
+ if (opts.action || opts.details || opts.error) {
64
+ body.result = {};
65
+ if (opts.action) body.result.action = opts.action;
66
+ if (opts.details) body.result.details = opts.details;
67
+ if (opts.error) body.result.error = opts.error;
68
+ }
69
+
70
+ const data = await api.put('/agents/tasks', body);
71
+ output.success(`Task ${taskId} → ${status}`);
72
+ if (output.isJsonMode()) output.json(data);
73
+ } catch (err) {
74
+ output.error(err.message);
75
+ process.exit(1);
76
+ }
77
+ });
78
+ };
package/lib/api.js ADDED
@@ -0,0 +1,58 @@
1
+ const config = require('./config');
2
+
3
+ async function request(method, path, { body, query, headers: extraHeaders, auth = true } = {}) {
4
+ const cfg = config.load();
5
+ const baseUrl = process.env.NETLIB_BASE_URL || cfg.baseUrl;
6
+ const apiKey = process.env.NETLIB_API_KEY || cfg.apiKey;
7
+
8
+ // Concatenate baseUrl + path directly (URL constructor ignores base path with absolute paths)
9
+ const fullUrl = baseUrl.replace(/\/$/, '') + (path.startsWith('/') ? path : `/${path}`);
10
+ const url = new URL(fullUrl);
11
+ if (query) {
12
+ for (const [k, v] of Object.entries(query)) {
13
+ if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
14
+ }
15
+ }
16
+
17
+ const headers = { ...extraHeaders };
18
+ if (auth && apiKey) {
19
+ headers['Authorization'] = `Bearer ${apiKey}`;
20
+ }
21
+
22
+ const fetchOpts = { method, headers };
23
+
24
+ if (body && typeof body === 'object' && typeof body.append === 'function') {
25
+ // FormData — let fetch set Content-Type with boundary
26
+ fetchOpts.body = body;
27
+ } else if (body) {
28
+ headers['Content-Type'] = 'application/json';
29
+ fetchOpts.body = JSON.stringify(body);
30
+ }
31
+
32
+ const res = await fetch(url.toString(), fetchOpts);
33
+ const text = await res.text();
34
+
35
+ let data;
36
+ try {
37
+ data = JSON.parse(text);
38
+ } catch {
39
+ data = { raw: text };
40
+ }
41
+
42
+ if (!res.ok) {
43
+ const msg = data?.error || data?.message || `HTTP ${res.status}`;
44
+ const err = new Error(msg);
45
+ err.status = res.status;
46
+ err.data = data;
47
+ throw err;
48
+ }
49
+
50
+ return data;
51
+ }
52
+
53
+ const get = (path, opts) => request('GET', path, { ...opts });
54
+ const post = (path, body, opts) => request('POST', path, { body, ...opts });
55
+ const put = (path, body, opts) => request('PUT', path, { body, ...opts });
56
+ const del = (path, body, opts) => request('DELETE', path, { body, ...opts });
57
+
58
+ module.exports = { request, get, post, put, del };
package/lib/config.js ADDED
@@ -0,0 +1,39 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ const CONFIG_DIR = path.join(os.homedir(), '.config', 'netlibrary');
6
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
7
+
8
+ const DEFAULTS = {
9
+ baseUrl: 'https://miniapp-generator-fid-282520-251210015136529.neynar.app/api/v1',
10
+ rpcUrl: 'https://base-mainnet.public.blastapi.io',
11
+ };
12
+
13
+ const VALID_KEYS = ['apiKey', 'baseUrl', 'wallet', 'rpcUrl', 'adminKey'];
14
+
15
+ function load() {
16
+ try {
17
+ const raw = fs.readFileSync(CONFIG_FILE, 'utf8');
18
+ return { ...DEFAULTS, ...JSON.parse(raw) };
19
+ } catch {
20
+ return { ...DEFAULTS };
21
+ }
22
+ }
23
+
24
+ function save(config) {
25
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
26
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n');
27
+ }
28
+
29
+ function get(key) {
30
+ return load()[key];
31
+ }
32
+
33
+ function set(key, value) {
34
+ const config = load();
35
+ config[key] = value;
36
+ save(config);
37
+ }
38
+
39
+ module.exports = { load, save, get, set, CONFIG_FILE, VALID_KEYS };
package/lib/output.js ADDED
@@ -0,0 +1,66 @@
1
+ const chalk = require('chalk');
2
+ const Table = require('cli-table3');
3
+
4
+ let jsonMode = false;
5
+
6
+ function setJsonMode(val) { jsonMode = !!val; }
7
+ function isJsonMode() { return jsonMode; }
8
+
9
+ function json(data) {
10
+ console.log(JSON.stringify(data, null, 2));
11
+ }
12
+
13
+ function success(msg) {
14
+ if (jsonMode) return json({ success: true, message: msg });
15
+ console.log(chalk.green('✓'), msg);
16
+ }
17
+
18
+ function error(msg) {
19
+ if (jsonMode) {
20
+ console.error(JSON.stringify({ error: msg }));
21
+ } else {
22
+ console.error(chalk.red('✗'), msg);
23
+ }
24
+ }
25
+
26
+ function warn(msg) {
27
+ if (!jsonMode) console.log(chalk.yellow('!'), msg);
28
+ }
29
+
30
+ function table(headers, rows, opts = {}) {
31
+ if (jsonMode) {
32
+ return json(rows.map(r =>
33
+ headers.reduce((o, h, i) => ({ ...o, [h]: r[i] }), {})
34
+ ));
35
+ }
36
+ if (rows.length === 0) {
37
+ console.log(chalk.dim('No results.'));
38
+ return;
39
+ }
40
+ const t = new Table({
41
+ head: headers.map(h => chalk.cyan(h)),
42
+ style: { head: [], border: [] },
43
+ wordWrap: true,
44
+ ...opts,
45
+ });
46
+ rows.forEach(r => t.push(r));
47
+ console.log(t.toString());
48
+ }
49
+
50
+ function item(data, fields) {
51
+ if (jsonMode) return json(data);
52
+ const t = new Table({ style: { head: [], border: [] } });
53
+ fields.forEach(([label, key, transform]) => {
54
+ const val = typeof key === 'function' ? key(data) : data[key];
55
+ const display = transform ? transform(val) : (val ?? chalk.dim('—'));
56
+ t.push({ [chalk.cyan(label)]: String(display) });
57
+ });
58
+ console.log(t.toString());
59
+ }
60
+
61
+ function pagination(p) {
62
+ if (jsonMode || !p) return;
63
+ console.log(chalk.dim(`\nPage ${p.page}/${p.totalPages || '?'} (${p.total} total)`));
64
+ }
65
+
66
+ module.exports = { setJsonMode, isJsonMode, json, success, error, warn, table, item, pagination };
package/lib/payment.js ADDED
@@ -0,0 +1,114 @@
1
+ const { execSync } = require('child_process');
2
+ const readline = require('readline');
3
+ const chalk = require('chalk');
4
+ const config = require('./config');
5
+ const output = require('./output');
6
+
7
+ const USDC_CONTRACT = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
8
+ const TREASURY = '0xAcAD71e697Ef3bb148093b2DD2fCf0845e957627';
9
+
10
+ function usdcAmount(dollars) {
11
+ return (dollars * 1_000_000).toString();
12
+ }
13
+
14
+ function hasCast() {
15
+ try {
16
+ execSync('which cast', { encoding: 'utf8', stdio: 'pipe' });
17
+ return true;
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+
23
+ async function confirm(message) {
24
+ if (output.isJsonMode()) return true;
25
+ const rl = readline.createInterface({
26
+ input: process.stdin,
27
+ output: process.stderr,
28
+ });
29
+ return new Promise((resolve) => {
30
+ rl.question(`${message} (y/N) `, (answer) => {
31
+ rl.close();
32
+ resolve(answer.toLowerCase() === 'y');
33
+ });
34
+ });
35
+ }
36
+
37
+ /**
38
+ * Handle USDC payment. Returns txHash.
39
+ *
40
+ * If --tx-hash is provided, use that directly.
41
+ * If Foundry's `cast` is available, send via cast.
42
+ * Otherwise, show manual payment instructions.
43
+ */
44
+ async function handlePayment(amountUsd, opts = {}) {
45
+ // If user already provided a tx hash, just return it
46
+ if (opts.txHash) return opts.txHash;
47
+
48
+ const cfg = config.load();
49
+ const wallet = opts.wallet || process.env.NETLIB_WALLET || cfg.wallet;
50
+ const rpcUrl = opts.rpcUrl || process.env.BASE_RPC_URL || cfg.rpcUrl;
51
+
52
+ if (!hasCast()) {
53
+ // No Foundry — show manual instructions
54
+ console.log('');
55
+ console.log(chalk.yellow(`Send $${amountUsd} USDC to the Net Library treasury on Base:`));
56
+ console.log('');
57
+ console.log(` Treasury: ${chalk.cyan(TREASURY)}`);
58
+ console.log(` USDC: ${chalk.cyan(USDC_CONTRACT)}`);
59
+ console.log(` Amount: ${chalk.green(`${amountUsd} USDC`)} (${usdcAmount(amountUsd)} raw)`);
60
+ console.log(` Chain: Base (8453)`);
61
+ console.log('');
62
+ console.log(`After sending, re-run this command with ${chalk.cyan('--tx-hash <hash>')}`);
63
+ process.exit(0);
64
+ }
65
+
66
+ if (!wallet) {
67
+ throw new Error('No wallet configured. Run: netlibrary config set wallet <address>');
68
+ }
69
+
70
+ const ok = await confirm(
71
+ `This will send $${amountUsd} USDC from ${wallet} to the Net Library treasury. Proceed?`
72
+ );
73
+ if (!ok) {
74
+ console.log('Cancelled.');
75
+ process.exit(0);
76
+ }
77
+
78
+ const amount = usdcAmount(amountUsd);
79
+ const cmd = [
80
+ 'cast', 'send', USDC_CONTRACT,
81
+ '"transfer(address,uint256)"',
82
+ TREASURY, amount,
83
+ '--rpc-url', rpcUrl,
84
+ '--json',
85
+ ];
86
+
87
+ const pk = opts.privateKey || process.env.PRIVATE_KEY;
88
+ if (pk) {
89
+ cmd.push('--private-key', pk);
90
+ } else {
91
+ cmd.push('--from', wallet);
92
+ }
93
+
94
+ if (!output.isJsonMode()) {
95
+ console.log(chalk.dim(`Sending $${amountUsd} USDC to treasury...`));
96
+ }
97
+
98
+ try {
99
+ const result = execSync(cmd.join(' '), {
100
+ encoding: 'utf8',
101
+ timeout: 120000,
102
+ });
103
+ const parsed = JSON.parse(result);
104
+ const txHash = parsed.transactionHash;
105
+ if (!output.isJsonMode()) {
106
+ console.log(chalk.green('✓'), `Payment sent: ${txHash}`);
107
+ }
108
+ return txHash;
109
+ } catch (err) {
110
+ throw new Error(`Payment failed: ${err.message}`);
111
+ }
112
+ }
113
+
114
+ module.exports = { handlePayment, USDC_CONTRACT, TREASURY, usdcAmount, confirm };
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "netlibrary-cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI for Net Library — the decentralized digital library on Base",
5
+ "bin": {
6
+ "netlibrary": "bin/netlibrary.js"
7
+ },
8
+ "keywords": [
9
+ "net-library",
10
+ "netlibrary",
11
+ "web3",
12
+ "base",
13
+ "decentralized",
14
+ "library",
15
+ "cli",
16
+ "onchain"
17
+ ],
18
+ "author": "Net Library",
19
+ "license": "MIT",
20
+ "engines": {
21
+ "node": ">=18.0.0"
22
+ },
23
+ "dependencies": {
24
+ "chalk": "^4.1.2",
25
+ "cli-table3": "^0.6.5",
26
+ "commander": "^12.0.0"
27
+ }
28
+ }