@sage-protocol/cli 0.3.10 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/cli/browser-wallet-integration.js +0 -1
  2. package/dist/cli/cast-wallet-manager.js +0 -1
  3. package/dist/cli/commands/interview.js +149 -0
  4. package/dist/cli/commands/personal.js +234 -89
  5. package/dist/cli/commands/stake-status.js +0 -2
  6. package/dist/cli/config.js +28 -8
  7. package/dist/cli/governance-manager.js +28 -19
  8. package/dist/cli/index.js +32 -8
  9. package/dist/cli/library-manager.js +16 -6
  10. package/dist/cli/mcp-server-stdio.js +549 -0
  11. package/dist/cli/mcp-server.js +4 -30
  12. package/dist/cli/mcp-setup.md +35 -34
  13. package/dist/cli/metamask-integration.js +0 -1
  14. package/dist/cli/privy-wallet-manager.js +2 -2
  15. package/dist/cli/prompt-manager.js +0 -1
  16. package/dist/cli/services/doctor/fixers.js +1 -1
  17. package/dist/cli/services/mcp/env-loader.js +2 -0
  18. package/dist/cli/services/mcp/quick-start.js +14 -15
  19. package/dist/cli/services/mcp/sage-tool-registry.js +330 -0
  20. package/dist/cli/services/mcp/tool-args-validator.js +31 -0
  21. package/dist/cli/services/metaprompt/anthropic-client.js +87 -0
  22. package/dist/cli/services/metaprompt/interview-driver.js +161 -0
  23. package/dist/cli/services/metaprompt/model-client.js +49 -0
  24. package/dist/cli/services/metaprompt/openai-client.js +67 -0
  25. package/dist/cli/services/metaprompt/persistence.js +86 -0
  26. package/dist/cli/services/metaprompt/prompt-builder.js +186 -0
  27. package/dist/cli/services/metaprompt/session.js +18 -80
  28. package/dist/cli/services/metaprompt/slot-planner.js +115 -0
  29. package/dist/cli/services/metaprompt/templates.json +130 -0
  30. package/dist/cli/subdao.js +0 -3
  31. package/dist/cli/sxxx-manager.js +0 -1
  32. package/dist/cli/utils/tx-wait.js +0 -3
  33. package/dist/cli/wallet-manager.js +18 -19
  34. package/dist/cli/walletconnect-integration.js +0 -1
  35. package/dist/cli/wizard-manager.js +0 -1
  36. package/package.json +3 -1
@@ -18,10 +18,10 @@ Create or update your `.env` file with the required configuration:
18
18
 
19
19
  ```bash
20
20
  # Subgraph (preferred) + Blockchain
21
- SUBGRAPH_URL=https://api.goldsky.com/api/public/project_cmhxp0fppsbdd01q56xp2gqw9/subgraphs/sxxx-protocol/1.0.1/gn
22
- RPC_URL=https://sepolia.base.org
23
- LIBRARY_REGISTRY_ADDRESS=0x032F2E93549640a319163Ba600fc50bA1AFBE50F
24
- SUBDAO_FACTORY_ADDRESS=0x6bb6A83149F84Df0584aC863e5fE3416AFb214aC
21
+ SUBGRAPH_URL=https://api.goldsky.com/api/public/project_cmhxp0fppsbdd01q56xp2gqw9/subgraphs/sxxx-protocol/1.0.2/gn
22
+ RPC_URL=https://base-sepolia.publicnode.com
23
+ LIBRARY_REGISTRY_ADDRESS=0x419Cd79d58db6A5aE1900dd7F0c1b8d153FfaD06
24
+ SUBDAO_FACTORY_ADDRESS=0xED573E7e4c00fE325f846B961Df3352807eA3807
25
25
 
26
26
  # Optional: Custom IPFS Gateway
27
27
  IPFS_GATEWAY=https://ipfs.io/ipfs
@@ -65,10 +65,10 @@ You should see a JSON response listing the available tools.
65
65
  "command": "node",
66
66
  "args": ["/absolute/path/to/sage-protocol/cli/mcp-server-stdio.js"],
67
67
  "env": {
68
- "SUBGRAPH_URL": "https://api.goldsky.com/api/public/project_cmhxp0fppsbdd01q56xp2gqw9/subgraphs/sxxx-protocol/1.0.1/gn",
69
- "RPC_URL": "https://sepolia.base.org",
70
- "LIBRARY_REGISTRY_ADDRESS": "0x032F2E93549640a319163Ba600fc50bA1AFBE50F",
71
- "SUBDAO_FACTORY_ADDRESS": "0x6bb6A83149F84Df0584aC863e5fE3416AFb214aC"
68
+ "SUBGRAPH_URL": "https://api.goldsky.com/api/public/project_cmhxp0fppsbdd01q56xp2gqw9/subgraphs/sxxx-protocol/1.0.2/gn",
69
+ "RPC_URL": "https://base-sepolia.publicnode.com",
70
+ "LIBRARY_REGISTRY_ADDRESS": "0x419Cd79d58db6A5aE1900dd7F0c1b8d153FfaD06",
71
+ "SUBDAO_FACTORY_ADDRESS": "0xED573E7e4c00fE325f846B961Df3352807eA3807"
72
72
  }
73
73
  }
74
74
  }
@@ -97,36 +97,37 @@ Can you search for on-chain prompts related to "real estate"?
97
97
  or
98
98
 
99
99
  ```
100
- Please list all available SubDAOs in the Sage Protocol.
100
+ Please list all available DAOs in the Sage Protocol.
101
101
  ```
102
102
 
103
103
  ## Available MCP Tools
104
104
 
105
- Your Sage Protocol MCP Server provides these tools:
106
-
107
- ### 1. `search_onchain_prompts`
108
- - **Purpose**: Search prompts from LibraryRegistry and SubDAO registries
109
- - **Parameters**:
110
- - `query` (optional): Search terms
111
- - `subdao` (optional): Filter by SubDAO address
112
- - `tags` (optional): Filter by tags array
113
- - `limit` (optional): Max results (default: 10)
114
- - `includeContent` (optional): Fetch full content (default: false)
115
-
116
- ### 2. `get_prompt_content`
117
- - **Purpose**: Fetch full prompt content from IPFS
118
- - **Parameters**:
119
- - `cid` (required): IPFS CID of the prompt
120
-
121
- ### 3. `list_subdaos`
122
- - **Purpose**: List all SubDAOs in the protocol
123
- - **Parameters**:
124
- - `limit` (optional): Max results (default: 20)
125
-
126
- ### 4. `get_library_manifests`
127
- - **Purpose**: Get executed library manifests from LibraryRegistry
128
- - **Parameters**:
129
- - `limit` (optional): Max results (default: 10)
105
+ Your Sage Protocol MCP Server provides these tools. For the full list, see `docs/development/mcp-config.md`.
106
+
107
+ ### Quick Workflow (recommended)
108
+ - `quick_create_prompt` - Create a new prompt (handles library creation automatically)
109
+ - `quick_iterate_prompt` - Update an existing prompt with automatic backup
110
+ - `quick_test_prompt` / `test_prompt` - Test a prompt with variable substitution
111
+ - `improve_prompt` - Analyze a prompt and suggest improvements
112
+ - `rename_prompt` - Rename a prompt's key/display name
113
+ - `help` - Get help on Sage MCP workflows
114
+
115
+ ### Discovery & Search
116
+ - `search_prompts` - Unified search across local and on-chain sources
117
+ - `search_onchain_prompts` - Search directly from on-chain registries
118
+ - `trending_prompts` - List trending prompts
119
+ - `list_prompts` - List prompts from local libraries
120
+ - `get_prompt` - Retrieve a specific prompt by key
121
+ - `get_prompt_content` - Fetch full content from IPFS by CID
122
+ - `list_libraries` - Page through libraries (local or on-chain)
123
+ - `list_subdaos` - List all available DAOs
124
+ - `get_library_manifests` - Get executed library manifests
125
+
126
+ ### Publishing & Governance
127
+ - `suggest_subdaos_for_library` - Recommend DAOs for publishing
128
+ - `generate_publishing_commands` - Generate CLI commands to publish
129
+ - `publish_manifest_flow` - Validate Push → Proposal payload (no signing)
130
+ - `list_proposals` - List active proposals for a DAO
130
131
 
131
132
  ## Troubleshooting
132
133
 
@@ -1,5 +1,4 @@
1
1
  const { ethers } = require('ethers');
2
- try { require('dotenv').config({ quiet: true }); } catch (_) {}
3
2
 
4
3
  class MetaMaskIntegration {
5
4
  constructor() {
@@ -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 = '';
@@ -1,7 +1,6 @@
1
1
  const { ethers } = require('ethers');
2
2
  const FactoryABI = require('./utils/factory-abi');
3
3
  const { resolveArtifact } = require('./utils/artifacts');
4
- try { require('dotenv').config({ quiet: true }); } catch (_) {}
5
4
 
6
5
  // Simple color functions
7
6
  const colors = {
@@ -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 { require('dotenv').config(); } catch(_){}
95
+ try { config.loadEnv(); } catch(_){}
96
96
  console.log('✅ Added Pinata keys to .env');
97
97
  }
98
98
  }
@@ -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
- fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
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
- const pinned = libraryManager.listPinned();
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
- const { manifest } = libraryManager.loadPinned(lib.cid);
138
- const prompt = manifest.prompts?.find(p => p.key === key);
139
- if (prompt) {
140
- found = prompt;
141
- const libDir = libraryManager.ensureLibrariesDir();
142
- foundLib = { ...lib, manifest, manifestPath: path.join(libDir, `${lib.cid}.json`) };
143
- break;
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
- fs.writeFileSync(foundLib.manifestPath, JSON.stringify(foundLib.manifest, null, 2), 'utf8');
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(''),