@sage-protocol/cli 0.3.10 → 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 (36) 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 +234 -89
  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/mcp-setup.md +35 -34
  13. package/dist/cli/metamask-integration.js +0 -1
  14. package/dist/cli/privy-wallet-manager.js +2 -2
  15. package/dist/cli/prompt-manager.js +0 -1
  16. package/dist/cli/services/doctor/fixers.js +1 -1
  17. package/dist/cli/services/mcp/env-loader.js +2 -0
  18. package/dist/cli/services/mcp/quick-start.js +14 -15
  19. package/dist/cli/services/mcp/sage-tool-registry.js +330 -0
  20. package/dist/cli/services/mcp/tool-args-validator.js +31 -0
  21. package/dist/cli/services/metaprompt/anthropic-client.js +87 -0
  22. package/dist/cli/services/metaprompt/interview-driver.js +161 -0
  23. package/dist/cli/services/metaprompt/model-client.js +49 -0
  24. package/dist/cli/services/metaprompt/openai-client.js +67 -0
  25. package/dist/cli/services/metaprompt/persistence.js +86 -0
  26. package/dist/cli/services/metaprompt/prompt-builder.js +186 -0
  27. package/dist/cli/services/metaprompt/session.js +18 -80
  28. package/dist/cli/services/metaprompt/slot-planner.js +115 -0
  29. package/dist/cli/services/metaprompt/templates.json +130 -0
  30. package/dist/cli/subdao.js +0 -3
  31. package/dist/cli/sxxx-manager.js +0 -1
  32. package/dist/cli/utils/tx-wait.js +0 -3
  33. package/dist/cli/wallet-manager.js +18 -19
  34. package/dist/cli/walletconnect-integration.js +0 -1
  35. package/dist/cli/wizard-manager.js +0 -1
  36. 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 };
@@ -2,6 +2,7 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const axios = require('axios');
4
4
  const { getAddress, id, parseUnits, formatUnits, Contract, MaxUint256 } = require('ethers');
5
+ const { decryptAesGcm, fromB64 } = require('../utils/aes');
5
6
  const sdk = require('@sage-protocol/sdk');
6
7
  const { formatJson } = require('../utils/format');
7
8
  const { getProvider, getWallet } = require('../utils/provider');
@@ -93,13 +94,134 @@ function resolveGateway(pref) {
93
94
  }
94
95
 
95
96
  function resolveSubgraphUrl(override) {
96
- return override
97
- || process.env.SUBGRAPH_URL
98
- || process.env.NEXT_PUBLIC_GRAPH_ENDPOINT
99
- || process.env.NEXT_PUBLIC_SUBGRAPH_URL
100
- || 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
+ );
101
120
  }
102
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
+
103
225
  async function createAction(opts) {
104
226
  const provider = await getProvider();
105
227
  const wallet = await getWallet(provider);
@@ -435,21 +557,8 @@ async function listAction(opts = {}) {
435
557
 
436
558
  if (!explicitKeys.length && subgraphUrl) {
437
559
  // First try to get all listings (PriceSet events)
438
- const listingsQuery = `
439
- query($creator: Bytes!, $first: Int!) {
440
- personalListings(where: { creator: $creator, listed: true }, first: $first, orderBy: updatedAt, orderDirection: desc) {
441
- id
442
- creator
443
- key
444
- price
445
- listed
446
- createdAt
447
- updatedAt
448
- }
449
- }
450
- `;
451
560
  try {
452
- const listingsData = await subgraphQuery(subgraphUrl, listingsQuery, {
561
+ const listingsData = await subgraphQuery(subgraphUrl, PERSONAL_LISTINGS_QUERY, {
453
562
  creator: creatorAddress.toLowerCase(),
454
563
  first: limit,
455
564
  });
@@ -475,21 +584,8 @@ async function listAction(opts = {}) {
475
584
  }
476
585
 
477
586
  // Also fetch purchases for additional metadata
478
- const purchasesQuery = `
479
- query($creator: Bytes!, $first: Int!) {
480
- licensePurchases(where: { creator: $creator }, first: $first, orderBy: blockTimestamp, orderDirection: desc) {
481
- key
482
- price
483
- receiptId
484
- buyer
485
- blockTimestamp
486
- encryptedCid
487
- metadata
488
- }
489
- }
490
- `;
491
587
  try {
492
- const data = await subgraphQuery(subgraphUrl, purchasesQuery, {
588
+ const data = await subgraphQuery(subgraphUrl, PERSONAL_PURCHASES_QUERY, {
493
589
  creator: creatorAddress.toLowerCase(),
494
590
  first: limit,
495
591
  });
@@ -523,18 +619,7 @@ async function listAction(opts = {}) {
523
619
  .filter(Boolean);
524
620
 
525
621
  if (receiptIdsForResources.length) {
526
- const resQuery = `
527
- query($ids: [BigInt!]) {
528
- personalResources(where: { receiptId_in: $ids }, first: 200, orderBy: recordedAt, orderDirection: desc) {
529
- key
530
- receiptId
531
- encryptedCid
532
- metadata
533
- recordedAt
534
- }
535
- }
536
- `;
537
- const resData = await subgraphQuery(subgraphUrl, resQuery, { ids: receiptIdsForResources });
622
+ const resData = await subgraphQuery(subgraphUrl, PERSONAL_RESOURCES_BY_RECEIPT_QUERY, { ids: receiptIdsForResources });
538
623
  personalResourceMap = new Map((resData?.personalResources || []).map((r) => {
539
624
  const keyHash = String(r.key);
540
625
  const receiptId = r.receiptId ? String(r.receiptId) : '';
@@ -639,33 +724,12 @@ async function myLicensesAction(opts = {}) {
639
724
  const holderAddress = opts.holder ? getAddress(opts.holder) : getAddress(await wallet.getAddress());
640
725
  const subgraphUrl = resolveSubgraphUrl(opts.subgraph);
641
726
  if (!subgraphUrl) {
642
- 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.');
643
728
  }
644
729
 
645
730
  const limit = opts.limit ? Math.max(1, parseInt(opts.limit, 10)) : DEFAULT_LIST_LIMIT;
646
731
 
647
- const document = `
648
- query($holder: Bytes!, $first: Int!) {
649
- receiptBalances(where: { holder: $holder, balance_gt: 0 }, first: $first, orderBy: updatedAt, orderDirection: desc) {
650
- receiptId
651
- balance
652
- updatedAt
653
- }
654
- licensePurchases(where: { buyer: $holder }, first: $first, orderBy: blockTimestamp, orderDirection: desc) {
655
- creator
656
- key
657
- price
658
- netToCreator
659
- fee
660
- receiptId
661
- blockTimestamp
662
- encryptedCid
663
- metadata
664
- }
665
- }
666
- `;
667
-
668
- const data = await subgraphQuery(subgraphUrl, document, {
732
+ const data = await subgraphQuery(subgraphUrl, MY_LICENSES_QUERY, {
669
733
  holder: holderAddress.toLowerCase(),
670
734
  first: limit,
671
735
  });
@@ -679,16 +743,7 @@ async function myLicensesAction(opts = {}) {
679
743
 
680
744
  let resourceByReceipt = new Map();
681
745
  if (balanceIds.length) {
682
- const enriched = await subgraphQuery(subgraphUrl, `
683
- query($ids: [BigInt!]) {
684
- personalResources(where: { receiptId_in: $ids }, first: 100, orderBy: recordedAt, orderDirection: desc) {
685
- receiptId
686
- encryptedCid
687
- metadata
688
- recordedAt
689
- }
690
- }
691
- `, { ids: balanceIds });
746
+ const enriched = await subgraphQuery(subgraphUrl, PERSONAL_RESOURCES_FOR_BALANCES_QUERY, { ids: balanceIds });
692
747
  resourceByReceipt = new Map((enriched?.personalResources || []).map((res) => [String(res.receiptId), res]));
693
748
  }
694
749
 
@@ -734,6 +789,10 @@ async function myLicensesAction(opts = {}) {
734
789
  console.log(JSON.stringify(results, null, 2));
735
790
  } else if (results.length === 0) {
736
791
  console.log('ℹ️ No active personal licenses found for this holder.');
792
+ console.log('');
793
+ console.log(' If you recently purchased a license, the subgraph may still be syncing.');
794
+ console.log(' To check a specific license directly on-chain, use:');
795
+ console.log(' sage personal premium access <creator> <key>');
737
796
  } else {
738
797
  results.forEach((item) => {
739
798
  console.log(`🧾 ${item.receiptIdHex} — balance ${item.balance}`);
@@ -897,6 +956,32 @@ async function accessAction(creator, key, opts) {
897
956
 
898
957
  let encryptedCid = resource?.encryptedCid || null;
899
958
  let metadata = resource?.metadata || null;
959
+ let manifestObj = null;
960
+
961
+ // If --manifest provided, fetch manifest from IPFS and extract encryptedCid
962
+ if (opts.manifest) {
963
+ const gateway = resolveGateway(opts.gateway);
964
+ try {
965
+ if (!opts.json) {
966
+ console.log(`📥 Fetching manifest from IPFS: ${opts.manifest}`);
967
+ }
968
+ const manifestResp = await axios.get(`${gateway}${opts.manifest}`, { timeout: 15000 });
969
+ const manifest = manifestResp.data;
970
+ manifestObj = manifest;
971
+ if (manifest?.encryptedCid) {
972
+ encryptedCid = manifest.encryptedCid;
973
+ if (!opts.json) {
974
+ console.log(`✅ Found encryptedCid in manifest: ${encryptedCid}`);
975
+ }
976
+ }
977
+ if (manifest?.metadata && !metadata) {
978
+ metadata = manifest.metadata;
979
+ }
980
+ } catch (err) {
981
+ console.warn(`⚠️ Failed to fetch manifest: ${err?.message || err}`);
982
+ }
983
+ }
984
+
900
985
  if (!encryptedCid && opts.encryptedCid) {
901
986
  encryptedCid = String(opts.encryptedCid);
902
987
  }
@@ -912,9 +997,12 @@ async function accessAction(creator, key, opts) {
912
997
  if (encryptedPayload && looksLikeCid(encryptedPayload)) {
913
998
  try {
914
999
  const resp = await axios.get(`${gateway}${encryptedPayload}`, { timeout: 10000 });
1000
+ if (resp.status !== 200) {
1001
+ throw new Error(`Status ${resp.status}`);
1002
+ }
915
1003
  encryptedPayload = resp.data;
916
1004
  } catch (error) {
917
- console.warn(`⚠️ Failed to fetch encrypted payload from IPFS via ${gateway}:`, error?.message || error);
1005
+ throw new Error(`Failed to fetch encrypted payload from IPFS via ${gateway}: ${error.message}`);
918
1006
  }
919
1007
  }
920
1008
 
@@ -963,15 +1051,66 @@ async function accessAction(creator, key, opts) {
963
1051
  }
964
1052
  }
965
1053
 
966
- decrypted = await sdk.personal.decryptWithLit({
967
- encryptedCid: payloadObject,
968
- receiptId,
969
- chain: litChain,
970
- receiptAddress,
971
- litClient: client,
972
- sessionSigs: sessionSigs || undefined,
973
- authSig: authSig || undefined,
974
- });
1054
+ // Check for Hybrid Encryption (Sage Personal Manifest)
1055
+ if (payloadObject.type === 'sage-personal-encrypted' && payloadObject.enc === 'aes-256-gcm') {
1056
+ if (!manifestObj || !manifestObj.lit) {
1057
+ throw new Error('Hybrid encryption detected but no Manifest provided (use --manifest <cid>).');
1058
+ }
1059
+ if (!opts.json) console.log('🔐 Decrypting hybrid content (Lit + AES-GCM)...');
1060
+
1061
+ const litParams = manifestObj.lit;
1062
+ const condition = litParams.evmContractConditions?.[0] || {
1063
+ conditionType: 'evmContract',
1064
+ contractAddress: receiptAddress,
1065
+ functionName: 'balanceOf',
1066
+ functionParams: [':userAddress', receiptId.toString()],
1067
+ functionAbi: {
1068
+ inputs: [{ internalType: 'address', name: 'account', type: 'address' }, { internalType: 'uint256', name: 'id', type: 'uint256' }],
1069
+ name: 'balanceOf',
1070
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
1071
+ stateMutability: 'view',
1072
+ type: 'function',
1073
+ },
1074
+ chain: litChain,
1075
+ returnValueTest: { comparator: '>', value: '0' },
1076
+ };
1077
+
1078
+ const request = {
1079
+ unifiedAccessControlConditions: [condition],
1080
+ chain: litChain,
1081
+ ciphertext: litParams.encryptedSymmetricKey,
1082
+ dataToEncryptHash: litParams.dataToEncryptHash,
1083
+ authSig,
1084
+ sessionSigs,
1085
+ };
1086
+
1087
+ const response = await client.decrypt(request);
1088
+ if (!response || !response.decryptedData) {
1089
+ throw new Error('[personal] Lit decryption of symmetric key failed');
1090
+ }
1091
+
1092
+ // response.decryptedData is Uint8Array
1093
+ const symmetricKey = response.decryptedData;
1094
+
1095
+ decrypted = decryptAesGcm(
1096
+ fromB64(payloadObject.ciphertext),
1097
+ Buffer.from(symmetricKey),
1098
+ fromB64(payloadObject.iv),
1099
+ fromB64(payloadObject.tag)
1100
+ );
1101
+
1102
+ } else {
1103
+ // Legacy/Direct Lit Decryption
1104
+ decrypted = await sdk.personal.decryptWithLit({
1105
+ encryptedCid: payloadObject,
1106
+ receiptId,
1107
+ chain: litChain,
1108
+ receiptAddress,
1109
+ litClient: client,
1110
+ sessionSigs: sessionSigs || undefined,
1111
+ authSig: authSig || undefined,
1112
+ });
1113
+ }
975
1114
 
976
1115
  if (opts.out) {
977
1116
  outputPath = path.resolve(opts.out);
@@ -1112,6 +1251,7 @@ function register(program) {
1112
1251
  .option('--out <file>', 'Write decrypted output to file')
1113
1252
  .option('--skip-decrypt', 'Skip decryption and only report license status')
1114
1253
  .option('--encrypted-cid <cid>', 'Override encrypted CID')
1254
+ .option('--manifest <cid>', 'Manifest CID to fetch encrypted content info from IPFS')
1115
1255
  .option('--json', 'Emit machine-readable JSON report', false)
1116
1256
  .action((creator, key, opts) => accessAction(creator, key, opts).catch((err) => { console.error(err.message); process.exit(1); }));
1117
1257
 
@@ -1130,7 +1270,12 @@ function register(program) {
1130
1270
  cmd.addCommand(premium);
1131
1271
  }
1132
1272
 
1133
- module.exports = { register };
1134
1273
  const { query: subgraphQuery } = sdk.subgraph;
1135
1274
 
1136
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
  },