@sage-protocol/cli 0.4.0 → 0.4.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.
Files changed (42) 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 +138 -79
  5. package/dist/cli/commands/prompts.js +242 -87
  6. package/dist/cli/commands/stake-status.js +0 -2
  7. package/dist/cli/config.js +28 -8
  8. package/dist/cli/governance-manager.js +28 -19
  9. package/dist/cli/index.js +32 -8
  10. package/dist/cli/library-manager.js +16 -6
  11. package/dist/cli/mcp-server-stdio.js +759 -156
  12. package/dist/cli/mcp-server.js +4 -30
  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/artifact-manager.js +198 -0
  17. package/dist/cli/services/doctor/fixers.js +1 -1
  18. package/dist/cli/services/mcp/env-loader.js +2 -0
  19. package/dist/cli/services/mcp/prompt-result-formatter.js +8 -1
  20. package/dist/cli/services/mcp/quick-start.js +14 -15
  21. package/dist/cli/services/mcp/sage-tool-registry.js +322 -0
  22. package/dist/cli/services/mcp/tool-args-validator.js +43 -0
  23. package/dist/cli/services/metaprompt/anthropic-client.js +87 -0
  24. package/dist/cli/services/metaprompt/interview-driver.js +161 -0
  25. package/dist/cli/services/metaprompt/model-client.js +49 -0
  26. package/dist/cli/services/metaprompt/openai-client.js +67 -0
  27. package/dist/cli/services/metaprompt/persistence.js +86 -0
  28. package/dist/cli/services/metaprompt/prompt-builder.js +186 -0
  29. package/dist/cli/services/metaprompt/session.js +18 -80
  30. package/dist/cli/services/metaprompt/slot-planner.js +115 -0
  31. package/dist/cli/services/metaprompt/templates.json +130 -0
  32. package/dist/cli/services/project-context.js +98 -0
  33. package/dist/cli/subdao.js +0 -3
  34. package/dist/cli/sxxx-manager.js +0 -1
  35. package/dist/cli/utils/aliases.js +0 -6
  36. package/dist/cli/utils/tx-wait.js +0 -3
  37. package/dist/cli/wallet-manager.js +18 -19
  38. package/dist/cli/walletconnect-integration.js +0 -1
  39. package/dist/cli/wizard-manager.js +0 -1
  40. package/package.json +3 -1
  41. package/dist/cli/commands/prompt-test.js +0 -176
  42. package/dist/cli/commands/prompt.js +0 -2531
@@ -11,6 +11,7 @@ const { ethers } = require('ethers');
11
11
  const { execSync } = require('child_process');
12
12
  const axios = require('axios');
13
13
  const fss = require('fs');
14
+ const { hydrateEnvFromSageConfig } = require('./services/mcp/env-loader');
14
15
  const { createLibraryManifestLister } = require('./services/mcp/library-manifests');
15
16
  const { createManifestFetcher } = require('./services/mcp/manifest-fetcher');
16
17
  const { createManifestWorkflows } = require('./services/mcp/manifest-workflows');
@@ -57,37 +58,10 @@ class MCPServer extends EventEmitter {
57
58
  }
58
59
 
59
60
  async initialize() {
60
- // Hydrate env from .sage/config.json if missing
61
+ // Hydrate env from .sage/config.json if missing (factory, registry, token, subgraph)
61
62
  try {
62
- let dir = process.cwd();
63
- let cfgPath = null;
64
- for (let i = 0; i < 6; i++) {
65
- const cand = path.join(dir, '.sage', 'config.json');
66
- if (fss.existsSync(cand)) { cfgPath = cand; break; }
67
- const next = path.dirname(dir);
68
- if (next === dir) break;
69
- dir = next;
70
- }
71
- if (!cfgPath) {
72
- const alt = path.join(__dirname, '..', '.sage', 'config.json');
73
- if (fss.existsSync(alt)) cfgPath = alt;
74
- }
75
- if (cfgPath) {
76
- const raw = fss.readFileSync(cfgPath, 'utf8');
77
- const j = JSON.parse(raw);
78
- const active = j.activeProfile || Object.keys(j.profiles || {})[0];
79
- const a = (j.profiles && j.profiles[active] && j.profiles[active].addresses) || {};
80
- if (!process.env.SUBDAO_FACTORY_ADDRESS && (a.SUBDAO_FACTORY_ADDRESS || a.SUBDAO_FACTORY)) {
81
- process.env.SUBDAO_FACTORY_ADDRESS = a.SUBDAO_FACTORY_ADDRESS || a.SUBDAO_FACTORY;
82
- }
83
- if (!process.env.LIBRARY_REGISTRY_ADDRESS && (a.LIBRARY_REGISTRY_ADDRESS || a.LIBRARY_REGISTRY)) {
84
- process.env.LIBRARY_REGISTRY_ADDRESS = a.LIBRARY_REGISTRY_ADDRESS || a.LIBRARY_REGISTRY;
85
- }
86
- if (!process.env.SXXX_TOKEN_ADDRESS && (a.SXXX_TOKEN_ADDRESS || a.SXXX)) {
87
- process.env.SXXX_TOKEN_ADDRESS = a.SXXX_TOKEN_ADDRESS || a.SXXX;
88
- }
89
- }
90
- } catch (_) { /* ignore */ }
63
+ hydrateEnvFromSageConfig({ cwd: process.cwd(), fallbackDir: path.join(__dirname, '..') });
64
+ } catch (_) { /* non-fatal */ }
91
65
  // Ensure directories exist
92
66
  await this.ensureDirectories();
93
67
 
@@ -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 = {
@@ -0,0 +1,198 @@
1
+
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const config = require('../config');
5
+ const { readWorkspace, computeChangeSet } = require('./prompts/workspace');
6
+
7
+ class ArtifactManager {
8
+ constructor(cwd = process.cwd()) {
9
+ this.cwd = cwd;
10
+ this.config = config;
11
+ this.promptsDir = path.join(this.cwd, 'prompts');
12
+ this.sageDir = path.join(this.cwd, '.sage');
13
+ }
14
+
15
+ async initialize() {
16
+ if (!fs.existsSync(this.promptsDir)) fs.mkdirSync(this.promptsDir, { recursive: true });
17
+ }
18
+
19
+ /**
20
+ * List all artifacts, optionally filtered
21
+ * @param {Object} filter { kind, publishable }
22
+ */
23
+ async listArtifacts(filter = {}) {
24
+ const artifacts = [];
25
+ const ws = readWorkspace();
26
+ const changes = ws ? computeChangeSet(ws) : { added: [], modified: [], removed: [] };
27
+
28
+ if (fs.existsSync(this.promptsDir)) {
29
+ const workspaceFiles = await this._scanDir(this.promptsDir);
30
+ for (const file of workspaceFiles) {
31
+ const artifact = await this._parseArtifact(file, ws, changes);
32
+ if (artifact) artifacts.push(artifact);
33
+ }
34
+ }
35
+
36
+ return artifacts.filter(a => {
37
+ if (filter.kind && a.kind !== filter.kind) return false;
38
+ if (filter.publishable !== undefined && a.publishable !== filter.publishable) return false;
39
+ return true;
40
+ });
41
+ }
42
+
43
+ async getArtifact(key) {
44
+ // Try to find the file. Key could be "skills/coding-helper" -> prompts/skills/coding-helper.md
45
+ const potentialPath = path.join(this.promptsDir, key + '.md');
46
+
47
+ if (fs.existsSync(potentialPath)) {
48
+ const ws = readWorkspace();
49
+ const changes = ws ? computeChangeSet(ws) : { added: [], modified: [], removed: [] };
50
+ return await this._parseArtifact(potentialPath, ws, changes);
51
+ }
52
+
53
+ return null;
54
+ }
55
+
56
+ async saveArtifact(artifact) {
57
+ const { key, kind, body, meta, targets, publishable } = artifact;
58
+
59
+ let relPath = key;
60
+ if (!relPath.endsWith('.md')) relPath += '.md';
61
+
62
+ // Enforce directory conventions
63
+ if (kind === 'skill' && !relPath.startsWith('skills/')) {
64
+ relPath = `skills/${relPath}`;
65
+ } else if (kind === 'snippet' && !relPath.startsWith('snippets/')) {
66
+ relPath = `snippets/${relPath}`;
67
+ }
68
+
69
+ const filePath = path.join(this.promptsDir, relPath);
70
+ const dir = path.dirname(filePath);
71
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
72
+
73
+ // Update meta
74
+ const frontmatter = {
75
+ ...meta,
76
+ kind,
77
+ publishable: publishable !== undefined ? publishable : (kind === 'prompt'),
78
+ targets: targets || meta?.targets || []
79
+ };
80
+
81
+ // Construct file content
82
+ const yaml = Object.entries(frontmatter)
83
+ .filter(([_, v]) => v !== undefined && v !== null)
84
+ .map(([k, v]) => `${k}: ${JSON.stringify(v)}`)
85
+ .join('\n');
86
+
87
+ const content = `---\n${yaml}\n---\n\n${body}`;
88
+
89
+ fs.writeFileSync(filePath, content);
90
+
91
+ return this._parseArtifact(filePath);
92
+ }
93
+
94
+ // --- Helpers ---
95
+
96
+ async _scanDir(dir, base = '') {
97
+ let results = [];
98
+ try {
99
+ const list = fs.readdirSync(dir);
100
+ for (const file of list) {
101
+ const fullPath = path.join(dir, file);
102
+ const stat = fs.statSync(fullPath);
103
+ if (stat && stat.isDirectory()) {
104
+ results = results.concat(await this._scanDir(fullPath));
105
+ } else {
106
+ if (file.endsWith('.md')) {
107
+ results.push(fullPath);
108
+ }
109
+ }
110
+ }
111
+ } catch (e) {
112
+ // ignore if dir doesn't exist
113
+ }
114
+ return results;
115
+ }
116
+
117
+ async _parseArtifact(filePath, ws = null, changes = null) {
118
+ try {
119
+ const content = fs.readFileSync(filePath, 'utf8');
120
+ const { attributes, body } = this._parseFrontMatter(content);
121
+
122
+ // Infer key from path relative to prompts dir
123
+ const relative = path.relative(this.promptsDir, filePath);
124
+ const key = relative.replace(/\.md$/, '');
125
+
126
+ // Infer kind
127
+ let kind = attributes.kind;
128
+ if (!kind) {
129
+ if (key.startsWith('skills/') || key.includes('/skills/')) kind = 'skill';
130
+ else if (key.startsWith('snippets/') || key.includes('/snippets/')) kind = 'snippet';
131
+ else kind = 'prompt';
132
+ }
133
+
134
+ // Infer publishable
135
+ // Default: true for prompts, false for skills/snippets
136
+ let publishable = attributes.publishable;
137
+ if (publishable === undefined) {
138
+ publishable = (kind === 'prompt');
139
+ }
140
+
141
+ // Derive publishing state
142
+ let status = 'unpublished';
143
+ if (ws && ws.files && ws.files[key]) {
144
+ if (changes && changes.modified.includes(key)) {
145
+ status = 'modified';
146
+ } else {
147
+ status = 'published'; // Assumed synced if in workspace and not modified
148
+ }
149
+ } else if (changes && changes.added.includes(key)) {
150
+ status = 'new';
151
+ }
152
+
153
+ return {
154
+ key,
155
+ kind,
156
+ publishable,
157
+ body,
158
+ meta: attributes,
159
+ targets: attributes.targets || [],
160
+ publishing: {
161
+ status,
162
+ lastSynced: ws?.files?.[key]?.mtimeMs || null
163
+ },
164
+ filePath
165
+ };
166
+ } catch (e) {
167
+ console.warn(`Failed to parse artifact at ${filePath}: `, e.message);
168
+ return null;
169
+ }
170
+ }
171
+
172
+ _parseFrontMatter(content) {
173
+ const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
174
+ if (match) {
175
+ const yaml = match[1];
176
+ const body = match[2];
177
+ const attributes = {};
178
+ yaml.split('\n').forEach(line => {
179
+ const parts = line.split(':');
180
+ if (parts.length >= 2) {
181
+ const k = parts[0].trim();
182
+ const v = parts.slice(1).join(':').trim();
183
+ if (k && v) {
184
+ try {
185
+ attributes[k] = JSON.parse(v);
186
+ } catch (e) {
187
+ attributes[k] = v;
188
+ }
189
+ }
190
+ }
191
+ });
192
+ return { attributes, body: body.trim() };
193
+ }
194
+ return { attributes: {}, body: content.trim() };
195
+ }
196
+ }
197
+
198
+ module.exports = ArtifactManager;
@@ -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]) {
@@ -23,7 +23,14 @@ function formatUnifiedResults(items = [], { total = 0, page = 1, pageSize = 10 }
23
23
  const libraryName = entry?.library?.name ? ` 🗂️ Library: ${entry.library.name}\n` : '';
24
24
  const cidLine = entry?.cid ? ` 🔗 CID: ${entry.cid}\n` : '';
25
25
  const origin = entry?.origin || entry?.source || 'unknown';
26
- return `${absoluteIndex}. **${promptName}**\n 📜 ${description}\n${libraryName}${cidLine} 🔖 Tags: ${tags}\n 🌐 Source: ${origin}\n`;
26
+
27
+ // New unified fields
28
+ const kind = entry?.kind ? ` 🔹 Kind: ${entry.kind}\n` : '';
29
+ const publishable = entry?.publishable !== undefined ? ` 📡 Publishable: ${entry.publishable}\n` : '';
30
+ const status = entry?.publishing?.status ? ` 📊 Status: ${entry.publishing.status}\n` : '';
31
+ const project = entry?.project ? ` 🏗️ Project: ${entry.project}\n` : '';
32
+
33
+ return `${absoluteIndex}. **${promptName}**\n 📜 ${description}\n${kind}${publishable}${status}${project}${libraryName}${cidLine} 🔖 Tags: ${tags}\n 🌐 Source: ${origin}\n`;
27
34
  }).join('\n');
28
35
 
29
36
  return head + lines;
@@ -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,