@sage-protocol/cli 0.3.0 → 0.3.3

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 (37) hide show
  1. package/dist/cli/commands/doctor.js +87 -8
  2. package/dist/cli/commands/gov-config.js +81 -0
  3. package/dist/cli/commands/governance.js +152 -72
  4. package/dist/cli/commands/library.js +9 -0
  5. package/dist/cli/commands/proposals.js +187 -17
  6. package/dist/cli/commands/skills.js +175 -21
  7. package/dist/cli/commands/subdao.js +22 -2
  8. package/dist/cli/config/playbooks.json +15 -0
  9. package/dist/cli/governance-manager.js +25 -4
  10. package/dist/cli/index.js +5 -6
  11. package/dist/cli/library-manager.js +79 -0
  12. package/dist/cli/mcp-server-stdio.js +1655 -82
  13. package/dist/cli/schemas/manifest.schema.json +55 -0
  14. package/dist/cli/services/doctor/fixers.js +134 -0
  15. package/dist/cli/services/mcp/bulk-operations.js +272 -0
  16. package/dist/cli/services/mcp/dependency-analyzer.js +202 -0
  17. package/dist/cli/services/mcp/library-listing.js +2 -2
  18. package/dist/cli/services/mcp/local-prompt-collector.js +1 -0
  19. package/dist/cli/services/mcp/manifest-downloader.js +5 -3
  20. package/dist/cli/services/mcp/manifest-fetcher.js +17 -1
  21. package/dist/cli/services/mcp/manifest-workflows.js +127 -15
  22. package/dist/cli/services/mcp/quick-start.js +287 -0
  23. package/dist/cli/services/mcp/stdio-runner.js +30 -5
  24. package/dist/cli/services/mcp/template-manager.js +156 -0
  25. package/dist/cli/services/mcp/templates/default-templates.json +84 -0
  26. package/dist/cli/services/mcp/tool-args-validator.js +66 -0
  27. package/dist/cli/services/mcp/trending-formatter.js +1 -1
  28. package/dist/cli/services/mcp/unified-prompt-search.js +2 -2
  29. package/dist/cli/services/metaprompt/designer.js +12 -5
  30. package/dist/cli/services/subdao/applier.js +208 -196
  31. package/dist/cli/services/subdao/planner.js +41 -6
  32. package/dist/cli/subdao-manager.js +14 -0
  33. package/dist/cli/utils/aliases.js +17 -2
  34. package/dist/cli/utils/contract-error-decoder.js +61 -0
  35. package/dist/cli/utils/suggestions.js +17 -12
  36. package/package.json +3 -2
  37. package/src/schemas/manifest.schema.json +55 -0
@@ -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,71 @@ 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(),
77
+ depth: Z.enum(['single', 'deep']).optional(),
78
+ focus: Z.enum(['variables', 'edge-cases', 'output-quality', 'reusability', 'all']).optional().default('all'),
79
+ }).transform((obj) => ({
80
+ key: obj.key,
81
+ library: obj.library,
82
+ pass: obj.depth || obj.pass || 'single',
83
+ focus: obj.focus,
84
+ })),
85
+ rename_prompt: Z.object({
86
+ key: Z.string().min(1).max(200),
87
+ newKey: Z.string().min(1).max(200).optional(),
88
+ name: Z.string().min(1).max(200).optional(),
89
+ }),
90
+ update_library_metadata: Z.object({
91
+ library: Z.string().min(1).max(200),
92
+ name: Z.string().min(1).max(200).optional(),
93
+ description: Z.string().max(1000).optional(),
94
+ tags: Z.array(Z.string().max(64)).optional(),
95
+ apply_to_prompts: Z.boolean().optional().default(false),
96
+ merge_mode: Z.enum(['replace', 'merge']).optional().default('merge'),
97
+ }),
98
+ bulk_update_prompts: Z.object({
99
+ library: Z.string().min(1).max(200),
100
+ updates: Z.array(
101
+ Z.object({
102
+ key: Z.string().min(1).max(200),
103
+ name: Z.string().min(1).max(200).optional(),
104
+ description: Z.string().max(2000).optional(),
105
+ tags: Z.array(Z.string().max(64)).optional(),
106
+ content: Z.string().optional(),
107
+ }),
108
+ )
109
+ .min(1),
110
+ dry_run: Z.boolean().optional().default(false),
111
+ }),
112
+ list_templates: Z.object({
113
+ category: Z.string().max(200).optional(),
114
+ search: Z.string().max(500).optional(),
115
+ }),
116
+ get_template: Z.object({
117
+ key: Z.string().min(1).max(200),
118
+ }),
119
+ create_from_template: Z.object({
120
+ template: Z.string().min(1).max(200),
121
+ customize: Z.record(Z.any()),
122
+ library: Z.string().min(1).max(200).optional(),
123
+ name: Z.string().min(1).max(200).optional(),
124
+ }),
125
+ analyze_dependencies: Z.object({
126
+ library: Z.string().min(1).max(200),
127
+ analysis_type: Z.enum(['variables', 'all']).optional().default('variables'),
128
+ }),
129
+ suggest_subdaos_for_library: Z.object({
130
+ library: Z.string().min(1).max(200),
131
+ limit: Z.number().int().min(1).max(20).optional().default(5),
132
+ mode_filter: Z.enum(['any', 'creator', 'squad', 'community']).optional().default('any'),
133
+ }),
134
+ generate_publishing_commands: Z.object({
135
+ library: Z.string().min(1).max(200),
136
+ target: Z.string().max(200).optional().default('auto'),
137
+ }),
72
138
  };
73
139
 
74
140
  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: 'json', text: JSON.stringify({ items }, null, 2) },
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: 'json',
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
  };
@@ -57,7 +57,7 @@ class MetapromptDesigner {
57
57
  const history = this.path.join(base, 'history');
58
58
  try {
59
59
  this.fs.mkdirSync(history, { recursive: true });
60
- } catch (_) {}
60
+ } catch (_) { }
61
61
  return history;
62
62
  }
63
63
 
@@ -103,7 +103,7 @@ class MetapromptDesigner {
103
103
  if (value.startsWith('[') && value.endsWith(']')) {
104
104
  try {
105
105
  value = JSON.parse(value.replace(/'/g, '"'));
106
- } catch (_) {}
106
+ } catch (_) { }
107
107
  }
108
108
  meta[key] = value;
109
109
  });
@@ -197,9 +197,10 @@ class MetapromptDesigner {
197
197
  goal = 'Design a system prompt for an AI assistant that specialises in Sage Protocol development and prompt operations.',
198
198
  model = 'gpt-5',
199
199
  interviewStyle = 'one-question-at-a-time',
200
+ additionalInstructions = '',
200
201
  } = options;
201
202
 
202
- return [
203
+ const lines = [
203
204
  'You are facilitating a metaprompt interview.',
204
205
  'Interview me one question at a time.',
205
206
  'Use each answer to decide the very next question.',
@@ -211,13 +212,19 @@ class MetapromptDesigner {
211
212
  'Offer optional follow-up artifacts (e.g., user prompt template, evaluation checklist).',
212
213
  'End by suggesting how to save or launch the prompt (e.g., via ChatGPT link).',
213
214
  `Interview cadence preference: ${interviewStyle}.`,
214
- ].join('\n');
215
+ ];
216
+
217
+ if (additionalInstructions) {
218
+ lines.push(additionalInstructions);
219
+ }
220
+
221
+ return lines.join('\n');
215
222
  }
216
223
 
217
224
  saveTranscript(transcriptPath, payload) {
218
225
  try {
219
226
  this.fs.mkdirSync(this.path.dirname(transcriptPath), { recursive: true });
220
- } catch (_) {}
227
+ } catch (_) { }
221
228
  this.fs.writeFileSync(transcriptPath, JSON.stringify(payload, null, 2));
222
229
  }
223
230
  }