@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.
Files changed (42) hide show
  1. package/dist/cli/commands/config.js +28 -0
  2. package/dist/cli/commands/doctor.js +87 -8
  3. package/dist/cli/commands/gov-config.js +81 -0
  4. package/dist/cli/commands/governance.js +152 -72
  5. package/dist/cli/commands/library.js +9 -0
  6. package/dist/cli/commands/proposals.js +187 -17
  7. package/dist/cli/commands/skills.js +737 -0
  8. package/dist/cli/commands/subdao.js +96 -132
  9. package/dist/cli/config/playbooks.json +62 -0
  10. package/dist/cli/config.js +15 -0
  11. package/dist/cli/governance-manager.js +25 -4
  12. package/dist/cli/index.js +6 -7
  13. package/dist/cli/library-manager.js +79 -0
  14. package/dist/cli/mcp-server-stdio.js +1387 -166
  15. package/dist/cli/schemas/manifest.schema.json +55 -0
  16. package/dist/cli/services/doctor/fixers.js +134 -0
  17. package/dist/cli/services/governance/doctor.js +140 -0
  18. package/dist/cli/services/governance/playbooks.js +97 -0
  19. package/dist/cli/services/mcp/bulk-operations.js +272 -0
  20. package/dist/cli/services/mcp/dependency-analyzer.js +202 -0
  21. package/dist/cli/services/mcp/library-listing.js +2 -2
  22. package/dist/cli/services/mcp/local-prompt-collector.js +1 -0
  23. package/dist/cli/services/mcp/manifest-downloader.js +5 -3
  24. package/dist/cli/services/mcp/manifest-fetcher.js +17 -1
  25. package/dist/cli/services/mcp/manifest-workflows.js +127 -15
  26. package/dist/cli/services/mcp/quick-start.js +287 -0
  27. package/dist/cli/services/mcp/stdio-runner.js +30 -5
  28. package/dist/cli/services/mcp/template-manager.js +156 -0
  29. package/dist/cli/services/mcp/templates/default-templates.json +84 -0
  30. package/dist/cli/services/mcp/tool-args-validator.js +56 -0
  31. package/dist/cli/services/mcp/trending-formatter.js +1 -1
  32. package/dist/cli/services/mcp/unified-prompt-search.js +2 -2
  33. package/dist/cli/services/metaprompt/designer.js +12 -5
  34. package/dist/cli/services/skills/discovery.js +99 -0
  35. package/dist/cli/services/subdao/applier.js +229 -0
  36. package/dist/cli/services/subdao/planner.js +142 -0
  37. package/dist/cli/subdao-manager.js +14 -0
  38. package/dist/cli/utils/aliases.js +28 -6
  39. package/dist/cli/utils/contract-error-decoder.js +61 -0
  40. package/dist/cli/utils/suggestions.js +25 -13
  41. package/package.json +3 -2
  42. 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
- const topic = helper.interface.getEventTopic('ProposalTupleIndexed');
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
- const GovernanceManager = require('../governance-manager');
156
- const gm = new GovernanceManager();
157
- await gm.configureContext({ provider, governor: ctx.governor, timelock: ctx.timelock, subdao: ctx.subdao });
158
- const summary = await gm.describeProposal?.(id).catch(async () => ({ id, description: '(unable to summarize with current provider limits)' }));
159
- if (opts.json) console.log(JSON.stringify(withJsonVersion(summary), null, 2));
160
- else {
161
- console.log(`Proposal ${summary.id}`);
162
- if (summary.title) console.log(summary.title);
163
- if (summary.description) console.log(summary.description);
164
- if (Array.isArray(summary.targets)) {
165
- console.log('Targets:', summary.targets.length);
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
- await gm.configureContext({ provider, governor: ctx.governor, timelock: ctx.timelock, subdao: ctx.subdao });
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
- await gm.configureContext({ provider, governor: ctx.governor, timelock: ctx.timelock, subdao: ctx.subdao });
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.queue(id);
331
+ await gm.queueProposal(id);
287
332
  } catch (_) {}
288
- await gm.execute(id);
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