@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
@@ -0,0 +1,130 @@
1
+ {
2
+ "coding-assistant": {
3
+ "key": "coding-assistant",
4
+ "name": "Coding Assistant",
5
+ "description": "An expert developer persona aware of your specific stack, testing frameworks, and project structure.",
6
+ "default_goal": "Write high-quality, tested, and idiomatic code following the project's established patterns.",
7
+ "planner_instructions": "Focus on extracting the user's technology stack (languages, frameworks), testing tools, preferred project structure (monorepo, folders), and specific coding conventions (linting, naming).",
8
+ "recommended_slots": [
9
+ {
10
+ "key": "tech_stack",
11
+ "label": "Technology Stack",
12
+ "description": "Languages, frameworks, and key libraries used in the project.",
13
+ "required": true,
14
+ "priority": 1,
15
+ "group": "context"
16
+ },
17
+ {
18
+ "key": "project_structure",
19
+ "label": "Project Structure",
20
+ "description": "How the codebase is organized (e.g., monorepo, src/ folders, conventions).",
21
+ "required": true,
22
+ "priority": 2,
23
+ "group": "constraints"
24
+ },
25
+ {
26
+ "key": "testing_framework",
27
+ "label": "Testing Framework",
28
+ "description": "Tools and patterns used for testing (e.g., Jest, Hardhat, Foundry).",
29
+ "required": false,
30
+ "priority": 3,
31
+ "group": "tools"
32
+ }
33
+ ],
34
+ "inject_tools": ["sage prompts", "sage project"]
35
+ },
36
+ "governance-helper": {
37
+ "key": "governance-helper",
38
+ "name": "Governance Helper",
39
+ "description": "A steward persona for drafting proposals, analyzing risk, and navigating DAO governance.",
40
+ "default_goal": "Assist in creating, reviewing, and executing governance proposals that align with DAO constraints.",
41
+ "planner_instructions": "Focus on the DAO's identity, risk parameters (conservative vs aggressive), voting thresholds, and required proposal formats.",
42
+ "recommended_slots": [
43
+ {
44
+ "key": "dao_name",
45
+ "label": "DAO Name",
46
+ "description": "The name and identity of the organization.",
47
+ "required": true,
48
+ "priority": 1,
49
+ "group": "identity"
50
+ },
51
+ {
52
+ "key": "risk_profile",
53
+ "label": "Risk Profile",
54
+ "description": "The DAO's tolerance for risk (e.g., conservative treasury management).",
55
+ "required": true,
56
+ "priority": 2,
57
+ "group": "constraints"
58
+ },
59
+ {
60
+ "key": "proposal_format",
61
+ "label": "Proposal Format",
62
+ "description": "Specific sections or templates required for proposals.",
63
+ "required": false,
64
+ "priority": 3,
65
+ "group": "output"
66
+ }
67
+ ],
68
+ "inject_tools": ["sage governance", "sage treasury"]
69
+ },
70
+ "research-analyst": {
71
+ "key": "research-analyst",
72
+ "name": "Research Analyst",
73
+ "description": "A data-focused persona for synthesizing information from subgraphs, specs, and documents.",
74
+ "default_goal": "Analyze provided data sources and synthesize clear, actionable reports.",
75
+ "planner_instructions": "Focus on the data sources available (Subgraphs, IPFS, APIs), the analytical lens (financial, technical, community), and the desired output format.",
76
+ "recommended_slots": [
77
+ {
78
+ "key": "data_sources",
79
+ "label": "Data Sources",
80
+ "description": "Where the agent should look for information (e.g., specific Subgraphs, docs).",
81
+ "required": true,
82
+ "priority": 1,
83
+ "group": "tools"
84
+ },
85
+ {
86
+ "key": "analytical_lens",
87
+ "label": "Analytical Lens",
88
+ "description": "The perspective to adopt when analyzing data (e.g., audit, growth, sentiment).",
89
+ "required": true,
90
+ "priority": 2,
91
+ "group": "role"
92
+ },
93
+ {
94
+ "key": "output_format",
95
+ "label": "Output Format",
96
+ "description": "The structure of the final report (e.g., executive summary, deep dive).",
97
+ "required": false,
98
+ "priority": 3,
99
+ "group": "output"
100
+ }
101
+ ],
102
+ "inject_tools": ["sage prompts"]
103
+ },
104
+ "custom": {
105
+ "key": "custom",
106
+ "name": "Custom Persona",
107
+ "description": "Build a persona from scratch for any domain.",
108
+ "default_goal": "Assist the user with their specific task.",
109
+ "planner_instructions": "Analyze the user's initial description to determine the most critical information gaps. Propose slots for Role, Goals, Constraints, and Output Format.",
110
+ "recommended_slots": [
111
+ {
112
+ "key": "role",
113
+ "label": "Role",
114
+ "description": "Who is the agent?",
115
+ "required": true,
116
+ "priority": 1,
117
+ "group": "identity"
118
+ },
119
+ {
120
+ "key": "goal",
121
+ "label": "Goal",
122
+ "description": "What is the agent trying to achieve?",
123
+ "required": true,
124
+ "priority": 1,
125
+ "group": "goals"
126
+ }
127
+ ],
128
+ "inject_tools": []
129
+ }
130
+ }
@@ -0,0 +1,98 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const config = require('../config');
4
+ const ArtifactManager = require('./artifact-manager');
5
+
6
+ class ProjectContextService {
7
+ constructor(cwd = process.cwd()) {
8
+ this.cwd = cwd;
9
+ this.config = config;
10
+ this.artifactManager = new ArtifactManager(cwd);
11
+ }
12
+
13
+ async getProjectContext() {
14
+ await this.artifactManager.initialize();
15
+
16
+ // Basic Project Info
17
+ const projectRoot = this.cwd;
18
+ const promptsDir = path.join(this.cwd, 'prompts');
19
+
20
+ // Load Sage Config
21
+ const profiles = this.config.readProfiles();
22
+ const activeProfile = profiles.profiles?.[profiles.activeProfile] || {};
23
+
24
+ // Load Workspace Config (.sage/workspace.json)
25
+ let workspaceConfig = {};
26
+ try {
27
+ const workspacePath = path.join(this.cwd, '.sage', 'workspace.json');
28
+ if (fs.existsSync(workspacePath)) {
29
+ workspaceConfig = JSON.parse(fs.readFileSync(workspacePath, 'utf8'));
30
+ }
31
+ } catch (e) {
32
+ // ignore
33
+ }
34
+
35
+ // Artifact Counts
36
+ const artifacts = await this.artifactManager.listArtifacts({});
37
+ const counts = {
38
+ promptCount: artifacts.filter(a => a.kind === 'prompt').length,
39
+ skillCount: artifacts.filter(a => a.kind === 'skill').length,
40
+ snippetCount: artifacts.filter(a => a.kind === 'snippet').length,
41
+ total: artifacts.length
42
+ };
43
+
44
+ // Libraries (from workspace config or local discovery)
45
+ // For now, just return what's in workspace config or empty
46
+ const libraries = workspaceConfig.libraries || [];
47
+
48
+ // Get addresses from profile
49
+ const addresses = activeProfile.addresses || {};
50
+
51
+ // Agent surfaces: where other tools expect configuration / rules
52
+ const agentSurfaces = {
53
+ cursorRulesDir: null,
54
+ claudeManifest: null,
55
+ copilotPromptsDir: null,
56
+ agentsFiles: [],
57
+ };
58
+
59
+ const cursorRulesDir = path.join(this.cwd, '.cursor', 'rules');
60
+ if (fs.existsSync(cursorRulesDir)) {
61
+ agentSurfaces.cursorRulesDir = '.cursor/rules';
62
+ }
63
+
64
+ const claudePath = path.join(this.cwd, 'CLAUDE.md');
65
+ if (fs.existsSync(claudePath)) {
66
+ agentSurfaces.claudeManifest = 'CLAUDE.md';
67
+ }
68
+
69
+ const copilotDir = path.join(this.cwd, '.github', 'prompts');
70
+ if (fs.existsSync(copilotDir)) {
71
+ agentSurfaces.copilotPromptsDir = '.github/prompts';
72
+ }
73
+
74
+ const agentsPath = path.join(this.cwd, 'AGENTS.md');
75
+ if (fs.existsSync(agentsPath)) {
76
+ agentSurfaces.agentsFiles.push('AGENTS.md');
77
+ }
78
+
79
+ return {
80
+ projectRoot,
81
+ promptsDir: 'prompts', // relative
82
+ subdao: addresses.SUBDAO || workspaceConfig.subdao || null,
83
+ registry: addresses.LIBRARY_REGISTRY || addresses.REGISTRY || workspaceConfig.registry || null,
84
+ network: process.env.SAGE_NETWORK || 'base-sepolia',
85
+ rpcUrl: activeProfile.rpcUrl || null,
86
+ workspace: counts,
87
+ libraries,
88
+ agentSurfaces,
89
+ };
90
+ }
91
+
92
+ // Backwards compatibility for any callers still using getContext()
93
+ async getContext() {
94
+ return this.getProjectContext();
95
+ }
96
+ }
97
+
98
+ module.exports = ProjectContextService;
@@ -7,9 +7,6 @@ const fs = require('fs');
7
7
  const path = require('path');
8
8
  const { resolveArtifact } = require('./utils/artifacts');
9
9
 
10
- // Load environment variables
11
- require('dotenv').config();
12
-
13
10
  class SubDAOManager {
14
11
  constructor() {
15
12
  this.provider = null;
@@ -5,7 +5,6 @@ const { resolveArtifact } = require('./utils/artifacts');
5
5
  const { withSpinner } = require('./utils/progress');
6
6
  const { waitForReceipt } = require('./utils/tx-wait');
7
7
  const cliConfig = require('./config');
8
- try { require('dotenv').config({ quiet: true }); } catch (_) {}
9
8
 
10
9
  // Simple color functions
11
10
  const colors = {
@@ -106,12 +106,6 @@ const COMMAND_CATALOG = {
106
106
  ex: 'examples'
107
107
  }
108
108
  },
109
- prompt: {
110
- aliases: ['p'],
111
- needsSubcommand: true,
112
- subcommands: ['list', 'show', 'pull', 'push', 'publish', 'propose', 'doctor', 'run', 'test', 'preview', 'validate', 'compare', 'fork'],
113
- subcommandAliases: { ls: 'list' }
114
- },
115
109
  dev: {
116
110
  aliases: [],
117
111
  needsSubcommand: true,
@@ -1,5 +1,3 @@
1
- try { require('dotenv').config({ quiet: true }); } catch (_) {}
2
-
3
1
  async function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
4
2
 
5
3
  /**
@@ -32,4 +30,3 @@ async function waitForReceipt(provider, txOrHash, timeoutMs, pollMs = 1500) {
32
30
  }
33
31
 
34
32
  module.exports = { waitForReceipt };
35
-
@@ -10,7 +10,6 @@ let CDPWalletManager;
10
10
  try { CDPWalletManager = require('./cdp-wallet-manager'); } catch (_) { CDPWalletManager = null; }
11
11
  let WalletShared;
12
12
  try { WalletShared = require('@sage-protocol/wallet-manager'); } catch (_) { WalletShared = null; }
13
- try { require('dotenv').config({ quiet: true }); } catch (_) {}
14
13
 
15
14
  class WalletManager {
16
15
  constructor() {
@@ -37,7 +36,7 @@ class WalletManager {
37
36
  // NEVER auto-switch to private key based on env alone; require explicit insecure opt-in
38
37
  const insecurePk = shouldAllowInsecurePrivateKey();
39
38
  if (!insecurePk && (process.env.SAGE_PRIVATE_KEY || process.env.PRIVATE_KEY)) {
40
- if (!process.env.SAGE_QUIET_JSON) {
39
+ if (!process.env.SAGE_QUIET_JSON && (process.env.SAGE_VERBOSE === '1')) {
41
40
  console.warn('⚠️ SAGE_PRIVATE_KEY/PRIVATE_KEY detected but ignored.');
42
41
  console.warn(' For safety, raw private keys are disabled by default.');
43
42
  console.warn(' To enable, pass --insecure on wallet commands or set SAGE_INSECURE_PK=1.');
@@ -82,7 +81,7 @@ class WalletManager {
82
81
  }
83
82
 
84
83
  async connectMetaMask() {
85
- if (!process.env.SAGE_QUIET_JSON) console.log('🔗 Connecting to MetaMask...');
84
+ if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') console.log('🔗 Connecting to MetaMask...');
86
85
 
87
86
  this.wallet = new MetaMaskIntegration();
88
87
  this.account = await this.wallet.connect();
@@ -90,11 +89,11 @@ class WalletManager {
90
89
  this.provider = this.wallet.getProvider();
91
90
  this.connected = true;
92
91
 
93
- if (!process.env.SAGE_QUIET_JSON) console.log('✅ MetaMask connected successfully');
92
+ if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') console.log('✅ MetaMask connected successfully');
94
93
  }
95
94
 
96
95
  async connectWalletConnect() {
97
- if (!process.env.SAGE_QUIET_JSON) console.log('🔗 Connecting via WalletConnect...');
96
+ if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') console.log('🔗 Connecting via WalletConnect...');
98
97
  if (WalletShared?.walletconnect?.fromWalletConnect && process.env.WALLETCONNECT_PROJECT_ID) {
99
98
  const wc = await WalletShared.walletconnect.fromWalletConnect({ projectId: process.env.WALLETCONNECT_PROJECT_ID, rpcUrl: process.env.RPC_URL, chainId: process.env.CHAIN_ID ? Number(process.env.CHAIN_ID) : undefined });
100
99
  const uri = wc.uri;
@@ -123,11 +122,11 @@ class WalletManager {
123
122
  this.signer = this.wallet.getSigner();
124
123
  this.provider = this.wallet.getProvider();
125
124
  this.connected = true;
126
- if (!process.env.SAGE_QUIET_JSON) console.log('✅ WalletConnect connected successfully');
125
+ if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') console.log('✅ WalletConnect connected successfully');
127
126
  }
128
127
 
129
128
  async connectWeb3Auth() {
130
- if (!process.env.SAGE_QUIET_JSON) console.log('🔗 Connecting with Web3Auth wallet...');
129
+ if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') console.log('🔗 Connecting with Web3Auth wallet...');
131
130
  if (WalletShared?.web3auth?.fromWeb3Auth && (process.env.WEB3AUTH_PRIVATE_KEY || process.env.WEB3AUTH_PK)) {
132
131
  const { signer, provider } = await WalletShared.web3auth.fromWeb3Auth({ rpcUrl: process.env.RPC_URL, privateKey: process.env.WEB3AUTH_PRIVATE_KEY || process.env.WEB3AUTH_PK, chainId: process.env.CHAIN_ID ? Number(process.env.CHAIN_ID) : undefined });
133
132
  this.signer = signer;
@@ -150,11 +149,11 @@ class WalletManager {
150
149
  this.signer = this.wallet.signer;
151
150
  this.provider = this.wallet.provider || (this.wallet.signer && this.wallet.signer.provider) || null;
152
151
  this.connected = true;
153
- if (!process.env.SAGE_QUIET_JSON) console.log('✅ Web3Auth wallet connected successfully');
152
+ if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') console.log('✅ Web3Auth wallet connected successfully');
154
153
  }
155
154
 
156
155
  async connectPrivy() {
157
- if (!process.env.SAGE_QUIET_JSON) console.log('🔗 Connecting with Privy wallet...');
156
+ if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') console.log('🔗 Connecting with Privy wallet...');
158
157
  const rpc = process.env.RPC_URL || process.env.BASE_SEPOLIA_RPC || process.env.BASE_RPC_URL || 'https://base-sepolia.publicnode.com';
159
158
  if (WalletShared?.privy?.fromPrivyDeterministic && process.env.PRIVY_EMAIL && process.env.PRIVY_PASSWORD) {
160
159
  const { signer, provider } = await WalletShared.privy.fromPrivyDeterministic({ rpcUrl: rpc, email: process.env.PRIVY_EMAIL, password: process.env.PRIVY_PASSWORD, chainId: process.env.CHAIN_ID ? Number(process.env.CHAIN_ID) : undefined });
@@ -182,11 +181,11 @@ class WalletManager {
182
181
  }
183
182
  this.connected = true;
184
183
  persistWalletContext({ config: this.config, account: this.account, type: 'privy' });
185
- if (!process.env.SAGE_QUIET_JSON) console.log('✅ Privy wallet connected successfully');
184
+ if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') console.log('✅ Privy wallet connected successfully');
186
185
  }
187
186
 
188
187
  async connectCast() {
189
- if (!process.env.SAGE_QUIET_JSON) console.log('🔗 Connecting with Cast wallet...');
188
+ if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') console.log('🔗 Connecting with Cast wallet...');
190
189
 
191
190
  this.wallet = new CastWalletManager();
192
191
  this.account = await this.wallet.connect();
@@ -194,11 +193,11 @@ class WalletManager {
194
193
  this.provider = this.wallet.getProvider();
195
194
  this.connected = true;
196
195
 
197
- if (!process.env.SAGE_QUIET_JSON) console.log('✅ Cast wallet connected successfully');
196
+ if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') console.log('✅ Cast wallet connected successfully');
198
197
  }
199
198
 
200
199
  async connectCDP() {
201
- if (!process.env.SAGE_QUIET_JSON) console.log('🔗 Connecting with CDP Embedded Wallet...');
200
+ if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') console.log('🔗 Connecting with CDP Embedded Wallet...');
202
201
  const rpc = process.env.RPC_URL || process.env.BASE_SEPOLIA_RPC || process.env.BASE_RPC_URL || 'https://base-sepolia.publicnode.com';
203
202
  if (WalletShared?.cdp?.fromCdp) {
204
203
  const { signer, provider } = await WalletShared.cdp.fromCdp({ rpcUrl: rpc, chainId: process.env.CHAIN_ID ? Number(process.env.CHAIN_ID) : undefined });
@@ -207,7 +206,7 @@ class WalletManager {
207
206
  try { this.account = await signer.getAddress(); } catch (_) { this.account = process.env.CDP_WALLET_ADDRESS || null; }
208
207
  this.connected = true;
209
208
  if (!this.account) throw new Error('Unable to determine CDP account address');
210
- if (!process.env.SAGE_QUIET_JSON) console.log('✅ CDP wallet connected successfully');
209
+ if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') console.log('✅ CDP wallet connected successfully');
211
210
  return;
212
211
  }
213
212
  if (!CDPWalletManager) throw new Error('CDP wallet not available. Ensure cli/cdp-wallet-manager.js exists and dependencies are installed.');
@@ -216,11 +215,11 @@ class WalletManager {
216
215
  this.signer = this.wallet.getSigner();
217
216
  this.provider = this.wallet.getProvider();
218
217
  this.connected = true;
219
- if (!process.env.SAGE_QUIET_JSON) console.log('✅ CDP wallet connected successfully');
218
+ if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') console.log('✅ CDP wallet connected successfully');
220
219
  }
221
220
 
222
221
  async connectPrivateKey() {
223
- if (!process.env.SAGE_QUIET_JSON) console.log('🔗 Connecting with private key...');
222
+ if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') console.log('🔗 Connecting with private key...');
224
223
 
225
224
  const envPk = process.env.SAGE_PRIVATE_KEY || process.env.PRIVATE_KEY;
226
225
  if (!envPk) {
@@ -241,7 +240,7 @@ class WalletManager {
241
240
  }
242
241
  this.account = await this.signer.getAddress();
243
242
  this.connected = true;
244
- if (!process.env.SAGE_QUIET_JSON) {
243
+ if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') {
245
244
  console.log('✅ Private key connected successfully:', this.account);
246
245
  console.warn('⚠️ Insecure mode in use. Consider using Cast keystore: sage wallet import --mnemonic "..."');
247
246
  }
@@ -289,7 +288,7 @@ class WalletManager {
289
288
  const h = await checkRpcHealth(url, Number(process.env.SAGE_RPC_TIMEOUT_MS || 8000));
290
289
  if (h.healthy) { rpc = url; break; }
291
290
  }
292
- if (!process.env.SAGE_QUIET_JSON) console.log(`🌐 RPC: ${rpc}`);
291
+ if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') console.log(`🌐 RPC: ${rpc}`);
293
292
  } catch (_) {}
294
293
  return rpc;
295
294
  }
@@ -464,7 +463,7 @@ class WalletManager {
464
463
  this.provider = null;
465
464
  this.wallet = null;
466
465
 
467
- if (!process.env.SAGE_QUIET_JSON) console.log('✅ Wallet disconnected');
466
+ if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') console.log('✅ Wallet disconnected');
468
467
  }
469
468
 
470
469
  isConnected() {
@@ -1,7 +1,6 @@
1
1
  const { SignClient } = require('@walletconnect/sign-client');
2
2
  const { ethers } = require('ethers');
3
3
  const qrcode = require('qrcode-terminal');
4
- try { require('dotenv').config({ quiet: true }); } catch (_) {}
5
4
 
6
5
  // Custom Signer class for ethers.js v6 integration
7
6
  class WalletConnectSigner extends ethers.AbstractSigner {
@@ -2,7 +2,6 @@
2
2
  const { spawn } = require('child_process');
3
3
  const { ethers } = require('ethers');
4
4
  const FactoryABI = require('./utils/factory-abi');
5
- try { require('dotenv').config({ quiet: true }); } catch (_) { }
6
5
 
7
6
  // Color functions for consistent styling
8
7
  const colors = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sage-protocol/cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Sage Protocol CLI for managing AI prompt libraries",
5
5
  "bin": {
6
6
  "sage": "./bin/sage.js"
@@ -33,11 +33,13 @@
33
33
  "build": "node scripts/build-cli.js"
34
34
  },
35
35
  "dependencies": {
36
+ "@anthropic-ai/sdk": "^0.71.0",
36
37
  "@sage-protocol/contracts": "^0.1.0",
37
38
  "@sage-protocol/shared": "^0.1.0",
38
39
  "@sage-protocol/wallet-manager": "^0.1.0",
39
40
  "@whetstone-research/doppler-sdk": "^0.0.1-alpha.36",
40
41
  "keytar": "^7.9.0",
42
+ "openai": "^4.58.0",
41
43
  "qrcode-terminal": "^0.12.0",
42
44
  "update-notifier": "^6.0.2",
43
45
  "viem": "^2.33.2"
@@ -1,176 +0,0 @@
1
- const { Command } = require('commander');
2
- const fs = require('fs');
3
- const path = require('path');
4
- const cliConfig = require('../config');
5
- const { getProvider } = require('../utils/cli-session');
6
- const { SageEchoExecutor } = require('@sage-protocol/sdk');
7
-
8
- function readJsonFile(filePath) {
9
- const resolved = path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath);
10
- const raw = fs.readFileSync(resolved, 'utf8');
11
- return JSON.parse(raw);
12
- }
13
-
14
- function buildContext(options) {
15
- if (options.contextFile) {
16
- return readJsonFile(options.contextFile);
17
- }
18
- if (options.context) {
19
- try {
20
- return JSON.parse(options.context);
21
- } catch (error) {
22
- throw new Error(`Failed to parse --context JSON: ${error.message}`);
23
- }
24
- }
25
- return {};
26
- }
27
-
28
- function resolveRegistry(passed) {
29
- if (passed) return passed;
30
-
31
- if (typeof cliConfig.readProfiles === 'function') {
32
- const profiles = cliConfig.readProfiles();
33
- const active = profiles.activeProfile || 'default';
34
- const addresses = profiles.profiles?.[active]?.addresses || {};
35
- if (addresses.PROMPT_REGISTRY_ADDRESS) return addresses.PROMPT_REGISTRY_ADDRESS;
36
- if (addresses.LIBRARY_REGISTRY_ADDRESS) return addresses.LIBRARY_REGISTRY_ADDRESS;
37
- }
38
-
39
- return process.env.PROMPT_REGISTRY_ADDRESS || process.env.LIBRARY_REGISTRY_ADDRESS || null;
40
- }
41
-
42
- function formatResult(result) {
43
- if (!result || typeof result !== 'object') return result;
44
- const { prompt, response, request } = result;
45
- return {
46
- prompt,
47
- request,
48
- response,
49
- };
50
- }
51
-
52
- const promptTest = new Command('test')
53
- .description('Execute a prompt from the registry via Echo')
54
- .argument('<promptKey>', 'Key of the prompt stored in the registry')
55
- .option('-r, --registry <address>', 'Prompt registry address (defaults to configured LIBRARY_REGISTRY_ADDRESS)')
56
- .option('-g, --gateway <url>', 'IPFS gateway to fetch prompt content', process.env.SAGE_IPFS_GATEWAY)
57
- .option('-m, --model <model>', 'Echo model to use (defaults to gpt-4o-mini or $ECHO_MODEL)', process.env.ECHO_MODEL)
58
- .option('--user <text>', 'User message to send alongside the prompt template')
59
- .option('--temperature <float>', 'Sampling temperature (e.g. 0.2)')
60
- .option('--max-tokens <int>', 'Maximum number of tokens to generate')
61
- .option('--top-p <float>', 'Top-p nucleus sampling value (0-1)')
62
- .option('--context <json>', 'Inline JSON payload passed to the prompt execution context')
63
- .option('--context-file <path>', 'Path to JSON file used as execution context')
64
- .option('--json', 'Emit raw JSON response only')
65
- .action(async (promptKey, options) => {
66
- try {
67
- if (typeof cliConfig.loadEnv === 'function') {
68
- try { cliConfig.loadEnv(); } catch (_) {}
69
- }
70
-
71
- // Read-only execution: only a provider is required
72
- const provider = await getProvider({ preferWallet: true });
73
- const registry = resolveRegistry(options.registry);
74
- if (!registry) {
75
- throw new Error('Prompt registry address not configured. Provide --registry or import addresses via `sage config addresses import`.');
76
- }
77
-
78
- const echoApiKey = process.env.ECHO_API_KEY;
79
- const echoAppId = process.env.ECHO_APP_ID || process.env.NEXT_PUBLIC_ECHO_APP_ID;
80
-
81
- if (!echoApiKey) {
82
- throw new Error('ECHO_API_KEY is not set. Create an Echo API key in the Echo dashboard and export it before running this command.');
83
- }
84
-
85
- const context = buildContext(options);
86
-
87
- if (process.env.SAGE_DEBUG_PROMPT_TEST === '1') {
88
- console.log('[debug] context loaded, initializing executor');
89
- }
90
-
91
- const executor = new SageEchoExecutor(provider, null, {
92
- registry,
93
- ipfsGateway: options.gateway,
94
- apiKey: echoApiKey,
95
- appId: echoAppId,
96
- model: options.model,
97
- routerUrl: process.env.ECHO_ROUTER_URL,
98
- });
99
-
100
- const execOptions = {
101
- registry,
102
- ipfsGateway: options.gateway,
103
- model: options.model,
104
- userMessage: options.user,
105
- };
106
-
107
- if (typeof options.temperature !== 'undefined') {
108
- const temp = Number(options.temperature);
109
- if (Number.isNaN(temp)) {
110
- throw new Error('Temperature must be a number.');
111
- }
112
- execOptions.temperature = temp;
113
- }
114
- if (typeof options.maxTokens !== 'undefined') {
115
- const maxTokens = parseInt(options.maxTokens, 10);
116
- if (Number.isNaN(maxTokens)) {
117
- throw new Error('max-tokens must be an integer.');
118
- }
119
- execOptions.maxTokens = maxTokens;
120
- }
121
- if (typeof options.topP !== 'undefined') {
122
- const topP = Number(options.topP);
123
- if (Number.isNaN(topP)) {
124
- throw new Error('top-p must be a number.');
125
- }
126
- execOptions.topP = topP;
127
- }
128
-
129
- const result = await executor.executePrompt(promptKey, context, execOptions);
130
-
131
- if (process.env.SAGE_DEBUG_PROMPT_TEST === '1') {
132
- console.log('[debug] execution complete');
133
- }
134
- const payload = formatResult(result);
135
- const { response } = payload;
136
- const raw = response?.raw || null;
137
- const firstChoice = Array.isArray(raw?.choices) ? raw.choices[0] : null;
138
- const completionText =
139
- response?.text ||
140
- firstChoice?.message?.content ||
141
- firstChoice?.text ||
142
- null;
143
- const usage = response?.usage || raw?.usage || null;
144
- const modelUsed = raw?.model || response?.model || execOptions.model || process.env.ECHO_MODEL || 'gpt-4o-mini';
145
- const responseId = raw?.id || response?.id || null;
146
-
147
- if (options.json || process.env.SAGE_QUIET_JSON === '1') {
148
- console.log(JSON.stringify(payload, null, 2));
149
- } else {
150
- console.log('✅ Echo prompt execution succeeded');
151
- console.log(`Model: ${modelUsed}`);
152
- if (usage) {
153
- console.log(`Tokens — prompt: ${usage.prompt_tokens ?? 'n/a'}, completion: ${usage.completion_tokens ?? 'n/a'}, total: ${usage.total_tokens ?? 'n/a'}`);
154
- }
155
- if (responseId) {
156
- console.log(`Response ID: ${responseId}`);
157
- }
158
- if (completionText) {
159
- console.log('\n--- Assistant Response ---\n');
160
- console.log(completionText);
161
- console.log('\n--------------------------');
162
- } else {
163
- console.log('\n(No assistant message returned — inspect JSON output for details)');
164
- }
165
- }
166
- } catch (error) {
167
- if (options.json || process.env.SAGE_QUIET_JSON === '1') {
168
- console.error(JSON.stringify({ error: error.message, code: error.code || null }));
169
- } else {
170
- console.error(`❌ Failed to execute prompt: ${error.message}`);
171
- }
172
- process.exitCode = 1;
173
- }
174
- });
175
-
176
- module.exports = promptTest;