@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
|
@@ -55,6 +55,8 @@ function hydrateEnvFromSageConfig(options = {}) {
|
|
|
55
55
|
SUBDAO_FACTORY_ADDRESS: addresses.SUBDAO_FACTORY_ADDRESS || addresses.SUBDAO_FACTORY,
|
|
56
56
|
LIBRARY_REGISTRY_ADDRESS: addresses.LIBRARY_REGISTRY_ADDRESS || addresses.LIBRARY_REGISTRY,
|
|
57
57
|
SXXX_TOKEN_ADDRESS: addresses.SXXX_TOKEN_ADDRESS || addresses.SXXX,
|
|
58
|
+
SUBGRAPH_URL: addresses.SAGE_SUBGRAPH_URL || addresses.SUBGRAPH_URL,
|
|
59
|
+
SAGE_SUBGRAPH_URL: addresses.SAGE_SUBGRAPH_URL,
|
|
58
60
|
};
|
|
59
61
|
for (const [key, value] of Object.entries(mapping)) {
|
|
60
62
|
if (value && typeof value === 'string' && !process.env[key]) {
|
|
@@ -60,13 +60,12 @@ function createQuickStart({
|
|
|
60
60
|
// Since LibraryManager doesn't expose "addPrompt", we'll implement it here for now
|
|
61
61
|
// In a real refactor, we should move this to LibraryManager
|
|
62
62
|
|
|
63
|
-
const libDir = libraryManager.ensureLibrariesDir();
|
|
64
|
-
const manifestPath = path.join(libDir, `${targetLib.cid}.json`);
|
|
65
|
-
|
|
66
63
|
// Create prompt file
|
|
67
64
|
// We'll store it in a 'prompts' subdirectory next to the manifest if possible,
|
|
68
65
|
// but for local libraries, they are flat in ~/.sage/libraries/
|
|
69
66
|
// Let's create a prompts directory inside ~/.sage/libraries/prompts/
|
|
67
|
+
const libDir = libraryManager.ensureLibrariesDir();
|
|
68
|
+
const manifestPath = libraryManager.getManifestPath(targetLib.cid);
|
|
70
69
|
const promptsDir = path.join(libDir, 'prompts');
|
|
71
70
|
if (!fs.existsSync(promptsDir)) {
|
|
72
71
|
fs.mkdirSync(promptsDir, { recursive: true });
|
|
@@ -114,7 +113,7 @@ function createQuickStart({
|
|
|
114
113
|
cid: '' // Local prompt
|
|
115
114
|
});
|
|
116
115
|
|
|
117
|
-
|
|
116
|
+
libraryManager.writeManifest(targetLib.cid, manifest);
|
|
118
117
|
|
|
119
118
|
return {
|
|
120
119
|
success: true,
|
|
@@ -129,19 +128,18 @@ function createQuickStart({
|
|
|
129
128
|
if (!key) throw new Error('Key is required');
|
|
130
129
|
|
|
131
130
|
// Find the prompt
|
|
132
|
-
|
|
131
|
+
const pinned = libraryManager.listPinned();
|
|
133
132
|
let found = null;
|
|
134
133
|
let foundLib = null;
|
|
135
134
|
|
|
136
135
|
for (const lib of pinned) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
136
|
+
const { manifest } = libraryManager.loadPinned(lib.cid);
|
|
137
|
+
const prompt = manifest.prompts?.find(p => p.key === key);
|
|
138
|
+
if (prompt) {
|
|
139
|
+
found = prompt;
|
|
140
|
+
foundLib = { ...lib, manifest };
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
145
143
|
}
|
|
146
144
|
|
|
147
145
|
if (!found) {
|
|
@@ -150,13 +148,13 @@ function createQuickStart({
|
|
|
150
148
|
|
|
151
149
|
// Update content if provided
|
|
152
150
|
if (content) {
|
|
153
|
-
const libDir = libraryManager.ensureLibrariesDir();
|
|
154
151
|
let promptFilePath;
|
|
155
152
|
|
|
156
153
|
if (found.files && found.files.length > 0) {
|
|
157
154
|
// Use existing file
|
|
158
155
|
const relativePath = found.files[0];
|
|
159
156
|
// Handle potential path issues
|
|
157
|
+
const libDir = libraryManager.ensureLibrariesDir();
|
|
160
158
|
promptFilePath = path.join(libDir, relativePath);
|
|
161
159
|
|
|
162
160
|
// Backup existing
|
|
@@ -166,6 +164,7 @@ function createQuickStart({
|
|
|
166
164
|
}
|
|
167
165
|
} else {
|
|
168
166
|
// Create new file if none existed (legacy/imported)
|
|
167
|
+
const libDir = libraryManager.ensureLibrariesDir();
|
|
169
168
|
const promptsDir = path.join(libDir, 'prompts');
|
|
170
169
|
if (!fs.existsSync(promptsDir)) fs.mkdirSync(promptsDir, { recursive: true });
|
|
171
170
|
const promptFileName = `${foundLib.cid}_${key}.md`;
|
|
@@ -210,7 +209,7 @@ function createQuickStart({
|
|
|
210
209
|
}
|
|
211
210
|
|
|
212
211
|
// Save manifest
|
|
213
|
-
|
|
212
|
+
libraryManager.writeManifest(foundLib.cid, foundLib.manifest);
|
|
214
213
|
|
|
215
214
|
return {
|
|
216
215
|
success: true,
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
// Central registry of core Sage MCP tools grouped by capability.
|
|
2
|
+
// This is used by suggest_sage_tools to provide lightweight routing
|
|
3
|
+
// and avoids hard-coding tool metadata in multiple places.
|
|
4
|
+
//
|
|
5
|
+
// Weight philosophy:
|
|
6
|
+
// - 1.0: Standard tools (general purpose)
|
|
7
|
+
// - 0.9: Primarily "list/browse" tools (lower-action, exploratory)
|
|
8
|
+
// - 1.1–1.2: High-value "action" tools that accomplish concrete tasks
|
|
9
|
+
|
|
10
|
+
const TOOL_REGISTRY = {
|
|
11
|
+
// ───────────── Prompt workspace tools ─────────────
|
|
12
|
+
search_prompts: {
|
|
13
|
+
category: 'prompt_workspace',
|
|
14
|
+
keywords: ['search', 'find', 'prompt', 'query', 'look', 'locate', 'discover'],
|
|
15
|
+
negativeKeywords: ['create', 'new', 'publish', 'delete'],
|
|
16
|
+
description: 'Search prompts across local workspace and on-chain libraries.',
|
|
17
|
+
whenToUse: 'Use when you need to find existing prompts by keyword, tag, or content.',
|
|
18
|
+
requiredParams: ['query'],
|
|
19
|
+
optionalParams: ['source', 'subdao', 'tags', 'includeContent'],
|
|
20
|
+
weight: 1.0,
|
|
21
|
+
},
|
|
22
|
+
list_prompts: {
|
|
23
|
+
category: 'prompt_workspace',
|
|
24
|
+
keywords: ['list', 'browse', 'prompt', 'workspace'],
|
|
25
|
+
negativeKeywords: ['search', 'publish'],
|
|
26
|
+
description: 'List prompts from local workspace and/or on-chain libraries.',
|
|
27
|
+
whenToUse: 'Use when you want a browseable list of prompts without a specific search query.',
|
|
28
|
+
requiredParams: [],
|
|
29
|
+
optionalParams: ['source', 'library', 'limit'],
|
|
30
|
+
weight: 0.9,
|
|
31
|
+
},
|
|
32
|
+
get_prompt: {
|
|
33
|
+
category: 'prompt_workspace',
|
|
34
|
+
keywords: ['get', 'inspect', 'view', 'prompt', 'details'],
|
|
35
|
+
negativeKeywords: ['search', 'list'],
|
|
36
|
+
description: 'Retrieve a specific prompt by key, including its content.',
|
|
37
|
+
whenToUse: 'Use when you already know the prompt key and want full details for editing or reuse.',
|
|
38
|
+
requiredParams: ['key'],
|
|
39
|
+
optionalParams: ['library'],
|
|
40
|
+
weight: 1.0,
|
|
41
|
+
},
|
|
42
|
+
quick_create_prompt: {
|
|
43
|
+
category: 'prompt_workspace',
|
|
44
|
+
keywords: ['create', 'new', 'write', 'add', 'draft', 'prompt'],
|
|
45
|
+
negativeKeywords: ['find', 'search', 'list'],
|
|
46
|
+
description: 'Create a new prompt with automatic library handling.',
|
|
47
|
+
whenToUse: 'Use when starting fresh with a new prompt idea.',
|
|
48
|
+
requiredParams: ['name', 'content'],
|
|
49
|
+
optionalParams: ['library', 'description', 'tags'],
|
|
50
|
+
weight: 1.2,
|
|
51
|
+
},
|
|
52
|
+
quick_iterate_prompt: {
|
|
53
|
+
category: 'prompt_workspace',
|
|
54
|
+
keywords: ['edit', 'update', 'improve', 'iterate', 'refine'],
|
|
55
|
+
negativeKeywords: ['create', 'search'],
|
|
56
|
+
description: 'Modify an existing prompt in-place, keeping history in the workspace.',
|
|
57
|
+
whenToUse: 'Use when you want to refine or adjust an existing prompt.',
|
|
58
|
+
requiredParams: ['key', 'content'],
|
|
59
|
+
optionalParams: ['library', 'description'],
|
|
60
|
+
weight: 1.1,
|
|
61
|
+
},
|
|
62
|
+
test_prompt: {
|
|
63
|
+
category: 'prompt_workspace',
|
|
64
|
+
keywords: ['test', 'render', 'evaluate', 'prompt', 'variables'],
|
|
65
|
+
negativeKeywords: ['search', 'list'],
|
|
66
|
+
description: 'Test a prompt by filling in ${variable} placeholders and viewing the rendered result.',
|
|
67
|
+
whenToUse: 'Use when you want to verify prompt behavior and variable substitution.',
|
|
68
|
+
requiredParams: ['key'],
|
|
69
|
+
optionalParams: ['library', 'variables'],
|
|
70
|
+
weight: 1.0,
|
|
71
|
+
},
|
|
72
|
+
quick_test_prompt: {
|
|
73
|
+
category: 'prompt_workspace',
|
|
74
|
+
keywords: ['test', 'quick', 'run', 'prompt', 'variables'],
|
|
75
|
+
negativeKeywords: ['search', 'list'],
|
|
76
|
+
description: 'Alias for test_prompt. Quickly test a prompt with variables.',
|
|
77
|
+
whenToUse: 'Use when you want a shortcut to test_prompt for fast validation.',
|
|
78
|
+
requiredParams: ['key'],
|
|
79
|
+
optionalParams: ['variables'],
|
|
80
|
+
weight: 1.0,
|
|
81
|
+
},
|
|
82
|
+
list_workspace_skills: {
|
|
83
|
+
category: 'prompt_workspace',
|
|
84
|
+
keywords: ['skill', 'persona', 'workspace', 'list', 'browse'],
|
|
85
|
+
negativeKeywords: ['governance', 'publish'],
|
|
86
|
+
description: 'List skills/personas defined in the current project workspace.',
|
|
87
|
+
whenToUse: 'Use when you want to see available personas/skills for this project.',
|
|
88
|
+
requiredParams: [],
|
|
89
|
+
optionalParams: ['promptsDir'],
|
|
90
|
+
weight: 1.0,
|
|
91
|
+
},
|
|
92
|
+
improve_prompt: {
|
|
93
|
+
category: 'prompt_workspace',
|
|
94
|
+
keywords: ['improve', 'refine', 'quality', 'analyze', 'prompt'],
|
|
95
|
+
negativeKeywords: ['create', 'publish'],
|
|
96
|
+
description: 'Analyze an existing prompt and suggest improvement areas and interview questions.',
|
|
97
|
+
whenToUse: 'Use when you have a working prompt and want structured suggestions for improvement.',
|
|
98
|
+
requiredParams: ['key'],
|
|
99
|
+
optionalParams: ['library', 'depth', 'focus'],
|
|
100
|
+
weight: 1.0,
|
|
101
|
+
},
|
|
102
|
+
rename_prompt: {
|
|
103
|
+
category: 'prompt_workspace',
|
|
104
|
+
keywords: ['rename', 'key', 'name', 'prompt'],
|
|
105
|
+
negativeKeywords: ['search'],
|
|
106
|
+
description: 'Rename a prompt key and/or display name while preserving content.',
|
|
107
|
+
whenToUse: 'Use when you need to change the identifier or display name of an existing prompt.',
|
|
108
|
+
requiredParams: ['key'],
|
|
109
|
+
optionalParams: ['newKey', 'name'],
|
|
110
|
+
weight: 0.9,
|
|
111
|
+
},
|
|
112
|
+
get_workspace_skill: {
|
|
113
|
+
category: 'prompt_workspace',
|
|
114
|
+
keywords: ['skill', 'persona', 'workspace', 'get', 'inspect'],
|
|
115
|
+
negativeKeywords: ['search'],
|
|
116
|
+
description: 'Load a specific workspace skill by path or key.',
|
|
117
|
+
whenToUse: 'Use when you know which skill you want to inspect or pass into an agent.',
|
|
118
|
+
requiredParams: ['path'],
|
|
119
|
+
optionalParams: [],
|
|
120
|
+
weight: 1.0,
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
// ───────────── Persona / metaprompt tools ─────────────
|
|
124
|
+
list_persona_templates: {
|
|
125
|
+
category: 'persona',
|
|
126
|
+
keywords: ['persona', 'template', 'list', 'browse', 'metaprompt'],
|
|
127
|
+
negativeKeywords: [],
|
|
128
|
+
description: 'List available persona templates (coding assistant, governance helper, etc.).',
|
|
129
|
+
whenToUse: 'Use when you are deciding what kind of assistant/persona to design.',
|
|
130
|
+
requiredParams: [],
|
|
131
|
+
optionalParams: [],
|
|
132
|
+
weight: 1.0,
|
|
133
|
+
},
|
|
134
|
+
run_persona_interview: {
|
|
135
|
+
category: 'persona',
|
|
136
|
+
keywords: ['persona', 'interview', 'one-shot', 'create', 'system prompt'],
|
|
137
|
+
negativeKeywords: ['stepwise', 'stateful'],
|
|
138
|
+
description: 'Build a persona/system prompt in one shot from provided answers.',
|
|
139
|
+
whenToUse: 'Use when you already have answers for the persona slots and just need a system prompt.',
|
|
140
|
+
requiredParams: ['template', 'answers'],
|
|
141
|
+
optionalParams: ['save', 'saveKey'],
|
|
142
|
+
weight: 1.2,
|
|
143
|
+
},
|
|
144
|
+
persona_interview_step: {
|
|
145
|
+
category: 'persona',
|
|
146
|
+
keywords: ['persona', 'interview', 'step', 'slot', 'stateful'],
|
|
147
|
+
negativeKeywords: ['one-shot'],
|
|
148
|
+
description: 'Advance a stateful persona interview by one step, returning slot metadata.',
|
|
149
|
+
whenToUse: 'Use when you want the host LLM to run a multi-turn interview over persona slots.',
|
|
150
|
+
requiredParams: [],
|
|
151
|
+
optionalParams: ['template', 'stateToken', 'answer'],
|
|
152
|
+
weight: 1.1,
|
|
153
|
+
},
|
|
154
|
+
save_metaprompt: {
|
|
155
|
+
category: 'persona',
|
|
156
|
+
keywords: ['metaprompt', 'save', 'store', 'persona'],
|
|
157
|
+
negativeKeywords: [],
|
|
158
|
+
description: 'Save a metaprompt/persona definition into the Sage workspace.',
|
|
159
|
+
whenToUse: 'Use when you want to persist a finalized persona or system prompt.',
|
|
160
|
+
requiredParams: ['title', 'body'],
|
|
161
|
+
optionalParams: ['summary', 'tags', 'appendAgentsFile'],
|
|
162
|
+
weight: 1.0,
|
|
163
|
+
},
|
|
164
|
+
get_metaprompt: {
|
|
165
|
+
category: 'persona',
|
|
166
|
+
keywords: ['metaprompt', 'persona', 'load', 'get'],
|
|
167
|
+
negativeKeywords: [],
|
|
168
|
+
description: 'Load an existing metaprompt/persona by slug.',
|
|
169
|
+
whenToUse: 'Use when you want to reuse or inspect a previously saved persona.',
|
|
170
|
+
requiredParams: ['slug'],
|
|
171
|
+
optionalParams: [],
|
|
172
|
+
weight: 1.0,
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
// ───────────── Library / manifest tools ─────────────
|
|
176
|
+
list_libraries: {
|
|
177
|
+
category: 'libraries',
|
|
178
|
+
keywords: ['library', 'libraries', 'list', 'browse', 'manifest'],
|
|
179
|
+
negativeKeywords: [],
|
|
180
|
+
description: 'List available prompt libraries from local workspace or on-chain.',
|
|
181
|
+
whenToUse: 'Use when you want to see which libraries/manifests are available.',
|
|
182
|
+
requiredParams: [],
|
|
183
|
+
optionalParams: ['source', 'subdao', 'limit'],
|
|
184
|
+
weight: 1.0,
|
|
185
|
+
},
|
|
186
|
+
get_prompts_from_manifest: {
|
|
187
|
+
category: 'libraries',
|
|
188
|
+
keywords: ['manifest', 'library', 'prompt', 'list', 'get'],
|
|
189
|
+
negativeKeywords: [],
|
|
190
|
+
description: 'Get all prompts from a specific manifest by CID.',
|
|
191
|
+
whenToUse: 'Use when you have a manifest CID and want to inspect its prompts.',
|
|
192
|
+
requiredParams: ['manifestCid'],
|
|
193
|
+
optionalParams: ['includeContent'],
|
|
194
|
+
weight: 1.0,
|
|
195
|
+
},
|
|
196
|
+
list_templates: {
|
|
197
|
+
category: 'libraries',
|
|
198
|
+
keywords: ['template', 'list', 'browse', 'prompt'],
|
|
199
|
+
negativeKeywords: [],
|
|
200
|
+
description: 'List available prompt templates for quick creation.',
|
|
201
|
+
whenToUse: 'Use when you want to see reusable prompt templates (not personas).',
|
|
202
|
+
requiredParams: [],
|
|
203
|
+
optionalParams: ['category', 'search'],
|
|
204
|
+
weight: 0.9,
|
|
205
|
+
},
|
|
206
|
+
create_from_template: {
|
|
207
|
+
category: 'libraries',
|
|
208
|
+
keywords: ['template', 'create', 'new', 'prompt'],
|
|
209
|
+
negativeKeywords: [],
|
|
210
|
+
description: 'Create a new prompt from a template and save into a library.',
|
|
211
|
+
whenToUse: 'Use when you want to quickly scaffold a prompt from an existing template.',
|
|
212
|
+
requiredParams: ['template', 'customize'],
|
|
213
|
+
optionalParams: ['library', 'name'],
|
|
214
|
+
weight: 1.1,
|
|
215
|
+
},
|
|
216
|
+
get_prompt_content: {
|
|
217
|
+
category: 'libraries',
|
|
218
|
+
keywords: ['get', 'prompt', 'content', 'ipfs', 'cid'],
|
|
219
|
+
negativeKeywords: ['search'],
|
|
220
|
+
description: 'Fetch full prompt content from IPFS by CID.',
|
|
221
|
+
whenToUse: 'Use when you have an IPFS CID and need the underlying prompt content.',
|
|
222
|
+
requiredParams: ['cid'],
|
|
223
|
+
optionalParams: [],
|
|
224
|
+
weight: 1.0,
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
// ───────────── Governance tools ─────────────
|
|
228
|
+
publish_manifest_flow: {
|
|
229
|
+
category: 'governance',
|
|
230
|
+
keywords: ['publish', 'manifest', 'dao', 'subdao', 'governance'],
|
|
231
|
+
negativeKeywords: [],
|
|
232
|
+
description: 'Prepare a manifest for on-chain publishing and generate CLI commands.',
|
|
233
|
+
whenToUse: 'Use when you are ready to publish a library/manifest to a SubDAO.',
|
|
234
|
+
requiredParams: ['manifest'],
|
|
235
|
+
optionalParams: ['subdao', 'description', 'dry_run'],
|
|
236
|
+
weight: 1.2,
|
|
237
|
+
},
|
|
238
|
+
list_proposals: {
|
|
239
|
+
category: 'governance',
|
|
240
|
+
keywords: ['proposal', 'governance', 'list', 'dao', 'subdao'],
|
|
241
|
+
negativeKeywords: [],
|
|
242
|
+
description: 'List active proposals for a specific SubDAO.',
|
|
243
|
+
whenToUse: 'Use when you want to inspect or track current governance proposals.',
|
|
244
|
+
requiredParams: [],
|
|
245
|
+
optionalParams: ['state', 'subdao', 'limit'],
|
|
246
|
+
weight: 1.0,
|
|
247
|
+
},
|
|
248
|
+
suggest_subdaos_for_library: {
|
|
249
|
+
category: 'governance',
|
|
250
|
+
keywords: ['subdao', 'suggest', 'library', 'publish', 'dao'],
|
|
251
|
+
negativeKeywords: [],
|
|
252
|
+
description: 'Suggest suitable SubDAOs for publishing a given library manifest.',
|
|
253
|
+
whenToUse: 'Use when you have a library and want advice on where to publish it.',
|
|
254
|
+
requiredParams: ['library'],
|
|
255
|
+
optionalParams: ['limit'],
|
|
256
|
+
weight: 1.0,
|
|
257
|
+
},
|
|
258
|
+
generate_publishing_commands: {
|
|
259
|
+
category: 'governance',
|
|
260
|
+
keywords: ['publish', 'commands', 'cli', 'dao', 'subdao'],
|
|
261
|
+
negativeKeywords: [],
|
|
262
|
+
description: 'Generate CLI commands for validating, uploading, and proposing a manifest.',
|
|
263
|
+
whenToUse: 'Use after selecting a SubDAO to get concrete CLI commands for publishing.',
|
|
264
|
+
requiredParams: ['library'],
|
|
265
|
+
optionalParams: ['target'],
|
|
266
|
+
weight: 1.0,
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
// ───────────── Treasury / SubDAO tools ─────────────
|
|
270
|
+
list_subdaos: {
|
|
271
|
+
category: 'treasury',
|
|
272
|
+
keywords: ['subdao', 'dao', 'list', 'treasury', 'governance'],
|
|
273
|
+
negativeKeywords: [],
|
|
274
|
+
description: 'List all available SubDAOs in the Sage Protocol.',
|
|
275
|
+
whenToUse: 'Use when you want an overview of existing SubDAOs/treasuries.',
|
|
276
|
+
requiredParams: [],
|
|
277
|
+
optionalParams: ['limit'],
|
|
278
|
+
weight: 0.9,
|
|
279
|
+
},
|
|
280
|
+
list_subdao_libraries: {
|
|
281
|
+
category: 'treasury',
|
|
282
|
+
keywords: ['subdao', 'library', 'libraries', 'list'],
|
|
283
|
+
negativeKeywords: [],
|
|
284
|
+
description: 'List libraries associated with a specific SubDAO.',
|
|
285
|
+
whenToUse: 'Use when you want to see what libraries a SubDAO currently governs.',
|
|
286
|
+
requiredParams: ['subdao'],
|
|
287
|
+
optionalParams: [],
|
|
288
|
+
weight: 1.0,
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
// ───────────── Discovery / helper tools ─────────────
|
|
292
|
+
trending_prompts: {
|
|
293
|
+
category: 'discovery',
|
|
294
|
+
keywords: ['trending', 'popular', 'recent', 'discover', 'prompt'],
|
|
295
|
+
negativeKeywords: ['create'],
|
|
296
|
+
description: 'List trending prompts from recent LibraryRegistry updates.',
|
|
297
|
+
whenToUse: 'Use when you want to discover popular or recently active prompts.',
|
|
298
|
+
requiredParams: [],
|
|
299
|
+
optionalParams: ['decayMinutes', 'limit'],
|
|
300
|
+
weight: 0.9,
|
|
301
|
+
},
|
|
302
|
+
help: {
|
|
303
|
+
category: 'discovery',
|
|
304
|
+
keywords: ['help', 'usage', 'docs', 'explain'],
|
|
305
|
+
negativeKeywords: [],
|
|
306
|
+
description: 'Get help on how to use Sage MCP tools and workflows.',
|
|
307
|
+
whenToUse: 'Use when you need guidance on which Sage tools to use or how to call them.',
|
|
308
|
+
requiredParams: [],
|
|
309
|
+
optionalParams: ['topic'],
|
|
310
|
+
weight: 0.9,
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
function getToolsForCategory(category) {
|
|
315
|
+
return Object.entries(TOOL_REGISTRY)
|
|
316
|
+
.filter(([, meta]) => meta.category === category)
|
|
317
|
+
.map(([name, meta]) => ({ name, ...meta }));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function getToolMeta(name) {
|
|
321
|
+
const meta = TOOL_REGISTRY[name];
|
|
322
|
+
if (!meta) return null;
|
|
323
|
+
return { name, ...meta };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
module.exports = {
|
|
327
|
+
TOOL_REGISTRY,
|
|
328
|
+
getToolsForCategory,
|
|
329
|
+
getToolMeta,
|
|
330
|
+
};
|
|
@@ -5,6 +5,24 @@ function createToolArgsValidator({ zodModule } = {}) {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
const schemas = {
|
|
8
|
+
suggest_sage_tools: Z.object({
|
|
9
|
+
goal: Z.string().min(1).max(1000),
|
|
10
|
+
stage: Z.enum([
|
|
11
|
+
'prompt_workspace',
|
|
12
|
+
'persona',
|
|
13
|
+
'libraries',
|
|
14
|
+
'governance',
|
|
15
|
+
'treasury',
|
|
16
|
+
'discovery',
|
|
17
|
+
]).optional(),
|
|
18
|
+
context: Z.object({
|
|
19
|
+
hasWorkspace: Z.boolean().optional(),
|
|
20
|
+
hasWallet: Z.boolean().optional(),
|
|
21
|
+
currentTask: Z.string().max(200).optional(),
|
|
22
|
+
}).optional(),
|
|
23
|
+
limit: Z.number().int().min(1).max(10).optional().default(5),
|
|
24
|
+
includeAlternatives: Z.boolean().optional().default(false),
|
|
25
|
+
}),
|
|
8
26
|
search_prompts: Z.object({
|
|
9
27
|
query: Z.string().max(256).optional().default(''),
|
|
10
28
|
source: Z.enum(['local', 'onchain', 'all']).optional().default('all'),
|
|
@@ -38,6 +56,19 @@ function createToolArgsValidator({ zodModule } = {}) {
|
|
|
38
56
|
model: Z.string().max(100).optional(),
|
|
39
57
|
interviewStyle: Z.string().max(200).optional(),
|
|
40
58
|
}),
|
|
59
|
+
list_persona_templates: Z.object({}),
|
|
60
|
+
run_persona_interview: Z.object({
|
|
61
|
+
template: Z.string().min(1).max(200),
|
|
62
|
+
answers: Z.record(Z.any()),
|
|
63
|
+
save: Z.boolean().optional().default(false),
|
|
64
|
+
saveKey: Z.string().min(1).max(200).optional(),
|
|
65
|
+
}),
|
|
66
|
+
persona_interview_step: Z.object({
|
|
67
|
+
// template is required on first call, optional when stateToken is provided
|
|
68
|
+
template: Z.string().min(1).max(200).optional().default('custom'),
|
|
69
|
+
stateToken: Z.string().max(16000).optional(), // increased for slots + answers
|
|
70
|
+
answer: Z.string().max(8000).optional(),
|
|
71
|
+
}),
|
|
41
72
|
save_metaprompt: Z.object({
|
|
42
73
|
title: Z.string().min(1).max(200),
|
|
43
74
|
summary: Z.string().max(500).optional().default(''),
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
let Anthropic;
|
|
2
|
+
try {
|
|
3
|
+
// Lazy / guarded require so OpenAI-only setups don't crash on import
|
|
4
|
+
// eslint-disable-next-line global-require
|
|
5
|
+
Anthropic = require('@anthropic-ai/sdk');
|
|
6
|
+
} catch (err) {
|
|
7
|
+
// Defer error until someone actually tries to construct the client
|
|
8
|
+
Anthropic = null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Client for interacting with Anthropic's Claude API.
|
|
13
|
+
* Follows the ModelClient interface.
|
|
14
|
+
*/
|
|
15
|
+
class AnthropicClient {
|
|
16
|
+
/**
|
|
17
|
+
* @param {object} config
|
|
18
|
+
* @param {string} config.apiKey
|
|
19
|
+
* @param {string} [config.model]
|
|
20
|
+
* @param {number} [config.temperature]
|
|
21
|
+
*/
|
|
22
|
+
constructor({ apiKey, model, temperature }) {
|
|
23
|
+
if (!Anthropic) {
|
|
24
|
+
throw new Error('Anthropic SDK not installed. Please add @anthropic-ai/sdk or configure a different provider.');
|
|
25
|
+
}
|
|
26
|
+
this.apiKey = apiKey;
|
|
27
|
+
this.model = model || 'claude-sonnet-4-20250514';
|
|
28
|
+
this.temperature = temperature ?? 0.7;
|
|
29
|
+
|
|
30
|
+
this.client = new Anthropic({
|
|
31
|
+
apiKey: this.apiKey,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Stream chat completion.
|
|
37
|
+
* @param {Array<{role:string, content:string}>} messages
|
|
38
|
+
* @param {(token:string) => void} [onToken] Callback for each token
|
|
39
|
+
* @returns {Promise<string>} Full response text
|
|
40
|
+
*/
|
|
41
|
+
async streamChat(messages, onToken) {
|
|
42
|
+
// Convert 'developer' role to 'system' property, as Anthropic handles system prompts separately
|
|
43
|
+
const systemMessage = messages.find(m => m.role === 'system' || m.role === 'developer');
|
|
44
|
+
const userAssistantMessages = messages.filter(m => m.role !== 'system' && m.role !== 'developer');
|
|
45
|
+
|
|
46
|
+
const stream = await this.client.messages.create({
|
|
47
|
+
model: this.model,
|
|
48
|
+
system: systemMessage ? systemMessage.content : undefined,
|
|
49
|
+
messages: userAssistantMessages,
|
|
50
|
+
max_tokens: 4096,
|
|
51
|
+
temperature: this.temperature,
|
|
52
|
+
stream: true,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
let fullText = '';
|
|
56
|
+
for await (const chunk of stream) {
|
|
57
|
+
if (chunk.type === 'content_block_delta' && chunk.delta.type === 'text_delta') {
|
|
58
|
+
const content = chunk.delta.text;
|
|
59
|
+
fullText += content;
|
|
60
|
+
if (onToken) onToken(content);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return fullText;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Non-streaming chat completion.
|
|
68
|
+
* @param {Array<{role:string, content:string}>} messages
|
|
69
|
+
* @returns {Promise<string>} Full response text
|
|
70
|
+
*/
|
|
71
|
+
async complete(messages) {
|
|
72
|
+
const systemMessage = messages.find(m => m.role === 'system' || m.role === 'developer');
|
|
73
|
+
const userAssistantMessages = messages.filter(m => m.role !== 'system' && m.role !== 'developer');
|
|
74
|
+
|
|
75
|
+
const response = await this.client.messages.create({
|
|
76
|
+
model: this.model,
|
|
77
|
+
system: systemMessage ? systemMessage.content : undefined,
|
|
78
|
+
messages: userAssistantMessages,
|
|
79
|
+
max_tokens: 4096,
|
|
80
|
+
temperature: this.temperature,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return response.content[0].text || '';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = AnthropicClient;
|