@sage-protocol/cli 0.4.0 → 0.4.1

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 (35) hide show
  1. package/dist/cli/browser-wallet-integration.js +0 -1
  2. package/dist/cli/cast-wallet-manager.js +0 -1
  3. package/dist/cli/commands/interview.js +149 -0
  4. package/dist/cli/commands/personal.js +138 -79
  5. package/dist/cli/commands/stake-status.js +0 -2
  6. package/dist/cli/config.js +28 -8
  7. package/dist/cli/governance-manager.js +28 -19
  8. package/dist/cli/index.js +32 -8
  9. package/dist/cli/library-manager.js +16 -6
  10. package/dist/cli/mcp-server-stdio.js +549 -0
  11. package/dist/cli/mcp-server.js +4 -30
  12. package/dist/cli/metamask-integration.js +0 -1
  13. package/dist/cli/privy-wallet-manager.js +2 -2
  14. package/dist/cli/prompt-manager.js +0 -1
  15. package/dist/cli/services/doctor/fixers.js +1 -1
  16. package/dist/cli/services/mcp/env-loader.js +2 -0
  17. package/dist/cli/services/mcp/quick-start.js +14 -15
  18. package/dist/cli/services/mcp/sage-tool-registry.js +330 -0
  19. package/dist/cli/services/mcp/tool-args-validator.js +31 -0
  20. package/dist/cli/services/metaprompt/anthropic-client.js +87 -0
  21. package/dist/cli/services/metaprompt/interview-driver.js +161 -0
  22. package/dist/cli/services/metaprompt/model-client.js +49 -0
  23. package/dist/cli/services/metaprompt/openai-client.js +67 -0
  24. package/dist/cli/services/metaprompt/persistence.js +86 -0
  25. package/dist/cli/services/metaprompt/prompt-builder.js +186 -0
  26. package/dist/cli/services/metaprompt/session.js +18 -80
  27. package/dist/cli/services/metaprompt/slot-planner.js +115 -0
  28. package/dist/cli/services/metaprompt/templates.json +130 -0
  29. package/dist/cli/subdao.js +0 -3
  30. package/dist/cli/sxxx-manager.js +0 -1
  31. package/dist/cli/utils/tx-wait.js +0 -3
  32. package/dist/cli/wallet-manager.js +18 -19
  33. package/dist/cli/walletconnect-integration.js +0 -1
  34. package/dist/cli/wizard-manager.js +0 -1
  35. package/package.json +3 -1
@@ -1,5 +1,4 @@
1
1
  const { ethers } = require('ethers');
2
- try { require('dotenv').config({ quiet: true }); } catch (_) {}
3
2
 
4
3
  class BrowserWalletIntegration {
5
4
  constructor(deps = {}) {
@@ -4,7 +4,6 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const readline = require('readline');
6
6
  const os = require('os');
7
- try { require('dotenv').config({ quiet: true }); } catch (_) {}
8
7
 
9
8
  // Simple color functions to replace chalk
10
9
  const colors = {
@@ -0,0 +1,149 @@
1
+ const { Command } = require('commander');
2
+ const readline = require('readline');
3
+ const InterviewDriver = require('../services/metaprompt/interview-driver');
4
+ const MetapromptPersistence = require('../services/metaprompt/persistence');
5
+ const config = require('../config');
6
+ const templates = require('../services/metaprompt/templates.json');
7
+
8
+ function register(program) {
9
+ program
10
+ .command('interview')
11
+ .description('Create a new AI persona via an interactive interview')
12
+ .alias('persona')
13
+ .option('-t, --template <key>', 'Persona template (coding-assistant, governance-helper, research-analyst, custom)', 'custom')
14
+ .option('-g, --goal <description>', 'Initial goal or description to seed the interview')
15
+ .option('-p, --provider <name>', 'AI provider (anthropic, openai)')
16
+ .option('-m, --model <name>', 'Specific model name')
17
+ .option('-k, --api-key <key>', 'API key override')
18
+ .option('--save-key <name>', 'Name for the generated skill file (defaults to template name + timestamp)')
19
+ .option('--list-templates', 'List available persona templates and exit')
20
+ .action(async (options) => {
21
+ if (options.listTemplates) {
22
+ listTemplates();
23
+ return;
24
+ }
25
+ await runInterview(options);
26
+ });
27
+ }
28
+
29
+ function listTemplates() {
30
+ console.log('\nAvailable Persona Templates:\n');
31
+ for (const [key, tmpl] of Object.entries(templates)) {
32
+ console.log(` ${key}`);
33
+ console.log(` ${tmpl.description}`);
34
+ console.log(` Default goal: ${tmpl.default_goal}`);
35
+ console.log('');
36
+ }
37
+ console.log('Usage: sage interview --template <key>');
38
+ }
39
+
40
+ async function runInterview(options) {
41
+ // 1. Check Config
42
+ const aiConfig = config.readAIConfig();
43
+ if (!aiConfig.anthropicApiKey && !aiConfig.openaiApiKey && !options.apiKey) {
44
+ console.log('⚠️ No AI keys found.');
45
+ console.log('Please run `sage config ai set --provider anthropic --key sk-...`');
46
+ console.log('Or provide --api-key and --provider flags.');
47
+ process.exit(1);
48
+ }
49
+
50
+ // 2. Select Template
51
+ let templateKey = options.template;
52
+ if (!templates[templateKey]) {
53
+ console.warn(`⚠️ Template '${templateKey}' not found. Falling back to 'custom'.`);
54
+ console.log('Use --list-templates to see available templates.\n');
55
+ templateKey = 'custom';
56
+ }
57
+ const template = templates[templateKey];
58
+
59
+ console.log(`\n🎤 Starting Interview: ${template.name}`);
60
+ console.log(`ℹ️ ${template.description}`);
61
+ if (options.goal) {
62
+ console.log(`📝 Initial context: "${options.goal}"`);
63
+ }
64
+ console.log('\nType /quit to abort the interview.\n');
65
+
66
+ const rl = readline.createInterface({
67
+ input: process.stdin,
68
+ output: process.stdout
69
+ });
70
+
71
+ // 3. Initialize Driver with initial description from --goal
72
+ const driver = new InterviewDriver(config, {
73
+ templateKey,
74
+ initialDescription: options.goal || '',
75
+ provider: options.provider,
76
+ model: options.model,
77
+ apiKey: options.apiKey
78
+ });
79
+
80
+ console.log('🔄 Planning session...');
81
+ await driver.init();
82
+
83
+ // Show which slots were pre-filled from the goal
84
+ const preFilledCount = Object.keys(driver.answers).length;
85
+ if (preFilledCount > 0) {
86
+ console.log(`✅ Pre-filled ${preFilledCount} slot(s) from your initial context.`);
87
+ }
88
+
89
+ // 4. Interview Loop
90
+ while (true) {
91
+ const question = await driver.getNextQuestion();
92
+
93
+ if (!question) {
94
+ console.log('\n✅ Interview complete! Generating persona...');
95
+ break;
96
+ }
97
+
98
+ // Show progress
99
+ const filled = Object.keys(driver.answers).length;
100
+ const total = driver.slots.length;
101
+ const progress = `[${filled}/${total}]`;
102
+
103
+ // Ask
104
+ const answer = await new Promise(resolve => {
105
+ rl.question(`\n${progress} 🤖 ${question}\n> `, resolve);
106
+ });
107
+
108
+ if (!answer.trim()) {
109
+ console.log('(Skipping...)');
110
+ continue;
111
+ }
112
+
113
+ if (answer.trim().toLowerCase() === '/quit') {
114
+ console.log('⚠️ Interview aborted.');
115
+ rl.close();
116
+ process.exit(0);
117
+ }
118
+
119
+ await driver.processAnswer(answer);
120
+ }
121
+
122
+ rl.close();
123
+
124
+ // 5. Generate and Save
125
+ const systemPrompt = driver.generateSystemPrompt();
126
+ const persistence = new MetapromptPersistence(config);
127
+
128
+ const slug = options.saveKey || `${templateKey}-${Date.now().toString().slice(-6)}`;
129
+
130
+ // Save Artifacts
131
+ const paths = persistence.saveMetaprompt(slug, {
132
+ templateKey,
133
+ transcript: driver.transcript,
134
+ answers: driver.answers
135
+ });
136
+
137
+ // Save Skill
138
+ const skillPath = persistence.saveSkill(slug, systemPrompt);
139
+ persistence.appendToAgentsList(slug);
140
+
141
+ console.log(`\n🎉 Persona Created: ${slug}`);
142
+ console.log(` 📄 Skill: ${skillPath}`);
143
+ console.log(` 📝 History: ${paths.metaprompt}`);
144
+ console.log(`\nNext Steps:`);
145
+ console.log(` sage prompts list-workspace-skills`);
146
+ console.log(` sage prompts export skills/${slug} --as cursor`);
147
+ }
148
+
149
+ module.exports = { register };
@@ -94,13 +94,134 @@ function resolveGateway(pref) {
94
94
  }
95
95
 
96
96
  function resolveSubgraphUrl(override) {
97
- return override
98
- || process.env.SUBGRAPH_URL
99
- || process.env.NEXT_PUBLIC_GRAPH_ENDPOINT
100
- || process.env.NEXT_PUBLIC_SUBGRAPH_URL
101
- || null;
97
+ if (override) return override;
98
+
99
+ // Prefer profile-configured subgraph URLs (addresses) over raw env
100
+ try {
101
+ const profiles = ConfigManager.readProfiles();
102
+ const active = profiles.activeProfile || 'default';
103
+ const profile = profiles.profiles?.[active] || {};
104
+ const addresses = profile.addresses || {};
105
+ const fromProfile = addresses.SAGE_SUBGRAPH_URL || addresses.SUBGRAPH_URL;
106
+ if (fromProfile && typeof fromProfile === 'string') {
107
+ return fromProfile;
108
+ }
109
+ } catch (_) {
110
+ // fall through to env-based resolution
111
+ }
112
+
113
+ return (
114
+ process.env.SAGE_SUBGRAPH_URL ||
115
+ process.env.SUBGRAPH_URL ||
116
+ process.env.NEXT_PUBLIC_GRAPH_ENDPOINT ||
117
+ process.env.NEXT_PUBLIC_SUBGRAPH_URL ||
118
+ null
119
+ );
102
120
  }
103
121
 
122
+ // Centralised subgraph queries for personal marketplace flows
123
+ const PERSONAL_LISTINGS_QUERY = `
124
+ query($creator: Bytes!, $first: Int!) {
125
+ personalListings(
126
+ where: { creator: $creator, listed: true }
127
+ first: $first
128
+ orderBy: updatedAt
129
+ orderDirection: desc
130
+ ) {
131
+ id
132
+ creator
133
+ key
134
+ price
135
+ listed
136
+ createdAt
137
+ updatedAt
138
+ }
139
+ }
140
+ `;
141
+
142
+ const PERSONAL_PURCHASES_QUERY = `
143
+ query($creator: Bytes!, $first: Int!) {
144
+ licensePurchases(
145
+ where: { creator: $creator }
146
+ first: $first
147
+ orderBy: blockTimestamp
148
+ orderDirection: desc
149
+ ) {
150
+ key
151
+ price
152
+ receiptId
153
+ buyer
154
+ blockTimestamp
155
+ encryptedCid
156
+ metadata
157
+ }
158
+ }
159
+ `;
160
+
161
+ const PERSONAL_RESOURCES_BY_RECEIPT_QUERY = `
162
+ query($ids: [BigInt!]) {
163
+ personalResources(
164
+ where: { receiptId_in: $ids }
165
+ first: 200
166
+ orderBy: recordedAt
167
+ orderDirection: desc
168
+ ) {
169
+ key
170
+ receiptId
171
+ encryptedCid
172
+ metadata
173
+ recordedAt
174
+ }
175
+ }
176
+ `;
177
+
178
+ const MY_LICENSES_QUERY = `
179
+ query($holder: Bytes!, $first: Int!) {
180
+ receiptBalances(
181
+ where: { holder: $holder, balance_gt: 0 }
182
+ first: $first
183
+ orderBy: updatedAt
184
+ orderDirection: desc
185
+ ) {
186
+ receiptId
187
+ balance
188
+ updatedAt
189
+ }
190
+ licensePurchases(
191
+ where: { buyer: $holder }
192
+ first: $first
193
+ orderBy: blockTimestamp
194
+ orderDirection: desc
195
+ ) {
196
+ creator
197
+ key
198
+ price
199
+ netToCreator
200
+ fee
201
+ receiptId
202
+ blockTimestamp
203
+ encryptedCid
204
+ metadata
205
+ }
206
+ }
207
+ `;
208
+
209
+ const PERSONAL_RESOURCES_FOR_BALANCES_QUERY = `
210
+ query($ids: [BigInt!]) {
211
+ personalResources(
212
+ where: { receiptId_in: $ids }
213
+ first: 100
214
+ orderBy: recordedAt
215
+ orderDirection: desc
216
+ ) {
217
+ receiptId
218
+ encryptedCid
219
+ metadata
220
+ recordedAt
221
+ }
222
+ }
223
+ `;
224
+
104
225
  async function createAction(opts) {
105
226
  const provider = await getProvider();
106
227
  const wallet = await getWallet(provider);
@@ -436,21 +557,8 @@ async function listAction(opts = {}) {
436
557
 
437
558
  if (!explicitKeys.length && subgraphUrl) {
438
559
  // First try to get all listings (PriceSet events)
439
- const listingsQuery = `
440
- query($creator: Bytes!, $first: Int!) {
441
- personalListings(where: { creator: $creator, listed: true }, first: $first, orderBy: updatedAt, orderDirection: desc) {
442
- id
443
- creator
444
- key
445
- price
446
- listed
447
- createdAt
448
- updatedAt
449
- }
450
- }
451
- `;
452
560
  try {
453
- const listingsData = await subgraphQuery(subgraphUrl, listingsQuery, {
561
+ const listingsData = await subgraphQuery(subgraphUrl, PERSONAL_LISTINGS_QUERY, {
454
562
  creator: creatorAddress.toLowerCase(),
455
563
  first: limit,
456
564
  });
@@ -476,21 +584,8 @@ async function listAction(opts = {}) {
476
584
  }
477
585
 
478
586
  // Also fetch purchases for additional metadata
479
- const purchasesQuery = `
480
- query($creator: Bytes!, $first: Int!) {
481
- licensePurchases(where: { creator: $creator }, first: $first, orderBy: blockTimestamp, orderDirection: desc) {
482
- key
483
- price
484
- receiptId
485
- buyer
486
- blockTimestamp
487
- encryptedCid
488
- metadata
489
- }
490
- }
491
- `;
492
587
  try {
493
- const data = await subgraphQuery(subgraphUrl, purchasesQuery, {
588
+ const data = await subgraphQuery(subgraphUrl, PERSONAL_PURCHASES_QUERY, {
494
589
  creator: creatorAddress.toLowerCase(),
495
590
  first: limit,
496
591
  });
@@ -524,18 +619,7 @@ async function listAction(opts = {}) {
524
619
  .filter(Boolean);
525
620
 
526
621
  if (receiptIdsForResources.length) {
527
- const resQuery = `
528
- query($ids: [BigInt!]) {
529
- personalResources(where: { receiptId_in: $ids }, first: 200, orderBy: recordedAt, orderDirection: desc) {
530
- key
531
- receiptId
532
- encryptedCid
533
- metadata
534
- recordedAt
535
- }
536
- }
537
- `;
538
- const resData = await subgraphQuery(subgraphUrl, resQuery, { ids: receiptIdsForResources });
622
+ const resData = await subgraphQuery(subgraphUrl, PERSONAL_RESOURCES_BY_RECEIPT_QUERY, { ids: receiptIdsForResources });
539
623
  personalResourceMap = new Map((resData?.personalResources || []).map((r) => {
540
624
  const keyHash = String(r.key);
541
625
  const receiptId = r.receiptId ? String(r.receiptId) : '';
@@ -640,33 +724,12 @@ async function myLicensesAction(opts = {}) {
640
724
  const holderAddress = opts.holder ? getAddress(opts.holder) : getAddress(await wallet.getAddress());
641
725
  const subgraphUrl = resolveSubgraphUrl(opts.subgraph);
642
726
  if (!subgraphUrl) {
643
- throw new Error('Subgraph URL not configured. Pass --subgraph or set SUBGRAPH_URL.');
727
+ throw new Error('Subgraph URL not configured. Pass --subgraph or configure SUBGRAPH_URL/SAGE_SUBGRAPH_URL via sage config addresses/import.');
644
728
  }
645
729
 
646
730
  const limit = opts.limit ? Math.max(1, parseInt(opts.limit, 10)) : DEFAULT_LIST_LIMIT;
647
731
 
648
- const document = `
649
- query($holder: Bytes!, $first: Int!) {
650
- receiptBalances(where: { holder: $holder, balance_gt: 0 }, first: $first, orderBy: updatedAt, orderDirection: desc) {
651
- receiptId
652
- balance
653
- updatedAt
654
- }
655
- licensePurchases(where: { buyer: $holder }, first: $first, orderBy: blockTimestamp, orderDirection: desc) {
656
- creator
657
- key
658
- price
659
- netToCreator
660
- fee
661
- receiptId
662
- blockTimestamp
663
- encryptedCid
664
- metadata
665
- }
666
- }
667
- `;
668
-
669
- const data = await subgraphQuery(subgraphUrl, document, {
732
+ const data = await subgraphQuery(subgraphUrl, MY_LICENSES_QUERY, {
670
733
  holder: holderAddress.toLowerCase(),
671
734
  first: limit,
672
735
  });
@@ -680,16 +743,7 @@ async function myLicensesAction(opts = {}) {
680
743
 
681
744
  let resourceByReceipt = new Map();
682
745
  if (balanceIds.length) {
683
- const enriched = await subgraphQuery(subgraphUrl, `
684
- query($ids: [BigInt!]) {
685
- personalResources(where: { receiptId_in: $ids }, first: 100, orderBy: recordedAt, orderDirection: desc) {
686
- receiptId
687
- encryptedCid
688
- metadata
689
- recordedAt
690
- }
691
- }
692
- `, { ids: balanceIds });
746
+ const enriched = await subgraphQuery(subgraphUrl, PERSONAL_RESOURCES_FOR_BALANCES_QUERY, { ids: balanceIds });
693
747
  resourceByReceipt = new Map((enriched?.personalResources || []).map((res) => [String(res.receiptId), res]));
694
748
  }
695
749
 
@@ -1216,7 +1270,12 @@ function register(program) {
1216
1270
  cmd.addCommand(premium);
1217
1271
  }
1218
1272
 
1219
- module.exports = { register };
1220
1273
  const { query: subgraphQuery } = sdk.subgraph;
1221
1274
 
1222
1275
  const DEFAULT_LIST_LIMIT = 25;
1276
+
1277
+ module.exports = {
1278
+ register,
1279
+ // exposed for unit tests
1280
+ resolveSubgraphUrl,
1281
+ };
@@ -12,8 +12,6 @@ function register(program) {
12
12
  const WalletManager = require('@sage-protocol/wallet-manager');
13
13
  const { resolveGovContext } = require('../utils/gov-context');
14
14
 
15
- // Load environment
16
- require('dotenv').config();
17
15
  const rpcUrl = process.env.RPC_URL || process.env.BASE_SEPOLIA_RPC || 'https://base-sepolia.publicnode.com';
18
16
  const provider = new ethers.JsonRpcProvider(rpcUrl);
19
17
 
@@ -32,6 +32,15 @@ function collectAliasKeys(key) {
32
32
  return ADDRESS_ALIAS_MAP.get(upper) || [upper];
33
33
  }
34
34
 
35
+ function isVerbose() {
36
+ return (
37
+ process.env.SAGE_VERBOSE === '1' ||
38
+ process.env.VERBOSE === 'true' ||
39
+ process.env.DEBUG === '*' ||
40
+ process.env.DEBUG === 'sage'
41
+ );
42
+ }
43
+
35
44
  function isQuiet() {
36
45
  return (
37
46
  process.env.SAGE_SUPPRESS_CONFIG_LOGS === '1' ||
@@ -285,13 +294,14 @@ function createLocalConfig() {
285
294
 
286
295
  loadEnv() {
287
296
  const envPath = path.join(this.projectDir, '.env');
297
+ const quietJson = String(process.env.SAGE_QUIET_JSON || '').trim() === '1';
288
298
  if (fs.existsSync(envPath)) {
289
299
  const override = process.env.SAGE_CLI_TEST_MODE === '1' ? false : true;
290
300
  try {
291
301
  require('dotenv').config({ path: envPath, override, quiet: true });
292
302
  } catch (_) {}
293
- if (!isQuiet()) console.log('📁 Loaded .env from project directory');
294
- } else if (!isQuiet()) {
303
+ if (isVerbose() && !quietJson) console.log('📁 Loaded .env from project directory');
304
+ } else if (isVerbose() && !quietJson) {
295
305
  console.log('⚠️ .env file not found in project directory');
296
306
  }
297
307
 
@@ -327,9 +337,9 @@ function createLocalConfig() {
327
337
  process.env.GITHUB_TOKEN = git.githubToken;
328
338
  }
329
339
  }
330
- if (!isQuiet()) console.log(`🧭 Loaded config profile: ${active}`);
340
+ if (isVerbose() && !quietJson) console.log(`🧭 Loaded config profile: ${active}`);
331
341
  } catch (e) {
332
- console.log('⚠️ Failed to load config profile:', e.message);
342
+ if (isVerbose() && !quietJson) console.log('⚠️ Failed to load config profile:', e.message);
333
343
  }
334
344
  return true;
335
345
  },
@@ -524,13 +534,19 @@ function createLocalConfig() {
524
534
  },
525
535
 
526
536
  getWalletConfig() {
527
- console.log('🔍 getWalletConfig called');
537
+ const verbose = process.env.SAGE_VERBOSE === '1';
538
+ const quietJson = String(process.env.SAGE_QUIET_JSON || '').trim() === '1';
539
+ if (verbose && !quietJson) {
540
+ console.log('🔍 getWalletConfig called');
541
+ }
528
542
  try {
529
543
  const profiles = this.readProfiles();
530
544
  const activeProfile = profiles.activeProfile || 'default';
531
545
  const profile = profiles.profiles?.[activeProfile];
532
546
  const walletType = (profile?.wallet?.type || 'cast').toLowerCase();
533
- console.log('🔍 Debug: walletType =', walletType, 'defaultAccount =', profile?.wallet?.defaultAccount);
547
+ if (verbose && !quietJson) {
548
+ console.log('🔍 Debug: walletType =', walletType, 'defaultAccount =', profile?.wallet?.defaultAccount);
549
+ }
534
550
  const defaultAccount = profile?.wallet?.defaultAccount;
535
551
  if (walletType === 'cast' && defaultAccount) {
536
552
  return { type: 'cast', account: defaultAccount, castFlag: `--from ${defaultAccount}` };
@@ -547,10 +563,14 @@ function createLocalConfig() {
547
563
  }
548
564
  if (walletType === 'privy' && defaultAccount) return { type: 'privy', account: defaultAccount, castFlag: '' };
549
565
  if (walletType === 'web3auth' && defaultAccount) return { type: 'web3auth', account: defaultAccount, castFlag: '' };
550
- console.log('⚠️ No default account configured. Using deployment-wallet. Set with: sage wallet use <address>');
566
+ if (!quietJson) {
567
+ console.log('⚠️ No default account configured. Using deployment-wallet. Set with: sage wallet use <address>');
568
+ }
551
569
  return { type: walletType, account: defaultAccount || 'deployment-wallet', castFlag: walletType === 'cast' ? '--account deployment-wallet' : '' };
552
570
  } catch (e) {
553
- console.log('⚠️ Failed to load wallet config:', e.message);
571
+ if (String(process.env.SAGE_QUIET_JSON || '').trim() !== '1') {
572
+ console.log('⚠️ Failed to load wallet config:', e.message);
573
+ }
554
574
  return { type: 'cast', account: 'deployment-wallet', castFlag: '--account deployment-wallet' };
555
575
  }
556
576
  },
@@ -1,6 +1,5 @@
1
1
  const { ethers } = require('ethers');
2
2
  const { resolveArtifact } = require('./utils/artifacts');
3
- try { require('dotenv').config({ quiet: true }); } catch (_) {}
4
3
  let colors; try { colors = (require('chalk').default || require('chalk')); } catch (e) { colors = { blue: (s)=>s, green: (s)=>s, red: (s)=>s, yellow: (s)=>s, cyan: (s)=>s }; }
5
4
  const fs = require('fs');
6
5
  const path = require('path');
@@ -1284,6 +1283,8 @@ class GovernanceManager {
1284
1283
  }
1285
1284
 
1286
1285
  async executeProposal(id, opts = {}) {
1286
+ const verbose = process.env.SAGE_VERBOSE === '1';
1287
+ const quietJson = String(process.env.SAGE_QUIET_JSON || '').trim() === '1';
1287
1288
  try {
1288
1289
  const { simulate } = opts;
1289
1290
  if (simulate) {
@@ -1352,19 +1353,23 @@ class GovernanceManager {
1352
1353
  throw new Error('Cached tuple has mismatched array lengths');
1353
1354
  }
1354
1355
  const descriptionHash = ethers.keccak256(ethers.toUtf8Bytes(description));
1355
- console.log('🔎 Using cached proposal tuple for execute');
1356
- console.log(' targets:', targets);
1357
- console.log(' values:', values.map(v => v.toString()));
1358
- console.log(' calldatas:', calldatas);
1359
- console.log(' descriptionHash:', descriptionHash);
1356
+ if (!quietJson) {
1357
+ console.log('🔎 Using cached proposal tuple for execute');
1358
+ if (verbose) {
1359
+ console.log(' targets:', targets);
1360
+ console.log(' values:', values.map(v => v.toString()));
1361
+ console.log(' calldatas:', calldatas);
1362
+ console.log(' descriptionHash:', descriptionHash);
1363
+ }
1364
+ }
1360
1365
  // Optional: verify readiness
1361
1366
  try {
1362
1367
  const st = await this.governor.state(this.normalizeProposalId(proposalId));
1363
1368
  const nm = this.getStateName(st);
1364
1369
  let eta = 0n;
1365
1370
  try { eta = await this.governor.proposalEta(this.normalizeProposalId(proposalId)); } catch {}
1366
- if (eta && eta > 0n) console.log(`🔍 Debug: ETA=${eta.toString()}`);
1367
- console.log(`🔍 Debug: Proposal state: ${nm} (${st})`);
1371
+ if (verbose && !quietJson && eta && eta > 0n) console.log(`🔍 Debug: ETA=${eta.toString()}`);
1372
+ if (verbose && !quietJson) console.log(`🔍 Debug: Proposal state: ${nm} (${st})`);
1368
1373
  } catch {}
1369
1374
 
1370
1375
  if (simulate) {
@@ -1447,7 +1452,9 @@ class GovernanceManager {
1447
1452
 
1448
1453
  // Fallback: if no indexed events found, try broader query
1449
1454
  if (!logs.length) {
1450
- console.log('Debug - No indexed events found, trying all ProposalCreated events...');
1455
+ if (verbose && !quietJson) {
1456
+ console.log('Debug - No indexed events found, trying all ProposalCreated events...');
1457
+ }
1451
1458
  const allLogs = await this.provider.getLogs({
1452
1459
  topics: [eventSig],
1453
1460
  address: governorAddr,
@@ -1508,26 +1515,28 @@ class GovernanceManager {
1508
1515
  // Compute descriptionHash exactly like the working script
1509
1516
  const descriptionHash = ethers.keccak256(ethers.toUtf8Bytes(description));
1510
1517
 
1511
- console.log('Governor:', governorAddr);
1512
- console.log('Executing with:');
1513
- console.log(' targets:', targets);
1514
- console.log(' values:', values.map(v => v.toString()));
1515
- console.log(' calldatas[0..]:', calldatas.length);
1516
- console.log(' descriptionHash:', descriptionHash);
1518
+ if (verbose && !quietJson) {
1519
+ console.log('Governor:', governorAddr);
1520
+ console.log('Executing with:');
1521
+ console.log(' targets:', targets);
1522
+ console.log(' values:', values.map(v => v.toString()));
1523
+ console.log(' calldatas[0..]:', calldatas.length);
1524
+ console.log(' descriptionHash:', descriptionHash);
1525
+ }
1517
1526
 
1518
1527
  // Check proposal state before executing
1519
- console.log('🔍 Debug: Checking proposal state...');
1528
+ if (verbose && !quietJson) console.log('🔍 Debug: Checking proposal state...');
1520
1529
  const state = await this.governor.state(this.normalizeProposalId(id));
1521
1530
  const stateNum = Number(state);
1522
1531
  const stateName = this.getStateName(stateNum);
1523
- console.log(`🔍 Debug: Proposal state: ${stateName} (${stateNum})`);
1532
+ if (verbose && !quietJson) console.log(`🔍 Debug: Proposal state: ${stateName} (${stateNum})`);
1524
1533
 
1525
1534
  if (stateNum !== 4) { // 4 = Succeeded
1526
1535
  console.log(`⚠️ Warning: Proposal is in state ${stateName}, not Succeeded. Execution may fail.`);
1527
1536
  }
1528
1537
 
1529
1538
  // Execute using tuple (like the working script)
1530
- console.log('🔍 Debug: Executing proposal...');
1539
+ if (verbose && !quietJson) console.log('🔍 Debug: Executing proposal...');
1531
1540
  try {
1532
1541
  const tx = await this.governor.execute(targets, values, calldatas, descriptionHash);
1533
1542
  console.log('⏳ Waiting for transaction confirmation...');
@@ -1559,7 +1568,7 @@ class GovernanceManager {
1559
1568
 
1560
1569
  // Check for LibraryUpdated events if this was a library proposal
1561
1570
  if (this.currentSubDAO) {
1562
- console.log('🔍 Debug: Checking for LibraryUpdated events...');
1571
+ if (verbose && !quietJson) console.log('🔍 Debug: Checking for LibraryUpdated events...');
1563
1572
  try {
1564
1573
  const { resolveRegistryAddress } = require('./utils/address-resolution');
1565
1574
  const registryAddress = resolveRegistryAddress().registry;