@sage-protocol/cli 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/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 +138 -79
- package/dist/cli/commands/prompts.js +242 -87
- 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 +759 -156
- package/dist/cli/mcp-server.js +4 -30
- 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/artifact-manager.js +198 -0
- package/dist/cli/services/doctor/fixers.js +1 -1
- package/dist/cli/services/mcp/env-loader.js +2 -0
- package/dist/cli/services/mcp/prompt-result-formatter.js +8 -1
- package/dist/cli/services/mcp/quick-start.js +14 -15
- package/dist/cli/services/mcp/sage-tool-registry.js +322 -0
- package/dist/cli/services/mcp/tool-args-validator.js +43 -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/services/project-context.js +98 -0
- package/dist/cli/subdao.js +0 -3
- package/dist/cli/sxxx-manager.js +0 -1
- package/dist/cli/utils/aliases.js +0 -6
- 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
- package/dist/cli/commands/prompt-test.js +0 -176
- package/dist/cli/commands/prompt.js +0 -2531
|
@@ -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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
+
};
|