@sage-protocol/cli 0.2.9 → 0.3.2
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/commands/config.js +28 -0
- package/dist/cli/commands/doctor.js +87 -8
- package/dist/cli/commands/gov-config.js +81 -0
- package/dist/cli/commands/governance.js +152 -72
- package/dist/cli/commands/library.js +9 -0
- package/dist/cli/commands/proposals.js +187 -17
- package/dist/cli/commands/skills.js +737 -0
- package/dist/cli/commands/subdao.js +96 -132
- package/dist/cli/config/playbooks.json +62 -0
- package/dist/cli/config.js +15 -0
- package/dist/cli/governance-manager.js +25 -4
- package/dist/cli/index.js +6 -7
- package/dist/cli/library-manager.js +79 -0
- package/dist/cli/mcp-server-stdio.js +1387 -166
- package/dist/cli/schemas/manifest.schema.json +55 -0
- package/dist/cli/services/doctor/fixers.js +134 -0
- package/dist/cli/services/governance/doctor.js +140 -0
- package/dist/cli/services/governance/playbooks.js +97 -0
- package/dist/cli/services/mcp/bulk-operations.js +272 -0
- package/dist/cli/services/mcp/dependency-analyzer.js +202 -0
- package/dist/cli/services/mcp/library-listing.js +2 -2
- package/dist/cli/services/mcp/local-prompt-collector.js +1 -0
- package/dist/cli/services/mcp/manifest-downloader.js +5 -3
- package/dist/cli/services/mcp/manifest-fetcher.js +17 -1
- package/dist/cli/services/mcp/manifest-workflows.js +127 -15
- package/dist/cli/services/mcp/quick-start.js +287 -0
- package/dist/cli/services/mcp/stdio-runner.js +30 -5
- package/dist/cli/services/mcp/template-manager.js +156 -0
- package/dist/cli/services/mcp/templates/default-templates.json +84 -0
- package/dist/cli/services/mcp/tool-args-validator.js +56 -0
- package/dist/cli/services/mcp/trending-formatter.js +1 -1
- package/dist/cli/services/mcp/unified-prompt-search.js +2 -2
- package/dist/cli/services/metaprompt/designer.js +12 -5
- package/dist/cli/services/skills/discovery.js +99 -0
- package/dist/cli/services/subdao/applier.js +229 -0
- package/dist/cli/services/subdao/planner.js +142 -0
- package/dist/cli/subdao-manager.js +14 -0
- package/dist/cli/utils/aliases.js +28 -6
- package/dist/cli/utils/contract-error-decoder.js +61 -0
- package/dist/cli/utils/suggestions.js +25 -13
- package/package.json +3 -2
- package/src/schemas/manifest.schema.json +55 -0
|
@@ -208,6 +208,74 @@ function register(program) {
|
|
|
208
208
|
.description('SubDAO management commands')
|
|
209
209
|
.addHelpText('afterAll', '\nGuided launch walkthrough: sage help workflow subdao-launch\n')
|
|
210
210
|
.addHelpText('after', `\nExamples:\n $ sage subdao save 0xAbc... --alias team-alpha\n $ sage subdao pick\n $ sage subdao use team-alpha\n $ sage subdao info team-alpha --json\n\nTemplates:\n 1. rapid-iteration - Fast community voting (testnet)\n 2. standard-secure - Balanced community governance\n 3. team - Safe multisig for small teams (was: operator)\n`)
|
|
211
|
+
.addCommand(
|
|
212
|
+
new Command('create-playbook')
|
|
213
|
+
.description('Create a new SubDAO using a governance playbook (Plan/Apply)')
|
|
214
|
+
.option('--playbook <id>', 'Playbook ID (creator, squad, community, community-long)')
|
|
215
|
+
.option('--name <name>', 'SubDAO name')
|
|
216
|
+
.option('--description <desc>', 'SubDAO description')
|
|
217
|
+
.option('--owners <addresses>', 'Safe owners (comma-separated)')
|
|
218
|
+
.option('--threshold <n>', 'Safe threshold')
|
|
219
|
+
.option('--min-stake <amount>', 'Minimum stake amount (SXXX)')
|
|
220
|
+
.option('--voting-period <duration>', 'Voting period (e.g., "3 days", "36 hours", "720 minutes")')
|
|
221
|
+
.option('--quorum-bps <bps>', 'Quorum basis points (e.g., 200 for 2%)')
|
|
222
|
+
.option('--proposal-threshold <amount>', 'Proposal threshold (wei or token units as string)')
|
|
223
|
+
.option('--block-time-sec <n>', 'Override block time (seconds per block); auto-detected when omitted')
|
|
224
|
+
.option('--wizard', 'Prompt for governance parameters interactively')
|
|
225
|
+
.option('--yes', 'Skip confirmation')
|
|
226
|
+
.option('--dry-run', 'Generate plan only (do not execute)')
|
|
227
|
+
.option('--apply <file>', 'Apply a previously generated plan file')
|
|
228
|
+
.action(async (opts) => {
|
|
229
|
+
try {
|
|
230
|
+
if (opts.apply) {
|
|
231
|
+
const { applyPlan } = require('../services/subdao/applier');
|
|
232
|
+
await applyPlan(opts.apply);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Optional wizard for governance parameters
|
|
237
|
+
if (opts.wizard) {
|
|
238
|
+
const inquirer = (require('inquirer').default || require('inquirer'));
|
|
239
|
+
const answers = await inquirer.prompt([
|
|
240
|
+
{ type: 'input', name: 'votingPeriod', message: 'Voting period (e.g., 7 days)', default: opts.votingPeriod || '' },
|
|
241
|
+
{ type: 'input', name: 'quorumBps', message: 'Quorum (bps, e.g., 200 for 2%)', default: opts.quorumBps || '' },
|
|
242
|
+
{ type: 'input', name: 'proposalThreshold', message: 'Proposal threshold (string/wei)', default: opts.proposalThreshold || '' },
|
|
243
|
+
{ type: 'input', name: 'blockTimeSec', message: 'Seconds per block (blank = auto-detect)', default: opts.blockTimeSec || '' },
|
|
244
|
+
]);
|
|
245
|
+
opts.votingPeriod = answers.votingPeriod || opts.votingPeriod;
|
|
246
|
+
opts.quorumBps = answers.quorumBps || opts.quorumBps;
|
|
247
|
+
opts.proposalThreshold = answers.proposalThreshold || opts.proposalThreshold;
|
|
248
|
+
opts.blockTimeSec = answers.blockTimeSec || opts.blockTimeSec;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const { planSubDAO } = require('../services/subdao/planner');
|
|
252
|
+
const plan = await planSubDAO(opts);
|
|
253
|
+
|
|
254
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
255
|
+
|
|
256
|
+
if (opts.dryRun) {
|
|
257
|
+
const fs = require('fs');
|
|
258
|
+
const filename = `subdao-plan-${Date.now()}.json`;
|
|
259
|
+
fs.writeFileSync(filename, JSON.stringify(plan, null, 2));
|
|
260
|
+
console.log(`\n✅ Plan saved to ${filename}`);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const inquirer = (require('inquirer').default || require('inquirer'));
|
|
265
|
+
if (!opts.yes) {
|
|
266
|
+
const { confirm } = await inquirer.prompt([{ type: 'confirm', name: 'confirm', message: 'Apply this plan?', default: true }]);
|
|
267
|
+
if (!confirm) return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const { applyPlan } = require('../services/subdao/applier');
|
|
271
|
+
await applyPlan(plan);
|
|
272
|
+
|
|
273
|
+
} catch (e) {
|
|
274
|
+
console.error('❌ Create failed:', e.message);
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
)
|
|
211
279
|
.addCommand(
|
|
212
280
|
new Command('create-wizard')
|
|
213
281
|
.description('Interactive SubDAO creation wizard (choose governance type and access model)')
|
|
@@ -556,140 +624,36 @@ function register(program) {
|
|
|
556
624
|
.option('--json', 'Output JSON', false)
|
|
557
625
|
.action(async (opts) => {
|
|
558
626
|
try {
|
|
559
|
-
const {
|
|
560
|
-
const {
|
|
627
|
+
const { diagnoseSubDAO } = require('../services/governance/doctor');
|
|
628
|
+
const { getSignerSession } = require('../utils/cli-session');
|
|
561
629
|
const { signer, provider } = await getSignerSession();
|
|
562
|
-
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
try {
|
|
569
|
-
const sd = new ethers.Contract(subdao, [
|
|
570
|
-
'function getGovernanceMode() view returns (uint8)',
|
|
571
|
-
'function promptRegistry() view returns (address)'
|
|
572
|
-
], provider);
|
|
573
|
-
out.mode = Number(await sd.getGovernanceMode());
|
|
574
|
-
out.registry = await sd.promptRegistry();
|
|
575
|
-
} catch(_){}
|
|
576
|
-
try { const tl = new ethers.Contract(timelock, ['function getMinDelay() view returns (uint256)','function PROPOSER_ROLE() view returns (bytes32)','function EXECUTOR_ROLE() view returns (bytes32)','function DEFAULT_ADMIN_ROLE() view returns (bytes32)','function hasRole(bytes32,address) view returns (bool)'], provider);
|
|
577
|
-
out.minDelay = String(await tl.getMinDelay().catch(()=>0n));
|
|
578
|
-
const PROPOSER = await tl.PROPOSER_ROLE();
|
|
579
|
-
const EXECUTOR = await tl.EXECUTOR_ROLE();
|
|
580
|
-
const ADMIN = await tl.DEFAULT_ADMIN_ROLE();
|
|
581
|
-
out.govIsProposer = await tl.hasRole(PROPOSER, governor).catch(()=>false);
|
|
582
|
-
out.anyoneExec = await tl.hasRole(EXECUTOR, ethers.ZeroAddress).catch(()=>false);
|
|
583
|
-
out.signerIsAdmin = await tl.hasRole(ADMIN, await signer.getAddress()).catch(()=>false);
|
|
584
|
-
} catch(_){}
|
|
585
|
-
// Prompt registry governance handover / timelock wiring
|
|
586
|
-
try {
|
|
587
|
-
if (out.registry) {
|
|
588
|
-
const reg = new ethers.Contract(out.registry, [
|
|
589
|
-
'function adminHandoverComplete() view returns (bool)',
|
|
590
|
-
'function governor() view returns (address)',
|
|
591
|
-
'function subDAO() view returns (address)'
|
|
592
|
-
], provider);
|
|
593
|
-
out.registryAdminHandoverComplete = await reg.adminHandoverComplete().catch(()=>false);
|
|
594
|
-
out.registryGovernor = await reg.governor().catch(()=>ethers.ZeroAddress);
|
|
595
|
-
out.registrySubDAO = await reg.subDAO().catch(()=>ethers.ZeroAddress);
|
|
596
|
-
}
|
|
597
|
-
} catch(_) {}
|
|
598
|
-
|
|
599
|
-
// LibraryRegistry scoped roles (if configured)
|
|
600
|
-
try {
|
|
601
|
-
const cfg = require('../config');
|
|
602
|
-
const libRegAddr = cfg.resolveAddress ? cfg.resolveAddress('LIBRARY_REGISTRY_ADDRESS', null) : (process.env.LIBRARY_REGISTRY_ADDRESS || null);
|
|
603
|
-
if (libRegAddr && out.timelock) {
|
|
604
|
-
const lib = new ethers.Contract(libRegAddr, [
|
|
605
|
-
'function libraryAdminRole(address subdao) view returns (bytes32)',
|
|
606
|
-
'function hasRole(bytes32,address) view returns (bool)'
|
|
607
|
-
], provider);
|
|
608
|
-
const scopedRole = await lib.libraryAdminRole(subdao);
|
|
609
|
-
out.libraryRegistryAddress = libRegAddr;
|
|
610
|
-
out.libraryAdminRole = scopedRole;
|
|
611
|
-
out.libraryTimelockHasRole = await lib.hasRole(scopedRole, out.timelock).catch(()=>false);
|
|
612
|
-
out.libraryGovernorHasRole = await lib.hasRole(scopedRole, out.governor).catch(()=>false);
|
|
613
|
-
}
|
|
614
|
-
} catch(_) {}
|
|
615
|
-
|
|
616
|
-
const promptsAddr = opts.prompts || process.env.PREMIUM_PROMPTS_ADDRESS;
|
|
617
|
-
if (promptsAddr) {
|
|
618
|
-
try {
|
|
619
|
-
const pr = new ethers.Contract(ethers.getAddress(promptsAddr), ['function factory() view returns (address)'], provider);
|
|
620
|
-
const factory = await pr.factory(); out.prompts = promptsAddr; out.factory = factory;
|
|
621
|
-
if (factory && /^0x/.test(factory)) {
|
|
622
|
-
const f = new ethers.Contract(factory, ['function isSubDAO(address) view returns (bool)'], provider);
|
|
623
|
-
out.factoryRecognizesSubDAO = await f.isSubDAO(subdao).catch(()=>false);
|
|
624
|
-
}
|
|
625
|
-
} catch(_){}
|
|
626
|
-
}
|
|
627
|
-
if (opts.json) { console.log(JSON.stringify(out, null, 2)); return; }
|
|
628
|
-
console.log('┌─ SubDAO Doctor ────────────────────────────┐');
|
|
629
|
-
console.log(`│ SubDAO : ${subdao}`);
|
|
630
|
-
console.log(`│ Governor : ${governor}`);
|
|
631
|
-
console.log(`│ Timelock : ${timelock}`);
|
|
632
|
-
// Effective mode label (treat non-proposer as Team-only UX)
|
|
633
|
-
if (out.mode !== undefined) {
|
|
634
|
-
let label = (out.mode===0?'Personal':'Community');
|
|
635
|
-
if (out.govIsProposer === false) label = 'Team-only';
|
|
636
|
-
console.log(`│ Mode : ${out.mode} (${label})`);
|
|
637
|
-
}
|
|
638
|
-
if (out.minDelay !== undefined) console.log(`│ MinDelay : ${out.minDelay}s`);
|
|
639
|
-
if (out.govIsProposer !== undefined) console.log(`│ Gov PROPOSER on TL: ${out.govIsProposer?'yes':'no'}`);
|
|
640
|
-
if (out.registry) console.log(`│ Registry : ${out.registry}`);
|
|
641
|
-
if (out.registryAdminHandoverComplete !== undefined) {
|
|
642
|
-
console.log(`│ Registry handover : ${out.registryAdminHandoverComplete ? 'complete' : 'NOT COMPLETE'}`);
|
|
643
|
-
}
|
|
644
|
-
if (out.libraryRegistryAddress) {
|
|
645
|
-
console.log(`│ LibraryRegistry : ${out.libraryRegistryAddress}`);
|
|
646
|
-
console.log(`│ TL has LIB_ADMIN : ${out.libraryTimelockHasRole ? 'yes' : 'no'}`);
|
|
647
|
-
console.log(`│ GOV has LIB_ADMIN: ${out.libraryGovernorHasRole ? 'yes' : 'no'}`);
|
|
648
|
-
}
|
|
649
|
-
if (out.prompts) console.log(`│ Premiums : ${out.prompts}`);
|
|
650
|
-
if (out.factory) console.log(`│ Factory : ${out.factory}`);
|
|
651
|
-
if (out.factoryRecognizesSubDAO !== undefined) console.log(`│ Factory isSubDAO(SubDAO): ${out.factoryRecognizesSubDAO?'yes':'no'}`);
|
|
652
|
-
console.log('└────────────────────────────────────────────┘');
|
|
653
|
-
console.log('\nRecommendations:');
|
|
654
|
-
if (!out.govIsProposer) {
|
|
655
|
-
console.log('- Grant PROPOSER_ROLE to the Governor on the SubDAO Timelock.');
|
|
656
|
-
if (process.env.SAGE_SHOW_CAST === '1') {
|
|
657
|
-
console.log(' Encode calldata:');
|
|
658
|
-
console.log(` cast calldata "grantRole(bytes32,address)" $(cast call ${timelock} "PROPOSER_ROLE()") ${governor}`);
|
|
659
|
-
}
|
|
660
|
-
console.log(' Schedule on timelock:');
|
|
661
|
-
console.log(` sage timelock schedule-call --subdao ${subdao} --to ${timelock} --sig 'grantRole(bytes32,address)' --args "$(cast call ${timelock} 'PROPOSER_ROLE()'),${governor}"`);
|
|
662
|
-
console.log(' Or run one-shot helper:');
|
|
663
|
-
console.log(` sage governance enable-proposals --subdao ${subdao}`);
|
|
664
|
-
}
|
|
665
|
-
if (out.registry && out.registryAdminHandoverComplete === false) {
|
|
666
|
-
console.log('- Complete PromptRegistry admin handover to the Timelock to avoid deployer EOAs retaining control.');
|
|
667
|
-
}
|
|
668
|
-
if (out.libraryRegistryAddress && !out.libraryTimelockHasRole) {
|
|
669
|
-
console.log('- Grant the scoped library admin role to the Timelock in LibraryRegistry for this SubDAO.');
|
|
670
|
-
}
|
|
671
|
-
console.log('- Prefer encoded helpers to avoid hex mistakes:');
|
|
672
|
-
console.log(' - Build data: sage timelock build-data --sig "grantRole(bytes32,address)" --args "PROPOSER_ROLE,', governor, '"');
|
|
673
|
-
console.log(' - Execute call: sage timelock execute-call --subdao', subdao, '--to', timelock, '--sig "grantRole(bytes32,address)" --args "PROPOSER_ROLE,', governor, '" --salt 0x...');
|
|
674
|
-
if (out.factoryRecognizesSubDAO === false) {
|
|
675
|
-
console.log('- PremiumPrompts factory does not recognize this SubDAO. Use a factory-created SubDAO for premium registration or extend the factory with an allowlist and redeploy.');
|
|
630
|
+
|
|
631
|
+
const report = await diagnoseSubDAO({ subdao: opts.subdao, provider, signer });
|
|
632
|
+
|
|
633
|
+
if (opts.json) {
|
|
634
|
+
console.log(JSON.stringify(report, null, 2));
|
|
635
|
+
return;
|
|
676
636
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
}
|
|
692
|
-
|
|
637
|
+
|
|
638
|
+
console.log('🔍 Governance Doctor Report');
|
|
639
|
+
console.log(`SubDAO: ${report.subdao}`);
|
|
640
|
+
console.log(`Mode: ${report.mode === 1 ? 'Community' : 'Personal/Team'}`);
|
|
641
|
+
console.log(`Governor: ${report.governor}`);
|
|
642
|
+
console.log(`Timelock: ${report.timelock}`);
|
|
643
|
+
|
|
644
|
+
if (report.recommendations.length === 0) {
|
|
645
|
+
console.log('✅ All checks passed.');
|
|
646
|
+
} else {
|
|
647
|
+
console.log('⚠️ Issues Found:');
|
|
648
|
+
report.recommendations.forEach(rec => {
|
|
649
|
+
console.log(` - ${rec.msg}`);
|
|
650
|
+
if (rec.fixCmd) console.log(` Fix Cmd: ${rec.fixCmd}`);
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
if (opts.fix) {
|
|
654
|
+
console.log('\n⚠️ Automatic --fix is deprecated. Please use the commands above.');
|
|
655
|
+
console.log(' Or for Safe/Tally payloads, rely on "sage skills publish" or "sage timelock" tools.');
|
|
656
|
+
}
|
|
693
657
|
}
|
|
694
658
|
} catch (e) {
|
|
695
659
|
console.error('❌ Doctor failed:', e.message);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "creator",
|
|
4
|
+
"name": "Creator Playbook (Solo)",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"description": "For publishing your own work. Direct control.",
|
|
7
|
+
"governance": "operator",
|
|
8
|
+
"params": {
|
|
9
|
+
"proposalThreshold": "0",
|
|
10
|
+
"votingPeriod": "0",
|
|
11
|
+
"quorumBps": 0
|
|
12
|
+
},
|
|
13
|
+
"roles": {
|
|
14
|
+
"description": "Owner has all roles. No voting."
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "squad",
|
|
19
|
+
"name": "Squad Playbook (Small Team)",
|
|
20
|
+
"version": "1.0.0",
|
|
21
|
+
"description": "Collaborate with a small team via a Safe multisig.",
|
|
22
|
+
"governance": "operator",
|
|
23
|
+
"params": {
|
|
24
|
+
"proposalThreshold": "0",
|
|
25
|
+
"votingPeriod": "0",
|
|
26
|
+
"quorumBps": 0
|
|
27
|
+
},
|
|
28
|
+
"roles": {
|
|
29
|
+
"description": "Safe multisig holds Admin/Proposer roles."
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "community",
|
|
34
|
+
"name": "Community Playbook (Decentralized)",
|
|
35
|
+
"version": "1.0.0",
|
|
36
|
+
"description": "Token-based voting governance.",
|
|
37
|
+
"governance": "token",
|
|
38
|
+
"params": {
|
|
39
|
+
"proposalThreshold": "10000",
|
|
40
|
+
"votingPeriod": "3 days",
|
|
41
|
+
"quorumBps": 400
|
|
42
|
+
},
|
|
43
|
+
"roles": {
|
|
44
|
+
"description": "Token holders propose/vote. Timelock executes."
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "community-long",
|
|
49
|
+
"name": "Community Playbook (Long Voting)",
|
|
50
|
+
"version": "1.0.0",
|
|
51
|
+
"description": "Lower quorum (2%) with a longer voting window (7 days) for broader participation.",
|
|
52
|
+
"governance": "token",
|
|
53
|
+
"params": {
|
|
54
|
+
"proposalThreshold": "10000",
|
|
55
|
+
"votingPeriod": "7 days",
|
|
56
|
+
"quorumBps": 200
|
|
57
|
+
},
|
|
58
|
+
"roles": {
|
|
59
|
+
"description": "Token holders propose/vote. Timelock executes. Lower quorum, longer window."
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
]
|
package/dist/cli/config.js
CHANGED
|
@@ -322,6 +322,10 @@ function createLocalConfig() {
|
|
|
322
322
|
if (ipfs.warmGateway !== undefined && !process.env.SAGE_IPFS_WARM) {
|
|
323
323
|
process.env.SAGE_IPFS_WARM = String(!!ipfs.warmGateway);
|
|
324
324
|
}
|
|
325
|
+
const git = profile.git || {};
|
|
326
|
+
if (git.githubToken && !process.env.GITHUB_TOKEN && !process.env.GH_TOKEN) {
|
|
327
|
+
process.env.GITHUB_TOKEN = git.githubToken;
|
|
328
|
+
}
|
|
325
329
|
}
|
|
326
330
|
if (!isQuiet()) console.log(`🧭 Loaded config profile: ${active}`);
|
|
327
331
|
} catch (e) {
|
|
@@ -410,6 +414,17 @@ function createLocalConfig() {
|
|
|
410
414
|
this.writeProfileSettings({ ipfs: settings }, options);
|
|
411
415
|
},
|
|
412
416
|
|
|
417
|
+
readGitConfig() {
|
|
418
|
+
const profiles = this.readProfiles();
|
|
419
|
+
const active = profiles.activeProfile || 'default';
|
|
420
|
+
const profile = profiles.profiles?.[active] || {};
|
|
421
|
+
return mergeObjects({}, profile.git || {});
|
|
422
|
+
},
|
|
423
|
+
|
|
424
|
+
writeGitConfig(settings = {}, options = {}) {
|
|
425
|
+
this.writeProfileSettings({ git: settings }, options);
|
|
426
|
+
},
|
|
427
|
+
|
|
413
428
|
getActiveProfile(options = {}) {
|
|
414
429
|
const profiles = this.readProfiles();
|
|
415
430
|
const profileName = options.profile || profiles.activeProfile || 'default';
|
|
@@ -1283,9 +1283,14 @@ class GovernanceManager {
|
|
|
1283
1283
|
}
|
|
1284
1284
|
}
|
|
1285
1285
|
|
|
1286
|
-
async executeProposal(id) {
|
|
1286
|
+
async executeProposal(id, opts = {}) {
|
|
1287
1287
|
try {
|
|
1288
|
-
|
|
1288
|
+
const { simulate } = opts;
|
|
1289
|
+
if (simulate) {
|
|
1290
|
+
console.log(colors.blue('🔮 Simulating proposal execution...'));
|
|
1291
|
+
} else {
|
|
1292
|
+
console.log(colors.blue('⚡ Executing proposal...'));
|
|
1293
|
+
}
|
|
1289
1294
|
console.log('Proposal ID:', id);
|
|
1290
1295
|
|
|
1291
1296
|
// Convert proposalId to BigInt for consistency
|
|
@@ -1309,9 +1314,9 @@ class GovernanceManager {
|
|
|
1309
1314
|
throw new Error(`Governor contract at ${governorAddr} is not deployed or has no code. This may be a network/fork issue.`);
|
|
1310
1315
|
}
|
|
1311
1316
|
|
|
1312
|
-
// Optional wait until ETA is reached
|
|
1317
|
+
// Optional wait until ETA is reached (only if not simulating)
|
|
1313
1318
|
try {
|
|
1314
|
-
if (process.env.SAGE_EXEC_WAIT === '1') {
|
|
1319
|
+
if (!simulate && process.env.SAGE_EXEC_WAIT === '1') {
|
|
1315
1320
|
const eta = await this.governor.proposalEta(this.normalizeProposalId(proposalId)).catch(() => 0n);
|
|
1316
1321
|
if (eta && eta > 0n) {
|
|
1317
1322
|
const now = BigInt(Math.floor(Date.now() / 1000));
|
|
@@ -1361,6 +1366,21 @@ class GovernanceManager {
|
|
|
1361
1366
|
if (eta && eta > 0n) console.log(`🔍 Debug: ETA=${eta.toString()}`);
|
|
1362
1367
|
console.log(`🔍 Debug: Proposal state: ${nm} (${st})`);
|
|
1363
1368
|
} catch {}
|
|
1369
|
+
|
|
1370
|
+
if (simulate) {
|
|
1371
|
+
// Simulation via call
|
|
1372
|
+
try {
|
|
1373
|
+
await this.governor.execute.staticCall(targets, values, calldatas, descriptionHash);
|
|
1374
|
+
console.log('✅ Simulation successful: Proposal would execute without reverting.');
|
|
1375
|
+
return;
|
|
1376
|
+
} catch (err) {
|
|
1377
|
+
const { decodeContractError } = require('./utils/contract-error-decoder');
|
|
1378
|
+
const reason = decodeContractError(err, [this.governor, this.timelock]);
|
|
1379
|
+
console.error('❌ Simulation failed:', reason);
|
|
1380
|
+
throw new Error(`Simulation failed: ${reason}`);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1364
1384
|
try {
|
|
1365
1385
|
const tx = await this.governor.execute(targets, values, calldatas, descriptionHash);
|
|
1366
1386
|
console.log('⏳ Waiting for transaction confirmation...');
|
|
@@ -1393,6 +1413,7 @@ class GovernanceManager {
|
|
|
1393
1413
|
}
|
|
1394
1414
|
}
|
|
1395
1415
|
} catch (e) {
|
|
1416
|
+
if (simulate && e.message.includes('Simulation failed')) throw e;
|
|
1396
1417
|
console.log('⚠️ Cache fast-path failed, falling back to log query:', e.message);
|
|
1397
1418
|
}
|
|
1398
1419
|
|
package/dist/cli/index.js
CHANGED
|
@@ -42,7 +42,7 @@ try {
|
|
|
42
42
|
};
|
|
43
43
|
const getArg = (name) => {
|
|
44
44
|
const i = process.argv.indexOf(name);
|
|
45
|
-
return i >= 0 && process.argv[i+1] && !process.argv[i+1].startsWith('-') ? process.argv[i+1] : null;
|
|
45
|
+
return i >= 0 && process.argv[i + 1] && !process.argv[i + 1].startsWith('-') ? process.argv[i + 1] : null;
|
|
46
46
|
};
|
|
47
47
|
const netArg = getArg('--net') || process.env.SAGE_NET || null;
|
|
48
48
|
if (netArg) {
|
|
@@ -86,10 +86,10 @@ program
|
|
|
86
86
|
|
|
87
87
|
// Reordered to highlight consolidated namespaces ('prompts' and 'gov')
|
|
88
88
|
const commandGroups = [
|
|
89
|
-
{ title: 'Content (new)', modules: ['prompts', 'prompt', 'library', 'premium', 'premium-pre', 'creator', 'contributor', 'bounty'] },
|
|
89
|
+
{ title: 'Content (new)', modules: ['skills', 'prompts', 'prompt', 'library', 'premium', 'premium-pre', 'creator', 'contributor', 'bounty'] },
|
|
90
90
|
{ title: 'Governance (new)', modules: ['proposals', 'governance', 'subdao', 'timelock', 'roles', 'members', 'gov-config', 'stake-status'] },
|
|
91
91
|
{ title: 'Treasury', modules: ['treasury', 'safe', 'boost'] },
|
|
92
|
-
{ title: 'Ops & Utilities', modules: ['config', 'wallet', 'sxxx', 'ipfs', 'ipns', 'context', 'doctor', 'factory', 'upgrade', 'resolve', 'dry-run-queue', 'mcp', 'start', 'wizard', 'init', 'subdao-config', 'pin', 'council', 'sbt', 'completion', 'help', 'hook'
|
|
92
|
+
{ title: 'Ops & Utilities', modules: ['config', 'wallet', 'sxxx', 'ipfs', 'ipns', 'context', 'doctor', 'factory', 'upgrade', 'resolve', 'dry-run-queue', 'mcp', 'start', 'wizard', 'init', 'subdao-config', 'pin', 'council', 'sbt', 'completion', 'help', 'hook'] },
|
|
93
93
|
{ title: 'Developer', modules: ['dev', 'registry'] }
|
|
94
94
|
];
|
|
95
95
|
|
|
@@ -116,7 +116,7 @@ program.addHelpText('beforeAll', () => {
|
|
|
116
116
|
if (!hasProjectConfig && !hasWorkspace) {
|
|
117
117
|
firstRun = '\nTip: first time here? Try:\n - sage wizard\n - or: sage ipfs setup && sage prompts init\n';
|
|
118
118
|
}
|
|
119
|
-
} catch (_) {}
|
|
119
|
+
} catch (_) { }
|
|
120
120
|
return `Top workflows: ${highlights}\n${lines}\n${note}${firstRun}\n`;
|
|
121
121
|
});
|
|
122
122
|
|
|
@@ -148,7 +148,6 @@ const additionalModules = [
|
|
|
148
148
|
'completion',
|
|
149
149
|
'help',
|
|
150
150
|
'hook'
|
|
151
|
-
, 'ipns'
|
|
152
151
|
];
|
|
153
152
|
additionalModules.forEach((mod) => moduleSet.add(mod));
|
|
154
153
|
|
|
@@ -168,7 +167,7 @@ if (cliTestMode) {
|
|
|
168
167
|
|
|
169
168
|
program.configureOutput({
|
|
170
169
|
writeOut: (str) => process.stdout.write(str),
|
|
171
|
-
writeErr: () => {},
|
|
170
|
+
writeErr: () => { },
|
|
172
171
|
outputError: (str, write) => {
|
|
173
172
|
if (!aliasSupport.handleOutputError(str)) {
|
|
174
173
|
write(str);
|
|
@@ -202,7 +201,7 @@ process.on('exit', () => {
|
|
|
202
201
|
process.stderr.write = originalStderrWrite;
|
|
203
202
|
aliasSupport.setStderrWriter(originalStderrWrite);
|
|
204
203
|
if (global.walletManager) {
|
|
205
|
-
global.walletManager.disconnect().catch(() => {});
|
|
204
|
+
global.walletManager.disconnect().catch(() => { });
|
|
206
205
|
}
|
|
207
206
|
});
|
|
208
207
|
|
|
@@ -79,6 +79,85 @@ class LibraryManager {
|
|
|
79
79
|
return searchLocalManifests({ config: cliConfig, query });
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Create a new local library manifest in the pinned libraries directory.
|
|
84
|
+
* This is used by MCP quick-start flows for "My Library" and ad-hoc libraries.
|
|
85
|
+
* Returns a pseudo-CID (local_*) that can be used with the local manifest loader.
|
|
86
|
+
*/
|
|
87
|
+
createLibrary(name, description = '', options = {}) {
|
|
88
|
+
const dir = this.ensureLibrariesDir();
|
|
89
|
+
const baseName = String(name || 'Library').trim() || 'Library';
|
|
90
|
+
const slugBase = baseName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'library';
|
|
91
|
+
let cid = `local_${slugBase}`;
|
|
92
|
+
let suffix = 1;
|
|
93
|
+
// Ensure uniqueness within the libraries dir
|
|
94
|
+
while (fs.existsSync(path.join(dir, `${cid}.json`))) {
|
|
95
|
+
cid = `local_${slugBase}_${suffix++}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const manifest = {
|
|
99
|
+
version: 2,
|
|
100
|
+
library: {
|
|
101
|
+
name: baseName,
|
|
102
|
+
description: String(description || ''),
|
|
103
|
+
...(options.metadata || {}),
|
|
104
|
+
},
|
|
105
|
+
prompts: [],
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const manifestPath = path.join(dir, `${cid}.json`);
|
|
109
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
110
|
+
|
|
111
|
+
return { cid, path: manifestPath, manifest };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Pin (or re-pin) a manifest into the local libraries directory.
|
|
116
|
+
* Accepts either a manifest object or { manifestPath, manifestData } from callers.
|
|
117
|
+
* Returns { cid, path } where cid may be provided or auto-assigned as local_*.
|
|
118
|
+
*/
|
|
119
|
+
pinLibrary(manifestOrOptions) {
|
|
120
|
+
const dir = this.ensureLibrariesDir();
|
|
121
|
+
let manifest = manifestOrOptions;
|
|
122
|
+
|
|
123
|
+
// Support callers that pass { manifestPath, manifestData }
|
|
124
|
+
if (manifestOrOptions && typeof manifestOrOptions === 'object' && (manifestOrOptions.manifest || manifestOrOptions.manifestData)) {
|
|
125
|
+
manifest = manifestOrOptions.manifest || manifestOrOptions.manifestData;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!manifest || typeof manifest !== 'object') {
|
|
129
|
+
throw new Error('pinLibrary requires a manifest object');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const libName = String(manifest.library?.name || 'Library').trim() || 'Library';
|
|
133
|
+
const slugBase = libName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'library';
|
|
134
|
+
|
|
135
|
+
let cid = manifest.cid || manifest.library?.cid || null;
|
|
136
|
+
if (!cid) {
|
|
137
|
+
cid = `local_${slugBase}`;
|
|
138
|
+
let suffix = 1;
|
|
139
|
+
while (fs.existsSync(path.join(dir, `${cid}.json`))) {
|
|
140
|
+
cid = `local_${slugBase}_${suffix++}`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Ensure manifest has expected top-level fields
|
|
145
|
+
if (!manifest.version) {
|
|
146
|
+
manifest.version = 2;
|
|
147
|
+
}
|
|
148
|
+
if (!manifest.library) {
|
|
149
|
+
manifest.library = { name: libName };
|
|
150
|
+
}
|
|
151
|
+
if (!Array.isArray(manifest.prompts)) {
|
|
152
|
+
manifest.prompts = [];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const manifestPath = path.join(dir, `${cid}.json`);
|
|
156
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
157
|
+
|
|
158
|
+
return { cid, path: manifestPath };
|
|
159
|
+
}
|
|
160
|
+
|
|
82
161
|
// Compute CID locally from bytes using ipfs-only-hash (UnixFS DAG-PB, CIDv0 by default)
|
|
83
162
|
// Compute CID locally from bytes using ipfs-only-hash (UnixFS DAG-PB, CIDv0 by default)
|
|
84
163
|
async _computeCidFromBuffer(buffer) {
|