@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.
- 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/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/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
|
@@ -53,6 +53,11 @@ const { createLibraryLister, formatLibraryList } = require('./services/mcp/libra
|
|
|
53
53
|
const { createSubdaoDiscovery } = require('./services/mcp/subdao-discovery');
|
|
54
54
|
const { createUnifiedPromptSearcher } = require('./services/mcp/unified-prompt-search');
|
|
55
55
|
const { createToolArgsValidator } = require('./services/mcp/tool-args-validator');
|
|
56
|
+
const {
|
|
57
|
+
TOOL_REGISTRY,
|
|
58
|
+
getToolsForCategory,
|
|
59
|
+
getToolMeta,
|
|
60
|
+
} = require('./services/mcp/sage-tool-registry');
|
|
56
61
|
const { createToolDispatcher } = require('./services/mcp/tool-dispatcher');
|
|
57
62
|
const { createProposalLister } = require('./services/mcp/proposal-lister');
|
|
58
63
|
const fs = require('fs');
|
|
@@ -79,6 +84,41 @@ class SageMCPServer {
|
|
|
79
84
|
};
|
|
80
85
|
|
|
81
86
|
this.tools = [
|
|
87
|
+
{
|
|
88
|
+
name: 'suggest_sage_tools',
|
|
89
|
+
description: `Suggest the most relevant Sage MCP tools for a given goal.
|
|
90
|
+
When to use:
|
|
91
|
+
- You have a vague goal and want help choosing the right Sage tools
|
|
92
|
+
- You want a ranked list of 3–5 tools with rationale and required parameters
|
|
93
|
+
|
|
94
|
+
This tool does not execute any actions; it only recommends which tools to call next.`,
|
|
95
|
+
inputSchema: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: {
|
|
98
|
+
goal: { type: 'string', description: 'Free-text description of what you are trying to do' },
|
|
99
|
+
stage: {
|
|
100
|
+
type: 'string',
|
|
101
|
+
enum: ['prompt_workspace', 'persona', 'libraries', 'governance', 'treasury', 'discovery'],
|
|
102
|
+
description: 'Optional hint about which area you are working in',
|
|
103
|
+
},
|
|
104
|
+
context: {
|
|
105
|
+
type: 'object',
|
|
106
|
+
description: 'Optional execution context (workspace, wallet, etc.)',
|
|
107
|
+
properties: {
|
|
108
|
+
hasWorkspace: { type: 'boolean', description: 'Whether a Sage prompt workspace is present' },
|
|
109
|
+
hasWallet: { type: 'boolean', description: 'Whether a wallet is configured/available' },
|
|
110
|
+
currentTask: { type: 'string', description: 'Short description of what you just did' },
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
limit: { type: 'number', description: 'Maximum number of primary recommendations (default: 5)' },
|
|
114
|
+
includeAlternatives: {
|
|
115
|
+
type: 'boolean',
|
|
116
|
+
description: 'Whether to include a secondary list of alternative tools (default: false)',
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
required: ['goal'],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
82
122
|
{
|
|
83
123
|
name: 'quick_create_prompt',
|
|
84
124
|
description: `Quickly create a new prompt. Handles library creation automatically.
|
|
@@ -374,6 +414,42 @@ Next steps:
|
|
|
374
414
|
required: []
|
|
375
415
|
}
|
|
376
416
|
},
|
|
417
|
+
{
|
|
418
|
+
name: 'list_persona_templates',
|
|
419
|
+
description: 'List available persona templates for the Metaprompt Engine',
|
|
420
|
+
inputSchema: {
|
|
421
|
+
type: 'object',
|
|
422
|
+
properties: {},
|
|
423
|
+
required: []
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
name: 'run_persona_interview',
|
|
428
|
+
description: 'Generate a persona system prompt from structured answers (One-Shot, no human loop)',
|
|
429
|
+
inputSchema: {
|
|
430
|
+
type: 'object',
|
|
431
|
+
properties: {
|
|
432
|
+
template: { type: 'string', description: 'Template key (e.g., coding-assistant, governance-helper)' },
|
|
433
|
+
answers: { type: 'object', description: 'Key-value map of slot answers' },
|
|
434
|
+
save: { type: 'boolean', description: 'Whether to save the artifact to disk (default: false)' },
|
|
435
|
+
saveKey: { type: 'string', description: 'Optional filename for saving' }
|
|
436
|
+
},
|
|
437
|
+
required: ['template', 'answers']
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
name: 'persona_interview_step',
|
|
442
|
+
description: 'Stateful persona interview. Returns slot metadata for the host agent to phrase questions. Pass answers back with the stateToken to progress through slots.',
|
|
443
|
+
inputSchema: {
|
|
444
|
+
type: 'object',
|
|
445
|
+
properties: {
|
|
446
|
+
template: { type: 'string', description: 'Template key (coding-assistant, governance-helper, research-analyst, custom). Required on first call.' },
|
|
447
|
+
stateToken: { type: 'string', description: 'Base64-encoded state from previous response. Omit on first call.' },
|
|
448
|
+
answer: { type: 'string', description: 'Answer for the slot identified by currentSlotKey in the previous response.' }
|
|
449
|
+
},
|
|
450
|
+
required: []
|
|
451
|
+
}
|
|
452
|
+
},
|
|
377
453
|
{
|
|
378
454
|
name: 'save_metaprompt',
|
|
379
455
|
description: 'Persist a metaprompt to the local Sage workspace and optionally append to agents.md',
|
|
@@ -1013,6 +1089,9 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
|
|
|
1013
1089
|
'tool:get_prompts_from_manifest': (params) => this.getPromptsFromManifest(params),
|
|
1014
1090
|
'tool:list_metaprompts': () => this.listMetaprompts(),
|
|
1015
1091
|
'tool:start_metaprompt_interview': (params) => this.startMetapromptInterview(params),
|
|
1092
|
+
'tool:list_persona_templates': () => this.listPersonaTemplates(),
|
|
1093
|
+
'tool:run_persona_interview': (params) => this.runPersonaInterview(params),
|
|
1094
|
+
'tool:persona_interview_step': (params) => this.personaInterviewStep(params),
|
|
1016
1095
|
'tool:save_metaprompt': (params) => this.saveMetaprompt(params),
|
|
1017
1096
|
'tool:get_metaprompt': (params) => this.getMetaprompt(params),
|
|
1018
1097
|
'tool:generate_metaprompt_link': (params) => this.generateMetapromptLink(params),
|
|
@@ -1035,6 +1114,7 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
|
|
|
1035
1114
|
'tool:analyze_dependencies': (params) => this.analyzeDependencies(params),
|
|
1036
1115
|
'tool:suggest_subdaos_for_library': (params) => this.suggestSubdaosForLibrary(params),
|
|
1037
1116
|
'tool:generate_publishing_commands': (params) => this.generatePublishingCommands(params),
|
|
1117
|
+
'tool:suggest_sage_tools': (params) => this.suggestSageTools(params),
|
|
1038
1118
|
});
|
|
1039
1119
|
|
|
1040
1120
|
const toolHandlers = {
|
|
@@ -2453,6 +2533,274 @@ Use \`help(topic="create")\` for more details.
|
|
|
2453
2533
|
return this.listLibrariesHandler(options);
|
|
2454
2534
|
}
|
|
2455
2535
|
|
|
2536
|
+
// ───────────────────────── Tool suggestion meta-router ─────────────────────────
|
|
2537
|
+
|
|
2538
|
+
tokenizeForMatching(text) {
|
|
2539
|
+
if (!text) return [];
|
|
2540
|
+
const lower = String(text).toLowerCase();
|
|
2541
|
+
const tokens = lower.split(/[^a-z0-9]+/g).filter(Boolean);
|
|
2542
|
+
// Small stopword list; just enough to ignore generic phrasing.
|
|
2543
|
+
const stop = new Set([
|
|
2544
|
+
'the', 'a', 'an', 'to', 'for', 'and', 'or', 'of', 'in', 'on', 'with', 'this', 'that',
|
|
2545
|
+
'i', 'me', 'my', 'we', 'our', 'you', 'your',
|
|
2546
|
+
'want', 'need', 'help', 'can', 'could', 'would', 'should',
|
|
2547
|
+
'how', 'do', 'does', 'did', 'what', 'is', 'are', 'it', 'be',
|
|
2548
|
+
]);
|
|
2549
|
+
return tokens.filter((t) => !stop.has(t));
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
inferStage(goal, explicitStage) {
|
|
2553
|
+
if (explicitStage) return explicitStage;
|
|
2554
|
+
if (!goal) return null;
|
|
2555
|
+
|
|
2556
|
+
const patterns = {
|
|
2557
|
+
prompt_workspace: /\b(prompt|skill|workspace|create|edit|draft|write|local)\b/gi,
|
|
2558
|
+
persona: /\b(persona|metaprompt|interview|agent|character|assistant|system prompt)\b/gi,
|
|
2559
|
+
libraries: /\b(library|manifest|template|collection|import|export)\b/gi,
|
|
2560
|
+
governance: /\b(publish|proposal|vote|subdao|dao|govern|approve|execute)\b/gi,
|
|
2561
|
+
treasury: /\b(treasury|bond|boost|withdraw|sxxx|token|stake)\b/gi,
|
|
2562
|
+
discovery: /\b(search|find|list|browse|explore|trending|discover)\b/gi,
|
|
2563
|
+
};
|
|
2564
|
+
|
|
2565
|
+
const scores = {};
|
|
2566
|
+
for (const [stage, pattern] of Object.entries(patterns)) {
|
|
2567
|
+
const matches = goal.match(pattern) || [];
|
|
2568
|
+
if (matches.length > 0) scores[stage] = matches.length;
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
const entries = Object.entries(scores);
|
|
2572
|
+
if (!entries.length) return null;
|
|
2573
|
+
entries.sort((a, b) => b[1] - a[1]);
|
|
2574
|
+
return entries[0][0];
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
detectWorkflow(goal) {
|
|
2578
|
+
if (!goal) return null;
|
|
2579
|
+
const lower = goal.toLowerCase();
|
|
2580
|
+
|
|
2581
|
+
const workflows = [
|
|
2582
|
+
{
|
|
2583
|
+
// Create & publish a prompt/library
|
|
2584
|
+
pattern: /create.*prompt.*publish|publish.*prompt|create.*library.*publish|publish.*library/i,
|
|
2585
|
+
tools: ['quick_create_prompt', 'improve_prompt', 'publish_manifest_flow'],
|
|
2586
|
+
},
|
|
2587
|
+
{
|
|
2588
|
+
// Design & (optionally) publish a persona
|
|
2589
|
+
pattern: /create.*persona.*publish|design.*persona.*publish|persona.*publish/i,
|
|
2590
|
+
tools: ['list_persona_templates', 'persona_interview_step', 'save_metaprompt', 'publish_manifest_flow'],
|
|
2591
|
+
},
|
|
2592
|
+
{
|
|
2593
|
+
// Design a persona without explicit publishing intent
|
|
2594
|
+
pattern: /design.*persona|create.*persona|persona.*workflow/i,
|
|
2595
|
+
tools: ['list_persona_templates', 'persona_interview_step', 'save_metaprompt'],
|
|
2596
|
+
},
|
|
2597
|
+
{
|
|
2598
|
+
// Find & iterate on prompts
|
|
2599
|
+
pattern: /find.*prompt.*iterate|iterate.*prompt|improve.*prompt/i,
|
|
2600
|
+
tools: ['search_prompts', 'get_prompt', 'quick_iterate_prompt'],
|
|
2601
|
+
},
|
|
2602
|
+
{
|
|
2603
|
+
// Publish to a DAO/SubDAO
|
|
2604
|
+
pattern: /publish.*dao|dao.*publish|publish.*subdao|subdao.*publish/i,
|
|
2605
|
+
tools: ['suggest_subdaos_for_library', 'generate_publishing_commands', 'publish_manifest_flow'],
|
|
2606
|
+
},
|
|
2607
|
+
];
|
|
2608
|
+
|
|
2609
|
+
for (const wf of workflows) {
|
|
2610
|
+
if (wf.pattern.test(lower)) {
|
|
2611
|
+
return wf.tools;
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
return null;
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
scoreToolForGoal(toolMeta, goalTokens, stage) {
|
|
2618
|
+
// BM25-inspired scoring with simple term frequency and length normalization.
|
|
2619
|
+
const baseTerms = [
|
|
2620
|
+
...(toolMeta.keywords || []),
|
|
2621
|
+
...this.tokenizeForMatching(toolMeta.description || ''),
|
|
2622
|
+
...this.tokenizeForMatching(toolMeta.whenToUse || ''),
|
|
2623
|
+
];
|
|
2624
|
+
|
|
2625
|
+
const docLen = baseTerms.length || 1;
|
|
2626
|
+
const avgLen = 15;
|
|
2627
|
+
const k1 = 1.2;
|
|
2628
|
+
const b = 0.75;
|
|
2629
|
+
|
|
2630
|
+
let score = 0;
|
|
2631
|
+
|
|
2632
|
+
for (const token of goalTokens) {
|
|
2633
|
+
const tf = baseTerms.filter((t) => t === token).length;
|
|
2634
|
+
if (tf > 0) {
|
|
2635
|
+
const tfScore = (tf * (k1 + 1)) / (tf + k1 * (1 - b + b * (docLen / avgLen)));
|
|
2636
|
+
score += tfScore;
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
// Negative keyword penalty.
|
|
2641
|
+
if (toolMeta.negativeKeywords && toolMeta.negativeKeywords.length) {
|
|
2642
|
+
let penalties = 0;
|
|
2643
|
+
for (const neg of toolMeta.negativeKeywords) {
|
|
2644
|
+
if (goalTokens.includes(neg)) penalties += 1;
|
|
2645
|
+
}
|
|
2646
|
+
if (penalties > 0) {
|
|
2647
|
+
score *= (0.5 ** penalties);
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
// Stage/category boost.
|
|
2652
|
+
if (stage && toolMeta.category === stage) {
|
|
2653
|
+
score *= 1.5;
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
// Base weight.
|
|
2657
|
+
const weight = typeof toolMeta.weight === 'number' ? toolMeta.weight : 1;
|
|
2658
|
+
score *= weight;
|
|
2659
|
+
|
|
2660
|
+
// Normalize to 0–1 range.
|
|
2661
|
+
return Math.min(score / 5, 1);
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
orderSuggestedTools(scored, limit) {
|
|
2665
|
+
if (!Array.isArray(scored) || !scored.length) return [];
|
|
2666
|
+
const sorted = [...scored].sort((a, b) => b.confidence - a.confidence).slice(0, limit);
|
|
2667
|
+
if (sorted.length <= 3) {
|
|
2668
|
+
return sorted.map((item, idx) => ({ ...item, priority: idx + 1 }));
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
// Simple U-shaped ordering: higher-ranked items gravitate to the ends.
|
|
2672
|
+
const result = [];
|
|
2673
|
+
for (let i = 0; i < sorted.length; i += 1) {
|
|
2674
|
+
if (i % 2 === 0) {
|
|
2675
|
+
// Even indices append to the end
|
|
2676
|
+
result.push(sorted[i]);
|
|
2677
|
+
} else {
|
|
2678
|
+
// Odd indices unshift to the front
|
|
2679
|
+
result.unshift(sorted[i]);
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
return result.map((item, idx) => ({ ...item, priority: idx + 1 }));
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
async suggestSageTools(params) {
|
|
2687
|
+
// Arguments are already validated by createToolArgsValidator.
|
|
2688
|
+
const { goal, stage, context, limit = 5, includeAlternatives = false } = params || {};
|
|
2689
|
+
|
|
2690
|
+
const effectiveStage = this.inferStage(goal, stage);
|
|
2691
|
+
const goalTokens = this.tokenizeForMatching(goal);
|
|
2692
|
+
|
|
2693
|
+
// Choose candidate tools from registry.
|
|
2694
|
+
let candidates = [];
|
|
2695
|
+
if (effectiveStage === 'discovery') {
|
|
2696
|
+
// Discovery is intentionally cross-cutting; surface the most browse/search oriented tools.
|
|
2697
|
+
const discoveryTools = ['search_prompts', 'list_prompts', 'trending_prompts', 'list_persona_templates', 'list_libraries'];
|
|
2698
|
+
candidates = discoveryTools
|
|
2699
|
+
.map((name) => getToolMeta(name))
|
|
2700
|
+
.filter(Boolean);
|
|
2701
|
+
} else if (effectiveStage) {
|
|
2702
|
+
candidates = getToolsForCategory(effectiveStage);
|
|
2703
|
+
} else {
|
|
2704
|
+
// If no clear stage, fall back to a small set of discovery-friendly tools.
|
|
2705
|
+
const discoveryTools = ['search_prompts', 'list_prompts', 'trending_prompts', 'list_persona_templates', 'list_libraries'];
|
|
2706
|
+
candidates = discoveryTools
|
|
2707
|
+
.map((name) => getToolMeta(name))
|
|
2708
|
+
.filter(Boolean);
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
// Cross-category fallback: if we have a clear stage, allow strongly-matching tools
|
|
2712
|
+
// from other categories to join the candidate set.
|
|
2713
|
+
if (effectiveStage) {
|
|
2714
|
+
const allTools = Object.entries(TOOL_REGISTRY).map(([name, meta]) => ({ name, ...meta }));
|
|
2715
|
+
const existingNames = new Set(candidates.map((c) => c.name));
|
|
2716
|
+
const crossCategory = allTools
|
|
2717
|
+
.filter((t) => t.category !== effectiveStage && !existingNames.has(t.name))
|
|
2718
|
+
.map((t) => ({
|
|
2719
|
+
...t,
|
|
2720
|
+
confidence: this.scoreToolForGoal(t, goalTokens, null),
|
|
2721
|
+
}))
|
|
2722
|
+
.filter((t) => t.confidence > 0.6);
|
|
2723
|
+
|
|
2724
|
+
if (crossCategory.length) {
|
|
2725
|
+
candidates = candidates.concat(crossCategory);
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
// Apply simple context-based filtering/hints.
|
|
2730
|
+
if (context && context.hasWorkspace === false) {
|
|
2731
|
+
// If there is no workspace, de-prioritize workspace-only tools.
|
|
2732
|
+
candidates = candidates.map((c) => {
|
|
2733
|
+
if (c.category === 'prompt_workspace') {
|
|
2734
|
+
return { ...c, weight: (c.weight || 1) * 0.7 };
|
|
2735
|
+
}
|
|
2736
|
+
return c;
|
|
2737
|
+
});
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
// Score tools.
|
|
2741
|
+
const scored = candidates.map((meta) => ({
|
|
2742
|
+
...meta,
|
|
2743
|
+
confidence: this.scoreToolForGoal(meta, goalTokens, effectiveStage),
|
|
2744
|
+
})).filter((item) => item.confidence >= 0.15);
|
|
2745
|
+
|
|
2746
|
+
if (!scored.length) {
|
|
2747
|
+
const fallback = {
|
|
2748
|
+
recommendations: [],
|
|
2749
|
+
alternatives: [],
|
|
2750
|
+
inferredStage: effectiveStage || null,
|
|
2751
|
+
suggestedWorkflow: null,
|
|
2752
|
+
};
|
|
2753
|
+
return {
|
|
2754
|
+
content: [{
|
|
2755
|
+
type: 'text',
|
|
2756
|
+
text: JSON.stringify(fallback, null, 2),
|
|
2757
|
+
}],
|
|
2758
|
+
};
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
const ordered = this.orderSuggestedTools(scored, limit);
|
|
2762
|
+
const workflow = this.detectWorkflow(goal);
|
|
2763
|
+
|
|
2764
|
+
const recommendations = ordered.map((item) => ({
|
|
2765
|
+
toolName: item.name,
|
|
2766
|
+
category: item.category,
|
|
2767
|
+
confidence: item.confidence,
|
|
2768
|
+
priority: item.priority,
|
|
2769
|
+
rationale: item.whenToUse,
|
|
2770
|
+
requiredParams: item.requiredParams || [],
|
|
2771
|
+
}));
|
|
2772
|
+
|
|
2773
|
+
let alternatives = [];
|
|
2774
|
+
if (includeAlternatives) {
|
|
2775
|
+
const remaining = scored
|
|
2776
|
+
.filter((s) => !ordered.find((r) => r.name === s.name))
|
|
2777
|
+
.sort((a, b) => b.confidence - a.confidence)
|
|
2778
|
+
.slice(0, limit);
|
|
2779
|
+
alternatives = remaining.map((item, idx) => ({
|
|
2780
|
+
toolName: item.name,
|
|
2781
|
+
category: item.category,
|
|
2782
|
+
confidence: item.confidence,
|
|
2783
|
+
priority: idx + 1,
|
|
2784
|
+
rationale: item.whenToUse,
|
|
2785
|
+
requiredParams: item.requiredParams || [],
|
|
2786
|
+
}));
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
const result = {
|
|
2790
|
+
recommendations,
|
|
2791
|
+
alternatives,
|
|
2792
|
+
inferredStage: effectiveStage || null,
|
|
2793
|
+
suggestedWorkflow: workflow || null,
|
|
2794
|
+
};
|
|
2795
|
+
|
|
2796
|
+
return {
|
|
2797
|
+
content: [{
|
|
2798
|
+
type: 'text',
|
|
2799
|
+
text: JSON.stringify(result, null, 2),
|
|
2800
|
+
}],
|
|
2801
|
+
};
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2456
2804
|
listMetaprompts() {
|
|
2457
2805
|
const rows = metapromptDesigner.listMetaprompts();
|
|
2458
2806
|
if (!rows.length) {
|
|
@@ -2848,6 +3196,207 @@ Use \`help(topic="create")\` for more details.
|
|
|
2848
3196
|
}
|
|
2849
3197
|
}
|
|
2850
3198
|
|
|
3199
|
+
// ───────────────────────────── Metaprompt Engine Tools ─────────────────────────────
|
|
3200
|
+
|
|
3201
|
+
async listPersonaTemplates() {
|
|
3202
|
+
try {
|
|
3203
|
+
const templates = require('./services/metaprompt/templates.json');
|
|
3204
|
+
const list = Object.values(templates).map(t => ({
|
|
3205
|
+
key: t.key,
|
|
3206
|
+
name: t.name,
|
|
3207
|
+
description: t.description,
|
|
3208
|
+
recommended_slots: t.recommended_slots?.map(s => s.label) || []
|
|
3209
|
+
}));
|
|
3210
|
+
|
|
3211
|
+
return {
|
|
3212
|
+
content: [
|
|
3213
|
+
{
|
|
3214
|
+
type: 'text',
|
|
3215
|
+
text: JSON.stringify(list, null, 2)
|
|
3216
|
+
}
|
|
3217
|
+
]
|
|
3218
|
+
};
|
|
3219
|
+
} catch (error) {
|
|
3220
|
+
return {
|
|
3221
|
+
isError: true,
|
|
3222
|
+
content: [{ type: 'text', text: `Failed to list templates: ${error.message}` }]
|
|
3223
|
+
};
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
async runPersonaInterview(params) {
|
|
3228
|
+
const { template, answers, save, saveKey } = params;
|
|
3229
|
+
try {
|
|
3230
|
+
const config = require('./config');
|
|
3231
|
+
const PromptBuilder = require('./services/metaprompt/prompt-builder');
|
|
3232
|
+
const MetapromptPersistence = require('./services/metaprompt/persistence');
|
|
3233
|
+
const templates = require('./services/metaprompt/templates.json');
|
|
3234
|
+
|
|
3235
|
+
// 1. Validate Template
|
|
3236
|
+
const templateDef = templates[template] || templates['custom'];
|
|
3237
|
+
|
|
3238
|
+
// 2. Build Prompt (One-Shot)
|
|
3239
|
+
// Note: run_persona_interview assumes the *agent* has already done the planning/filling.
|
|
3240
|
+
// It bypasses the SlotPlanner and just builds the artifact.
|
|
3241
|
+
const builder = new PromptBuilder();
|
|
3242
|
+
// We need the full slot definitions to build dynamic sections.
|
|
3243
|
+
// Since we skipped the planner, we'll use the template's recommended slots as the definition.
|
|
3244
|
+
const slots = templateDef.recommended_slots || [];
|
|
3245
|
+
|
|
3246
|
+
const systemPrompt = builder.buildSystemPrompt(template, slots, answers || {});
|
|
3247
|
+
|
|
3248
|
+
let result = {
|
|
3249
|
+
systemPrompt,
|
|
3250
|
+
slug: '',
|
|
3251
|
+
paths: {}
|
|
3252
|
+
};
|
|
3253
|
+
|
|
3254
|
+
// 3. Save (Optional)
|
|
3255
|
+
if (save) {
|
|
3256
|
+
const persistence = new MetapromptPersistence(config);
|
|
3257
|
+
const slug = saveKey || `${template}-${Date.now().toString().slice(-6)}`;
|
|
3258
|
+
|
|
3259
|
+
// Save dummy history so the artifact exists
|
|
3260
|
+
const historyPaths = persistence.saveMetaprompt(slug, {
|
|
3261
|
+
templateKey: template,
|
|
3262
|
+
transcript: [{ role: 'system', content: 'Generated via MCP run_persona_interview (one-shot).' }],
|
|
3263
|
+
answers: answers || {}
|
|
3264
|
+
});
|
|
3265
|
+
|
|
3266
|
+
// Save Skill
|
|
3267
|
+
const skillPath = persistence.saveSkill(slug, systemPrompt);
|
|
3268
|
+
|
|
3269
|
+
result.slug = slug;
|
|
3270
|
+
result.paths = {
|
|
3271
|
+
metaprompt: historyPaths.metaprompt,
|
|
3272
|
+
skill: skillPath
|
|
3273
|
+
};
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3276
|
+
return {
|
|
3277
|
+
content: [
|
|
3278
|
+
{
|
|
3279
|
+
type: 'text',
|
|
3280
|
+
text: JSON.stringify(result, null, 2)
|
|
3281
|
+
}
|
|
3282
|
+
]
|
|
3283
|
+
};
|
|
3284
|
+
|
|
3285
|
+
} catch (error) {
|
|
3286
|
+
return {
|
|
3287
|
+
isError: true,
|
|
3288
|
+
content: [{ type: 'text', text: `Failed to run persona interview: ${error.message}` }]
|
|
3289
|
+
};
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
/**
|
|
3294
|
+
* Stateful persona interview that walks through template slots.
|
|
3295
|
+
*
|
|
3296
|
+
* Returns slot metadata (label, description, group) for the host agent to phrase questions.
|
|
3297
|
+
* The agent passes answers back with the stateToken to progress through slots.
|
|
3298
|
+
* When all required slots are filled, returns the generated system prompt.
|
|
3299
|
+
*
|
|
3300
|
+
* @param {object} params
|
|
3301
|
+
* @param {string} params.template - Template key (coding-assistant, governance-helper, etc.)
|
|
3302
|
+
* @param {string} [params.stateToken] - Base64-encoded state from previous call
|
|
3303
|
+
* @param {string} [params.answer] - Answer for the current slot (currentSlotKey from previous response)
|
|
3304
|
+
*/
|
|
3305
|
+
async personaInterviewStep(params) {
|
|
3306
|
+
const { template, stateToken, answer } = params;
|
|
3307
|
+
try {
|
|
3308
|
+
const templates = require('./services/metaprompt/templates.json');
|
|
3309
|
+
const PromptBuilder = require('./services/metaprompt/prompt-builder');
|
|
3310
|
+
|
|
3311
|
+
// 1. Restore or initialize state
|
|
3312
|
+
let state;
|
|
3313
|
+
if (stateToken) {
|
|
3314
|
+
try {
|
|
3315
|
+
state = JSON.parse(Buffer.from(stateToken, 'base64').toString('utf8'));
|
|
3316
|
+
} catch {
|
|
3317
|
+
return { isError: true, content: [{ type: 'text', text: 'Invalid stateToken' }] };
|
|
3318
|
+
}
|
|
3319
|
+
} else {
|
|
3320
|
+
// Initialize new interview state
|
|
3321
|
+
const templateKey = template || 'custom';
|
|
3322
|
+
const templateDef = templates[templateKey] || templates.custom;
|
|
3323
|
+
state = {
|
|
3324
|
+
templateKey,
|
|
3325
|
+
slots: templateDef.recommended_slots || [],
|
|
3326
|
+
answers: {},
|
|
3327
|
+
currentSlotKey: null,
|
|
3328
|
+
};
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
// 2. Record answer for the current slot (if provided)
|
|
3332
|
+
if (answer && state.currentSlotKey) {
|
|
3333
|
+
state.answers[state.currentSlotKey] = answer;
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
// 3. Find next unanswered slot (required first, then optional by priority)
|
|
3337
|
+
const unanswered = state.slots.filter(s => !state.answers[s.key]);
|
|
3338
|
+
const requiredUnanswered = unanswered.filter(s => s.required);
|
|
3339
|
+
const nextSlot = requiredUnanswered[0] || unanswered.find(s => s.priority <= 2) || null;
|
|
3340
|
+
|
|
3341
|
+
// 4. Update state with current slot key
|
|
3342
|
+
state.currentSlotKey = nextSlot?.key || null;
|
|
3343
|
+
|
|
3344
|
+
// 5. Serialize state for next call
|
|
3345
|
+
const newStateToken = Buffer.from(JSON.stringify(state)).toString('base64');
|
|
3346
|
+
|
|
3347
|
+
// 6. Check if interview is complete
|
|
3348
|
+
if (!nextSlot) {
|
|
3349
|
+
// All required slots filled - build the system prompt (no LLM needed)
|
|
3350
|
+
const builder = new PromptBuilder();
|
|
3351
|
+
const systemPrompt = builder.buildSystemPrompt(state.templateKey, state.slots, state.answers);
|
|
3352
|
+
|
|
3353
|
+
return {
|
|
3354
|
+
content: [{
|
|
3355
|
+
type: 'text',
|
|
3356
|
+
text: JSON.stringify({
|
|
3357
|
+
done: true,
|
|
3358
|
+
systemPrompt,
|
|
3359
|
+
template: state.templateKey,
|
|
3360
|
+
answers: state.answers,
|
|
3361
|
+
filledSlots: Object.keys(state.answers).length,
|
|
3362
|
+
totalSlots: state.slots.length,
|
|
3363
|
+
stateToken: newStateToken,
|
|
3364
|
+
}, null, 2)
|
|
3365
|
+
}]
|
|
3366
|
+
};
|
|
3367
|
+
}
|
|
3368
|
+
|
|
3369
|
+
// 7. Return next slot info for the host LLM to phrase the question
|
|
3370
|
+
// The host LLM should use slotLabel/slotDescription to ask the user
|
|
3371
|
+
const suggestedQuestion = `Please provide: ${nextSlot.label}. ${nextSlot.description || ''}`.trim();
|
|
3372
|
+
|
|
3373
|
+
return {
|
|
3374
|
+
content: [{
|
|
3375
|
+
type: 'text',
|
|
3376
|
+
text: JSON.stringify({
|
|
3377
|
+
done: false,
|
|
3378
|
+
currentSlotKey: nextSlot.key,
|
|
3379
|
+
slotLabel: nextSlot.label,
|
|
3380
|
+
slotDescription: nextSlot.description || '',
|
|
3381
|
+
slotGroup: nextSlot.group || 'general',
|
|
3382
|
+
slotRequired: nextSlot.required || false,
|
|
3383
|
+
suggestedQuestion,
|
|
3384
|
+
filledSlots: Object.keys(state.answers).length,
|
|
3385
|
+
totalSlots: state.slots.length,
|
|
3386
|
+
remainingRequired: requiredUnanswered.length,
|
|
3387
|
+
stateToken: newStateToken,
|
|
3388
|
+
}, null, 2)
|
|
3389
|
+
}]
|
|
3390
|
+
};
|
|
3391
|
+
|
|
3392
|
+
} catch (error) {
|
|
3393
|
+
return {
|
|
3394
|
+
isError: true,
|
|
3395
|
+
content: [{ type: 'text', text: `Failed to execute interview step: ${error.message}` }]
|
|
3396
|
+
};
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
|
|
2851
3400
|
async getSubDAOList(options = {}) {
|
|
2852
3401
|
try {
|
|
2853
3402
|
return await this.subdaoDiscovery.listSubDaos(options);
|
package/dist/cli/mcp-server.js
CHANGED
|
@@ -11,6 +11,7 @@ const { ethers } = require('ethers');
|
|
|
11
11
|
const { execSync } = require('child_process');
|
|
12
12
|
const axios = require('axios');
|
|
13
13
|
const fss = require('fs');
|
|
14
|
+
const { hydrateEnvFromSageConfig } = require('./services/mcp/env-loader');
|
|
14
15
|
const { createLibraryManifestLister } = require('./services/mcp/library-manifests');
|
|
15
16
|
const { createManifestFetcher } = require('./services/mcp/manifest-fetcher');
|
|
16
17
|
const { createManifestWorkflows } = require('./services/mcp/manifest-workflows');
|
|
@@ -57,37 +58,10 @@ class MCPServer extends EventEmitter {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
async initialize() {
|
|
60
|
-
// Hydrate env from .sage/config.json if missing
|
|
61
|
+
// Hydrate env from .sage/config.json if missing (factory, registry, token, subgraph)
|
|
61
62
|
try {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
for (let i = 0; i < 6; i++) {
|
|
65
|
-
const cand = path.join(dir, '.sage', 'config.json');
|
|
66
|
-
if (fss.existsSync(cand)) { cfgPath = cand; break; }
|
|
67
|
-
const next = path.dirname(dir);
|
|
68
|
-
if (next === dir) break;
|
|
69
|
-
dir = next;
|
|
70
|
-
}
|
|
71
|
-
if (!cfgPath) {
|
|
72
|
-
const alt = path.join(__dirname, '..', '.sage', 'config.json');
|
|
73
|
-
if (fss.existsSync(alt)) cfgPath = alt;
|
|
74
|
-
}
|
|
75
|
-
if (cfgPath) {
|
|
76
|
-
const raw = fss.readFileSync(cfgPath, 'utf8');
|
|
77
|
-
const j = JSON.parse(raw);
|
|
78
|
-
const active = j.activeProfile || Object.keys(j.profiles || {})[0];
|
|
79
|
-
const a = (j.profiles && j.profiles[active] && j.profiles[active].addresses) || {};
|
|
80
|
-
if (!process.env.SUBDAO_FACTORY_ADDRESS && (a.SUBDAO_FACTORY_ADDRESS || a.SUBDAO_FACTORY)) {
|
|
81
|
-
process.env.SUBDAO_FACTORY_ADDRESS = a.SUBDAO_FACTORY_ADDRESS || a.SUBDAO_FACTORY;
|
|
82
|
-
}
|
|
83
|
-
if (!process.env.LIBRARY_REGISTRY_ADDRESS && (a.LIBRARY_REGISTRY_ADDRESS || a.LIBRARY_REGISTRY)) {
|
|
84
|
-
process.env.LIBRARY_REGISTRY_ADDRESS = a.LIBRARY_REGISTRY_ADDRESS || a.LIBRARY_REGISTRY;
|
|
85
|
-
}
|
|
86
|
-
if (!process.env.SXXX_TOKEN_ADDRESS && (a.SXXX_TOKEN_ADDRESS || a.SXXX)) {
|
|
87
|
-
process.env.SXXX_TOKEN_ADDRESS = a.SXXX_TOKEN_ADDRESS || a.SXXX;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
} catch (_) { /* ignore */ }
|
|
63
|
+
hydrateEnvFromSageConfig({ cwd: process.cwd(), fallbackDir: path.join(__dirname, '..') });
|
|
64
|
+
} catch (_) { /* non-fatal */ }
|
|
91
65
|
// Ensure directories exist
|
|
92
66
|
await this.ensureDirectories();
|
|
93
67
|
|
|
@@ -142,7 +142,7 @@ class PrivyWalletManager {
|
|
|
142
142
|
|
|
143
143
|
async initialize() {
|
|
144
144
|
try {
|
|
145
|
-
console.log('✅ Deterministic wallet manager initialized');
|
|
145
|
+
if (process.env.SAGE_VERBOSE === '1') console.log('✅ Deterministic wallet manager initialized');
|
|
146
146
|
return true;
|
|
147
147
|
} catch (error) {
|
|
148
148
|
console.error('❌ Wallet initialization failed:', error.message);
|
|
@@ -208,7 +208,7 @@ class PrivyWalletManager {
|
|
|
208
208
|
async promptEmail() {
|
|
209
209
|
let cached = getCachedPrivyEmail();
|
|
210
210
|
if (cached && cached.includes('@')) {
|
|
211
|
-
if (!process.env.SAGE_QUIET_JSON) console.log(`📧 Using cached Privy email: ${cached}`);
|
|
211
|
+
if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') console.log(`📧 Using cached Privy email: ${cached}`);
|
|
212
212
|
return cached;
|
|
213
213
|
}
|
|
214
214
|
let email = '';
|
|
@@ -92,7 +92,7 @@ async function fixIpfsKeys(context = {}) {
|
|
|
92
92
|
if (!envContent.includes('PINATA_SECRET_API_KEY=')) envContent += `\nPINATA_SECRET_API_KEY=${answers.secret}`;
|
|
93
93
|
if (answers.jwt && !envContent.includes('PINATA_JWT=')) envContent += `\nPINATA_JWT=${answers.jwt}`;
|
|
94
94
|
fs.writeFileSync(envPath, envContent);
|
|
95
|
-
try {
|
|
95
|
+
try { config.loadEnv(); } catch(_){}
|
|
96
96
|
console.log('✅ Added Pinata keys to .env');
|
|
97
97
|
}
|
|
98
98
|
}
|