@sage-protocol/cli 0.3.0 → 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/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 +175 -21
- package/dist/cli/commands/subdao.js +22 -2
- package/dist/cli/config/playbooks.json +15 -0
- package/dist/cli/governance-manager.js +25 -4
- package/dist/cli/index.js +5 -6
- package/dist/cli/library-manager.js +79 -0
- package/dist/cli/mcp-server-stdio.js +1374 -82
- package/dist/cli/schemas/manifest.schema.json +55 -0
- package/dist/cli/services/doctor/fixers.js +134 -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/subdao/applier.js +208 -196
- package/dist/cli/services/subdao/planner.js +41 -6
- package/dist/cli/subdao-manager.js +14 -0
- package/dist/cli/utils/aliases.js +17 -2
- package/dist/cli/utils/contract-error-decoder.js +61 -0
- package/dist/cli/utils/suggestions.js +17 -12
- package/package.json +3 -2
- package/src/schemas/manifest.schema.json +55 -0
|
@@ -41,7 +41,8 @@ function register(program) {
|
|
|
41
41
|
try {
|
|
42
42
|
const HelperABI = ['event ProposalTupleIndexed(uint256 indexed proposalId,address[] targets,uint256[] values,bytes[] calldatas,bytes32 descriptionHash)'];
|
|
43
43
|
const helper = new ethers.Contract(helperAddr, HelperABI, provider);
|
|
44
|
-
|
|
44
|
+
// Ethers v6 compatibility: compute topic hash directly from signature
|
|
45
|
+
const topic = (ethers.id || ((s)=>require('ethers').id(s)))('ProposalTupleIndexed(uint256,address[],uint256[],bytes[],bytes32)');
|
|
45
46
|
const address = helperAddr;
|
|
46
47
|
const filter = { address, topics: [topic] };
|
|
47
48
|
// Respect from-block and max-range by chunking if needed
|
|
@@ -152,18 +153,37 @@ function register(program) {
|
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
155
|
if (!emitted) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
156
|
+
// Fallback: use SDK getProposalDetails
|
|
157
|
+
try {
|
|
158
|
+
const { getProposalDetails } = require('@sage-protocol/sdk');
|
|
159
|
+
const details = await getProposalDetails({
|
|
160
|
+
governorAddress: ctx.governor,
|
|
161
|
+
proposalId: pid,
|
|
162
|
+
provider,
|
|
163
|
+
helperAddress: helperAddr
|
|
164
|
+
});
|
|
165
|
+
const summary = {
|
|
166
|
+
id: pid.toString(),
|
|
167
|
+
source: 'sdk',
|
|
168
|
+
actionCount: details.targets?.length || 0,
|
|
169
|
+
targets: details.targets || [],
|
|
170
|
+
values: (details.values || []).map(v => v.toString()),
|
|
171
|
+
selectors: (details.calldatas || []).map(d => (typeof d === 'string' ? d.slice(0, 10) : '0x')),
|
|
172
|
+
description: details.description || ''
|
|
173
|
+
};
|
|
174
|
+
if (opts.json) console.log(JSON.stringify(withJsonVersion(summary), null, 2));
|
|
175
|
+
else {
|
|
176
|
+
console.log(`Proposal ${summary.id} (SDK)`);
|
|
177
|
+
console.log(` Actions: ${summary.actionCount}`);
|
|
178
|
+
if (summary.targets.length) console.log(` First target: ${summary.targets[0]}`);
|
|
179
|
+
if (summary.selectors.length) console.log(` First selector: ${summary.selectors[0]}`);
|
|
180
|
+
if (summary.description) console.log(` Description: ${summary.description.slice(0, 100)}...`);
|
|
166
181
|
}
|
|
182
|
+
} catch (e) {
|
|
183
|
+
// Final fallback: minimal info
|
|
184
|
+
const summary = { id: pid.toString(), description: '(unable to fetch proposal details)' };
|
|
185
|
+
if (opts.json) console.log(JSON.stringify(withJsonVersion(summary), null, 2));
|
|
186
|
+
else console.log(`Proposal ${summary.id} - ${summary.description}`);
|
|
167
187
|
}
|
|
168
188
|
}
|
|
169
189
|
} catch (e) {
|
|
@@ -252,9 +272,17 @@ function register(program) {
|
|
|
252
272
|
const ctx = await resolveGovContext({ govOpt: opts.gov, subdaoOpt: opts.subdao, provider });
|
|
253
273
|
try { const net = await provider.getNetwork(); ctx.chainId = Number(net.chainId || 0); } catch (_) {}
|
|
254
274
|
printContextBanner(ctx, opts);
|
|
275
|
+
// Initialize wallet first, then GovernanceManager
|
|
276
|
+
const WalletManager = require('../wallet-manager');
|
|
277
|
+
const wm = new WalletManager();
|
|
278
|
+
await wm.connect();
|
|
279
|
+
const signer = wm.getSigner();
|
|
280
|
+
const walletProvider = wm.getProvider();
|
|
281
|
+
|
|
255
282
|
const GovernanceManager = require('../governance-manager');
|
|
256
283
|
const gm = new GovernanceManager();
|
|
257
|
-
|
|
284
|
+
// Initialize with wallet connection
|
|
285
|
+
await gm.initialize(ctx.subdao || null, { governorOverride: ctx.governor });
|
|
258
286
|
await gm.vote(id, v);
|
|
259
287
|
console.log('✅ Vote submitted');
|
|
260
288
|
} catch (e) {
|
|
@@ -269,6 +297,7 @@ function register(program) {
|
|
|
269
297
|
.argument('<id>', 'Proposal ID')
|
|
270
298
|
.option('--gov <address>', 'Governor address (override)')
|
|
271
299
|
.option('--subdao <address>', 'SubDAO address (derive governor)')
|
|
300
|
+
.option('--simulate', 'Simulate execution without broadcasting a transaction', false)
|
|
272
301
|
.action(async (id, opts) => {
|
|
273
302
|
try {
|
|
274
303
|
enterJsonMode(opts);
|
|
@@ -277,15 +306,31 @@ function register(program) {
|
|
|
277
306
|
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL || 'https://base-sepolia.publicnode.com');
|
|
278
307
|
const ctx = await resolveGovContext({ govOpt: opts.gov, subdaoOpt: opts.subdao, provider });
|
|
279
308
|
try { const net = await provider.getNetwork(); ctx.chainId = Number(net.chainId || 0); } catch (_) {}
|
|
280
|
-
printContextBanner(ctx, opts);
|
|
309
|
+
if (!opts.simulate) printContextBanner(ctx, opts);
|
|
310
|
+
|
|
311
|
+
// Initialize wallet first, then GovernanceManager
|
|
312
|
+
const WalletManager = require('../wallet-manager');
|
|
313
|
+
const wm = new WalletManager();
|
|
314
|
+
if (!opts.simulate) {
|
|
315
|
+
await wm.connect();
|
|
316
|
+
}
|
|
317
|
+
|
|
281
318
|
const GovernanceManager = require('../governance-manager');
|
|
282
319
|
const gm = new GovernanceManager();
|
|
283
|
-
|
|
320
|
+
// Initialize with wallet connection (or just provider for simulation)
|
|
321
|
+
await gm.initialize(ctx.subdao || null, { governorOverride: ctx.governor, providerOverride: provider, signerOverride: opts.simulate ? null : undefined });
|
|
322
|
+
|
|
323
|
+
// If simulate, pass flag
|
|
324
|
+
if (opts.simulate) {
|
|
325
|
+
await gm.executeProposal(id, { simulate: true });
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
284
329
|
// Delegate to existing library-manager execute flow if it is a library update; otherwise, try queue/execute via gm
|
|
285
330
|
try {
|
|
286
|
-
await gm.
|
|
331
|
+
await gm.queueProposal(id);
|
|
287
332
|
} catch (_) {}
|
|
288
|
-
await gm.
|
|
333
|
+
await gm.executeProposal(id);
|
|
289
334
|
console.log('✅ Execute complete');
|
|
290
335
|
} catch (e) {
|
|
291
336
|
const { handleCLIError } = require('../utils/error-handler');
|
|
@@ -293,6 +338,131 @@ function register(program) {
|
|
|
293
338
|
}
|
|
294
339
|
});
|
|
295
340
|
|
|
341
|
+
cmd
|
|
342
|
+
.command('status')
|
|
343
|
+
.description('Show inbox + next recommended action for proposals')
|
|
344
|
+
.option('--gov <address>', 'Governor address (override)')
|
|
345
|
+
.option('--subdao <address>', 'SubDAO address (derive governor)')
|
|
346
|
+
.option('--json', 'Emit JSON output', false)
|
|
347
|
+
.action(async (opts) => {
|
|
348
|
+
try {
|
|
349
|
+
enterJsonMode(opts);
|
|
350
|
+
const { ethers } = require('ethers');
|
|
351
|
+
const { resolveGovContext } = require('../utils/gov-context');
|
|
352
|
+
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL || 'https://base-sepolia.publicnode.com');
|
|
353
|
+
const ctx = await resolveGovContext({ govOpt: opts.gov, subdaoOpt: opts.subdao, provider });
|
|
354
|
+
|
|
355
|
+
// Get inbox
|
|
356
|
+
const inboxCmd = cmd.commands.find(c => c.name() === 'inbox');
|
|
357
|
+
if (!inboxCmd) {
|
|
358
|
+
throw new Error('inbox command not found');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Call inbox logic inline
|
|
362
|
+
const latest = await provider.getBlockNumber();
|
|
363
|
+
const windowDefault = Number(process.env.SAGE_INBOX_BLOCK_WINDOW || 50000);
|
|
364
|
+
const fromBlock = Math.max(0, Number(latest) - windowDefault);
|
|
365
|
+
const toBlock = 'latest';
|
|
366
|
+
|
|
367
|
+
const out = [];
|
|
368
|
+
const helperAddr = opts.helper || process.env.GOVERNANCE_HELPER_ADDRESS || process.env.SAGE_GOVERNANCE_HELPER || null;
|
|
369
|
+
if (helperAddr && /^0x[0-9a-fA-F]{40}$/.test(helperAddr)) {
|
|
370
|
+
try {
|
|
371
|
+
const HelperABI = ['event ProposalTupleIndexed(uint256 indexed proposalId,address[] targets,uint256[] values,bytes[] calldatas,bytes32 descriptionHash)'];
|
|
372
|
+
const helper = new ethers.Contract(helperAddr, HelperABI, provider);
|
|
373
|
+
const topic = (ethers.id || ((s)=>require('ethers').id(s)))('ProposalTupleIndexed(uint256,address[],uint256[],bytes[],bytes32)');
|
|
374
|
+
const filter = { address: helperAddr, topics: [topic] };
|
|
375
|
+
const logs = await provider.getLogs({ ...filter, fromBlock, toBlock });
|
|
376
|
+
for (const log of logs) {
|
|
377
|
+
try {
|
|
378
|
+
const parsed = helper.interface.parseLog(log);
|
|
379
|
+
const id = parsed?.args?.proposalId?.toString?.() || String(parsed?.args?.[0] || '');
|
|
380
|
+
const GovABI = require('../utils/artifacts').resolveArtifact('contracts/cloneable/PromptGovernorCloneable.sol/PromptGovernorCloneable.json').abi;
|
|
381
|
+
const gov = new ethers.Contract(ctx.governor, GovABI, provider);
|
|
382
|
+
let state = 'unknown';
|
|
383
|
+
try { state = String(await gov.state(BigInt(id))); } catch (_) {}
|
|
384
|
+
out.push({ id, state, title: '(indexed)', description: '' });
|
|
385
|
+
} catch (_) {}
|
|
386
|
+
}
|
|
387
|
+
} catch (e) {
|
|
388
|
+
console.log(`⚠️ Helper scan failed: ${e.message}. Falling back to Governor logs.`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (!out.length) {
|
|
392
|
+
const GovABI = require('../utils/artifacts').resolveArtifact('contracts/cloneable/PromptGovernorCloneable.sol/PromptGovernorCloneable.json').abi;
|
|
393
|
+
const gov = new ethers.Contract(ctx.governor, GovABI, provider);
|
|
394
|
+
const filter = gov.filters?.ProposalCreated ? gov.filters.ProposalCreated() : null;
|
|
395
|
+
if (filter) {
|
|
396
|
+
const events = await gov.queryFilter(filter, fromBlock, toBlock);
|
|
397
|
+
for (const ev of events) {
|
|
398
|
+
try {
|
|
399
|
+
const parsed = ev.args || {};
|
|
400
|
+
const id = parsed.proposalId?.toString?.() || parsed[0]?.toString?.() || String(ev.topics?.[1] || '');
|
|
401
|
+
const desc = parsed.description || '';
|
|
402
|
+
const title = (desc.split('\n')[0] || '').slice(0, 80);
|
|
403
|
+
let state = 'unknown';
|
|
404
|
+
try { state = String(await gov.state(BigInt(id))); } catch (_) {}
|
|
405
|
+
out.push({ id, state, title, description: desc });
|
|
406
|
+
} catch (_) {}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (opts.json) {
|
|
412
|
+
console.log(JSON.stringify({ version: '1.0', results: out, nextAction: out.length ? 'vote' : 'none' }, null, 2));
|
|
413
|
+
} else {
|
|
414
|
+
if (!out.length) {
|
|
415
|
+
console.log('No active proposals found.');
|
|
416
|
+
console.log('\n💡 Next: Create a proposal with:');
|
|
417
|
+
console.log(' sage library push <manifest.json> --subdao <subdao-address>');
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
console.log(`Found ${out.length} proposal(s):`);
|
|
421
|
+
out.forEach((r, idx) => {
|
|
422
|
+
const stateName = { '0': 'Pending', '1': 'Active', '2': 'Canceled', '3': 'Defeated', '4': 'Succeeded', '5': 'Queued', '6': 'Expired', '7': 'Executed' }[r.state] || r.state;
|
|
423
|
+
console.log(`\n${idx + 1}. #${r.id} [${stateName}] ${r.title || '(no title)'}`);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Recommend next action
|
|
427
|
+
const active = out.filter(r => r.state === '1');
|
|
428
|
+
const succeeded = out.filter(r => r.state === '4');
|
|
429
|
+
const queued = out.filter(r => r.state === '5');
|
|
430
|
+
|
|
431
|
+
console.log('\n📋 Recommended next actions:');
|
|
432
|
+
if (active.length) {
|
|
433
|
+
const first = active[0];
|
|
434
|
+
const subdao = ctx.subdao || opts.subdao;
|
|
435
|
+
if (subdao) {
|
|
436
|
+
console.log(` sage proposals vote ${first.id} for --subdao ${subdao}`);
|
|
437
|
+
} else {
|
|
438
|
+
console.log(` sage proposals vote ${first.id} for`);
|
|
439
|
+
}
|
|
440
|
+
} else if (succeeded.length) {
|
|
441
|
+
const first = succeeded[0];
|
|
442
|
+
const subdao = ctx.subdao || opts.subdao;
|
|
443
|
+
if (subdao) {
|
|
444
|
+
console.log(` sage proposals execute ${first.id} --subdao ${subdao}`);
|
|
445
|
+
} else {
|
|
446
|
+
console.log(` sage proposals execute ${first.id}`);
|
|
447
|
+
}
|
|
448
|
+
} else if (queued.length) {
|
|
449
|
+
const first = queued[0];
|
|
450
|
+
const subdao = ctx.subdao || opts.subdao;
|
|
451
|
+
if (subdao) {
|
|
452
|
+
console.log(` sage proposals execute ${first.id} --subdao ${subdao}`);
|
|
453
|
+
} else {
|
|
454
|
+
console.log(` sage proposals execute ${first.id}`);
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
console.log(' No actionable proposals at this time.');
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
} catch (e) {
|
|
461
|
+
const { handleCLIError } = require('../utils/error-handler');
|
|
462
|
+
handleCLIError('proposals:status', e, { context: { gov: opts.gov, subdao: opts.subdao } });
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
|
|
296
466
|
program.addCommand(cmd);
|
|
297
467
|
}
|
|
298
468
|
|
|
@@ -422,7 +422,7 @@ function register(program) {
|
|
|
422
422
|
.option('--dry-run', 'Skip IPFS upload and transaction generation')
|
|
423
423
|
.action(async (key, opts) => {
|
|
424
424
|
try {
|
|
425
|
-
const
|
|
425
|
+
const fs = require('fs');
|
|
426
426
|
const { diagnoseSubDAO } = require('../services/governance/doctor');
|
|
427
427
|
const WalletManager = require('../wallet-manager');
|
|
428
428
|
const { readWorkspace } = require('../services/prompts/workspace');
|
|
@@ -435,29 +435,29 @@ function register(program) {
|
|
|
435
435
|
const signer = wallet.getSigner();
|
|
436
436
|
|
|
437
437
|
// 2. Doctor Preflight
|
|
438
|
-
console.log(
|
|
438
|
+
console.log('🩺 Running Governance Doctor...');
|
|
439
439
|
const doctor = await diagnoseSubDAO({ subdao: opts.subdao, provider, signer });
|
|
440
440
|
|
|
441
441
|
if (doctor.recommendations.length > 0) {
|
|
442
|
-
console.log(
|
|
442
|
+
console.log('⚠️ Issues detected:');
|
|
443
443
|
doctor.recommendations.forEach(r => {
|
|
444
444
|
const msg = typeof r==='string' ? r : r.msg;
|
|
445
445
|
const fix = typeof r==='string' ? '' : (r.fix || r.fixCmd);
|
|
446
446
|
console.log(` - ${msg} ${fix ? `(Fix: ${fix})` : ''}`);
|
|
447
447
|
});
|
|
448
|
-
if (!opts.force) {
|
|
449
|
-
console.error(
|
|
448
|
+
if (!opts.force && !process.env.SAGE_FORCE) {
|
|
449
|
+
console.error('❌ Aborting publish due to governance issues. Use --force to ignore.');
|
|
450
450
|
process.exit(1);
|
|
451
451
|
}
|
|
452
452
|
} else {
|
|
453
|
-
console.log(
|
|
453
|
+
console.log('✅ Governance checks passed.');
|
|
454
454
|
}
|
|
455
455
|
|
|
456
456
|
const subdaoAddr = doctor.subdao;
|
|
457
457
|
if (!subdaoAddr) throw new Error('SubDAO not resolved.');
|
|
458
458
|
|
|
459
459
|
// 3. Build Manifest
|
|
460
|
-
console.log(
|
|
460
|
+
console.log('📦 Building manifest...');
|
|
461
461
|
const ws = readWorkspace() || {};
|
|
462
462
|
const skills = findWorkspaceSkills({ promptsDir: ws.promptsDir || 'prompts' });
|
|
463
463
|
// Filter if key provided? "sage skills publish my-skill" usually implies updating just that one?
|
|
@@ -486,25 +486,25 @@ function register(program) {
|
|
|
486
486
|
}
|
|
487
487
|
|
|
488
488
|
// 4. IPFS Upload
|
|
489
|
-
console.log(
|
|
489
|
+
console.log('☁️ Uploading to IPFS...');
|
|
490
490
|
// Use IPFS Manager or Worker directly?
|
|
491
491
|
// Try to require IpfsManager
|
|
492
492
|
let cid;
|
|
493
493
|
try {
|
|
494
494
|
const IpfsManager = require('../ipfs-manager');
|
|
495
495
|
const ipfs = new IpfsManager();
|
|
496
|
-
// ipfs.
|
|
497
|
-
|
|
498
|
-
cid
|
|
496
|
+
// ipfs.uploadJson(json) returns CID string directly
|
|
497
|
+
cid = await ipfs.uploadJson(manifest, 'skills-manifest');
|
|
498
|
+
if (!cid) throw new Error('IPFS upload returned no CID');
|
|
499
499
|
} catch (e) {
|
|
500
500
|
// Fallback or error
|
|
501
501
|
throw new Error(`IPFS upload failed: ${e.message}`);
|
|
502
502
|
}
|
|
503
503
|
|
|
504
|
-
console.log(
|
|
504
|
+
console.log(`✅ Manifest uploaded: ${cid}`);
|
|
505
505
|
|
|
506
506
|
// 5. Generate Transaction Payload
|
|
507
|
-
console.log(
|
|
507
|
+
console.log('🚀 Generating Governance Payload...');
|
|
508
508
|
|
|
509
509
|
// We need to call updateLibrary(cid) on the Registry or SubDAO?
|
|
510
510
|
// SubDAOs usually have a `updateLibrary` or `publishManifest` method?
|
|
@@ -516,20 +516,32 @@ function register(program) {
|
|
|
516
516
|
|
|
517
517
|
const RegistryABI = require('../utils/factory-abi'); // Wait, need LibraryRegistry ABI
|
|
518
518
|
// We can construct Interface manually for updateLibrary
|
|
519
|
-
// function
|
|
519
|
+
// function updateLibraryForSubDAO(address subdao, string calldata manifestCid)
|
|
520
520
|
const { ethers } = require('ethers');
|
|
521
|
-
const iface = new ethers.Interface(['function
|
|
522
|
-
const calldata = iface.encodeFunctionData('
|
|
523
|
-
const
|
|
521
|
+
const iface = new ethers.Interface(['function updateLibraryForSubDAO(address subdao, string manifestCid)']);
|
|
522
|
+
const calldata = iface.encodeFunctionData('updateLibraryForSubDAO', [subdaoAddr, cid]);
|
|
523
|
+
const config = require('../config');
|
|
524
|
+
// Resolve from env first, then active profile addresses (aliases supported)
|
|
525
|
+
const resolvedFromConfig = (() => {
|
|
526
|
+
try { return config.resolveAddress('LIBRARY_REGISTRY_ADDRESS', null); } catch (_) { return null; }
|
|
527
|
+
})();
|
|
528
|
+
const target = process.env.LIBRARY_REGISTRY_ADDRESS || resolvedFromConfig;
|
|
529
|
+
if (!process.env.LIBRARY_REGISTRY_ADDRESS && resolvedFromConfig) {
|
|
530
|
+
try {
|
|
531
|
+
const profiles = config.readProfiles?.() || {};
|
|
532
|
+
const active = profiles.activeProfile || 'default';
|
|
533
|
+
console.log(`ℹ️ Using LIBRARY_REGISTRY_ADDRESS from profile '${active}': ${target}`);
|
|
534
|
+
} catch (_) {}
|
|
535
|
+
}
|
|
524
536
|
// We need to ensure target is known. Doctor found it?
|
|
525
537
|
// doctor.registry might be PromptRegistry, NOT LibraryRegistry.
|
|
526
538
|
// We need LIBRARY_REGISTRY_ADDRESS.
|
|
527
|
-
if (!target) throw new Error('LIBRARY_REGISTRY_ADDRESS not set.');
|
|
539
|
+
if (!target) throw new Error('LIBRARY_REGISTRY_ADDRESS not set in env or active profile. Set env: export LIBRARY_REGISTRY_ADDRESS=0x... or run: sage config addresses (and add LIBRARY_REGISTRY_ADDRESS).');
|
|
528
540
|
|
|
529
541
|
// 6. Route based on Mode
|
|
530
542
|
if (doctor.mode === 1) {
|
|
531
543
|
// Community -> Tally
|
|
532
|
-
console.log(
|
|
544
|
+
console.log('\n🗳️ Community Governance Detected (Token Voting)');
|
|
533
545
|
// Generate Tally URL
|
|
534
546
|
// Tally URL format: https://www.tally.xyz/gov/[slug]/proposal/create? ...
|
|
535
547
|
// We need the DAO slug or address. Tally uses address or slug.
|
|
@@ -549,7 +561,7 @@ function register(program) {
|
|
|
549
561
|
// Check if we are owner/admin
|
|
550
562
|
// If Squad (Multisig), output Safe JSON.
|
|
551
563
|
|
|
552
|
-
console.log(
|
|
564
|
+
console.log('\n🛡️ Team/Creator Governance Detected');
|
|
553
565
|
|
|
554
566
|
// Output Safe JSON
|
|
555
567
|
const safeBatch = {
|
|
@@ -568,7 +580,13 @@ function register(program) {
|
|
|
568
580
|
const filename = `safe-tx-${Date.now()}.json`;
|
|
569
581
|
fs.writeFileSync(filename, JSON.stringify(safeBatch, null, 2));
|
|
570
582
|
console.log(`✅ Safe Transaction Builder JSON saved to ${filename}`);
|
|
571
|
-
console.log('
|
|
583
|
+
console.log('\n📋 Next steps:');
|
|
584
|
+
console.log(` 1. Import ${filename} in Safe Transaction Builder`);
|
|
585
|
+
console.log(` 2. Review and sign the transaction`);
|
|
586
|
+
console.log(` 3. Execute the transaction`);
|
|
587
|
+
if (subdaoAddr) {
|
|
588
|
+
console.log(` 4. Verify: sage library status --subdao ${subdaoAddr}`);
|
|
589
|
+
}
|
|
572
590
|
}
|
|
573
591
|
|
|
574
592
|
} catch (e) {
|
|
@@ -577,6 +595,142 @@ function register(program) {
|
|
|
577
595
|
}
|
|
578
596
|
});
|
|
579
597
|
|
|
598
|
+
// QoL: publish-manifest (pin only, no governance)
|
|
599
|
+
cmd
|
|
600
|
+
.command('publish-manifest')
|
|
601
|
+
.description('Build and pin a manifest from workspace skills without proposing')
|
|
602
|
+
.option('--json', 'Output CID as JSON')
|
|
603
|
+
.action(async (opts) => {
|
|
604
|
+
try {
|
|
605
|
+
const { readWorkspace } = require('../services/prompts/workspace');
|
|
606
|
+
const { findWorkspaceSkills } = require('../services/skills/discovery');
|
|
607
|
+
const IpfsManager = require('../ipfs-manager');
|
|
608
|
+
const fs = require('fs');
|
|
609
|
+
|
|
610
|
+
console.log('📦 Building manifest...');
|
|
611
|
+
const ws = readWorkspace() || {};
|
|
612
|
+
const skills = findWorkspaceSkills({ promptsDir: ws.promptsDir || 'prompts' });
|
|
613
|
+
if (skills.length === 0) throw new Error('No skills found in workspace');
|
|
614
|
+
const manifest = {
|
|
615
|
+
version: '1.0.0',
|
|
616
|
+
timestamp: new Date().toISOString(),
|
|
617
|
+
skills: skills.map((s) => ({ id: s.key, metadata: s.meta, content: fs.readFileSync(s.path, 'utf8') })),
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
console.log('☁️ Uploading to IPFS...');
|
|
621
|
+
const ipfs = new IpfsManager();
|
|
622
|
+
const { cid } = await ipfs.uploadJson(manifest, 'skills-manifest');
|
|
623
|
+
if (opts.json) {
|
|
624
|
+
console.log(JSON.stringify({ ok: true, cid }, null, 2));
|
|
625
|
+
} else {
|
|
626
|
+
console.log(`✅ Manifest CID: ${cid}`);
|
|
627
|
+
}
|
|
628
|
+
} catch (e) {
|
|
629
|
+
console.error('❌ publish-manifest failed:', e.message);
|
|
630
|
+
process.exit(1);
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// QoL: doctor wrapper for skills/governance readiness
|
|
635
|
+
cmd
|
|
636
|
+
.command('doctor')
|
|
637
|
+
.description('Validate workspace skills and diagnose SubDAO governance readiness')
|
|
638
|
+
.argument('[key]', 'Optional skill key to validate frontmatter')
|
|
639
|
+
.option('--subdao <address>', 'SubDAO address for governance checks')
|
|
640
|
+
.action(async (key, opts) => {
|
|
641
|
+
try {
|
|
642
|
+
const { readWorkspace } = require('../services/prompts/workspace');
|
|
643
|
+
const { resolveSkillFileByKey, readFrontmatter, findWorkspaceSkills } = require('../services/skills/discovery');
|
|
644
|
+
const { diagnoseSubDAO } = require('../services/governance/doctor');
|
|
645
|
+
const WalletManager = require('../wallet-manager');
|
|
646
|
+
|
|
647
|
+
// Validate skill frontmatter if requested
|
|
648
|
+
if (key) {
|
|
649
|
+
const ws = readWorkspace() || {};
|
|
650
|
+
const resolved = resolveSkillFileByKey({ promptsDir: ws.promptsDir || 'prompts', key });
|
|
651
|
+
if (!resolved) throw new Error(`Skill not found for key '${key}'`);
|
|
652
|
+
const fm = readFrontmatter(resolved.path);
|
|
653
|
+
console.log(`✅ Skill frontmatter OK for ${key}`);
|
|
654
|
+
console.log(fm);
|
|
655
|
+
} else {
|
|
656
|
+
// List count as a quick sanity
|
|
657
|
+
const ws = readWorkspace() || {};
|
|
658
|
+
const list = findWorkspaceSkills({ promptsDir: ws.promptsDir || 'prompts' });
|
|
659
|
+
console.log(`✅ Workspace skills found: ${list.length}`);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (opts.subdao) {
|
|
663
|
+
const wallet = new WalletManager();
|
|
664
|
+
await wallet.connect();
|
|
665
|
+
const provider = wallet.getProvider();
|
|
666
|
+
const signer = wallet.getSigner();
|
|
667
|
+
console.log('🩺 Running Governance Doctor...');
|
|
668
|
+
const doctor = await diagnoseSubDAO({ subdao: opts.subdao, provider, signer });
|
|
669
|
+
console.log(JSON.stringify({
|
|
670
|
+
status: doctor.recommendations.length ? 'warn' : 'ok',
|
|
671
|
+
canPropose: doctor.canPropose === true,
|
|
672
|
+
subdao: doctor.subdao,
|
|
673
|
+
issues: doctor.recommendations,
|
|
674
|
+
}, null, 2));
|
|
675
|
+
}
|
|
676
|
+
} catch (e) {
|
|
677
|
+
console.error('❌ doctor failed:', e.message);
|
|
678
|
+
process.exit(1);
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
// QoL: use (MCP helper)
|
|
683
|
+
cmd
|
|
684
|
+
.command('use')
|
|
685
|
+
.description('Help use a skill with MCP (prints config; optional server start)')
|
|
686
|
+
.argument('<key>', 'Skill key (e.g., skills/my-skill)')
|
|
687
|
+
.option('--server-start', 'Start MCP HTTP server')
|
|
688
|
+
.option('--stdio-config', 'Print Claude Desktop stdio config snippet instead')
|
|
689
|
+
.action(async (key, opts) => {
|
|
690
|
+
try {
|
|
691
|
+
const path = require('path');
|
|
692
|
+
const chalk = require('chalk');
|
|
693
|
+
const { readWorkspace } = require('../services/prompts/workspace');
|
|
694
|
+
const { resolveSkillFileByKey } = require('../services/skills/discovery');
|
|
695
|
+
|
|
696
|
+
const ws = readWorkspace() || {};
|
|
697
|
+
const resolved = resolveSkillFileByKey({ promptsDir: ws.promptsDir || 'prompts', key });
|
|
698
|
+
if (!resolved) throw new Error(`Skill not found for key '${key}'`);
|
|
699
|
+
|
|
700
|
+
if (opts.stdioConfig) {
|
|
701
|
+
const stdioPath = path.resolve(__dirname, '..', 'mcp-server-stdio.js');
|
|
702
|
+
const snippet = {
|
|
703
|
+
mcpServers: {
|
|
704
|
+
sage: {
|
|
705
|
+
command: process.execPath,
|
|
706
|
+
args: [stdioPath],
|
|
707
|
+
env: {},
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
console.log(JSON.stringify(snippet, null, 2));
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (opts.serverStart) {
|
|
716
|
+
const MCPServer = require('../mcp-server');
|
|
717
|
+
const server = new MCPServer({ port: 3333 });
|
|
718
|
+
await server.start();
|
|
719
|
+
console.log(chalk.green('✅ MCP server started on http://localhost:3333'));
|
|
720
|
+
console.log(chalk.cyan('Use in Cursor/Claude by configuring the HTTP/WS MCP server.'));
|
|
721
|
+
console.log(chalk.gray('Press Ctrl+C to stop.'));
|
|
722
|
+
} else {
|
|
723
|
+
console.log(chalk.cyan('To start the MCP server, run:'));
|
|
724
|
+
console.log(' sage mcp start');
|
|
725
|
+
console.log(chalk.cyan('For Claude stdio config, run:'));
|
|
726
|
+
console.log(' sage skills use', key, '--stdio-config');
|
|
727
|
+
}
|
|
728
|
+
} catch (e) {
|
|
729
|
+
console.error('❌ use failed:', e.message);
|
|
730
|
+
process.exit(1);
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
|
|
580
734
|
program.addCommand(cmd);
|
|
581
735
|
}
|
|
582
736
|
|
|
@@ -209,14 +209,19 @@ function register(program) {
|
|
|
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
211
|
.addCommand(
|
|
212
|
-
new Command('create')
|
|
212
|
+
new Command('create-playbook')
|
|
213
213
|
.description('Create a new SubDAO using a governance playbook (Plan/Apply)')
|
|
214
|
-
.option('--playbook <id>', 'Playbook ID (creator, squad, community)')
|
|
214
|
+
.option('--playbook <id>', 'Playbook ID (creator, squad, community, community-long)')
|
|
215
215
|
.option('--name <name>', 'SubDAO name')
|
|
216
216
|
.option('--description <desc>', 'SubDAO description')
|
|
217
217
|
.option('--owners <addresses>', 'Safe owners (comma-separated)')
|
|
218
218
|
.option('--threshold <n>', 'Safe threshold')
|
|
219
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')
|
|
220
225
|
.option('--yes', 'Skip confirmation')
|
|
221
226
|
.option('--dry-run', 'Generate plan only (do not execute)')
|
|
222
227
|
.option('--apply <file>', 'Apply a previously generated plan file')
|
|
@@ -228,6 +233,21 @@ function register(program) {
|
|
|
228
233
|
return;
|
|
229
234
|
}
|
|
230
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
|
+
|
|
231
251
|
const { planSubDAO } = require('../services/subdao/planner');
|
|
232
252
|
const plan = await planSubDAO(opts);
|
|
233
253
|
|
|
@@ -43,5 +43,20 @@
|
|
|
43
43
|
"roles": {
|
|
44
44
|
"description": "Token holders propose/vote. Timelock executes."
|
|
45
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
|
+
}
|
|
46
61
|
}
|
|
47
62
|
]
|
|
@@ -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
|
|