@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.
- package/dist/cli/browser-wallet-integration.js +0 -1
- package/dist/cli/cast-wallet-manager.js +0 -1
- package/dist/cli/commands/interview.js +149 -0
- package/dist/cli/commands/personal.js +234 -89
- package/dist/cli/commands/stake-status.js +0 -2
- package/dist/cli/config.js +28 -8
- package/dist/cli/governance-manager.js +28 -19
- package/dist/cli/index.js +32 -8
- package/dist/cli/library-manager.js +16 -6
- package/dist/cli/mcp-server-stdio.js +549 -0
- package/dist/cli/mcp-server.js +4 -30
- package/dist/cli/mcp-setup.md +35 -34
- package/dist/cli/metamask-integration.js +0 -1
- package/dist/cli/privy-wallet-manager.js +2 -2
- package/dist/cli/prompt-manager.js +0 -1
- package/dist/cli/services/doctor/fixers.js +1 -1
- package/dist/cli/services/mcp/env-loader.js +2 -0
- package/dist/cli/services/mcp/quick-start.js +14 -15
- package/dist/cli/services/mcp/sage-tool-registry.js +330 -0
- package/dist/cli/services/mcp/tool-args-validator.js +31 -0
- package/dist/cli/services/metaprompt/anthropic-client.js +87 -0
- package/dist/cli/services/metaprompt/interview-driver.js +161 -0
- package/dist/cli/services/metaprompt/model-client.js +49 -0
- package/dist/cli/services/metaprompt/openai-client.js +67 -0
- package/dist/cli/services/metaprompt/persistence.js +86 -0
- package/dist/cli/services/metaprompt/prompt-builder.js +186 -0
- package/dist/cli/services/metaprompt/session.js +18 -80
- package/dist/cli/services/metaprompt/slot-planner.js +115 -0
- package/dist/cli/services/metaprompt/templates.json +130 -0
- package/dist/cli/subdao.js +0 -3
- package/dist/cli/sxxx-manager.js +0 -1
- package/dist/cli/utils/tx-wait.js +0 -3
- package/dist/cli/wallet-manager.js +18 -19
- package/dist/cli/walletconnect-integration.js +0 -1
- package/dist/cli/wizard-manager.js +0 -1
- package/package.json +3 -1
|
@@ -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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
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
|
|
package/dist/cli/config.js
CHANGED
|
@@ -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 (
|
|
294
|
-
} else if (
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
},
|