@sage-protocol/cli 0.8.0 → 0.8.3
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.
Potentially problematic release.
This version of @sage-protocol/cli might be problematic. Click here for more details.
- package/README.md +12 -11
- package/dist/cli/commands/boost.js +339 -62
- package/dist/cli/commands/bounty.js +28 -4
- package/dist/cli/commands/config.js +10 -1
- package/dist/cli/commands/contributor.js +16 -6
- package/dist/cli/commands/dao.js +1 -1
- package/dist/cli/commands/discover.js +3 -3
- package/dist/cli/commands/governance.js +141 -58
- package/dist/cli/commands/install.js +178 -36
- package/dist/cli/commands/ipfs.js +12 -2
- package/dist/cli/commands/library.js +277 -268
- package/dist/cli/commands/members.js +132 -18
- package/dist/cli/commands/multiplier.js +101 -13
- package/dist/cli/commands/nft.js +16 -3
- package/dist/cli/commands/personal.js +69 -2
- package/dist/cli/commands/prompt.js +1 -1
- package/dist/cli/commands/proposals.js +153 -3
- package/dist/cli/commands/stake-status.js +130 -56
- package/dist/cli/commands/sxxx.js +37 -4
- package/dist/cli/commands/wallet.js +5 -10
- package/dist/cli/contracts/index.js +2 -1
- package/dist/cli/index.js +5 -0
- package/dist/cli/privy-auth-wallet-manager.js +3 -2
- package/dist/cli/services/config/chain-defaults.js +1 -1
- package/dist/cli/services/config/manager.js +3 -0
- package/dist/cli/services/config/schema.js +1 -0
- package/dist/cli/services/ipfs/onboarding.js +11 -0
- package/dist/cli/utils/aliases.js +62 -3
- package/dist/cli/utils/cli-ui.js +1 -1
- package/dist/cli/utils/provider.js +7 -3
- package/dist/cli/wallet-manager.js +7 -12
- package/dist/prompts/e2e-test-prompt.md +22 -0
- package/dist/prompts/skills/build-web3/plugin.json +11 -0
- package/package.json +1 -1
|
@@ -18,6 +18,92 @@ const { handleCLIError } = require('../utils/error-handler');
|
|
|
18
18
|
const { withSpinner } = require('../utils/progress');
|
|
19
19
|
const { readWorkspace, addDependency, initWorkspace } = require('../services/prompts/workspace');
|
|
20
20
|
|
|
21
|
+
function uniqStrings(list) {
|
|
22
|
+
const seen = new Set();
|
|
23
|
+
const out = [];
|
|
24
|
+
for (const item of list || []) {
|
|
25
|
+
const value = String(item || '').trim();
|
|
26
|
+
if (!value || seen.has(value)) continue;
|
|
27
|
+
seen.add(value);
|
|
28
|
+
out.push(value);
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function buildGatewayUrl(base, cid) {
|
|
34
|
+
const trimmed = String(base || '').trim().replace(/\/$/, '');
|
|
35
|
+
if (!trimmed) return '';
|
|
36
|
+
if (trimmed.endsWith('/ipfs')) return `${trimmed}/${cid}`;
|
|
37
|
+
if (trimmed.includes('/ipfs/')) return `${trimmed}/${cid}`;
|
|
38
|
+
return `${trimmed}/ipfs/${cid}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildGatewayList(preferred) {
|
|
42
|
+
const envList = process.env.SAGE_IPFS_GATEWAYS
|
|
43
|
+
? process.env.SAGE_IPFS_GATEWAYS.split(',').map((v) => v.trim())
|
|
44
|
+
: [];
|
|
45
|
+
const defaults = [
|
|
46
|
+
preferred,
|
|
47
|
+
'https://dweb.link/ipfs/',
|
|
48
|
+
'https://nftstorage.link/ipfs/',
|
|
49
|
+
'https://ipfs.io/ipfs/',
|
|
50
|
+
'https://gateway.pinata.cloud/ipfs/',
|
|
51
|
+
];
|
|
52
|
+
return uniqStrings([...envList, ...defaults]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseIpfsBody(body) {
|
|
56
|
+
if (body && typeof body.content === 'string') return { raw: body.content };
|
|
57
|
+
if (body && body.library) return { json: body };
|
|
58
|
+
if (typeof body === 'string') return { raw: body };
|
|
59
|
+
if (body && typeof body === 'object') return { json: body };
|
|
60
|
+
return {};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function fetchIpfsContent({ cid, workerClient, gateways, logger }) {
|
|
64
|
+
if (workerClient) {
|
|
65
|
+
try {
|
|
66
|
+
const { res, body } = await workerClient._fetchJson(`/ipfs/content/${cid}`);
|
|
67
|
+
const parsed = parseIpfsBody(body);
|
|
68
|
+
if (res?.ok && (parsed.raw || parsed.json)) {
|
|
69
|
+
return { source: 'worker', ...parsed };
|
|
70
|
+
}
|
|
71
|
+
} catch (err) {
|
|
72
|
+
logger?.debug?.('install_worker_fetch_failed', { cid, err: err.message || String(err) });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const axios = require('axios');
|
|
77
|
+
let lastError;
|
|
78
|
+
for (const gateway of gateways || []) {
|
|
79
|
+
const url = buildGatewayUrl(gateway, cid);
|
|
80
|
+
if (!url) continue;
|
|
81
|
+
try {
|
|
82
|
+
const response = await axios.get(url, {
|
|
83
|
+
timeout: 12000,
|
|
84
|
+
maxRedirects: 5,
|
|
85
|
+
validateStatus: (status) => status < 400,
|
|
86
|
+
});
|
|
87
|
+
const parsed = parseIpfsBody(response.data);
|
|
88
|
+
if (parsed.raw || parsed.json) {
|
|
89
|
+
return { source: 'gateway', url, ...parsed };
|
|
90
|
+
}
|
|
91
|
+
if (typeof response.data === 'string') {
|
|
92
|
+
return { source: 'gateway', url, raw: response.data };
|
|
93
|
+
}
|
|
94
|
+
// If object, treat as json
|
|
95
|
+
if (response.data && typeof response.data === 'object') {
|
|
96
|
+
return { source: 'gateway', url, json: response.data };
|
|
97
|
+
}
|
|
98
|
+
} catch (err) {
|
|
99
|
+
logger?.debug?.('install_gateway_fetch_failed', { cid, gateway: url, err: err.message || String(err) });
|
|
100
|
+
lastError = err;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const message = lastError ? lastError.message || String(lastError) : `Failed to download content from ${cid}`;
|
|
104
|
+
throw new Error(message);
|
|
105
|
+
}
|
|
106
|
+
|
|
21
107
|
/**
|
|
22
108
|
* Detect source type from input string
|
|
23
109
|
* @param {string} source - Input source string
|
|
@@ -161,6 +247,7 @@ async function installFromDao(daoAddress, projectDir, opts) {
|
|
|
161
247
|
const { resolveRegistryAddress } = require('../utils/address-resolution');
|
|
162
248
|
const { WorkerClient } = require('../services/ipfs/worker-client');
|
|
163
249
|
const { DEFAULT_WORKER_BASE } = require('../services/ipfs/onboarding');
|
|
250
|
+
const IPFSManager = require('../ipfs-manager');
|
|
164
251
|
const config = require('../config');
|
|
165
252
|
|
|
166
253
|
const provider = new ethers.JsonRpcProvider(
|
|
@@ -196,17 +283,28 @@ async function installFromDao(daoAddress, projectDir, opts) {
|
|
|
196
283
|
process.env.SAGE_IPFS_WORKER_URL ||
|
|
197
284
|
DEFAULT_WORKER_BASE;
|
|
198
285
|
|
|
286
|
+
const gatewayConfig = config.readIpfsConfig?.().gateway || process.env.SAGE_IPFS_GATEWAY || '';
|
|
287
|
+
const gatewayList = buildGatewayList(gatewayConfig);
|
|
288
|
+
|
|
199
289
|
const client = new WorkerClient({ baseUrl });
|
|
200
|
-
const
|
|
290
|
+
const ipfs = new IPFSManager({ gateway: gatewayConfig });
|
|
291
|
+
await ipfs.initialize();
|
|
201
292
|
|
|
202
|
-
// Worker returns wrapped content - parse if needed
|
|
203
293
|
let manifest;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
294
|
+
try {
|
|
295
|
+
const result = await fetchIpfsContent({
|
|
296
|
+
cid: manifestCID,
|
|
297
|
+
workerClient: client,
|
|
298
|
+
gateways: gatewayList,
|
|
299
|
+
logger: ui,
|
|
300
|
+
});
|
|
301
|
+
if (result.json) {
|
|
302
|
+
manifest = result.json;
|
|
303
|
+
} else if (result.raw) {
|
|
304
|
+
manifest = JSON.parse(result.raw);
|
|
305
|
+
}
|
|
306
|
+
} catch (err) {
|
|
307
|
+
throw new Error(`Failed to download manifest from ${manifestCID}: ${err.message}`);
|
|
210
308
|
}
|
|
211
309
|
|
|
212
310
|
const libraryName = manifest.library?.name || 'library';
|
|
@@ -234,15 +332,26 @@ async function installFromDao(daoAddress, projectDir, opts) {
|
|
|
234
332
|
if (!prompt.cid) continue;
|
|
235
333
|
|
|
236
334
|
try {
|
|
237
|
-
const { body: contentBody } = await client._fetchJson(`/ipfs/content/${prompt.cid}`);
|
|
238
|
-
|
|
239
|
-
// Worker returns wrapped content
|
|
240
335
|
let content;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
336
|
+
try {
|
|
337
|
+
const result = await fetchIpfsContent({
|
|
338
|
+
cid: prompt.cid,
|
|
339
|
+
workerClient: client,
|
|
340
|
+
gateways: gatewayList,
|
|
341
|
+
logger: ui,
|
|
342
|
+
});
|
|
343
|
+
if (result.raw) {
|
|
344
|
+
content = result.raw;
|
|
345
|
+
} else if (result.json) {
|
|
346
|
+
content = JSON.stringify(result.json, null, 2);
|
|
347
|
+
}
|
|
348
|
+
} catch (err) {
|
|
349
|
+
if (verbose) ui.warn(`Worker fetch failed for ${prompt.cid}: ${err.message}. Trying gateway download...`);
|
|
350
|
+
const data = await ipfs.downloadJson(prompt.cid);
|
|
351
|
+
content = data?.content || JSON.stringify(data, null, 2);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (!content) {
|
|
246
355
|
throw new Error('Unexpected content format');
|
|
247
356
|
}
|
|
248
357
|
|
|
@@ -345,30 +454,45 @@ async function installFromCid(cid, projectDir, opts) {
|
|
|
345
454
|
const { verbose = false, name } = opts;
|
|
346
455
|
const { WorkerClient } = require('../services/ipfs/worker-client');
|
|
347
456
|
const { DEFAULT_WORKER_BASE } = require('../services/ipfs/onboarding');
|
|
457
|
+
const IPFSManager = require('../ipfs-manager');
|
|
348
458
|
const config = require('../config');
|
|
349
459
|
|
|
350
460
|
const baseUrl = config.readIpfsConfig?.().workerBaseUrl ||
|
|
351
461
|
process.env.SAGE_IPFS_WORKER_URL ||
|
|
352
462
|
DEFAULT_WORKER_BASE;
|
|
353
463
|
|
|
354
|
-
const
|
|
464
|
+
const gatewayConfig = config.readIpfsConfig?.().gateway || process.env.SAGE_IPFS_GATEWAY || '';
|
|
465
|
+
const gatewayList = buildGatewayList(gatewayConfig);
|
|
355
466
|
|
|
356
|
-
|
|
357
|
-
const
|
|
467
|
+
const client = new WorkerClient({ baseUrl });
|
|
468
|
+
const ipfs = new IPFSManager({ gateway: gatewayConfig });
|
|
469
|
+
await ipfs.initialize();
|
|
358
470
|
|
|
359
|
-
//
|
|
471
|
+
// Try to download content (worker first, then gateways)
|
|
360
472
|
let manifest;
|
|
361
473
|
let rawContent;
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
474
|
+
try {
|
|
475
|
+
const result = await fetchIpfsContent({
|
|
476
|
+
cid,
|
|
477
|
+
workerClient: client,
|
|
478
|
+
gateways: gatewayList,
|
|
479
|
+
logger: ui,
|
|
480
|
+
});
|
|
481
|
+
if (result.json) {
|
|
482
|
+
if (result.json.library) {
|
|
483
|
+
manifest = result.json;
|
|
484
|
+
} else {
|
|
485
|
+
rawContent = result.json.content || JSON.stringify(result.json, null, 2);
|
|
486
|
+
}
|
|
487
|
+
} else if (result.raw) {
|
|
488
|
+
try {
|
|
489
|
+
manifest = JSON.parse(result.raw);
|
|
490
|
+
} catch (_) {
|
|
491
|
+
rawContent = result.raw;
|
|
492
|
+
}
|
|
367
493
|
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
} else if (typeof body === 'string') {
|
|
371
|
-
rawContent = body;
|
|
494
|
+
} catch (err) {
|
|
495
|
+
throw new Error(`Failed to download content from ${cid}: ${err.message}`);
|
|
372
496
|
}
|
|
373
497
|
|
|
374
498
|
if (manifest && manifest.library) {
|
|
@@ -388,13 +512,26 @@ async function installFromCid(cid, projectDir, opts) {
|
|
|
388
512
|
for (const prompt of manifest.prompts || []) {
|
|
389
513
|
if (!prompt.cid) continue;
|
|
390
514
|
try {
|
|
391
|
-
const { body: contentBody } = await client._fetchJson(`/ipfs/content/${prompt.cid}`);
|
|
392
515
|
let content;
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
516
|
+
try {
|
|
517
|
+
const result = await fetchIpfsContent({
|
|
518
|
+
cid: prompt.cid,
|
|
519
|
+
workerClient: client,
|
|
520
|
+
gateways: gatewayList,
|
|
521
|
+
logger: ui,
|
|
522
|
+
});
|
|
523
|
+
if (result.raw) {
|
|
524
|
+
content = result.raw;
|
|
525
|
+
} else if (result.json) {
|
|
526
|
+
content = JSON.stringify(result.json, null, 2);
|
|
527
|
+
}
|
|
528
|
+
} catch (err) {
|
|
529
|
+
if (verbose) ui.warn(`Worker fetch failed for ${prompt.cid}: ${err.message}. Trying gateway download...`);
|
|
530
|
+
const data = await ipfs.downloadJson(prompt.cid);
|
|
531
|
+
content = data?.content || JSON.stringify(data, null, 2);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (!content) {
|
|
398
535
|
throw new Error('Unexpected content format');
|
|
399
536
|
}
|
|
400
537
|
|
|
@@ -419,7 +556,7 @@ async function installFromCid(cid, projectDir, opts) {
|
|
|
419
556
|
};
|
|
420
557
|
} else {
|
|
421
558
|
// Single file - use raw content
|
|
422
|
-
const content = rawContent ||
|
|
559
|
+
const content = rawContent || '';
|
|
423
560
|
if (!content) {
|
|
424
561
|
throw new Error(`Failed to download content from ${cid}`);
|
|
425
562
|
}
|
|
@@ -458,6 +595,7 @@ function resolveAlias(alias) {
|
|
|
458
595
|
*/
|
|
459
596
|
function copyDirRecursive(src, dest, copied, verbose, label) {
|
|
460
597
|
for (const file of fs.readdirSync(src)) {
|
|
598
|
+
if (file === '.git') continue;
|
|
461
599
|
const srcPath = path.join(src, file);
|
|
462
600
|
const destPath = path.join(dest, file);
|
|
463
601
|
const stat = fs.statSync(srcPath);
|
|
@@ -582,6 +720,7 @@ function register(program) {
|
|
|
582
720
|
.option('--to <dir>', 'Installation directory (default: auto-detect)')
|
|
583
721
|
.option('--as-prompt', 'Force install as prompt (to prompts/)')
|
|
584
722
|
.option('--branch <name>', 'Git branch for GitHub sources', 'main')
|
|
723
|
+
.option('--subpath <path>', 'Subpath within a GitHub repo (overrides path in github:owner/repo/path)')
|
|
585
724
|
.option('--name <name>', 'Override the installed name')
|
|
586
725
|
.option('-y, --yes', 'Skip confirmations')
|
|
587
726
|
.option('-v, --verbose', 'Show detailed output')
|
|
@@ -592,6 +731,9 @@ function register(program) {
|
|
|
592
731
|
|
|
593
732
|
const projectDir = opts.to || process.cwd();
|
|
594
733
|
const sourceInfo = detectSourceType(source);
|
|
734
|
+
if (sourceInfo.type === 'github' && opts.subpath) {
|
|
735
|
+
sourceInfo.subpath = opts.subpath;
|
|
736
|
+
}
|
|
595
737
|
|
|
596
738
|
if (!opts.json) {
|
|
597
739
|
ui.info(`Installing from ${sourceInfo.type}: ${source}`);
|
|
@@ -369,9 +369,19 @@ function register(program) {
|
|
|
369
369
|
ui.configure({ verbose: opts.verbose, json: opts.json });
|
|
370
370
|
|
|
371
371
|
const baseUrl = opts.workerUrl || (config.readIpfsConfig && (config.readIpfsConfig().workerBaseUrl || '')) || DEFAULT_WORKER_BASE;
|
|
372
|
-
|
|
372
|
+
let workerAddress = opts.workerAddress;
|
|
373
|
+
if (!workerAddress) {
|
|
374
|
+
try {
|
|
375
|
+
const wallet = await getConnectedWallet();
|
|
376
|
+
workerAddress = wallet?.account || (wallet?.signer && await wallet.signer.getAddress()) || null;
|
|
377
|
+
} catch (_) { /* ignore */ }
|
|
378
|
+
}
|
|
379
|
+
if (!workerAddress) {
|
|
380
|
+
throw new Error('address required (set --worker-address or configure a wallet)');
|
|
381
|
+
}
|
|
382
|
+
const wc = new WorkerClient({ baseUrl, token: opts.workerToken, address: workerAddress });
|
|
373
383
|
const result = await wc.listPins({
|
|
374
|
-
address:
|
|
384
|
+
address: workerAddress,
|
|
375
385
|
status: opts.status,
|
|
376
386
|
limit: opts.limit,
|
|
377
387
|
});
|