@sage-protocol/cli 0.2.9 → 0.3.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/commands/config.js +28 -0
- package/dist/cli/commands/doctor.js +87 -8
- package/dist/cli/commands/gov-config.js +81 -0
- package/dist/cli/commands/governance.js +152 -72
- package/dist/cli/commands/library.js +9 -0
- package/dist/cli/commands/proposals.js +187 -17
- package/dist/cli/commands/skills.js +737 -0
- package/dist/cli/commands/subdao.js +96 -132
- package/dist/cli/config/playbooks.json +62 -0
- package/dist/cli/config.js +15 -0
- package/dist/cli/governance-manager.js +25 -4
- package/dist/cli/index.js +6 -7
- package/dist/cli/library-manager.js +79 -0
- package/dist/cli/mcp-server-stdio.js +1387 -166
- package/dist/cli/schemas/manifest.schema.json +55 -0
- package/dist/cli/services/doctor/fixers.js +134 -0
- package/dist/cli/services/governance/doctor.js +140 -0
- package/dist/cli/services/governance/playbooks.js +97 -0
- package/dist/cli/services/mcp/bulk-operations.js +272 -0
- package/dist/cli/services/mcp/dependency-analyzer.js +202 -0
- package/dist/cli/services/mcp/library-listing.js +2 -2
- package/dist/cli/services/mcp/local-prompt-collector.js +1 -0
- package/dist/cli/services/mcp/manifest-downloader.js +5 -3
- package/dist/cli/services/mcp/manifest-fetcher.js +17 -1
- package/dist/cli/services/mcp/manifest-workflows.js +127 -15
- package/dist/cli/services/mcp/quick-start.js +287 -0
- package/dist/cli/services/mcp/stdio-runner.js +30 -5
- package/dist/cli/services/mcp/template-manager.js +156 -0
- package/dist/cli/services/mcp/templates/default-templates.json +84 -0
- package/dist/cli/services/mcp/tool-args-validator.js +56 -0
- package/dist/cli/services/mcp/trending-formatter.js +1 -1
- package/dist/cli/services/mcp/unified-prompt-search.js +2 -2
- package/dist/cli/services/metaprompt/designer.js +12 -5
- package/dist/cli/services/skills/discovery.js +99 -0
- package/dist/cli/services/subdao/applier.js +229 -0
- package/dist/cli/services/subdao/planner.js +142 -0
- package/dist/cli/subdao-manager.js +14 -0
- package/dist/cli/utils/aliases.js +28 -6
- package/dist/cli/utils/contract-error-decoder.js +61 -0
- package/dist/cli/utils/suggestions.js +25 -13
- package/package.json +3 -2
- package/src/schemas/manifest.schema.json +55 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const { LibraryManager } = require('../../library-manager');
|
|
4
|
+
|
|
5
|
+
function createQuickStart({
|
|
6
|
+
libraryManager = new LibraryManager(),
|
|
7
|
+
logger = console,
|
|
8
|
+
}) {
|
|
9
|
+
|
|
10
|
+
function ensureDefaultLibrary() {
|
|
11
|
+
const pinned = libraryManager.listPinned();
|
|
12
|
+
// Look for "My Library" or "default"
|
|
13
|
+
let defaultLib = pinned.find(p => p.name === 'My Library' || p.name === 'default');
|
|
14
|
+
|
|
15
|
+
if (!defaultLib) {
|
|
16
|
+
// Create "My Library"
|
|
17
|
+
const result = libraryManager.createLibrary('My Library', 'Default library for quick prompts');
|
|
18
|
+
defaultLib = { cid: result.cid, name: 'My Library' };
|
|
19
|
+
}
|
|
20
|
+
return defaultLib;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function generateKey(name) {
|
|
24
|
+
return name.toLowerCase()
|
|
25
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
26
|
+
.replace(/^-+|-+$/g, '');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function quickCreatePrompt({ name, content, description = '', library = '', tags } = {}) {
|
|
30
|
+
if (!name || !content) {
|
|
31
|
+
throw new Error('Name and content are required');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let targetLib;
|
|
35
|
+
if (library) {
|
|
36
|
+
const pinned = libraryManager.listPinned();
|
|
37
|
+
targetLib = pinned.find(p =>
|
|
38
|
+
p.name.toLowerCase() === library.toLowerCase() ||
|
|
39
|
+
p.cid === library
|
|
40
|
+
);
|
|
41
|
+
if (!targetLib) {
|
|
42
|
+
// If library doesn't exist, create it
|
|
43
|
+
const result = libraryManager.createLibrary(library, '');
|
|
44
|
+
targetLib = { cid: result.cid, name: library };
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
targetLib = ensureDefaultLibrary();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const key = generateKey(name);
|
|
51
|
+
|
|
52
|
+
// Check if key exists
|
|
53
|
+
const { manifest } = libraryManager.loadPinned(targetLib.cid);
|
|
54
|
+
if (manifest.prompts?.some(p => p.key === key)) {
|
|
55
|
+
throw new Error(`Prompt with key "${key}" already exists in library "${targetLib.name}". Use quick_iterate_prompt to update it.`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Add prompt
|
|
59
|
+
// We need to use the library manager's internal methods or replicate logic
|
|
60
|
+
// Since LibraryManager doesn't expose "addPrompt", we'll implement it here for now
|
|
61
|
+
// In a real refactor, we should move this to LibraryManager
|
|
62
|
+
|
|
63
|
+
const libDir = libraryManager.ensureLibrariesDir();
|
|
64
|
+
const manifestPath = path.join(libDir, `${targetLib.cid}.json`);
|
|
65
|
+
|
|
66
|
+
// Create prompt file
|
|
67
|
+
// We'll store it in a 'prompts' subdirectory next to the manifest if possible,
|
|
68
|
+
// but for local libraries, they are flat in ~/.sage/libraries/
|
|
69
|
+
// Let's create a prompts directory inside ~/.sage/libraries/prompts/
|
|
70
|
+
const promptsDir = path.join(libDir, 'prompts');
|
|
71
|
+
if (!fs.existsSync(promptsDir)) {
|
|
72
|
+
fs.mkdirSync(promptsDir, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const promptFileName = `${targetLib.cid}_${key}.md`;
|
|
76
|
+
const promptFilePath = path.join(promptsDir, promptFileName);
|
|
77
|
+
fs.writeFileSync(promptFilePath, content, 'utf8');
|
|
78
|
+
|
|
79
|
+
// Normalise tags (optional)
|
|
80
|
+
let promptTags = [];
|
|
81
|
+
if (tags !== undefined) {
|
|
82
|
+
let normalized = tags;
|
|
83
|
+
if (typeof normalized === 'string') {
|
|
84
|
+
try {
|
|
85
|
+
const parsed = JSON.parse(normalized);
|
|
86
|
+
if (Array.isArray(parsed)) {
|
|
87
|
+
normalized = parsed;
|
|
88
|
+
} else {
|
|
89
|
+
normalized = String(normalized)
|
|
90
|
+
.split(/[,;]/)
|
|
91
|
+
.map((t) => t.trim())
|
|
92
|
+
.filter(Boolean);
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
normalized = String(normalized)
|
|
96
|
+
.split(/[,;]/)
|
|
97
|
+
.map((t) => t.trim())
|
|
98
|
+
.filter(Boolean);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (Array.isArray(normalized)) {
|
|
102
|
+
promptTags = normalized.map((t) => String(t));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Update manifest
|
|
107
|
+
if (!manifest.prompts) manifest.prompts = [];
|
|
108
|
+
manifest.prompts.push({
|
|
109
|
+
key,
|
|
110
|
+
name,
|
|
111
|
+
description,
|
|
112
|
+
files: [`prompts/${promptFileName}`], // Relative path
|
|
113
|
+
tags: promptTags,
|
|
114
|
+
cid: '' // Local prompt
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
success: true,
|
|
121
|
+
key,
|
|
122
|
+
library: targetLib.name,
|
|
123
|
+
cid: targetLib.cid,
|
|
124
|
+
message: `Created prompt "${name}" (${key}) in "${targetLib.name}"`
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function quickIteratePrompt({ key, content, name, description, tags }) {
|
|
129
|
+
if (!key) throw new Error('Key is required');
|
|
130
|
+
|
|
131
|
+
// Find the prompt
|
|
132
|
+
const pinned = libraryManager.listPinned();
|
|
133
|
+
let found = null;
|
|
134
|
+
let foundLib = null;
|
|
135
|
+
|
|
136
|
+
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
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!found) {
|
|
148
|
+
throw new Error(`Prompt with key "${key}" not found. Use quick_create_prompt to create it.`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Update content if provided
|
|
152
|
+
if (content) {
|
|
153
|
+
const libDir = libraryManager.ensureLibrariesDir();
|
|
154
|
+
let promptFilePath;
|
|
155
|
+
|
|
156
|
+
if (found.files && found.files.length > 0) {
|
|
157
|
+
// Use existing file
|
|
158
|
+
const relativePath = found.files[0];
|
|
159
|
+
// Handle potential path issues
|
|
160
|
+
promptFilePath = path.join(libDir, relativePath);
|
|
161
|
+
|
|
162
|
+
// Backup existing
|
|
163
|
+
if (fs.existsSync(promptFilePath)) {
|
|
164
|
+
const backupPath = `${promptFilePath}.v${Date.now()}.bak`;
|
|
165
|
+
fs.copyFileSync(promptFilePath, backupPath);
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
// Create new file if none existed (legacy/imported)
|
|
169
|
+
const promptsDir = path.join(libDir, 'prompts');
|
|
170
|
+
if (!fs.existsSync(promptsDir)) fs.mkdirSync(promptsDir, { recursive: true });
|
|
171
|
+
const promptFileName = `${foundLib.cid}_${key}.md`;
|
|
172
|
+
promptFilePath = path.join(promptsDir, promptFileName);
|
|
173
|
+
found.files = [`prompts/${promptFileName}`];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
fs.writeFileSync(promptFilePath, content, 'utf8');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Update metadata
|
|
180
|
+
if (name) {
|
|
181
|
+
found.name = name;
|
|
182
|
+
}
|
|
183
|
+
if (typeof description === 'string') {
|
|
184
|
+
found.description = description;
|
|
185
|
+
}
|
|
186
|
+
if (tags !== undefined) {
|
|
187
|
+
let normalized = tags;
|
|
188
|
+
if (typeof normalized === 'string') {
|
|
189
|
+
// Accept JSON array string or comma/space separated list
|
|
190
|
+
try {
|
|
191
|
+
const parsed = JSON.parse(normalized);
|
|
192
|
+
if (Array.isArray(parsed)) {
|
|
193
|
+
normalized = parsed;
|
|
194
|
+
} else {
|
|
195
|
+
normalized = String(normalized)
|
|
196
|
+
.split(/[,;]/)
|
|
197
|
+
.map((t) => t.trim())
|
|
198
|
+
.filter(Boolean);
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
normalized = String(normalized)
|
|
202
|
+
.split(/[,;]/)
|
|
203
|
+
.map((t) => t.trim())
|
|
204
|
+
.filter(Boolean);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (Array.isArray(normalized)) {
|
|
208
|
+
found.tags = normalized.map((t) => String(t));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Save manifest
|
|
213
|
+
fs.writeFileSync(foundLib.manifestPath, JSON.stringify(foundLib.manifest, null, 2), 'utf8');
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
success: true,
|
|
217
|
+
key,
|
|
218
|
+
library: foundLib.name,
|
|
219
|
+
message: `Updated prompt "${key}" (name: ${found.name || name || key})`
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function renamePrompt({ key, newKey, name }) {
|
|
224
|
+
if (!key) throw new Error('Key is required');
|
|
225
|
+
if (!newKey && !name) throw new Error('newKey or name required');
|
|
226
|
+
|
|
227
|
+
const pinned = libraryManager.listPinned();
|
|
228
|
+
let found = null;
|
|
229
|
+
let foundLib = null;
|
|
230
|
+
|
|
231
|
+
for (const lib of pinned) {
|
|
232
|
+
const { manifest } = libraryManager.loadPinned(lib.cid);
|
|
233
|
+
const prompt = manifest.prompts?.find((p) => p.key === key);
|
|
234
|
+
if (prompt) {
|
|
235
|
+
const libDir = libraryManager.ensureLibrariesDir();
|
|
236
|
+
found = prompt;
|
|
237
|
+
foundLib = {
|
|
238
|
+
...lib,
|
|
239
|
+
manifest,
|
|
240
|
+
manifestPath: path.join(libDir, `${lib.cid}.json`),
|
|
241
|
+
};
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!found || !foundLib) {
|
|
247
|
+
throw new Error(`Prompt with key "${key}" not found. Use list_prompts or search_prompts to discover keys.`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Handle key change with collision check
|
|
251
|
+
let finalKey = key;
|
|
252
|
+
if (newKey && newKey !== key) {
|
|
253
|
+
const exists = (foundLib.manifest.prompts || []).some(
|
|
254
|
+
(p) => p !== found && p.key === newKey,
|
|
255
|
+
);
|
|
256
|
+
if (exists) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
`Prompt with key "${newKey}" already exists in library "${foundLib.name}". Choose a different key.`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
found.key = newKey;
|
|
262
|
+
finalKey = newKey;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (name) {
|
|
266
|
+
found.name = name;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
fs.writeFileSync(foundLib.manifestPath, JSON.stringify(foundLib.manifest, null, 2));
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
success: true,
|
|
273
|
+
oldKey: key,
|
|
274
|
+
key: finalKey,
|
|
275
|
+
library: foundLib.name,
|
|
276
|
+
message: `Renamed prompt "${key}" to "${finalKey}" (display name: ${found.name || finalKey})`,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
quickCreatePrompt,
|
|
282
|
+
quickIteratePrompt,
|
|
283
|
+
renamePrompt,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
module.exports = { createQuickStart };
|
|
@@ -35,12 +35,37 @@ function createStdIoRunner(baseOptions = {}) {
|
|
|
35
35
|
terminal: false,
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
+
let outputClosed = false;
|
|
39
|
+
if (output && typeof output.on === 'function') {
|
|
40
|
+
output.on('error', (err) => {
|
|
41
|
+
if (err && err.code === 'EPIPE') {
|
|
42
|
+
// Client closed the pipe; mark closed and let the process exit cleanly.
|
|
43
|
+
outputClosed = true;
|
|
44
|
+
try { rl.close(); } catch (_) {}
|
|
45
|
+
} else if (debugEnabled) {
|
|
46
|
+
errorWriter(`stdout error: ${err.message || String(err)}`);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
38
51
|
const writeJson = (payload) => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
if (outputClosed) return;
|
|
53
|
+
try {
|
|
54
|
+
const text = typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
55
|
+
if (typeof output?.write === 'function') {
|
|
56
|
+
output.write(`${text}\n`);
|
|
57
|
+
} else {
|
|
58
|
+
console.log(text); // eslint-disable-line no-console
|
|
59
|
+
}
|
|
60
|
+
} catch (err) {
|
|
61
|
+
if (err && err.code === 'EPIPE') {
|
|
62
|
+
outputClosed = true;
|
|
63
|
+
try { rl.close(); } catch (_) {}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (debugEnabled) {
|
|
67
|
+
errorWriter(`writeJson error: ${err.message || String(err)}`);
|
|
68
|
+
}
|
|
44
69
|
}
|
|
45
70
|
};
|
|
46
71
|
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Simple prompt template manager for MCP flows.
|
|
6
|
+
*
|
|
7
|
+
* Templates are defined as JSON data and rendered into concrete prompts
|
|
8
|
+
* which are persisted using quickStart.quickCreatePrompt.
|
|
9
|
+
*/
|
|
10
|
+
function createTemplateManager({
|
|
11
|
+
quickStart,
|
|
12
|
+
fsModule = fs,
|
|
13
|
+
pathModule = path,
|
|
14
|
+
logger = console,
|
|
15
|
+
} = {}) {
|
|
16
|
+
if (!quickStart || typeof quickStart.quickCreatePrompt !== 'function') {
|
|
17
|
+
throw new Error('TemplateManager requires quickStart.quickCreatePrompt');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const defaultTemplatePath = pathModule.join(__dirname, 'templates', 'default-templates.json');
|
|
21
|
+
let templatesCache = null;
|
|
22
|
+
|
|
23
|
+
function loadTemplates() {
|
|
24
|
+
if (templatesCache) return templatesCache;
|
|
25
|
+
try {
|
|
26
|
+
const raw = fsModule.readFileSync(defaultTemplatePath, 'utf8');
|
|
27
|
+
const parsed = JSON.parse(raw);
|
|
28
|
+
const list = Array.isArray(parsed) ? parsed : parsed.templates || [];
|
|
29
|
+
templatesCache = list;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
logger?.warn?.('template_manager_load_failed', { path: defaultTemplatePath, err: error.message || String(error) });
|
|
32
|
+
templatesCache = [];
|
|
33
|
+
}
|
|
34
|
+
return templatesCache;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function listTemplates({ category, search } = {}) {
|
|
38
|
+
const all = loadTemplates();
|
|
39
|
+
const cat = (category || '').toLowerCase();
|
|
40
|
+
const term = (search || '').toLowerCase();
|
|
41
|
+
const filtered = all.filter((tpl) => {
|
|
42
|
+
if (cat && String(tpl.category || '').toLowerCase() !== cat) return false;
|
|
43
|
+
if (!term) return true;
|
|
44
|
+
const haystack = [
|
|
45
|
+
tpl.key || '',
|
|
46
|
+
tpl.name || '',
|
|
47
|
+
tpl.description || '',
|
|
48
|
+
(tpl.metadata && tpl.metadata.suggested_description) || '',
|
|
49
|
+
]
|
|
50
|
+
.join(' ')
|
|
51
|
+
.toLowerCase();
|
|
52
|
+
return haystack.includes(term);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const items = filtered.map((tpl) => {
|
|
56
|
+
const vars = Array.isArray(tpl.variables) ? tpl.variables : [];
|
|
57
|
+
return {
|
|
58
|
+
key: tpl.key,
|
|
59
|
+
name: tpl.name,
|
|
60
|
+
category: tpl.category || '',
|
|
61
|
+
summary: tpl.description || (tpl.metadata && tpl.metadata.suggested_description) || '',
|
|
62
|
+
requiredVariables: vars.filter((v) => v && v.required).map((v) => v.name),
|
|
63
|
+
optionalVariables: vars.filter((v) => v && !v.required).map((v) => v.name),
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return { templates: items };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getTemplate({ key }) {
|
|
71
|
+
const all = loadTemplates();
|
|
72
|
+
const id = String(key || '').trim();
|
|
73
|
+
if (!id) {
|
|
74
|
+
throw new Error('template key is required');
|
|
75
|
+
}
|
|
76
|
+
const tpl = all.find((t) => t.key === id);
|
|
77
|
+
if (!tpl) {
|
|
78
|
+
throw new Error(`Template not found for key "${key}"`);
|
|
79
|
+
}
|
|
80
|
+
return { template: tpl };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function renderWithVars(templateString, customize, variables) {
|
|
84
|
+
let result = String(templateString || '');
|
|
85
|
+
const vars = Array.isArray(variables) ? variables : [];
|
|
86
|
+
for (const v of vars) {
|
|
87
|
+
if (!v || !v.name) continue;
|
|
88
|
+
const value = customize && Object.prototype.hasOwnProperty.call(customize, v.name)
|
|
89
|
+
? String(customize[v.name])
|
|
90
|
+
: (v.default !== undefined ? String(v.default) : `\${${v.name}}`);
|
|
91
|
+
const pattern = new RegExp(`\\$\\{${v.name}\\}`, 'g');
|
|
92
|
+
result = result.replace(pattern, value);
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function createFromTemplate({ template, customize, library, name } = {}) {
|
|
98
|
+
const { template: tpl } = getTemplate({ key: template });
|
|
99
|
+
const vars = Array.isArray(tpl.variables) ? tpl.variables : [];
|
|
100
|
+
const missing = vars
|
|
101
|
+
.filter((v) => v && v.required)
|
|
102
|
+
.filter((v) => !customize || customize[v.name] === undefined || customize[v.name] === null || customize[v.name] === '');
|
|
103
|
+
|
|
104
|
+
if (missing.length) {
|
|
105
|
+
const names = missing.map((v) => v.name).join(', ');
|
|
106
|
+
throw new Error(`Missing required variables for template "${tpl.key}": ${names}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const content = renderWithVars(tpl.content_template || '', customize, vars);
|
|
110
|
+
|
|
111
|
+
const promptName =
|
|
112
|
+
name ||
|
|
113
|
+
tpl.name ||
|
|
114
|
+
(customize && customize.title) ||
|
|
115
|
+
(customize && customize.project_type && `${customize.project_type} Prompt`) ||
|
|
116
|
+
tpl.key;
|
|
117
|
+
|
|
118
|
+
let description = '';
|
|
119
|
+
if (tpl.metadata && tpl.metadata.suggested_description) {
|
|
120
|
+
description = renderWithVars(tpl.metadata.suggested_description, customize, vars);
|
|
121
|
+
} else {
|
|
122
|
+
description = tpl.description || '';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const defaultTags = Array.isArray(tpl.metadata?.default_tags)
|
|
126
|
+
? tpl.metadata.default_tags.map((t) => String(t).trim()).filter(Boolean)
|
|
127
|
+
: [];
|
|
128
|
+
const tags = defaultTags;
|
|
129
|
+
|
|
130
|
+
const quickResult = await quickStart.quickCreatePrompt({
|
|
131
|
+
name: promptName,
|
|
132
|
+
content,
|
|
133
|
+
description,
|
|
134
|
+
library,
|
|
135
|
+
tags,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
success: true,
|
|
140
|
+
template: tpl.key,
|
|
141
|
+
variables: customize || {},
|
|
142
|
+
tags,
|
|
143
|
+
quickCreate: quickResult,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
listTemplates,
|
|
149
|
+
getTemplate,
|
|
150
|
+
createFromTemplate,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = {
|
|
155
|
+
createTemplateManager,
|
|
156
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"key": "landscape-design-planner",
|
|
4
|
+
"name": "Landscape Design Planner",
|
|
5
|
+
"category": "design",
|
|
6
|
+
"description": "Structured planner for landscape and courtyard design projects.",
|
|
7
|
+
"variables": [
|
|
8
|
+
{ "name": "project_type", "required": true, "description": "courtyard, garden, patio, deck, etc." },
|
|
9
|
+
{ "name": "region", "required": true, "description": "location or climate zone" },
|
|
10
|
+
{ "name": "style", "required": false, "description": "modern, traditional, cottage, etc.", "default": "modern" },
|
|
11
|
+
{ "name": "budget_range", "required": false, "description": "low, medium, high, or numeric range" }
|
|
12
|
+
],
|
|
13
|
+
"content_template": "You are an experienced landscape designer specializing in ${project_type} projects in ${region}.\n\nYour tasks:\n1. Analyze the site conditions and constraints.\n2. Propose a high-level layout for the ${project_type}.\n3. Recommend materials, planting zones, and functional zones.\n4. Highlight tradeoffs given the budget range (${budget_range}).\n\nRespond with:\n- Site assumptions\n- Layout description\n- Material and planting recommendations\n- Risks and open questions.",
|
|
14
|
+
"metadata": {
|
|
15
|
+
"default_tags": ["design", "landscape", "courtyard"],
|
|
16
|
+
"suggested_description": "${project_type} planning prompt for projects in ${region}."
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"key": "code-review-assistant",
|
|
21
|
+
"name": "Code Review Assistant",
|
|
22
|
+
"category": "development",
|
|
23
|
+
"description": "Prompt for structured, actionable code review feedback.",
|
|
24
|
+
"variables": [
|
|
25
|
+
{ "name": "language", "required": true, "description": "primary language or stack" },
|
|
26
|
+
{ "name": "context", "required": false, "description": "repository or feature context", "default": "general" },
|
|
27
|
+
{ "name": "focus_areas", "required": false, "description": "performance, readability, security, etc." }
|
|
28
|
+
],
|
|
29
|
+
"content_template": "You are a senior ${language} engineer performing a code review.\n\nContext: ${context}\nFocus areas: ${focus_areas}\n\nFor the given code, provide:\n1. High-level summary of what the code does.\n2. Strengths.\n3. Issues grouped by severity (critical, important, minor).\n4. Concrete suggestions and examples for improvement.\n5. Any follow-up questions.\n\nBe concise but specific; avoid restating the code.",
|
|
30
|
+
"metadata": {
|
|
31
|
+
"default_tags": ["code-review", "development"],
|
|
32
|
+
"suggested_description": "Structured ${language} code review assistant prompt."
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"key": "data-analysis-brief",
|
|
37
|
+
"name": "Data Analysis Brief",
|
|
38
|
+
"category": "analysis",
|
|
39
|
+
"description": "Briefing prompt for exploratory or explanatory data analysis.",
|
|
40
|
+
"variables": [
|
|
41
|
+
{ "name": "dataset_description", "required": true, "description": "what the data represents" },
|
|
42
|
+
{ "name": "audience", "required": false, "description": "decision makers, technical team, etc.", "default": "mixed" },
|
|
43
|
+
{ "name": "primary_question", "required": true, "description": "main question to answer" }
|
|
44
|
+
],
|
|
45
|
+
"content_template": "You are a data analyst preparing a structured analysis plan.\n\nDataset: ${dataset_description}\nAudience: ${audience}\nPrimary question: ${primary_question}\n\nProvide:\n1. Clarifying questions you would ask before analysis.\n2. Proposed analysis steps and methods.\n3. Key metrics and visualizations you would produce.\n4. Potential risks, caveats, or biases.\n5. How you would communicate results to the audience.",
|
|
46
|
+
"metadata": {
|
|
47
|
+
"default_tags": ["analysis", "planning"],
|
|
48
|
+
"suggested_description": "Plan an analysis for ${dataset_description} focusing on ${primary_question}."
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"key": "writing-assistant",
|
|
53
|
+
"name": "Writing Assistant",
|
|
54
|
+
"category": "writing",
|
|
55
|
+
"description": "General-purpose structured writing assistant prompt.",
|
|
56
|
+
"variables": [
|
|
57
|
+
{ "name": "piece_type", "required": true, "description": "blog post, email, announcement, etc." },
|
|
58
|
+
{ "name": "topic", "required": true, "description": "subject of the piece" },
|
|
59
|
+
{ "name": "tone", "required": false, "description": "professional, friendly, playful, etc.", "default": "professional" }
|
|
60
|
+
],
|
|
61
|
+
"content_template": "You are an assistant helping draft a ${piece_type} about ${topic} in a ${tone} tone.\n\nProvide:\n1. A suggested outline.\n2. A draft introduction paragraph.\n3. 2–3 key points with short supporting paragraphs.\n4. A concise conclusion or call to action.\n\nKeep the language clear and accessible.",
|
|
62
|
+
"metadata": {
|
|
63
|
+
"default_tags": ["writing", "content"],
|
|
64
|
+
"suggested_description": "${piece_type} assistant prompt for ${topic}."
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"key": "governance-library-publish-helper",
|
|
69
|
+
"name": "Governance Library Publish Helper",
|
|
70
|
+
"category": "governance",
|
|
71
|
+
"description": "Helper prompt to plan publishing a Sage library to a SubDAO.",
|
|
72
|
+
"variables": [
|
|
73
|
+
{ "name": "library_name", "required": true, "description": "name of the library being published" },
|
|
74
|
+
{ "name": "subdao_name", "required": false, "description": "name of the target SubDAO" },
|
|
75
|
+
{ "name": "risk_level", "required": false, "description": "low, medium, high", "default": "medium" }
|
|
76
|
+
],
|
|
77
|
+
"content_template": "You are assisting with publishing the \"${library_name}\" library to a Sage SubDAO (${subdao_name}).\n\nFor this publication, outline:\n1. Why this library is valuable to the SubDAO.\n2. Any risks or tradeoffs (risk level: ${risk_level}).\n3. Preconditions for a safe rollout (tests, review, etc.).\n4. A short, clear proposal description for governance.\n\nRespond as a structured brief the proposer can paste into their governance proposal.",
|
|
78
|
+
"metadata": {
|
|
79
|
+
"default_tags": ["governance", "library", "publish"],
|
|
80
|
+
"suggested_description": "Governance brief helper for publishing ${library_name}."
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
|
|
@@ -61,6 +61,7 @@ function createToolArgsValidator({ zodModule } = {}) {
|
|
|
61
61
|
manifest: Z.record(Z.any()),
|
|
62
62
|
subdao: Z.string().regex(/^0x[a-fA-F0-9]{40}$/).optional().or(Z.literal('')).default(''),
|
|
63
63
|
description: Z.string().max(280).optional().default(''),
|
|
64
|
+
dry_run: Z.boolean().optional().default(false),
|
|
64
65
|
}),
|
|
65
66
|
propose_manifest: Z.object({
|
|
66
67
|
cid: Z.string().min(10).max(200),
|
|
@@ -69,6 +70,61 @@ function createToolArgsValidator({ zodModule } = {}) {
|
|
|
69
70
|
promptCount: Z.number().int().min(0).optional(),
|
|
70
71
|
manifest: Z.record(Z.any()).optional(),
|
|
71
72
|
}),
|
|
73
|
+
improve_prompt: Z.object({
|
|
74
|
+
key: Z.string().min(1).max(200),
|
|
75
|
+
library: Z.string().min(1).max(200).optional(),
|
|
76
|
+
pass: Z.enum(['single', 'deep']).optional().default('single'),
|
|
77
|
+
focus: Z.enum(['variables', 'edge-cases', 'output-quality', 'reusability', 'all']).optional().default('all'),
|
|
78
|
+
}),
|
|
79
|
+
rename_prompt: Z.object({
|
|
80
|
+
key: Z.string().min(1).max(200),
|
|
81
|
+
newKey: Z.string().min(1).max(200).optional(),
|
|
82
|
+
name: Z.string().min(1).max(200).optional(),
|
|
83
|
+
}),
|
|
84
|
+
update_library_metadata: Z.object({
|
|
85
|
+
library: Z.string().min(1).max(200),
|
|
86
|
+
name: Z.string().min(1).max(200).optional(),
|
|
87
|
+
description: Z.string().max(1000).optional(),
|
|
88
|
+
tags: Z.array(Z.string().max(64)).optional(),
|
|
89
|
+
apply_to_prompts: Z.boolean().optional().default(false),
|
|
90
|
+
merge_mode: Z.enum(['replace', 'merge']).optional().default('merge'),
|
|
91
|
+
}),
|
|
92
|
+
bulk_update_prompts: Z.object({
|
|
93
|
+
library: Z.string().min(1).max(200),
|
|
94
|
+
updates: Z.array(
|
|
95
|
+
Z.object({
|
|
96
|
+
key: Z.string().min(1).max(200),
|
|
97
|
+
name: Z.string().min(1).max(200).optional(),
|
|
98
|
+
description: Z.string().max(2000).optional(),
|
|
99
|
+
tags: Z.array(Z.string().max(64)).optional(),
|
|
100
|
+
content: Z.string().optional(),
|
|
101
|
+
}),
|
|
102
|
+
)
|
|
103
|
+
.min(1),
|
|
104
|
+
dry_run: Z.boolean().optional().default(false),
|
|
105
|
+
}),
|
|
106
|
+
list_templates: Z.object({
|
|
107
|
+
category: Z.string().max(200).optional(),
|
|
108
|
+
search: Z.string().max(500).optional(),
|
|
109
|
+
}),
|
|
110
|
+
get_template: Z.object({
|
|
111
|
+
key: Z.string().min(1).max(200),
|
|
112
|
+
}),
|
|
113
|
+
create_from_template: Z.object({
|
|
114
|
+
template: Z.string().min(1).max(200),
|
|
115
|
+
customize: Z.record(Z.any()),
|
|
116
|
+
library: Z.string().min(1).max(200).optional(),
|
|
117
|
+
name: Z.string().min(1).max(200).optional(),
|
|
118
|
+
}),
|
|
119
|
+
analyze_dependencies: Z.object({
|
|
120
|
+
library: Z.string().min(1).max(200),
|
|
121
|
+
analysis_type: Z.enum(['variables', 'all']).optional().default('variables'),
|
|
122
|
+
}),
|
|
123
|
+
suggest_subdaos_for_library: Z.object({
|
|
124
|
+
library: Z.string().min(1).max(200),
|
|
125
|
+
limit: Z.number().int().min(1).max(20).optional().default(5),
|
|
126
|
+
mode_filter: Z.enum(['any', 'creator', 'squad', 'community']).optional().default('any'),
|
|
127
|
+
}),
|
|
72
128
|
};
|
|
73
129
|
|
|
74
130
|
return function validate(name, args) {
|
|
@@ -14,7 +14,7 @@ function buildTrendingResponse(suggestions = [], limit = 10) {
|
|
|
14
14
|
return {
|
|
15
15
|
content: [
|
|
16
16
|
{ type: 'text', text },
|
|
17
|
-
{ type: '
|
|
17
|
+
{ type: 'text', text: '```json\n' + JSON.stringify({ items }, null, 2) + '\n```' },
|
|
18
18
|
],
|
|
19
19
|
};
|
|
20
20
|
}
|
|
@@ -76,8 +76,8 @@ function createUnifiedPromptSearcher({
|
|
|
76
76
|
content: [
|
|
77
77
|
{ type: 'text', text },
|
|
78
78
|
{
|
|
79
|
-
type: '
|
|
80
|
-
text: JSON.stringify({ total, page: normalizedPage, pageSize: normalizedPageSize, results: pageResults }, null, DEFAULT_INDENT),
|
|
79
|
+
type: 'text',
|
|
80
|
+
text: '```json\n' + JSON.stringify({ total, page: normalizedPage, pageSize: normalizedPageSize, results: pageResults }, null, DEFAULT_INDENT) + '\n```',
|
|
81
81
|
},
|
|
82
82
|
],
|
|
83
83
|
};
|