@sage-protocol/cli 0.3.10 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/cli/browser-wallet-integration.js +0 -1
  2. package/dist/cli/cast-wallet-manager.js +0 -1
  3. package/dist/cli/commands/interview.js +149 -0
  4. package/dist/cli/commands/personal.js +234 -89
  5. package/dist/cli/commands/stake-status.js +0 -2
  6. package/dist/cli/config.js +28 -8
  7. package/dist/cli/governance-manager.js +28 -19
  8. package/dist/cli/index.js +32 -8
  9. package/dist/cli/library-manager.js +16 -6
  10. package/dist/cli/mcp-server-stdio.js +549 -0
  11. package/dist/cli/mcp-server.js +4 -30
  12. package/dist/cli/mcp-setup.md +35 -34
  13. package/dist/cli/metamask-integration.js +0 -1
  14. package/dist/cli/privy-wallet-manager.js +2 -2
  15. package/dist/cli/prompt-manager.js +0 -1
  16. package/dist/cli/services/doctor/fixers.js +1 -1
  17. package/dist/cli/services/mcp/env-loader.js +2 -0
  18. package/dist/cli/services/mcp/quick-start.js +14 -15
  19. package/dist/cli/services/mcp/sage-tool-registry.js +330 -0
  20. package/dist/cli/services/mcp/tool-args-validator.js +31 -0
  21. package/dist/cli/services/metaprompt/anthropic-client.js +87 -0
  22. package/dist/cli/services/metaprompt/interview-driver.js +161 -0
  23. package/dist/cli/services/metaprompt/model-client.js +49 -0
  24. package/dist/cli/services/metaprompt/openai-client.js +67 -0
  25. package/dist/cli/services/metaprompt/persistence.js +86 -0
  26. package/dist/cli/services/metaprompt/prompt-builder.js +186 -0
  27. package/dist/cli/services/metaprompt/session.js +18 -80
  28. package/dist/cli/services/metaprompt/slot-planner.js +115 -0
  29. package/dist/cli/services/metaprompt/templates.json +130 -0
  30. package/dist/cli/subdao.js +0 -3
  31. package/dist/cli/sxxx-manager.js +0 -1
  32. package/dist/cli/utils/tx-wait.js +0 -3
  33. package/dist/cli/wallet-manager.js +18 -19
  34. package/dist/cli/walletconnect-integration.js +0 -1
  35. package/dist/cli/wizard-manager.js +0 -1
  36. package/package.json +3 -1
@@ -1,32 +1,33 @@
1
+ const { createModelClient } = require('./model-client');
2
+
1
3
  class MetapromptSession {
2
4
  constructor(options = {}) {
3
5
  const {
4
6
  title,
5
7
  goal,
6
- targetModel = 'gpt-5',
7
8
  interviewStyle = 'one-question-at-a-time',
8
- apiKey,
9
9
  temperature = 0.7,
10
- axios,
11
- process,
12
- env,
13
- openAiModel = (env || process?.env || global.process.env).SAGE_METAPROMPT_MODEL || 'gpt-4o-mini',
10
+ process: proc,
11
+ config, // injected config dependency
12
+ provider, // optional override
13
+ model, // optional override
14
+ apiKey, // optional override
14
15
  } = options;
15
16
 
16
- this.axios = axios || require('axios');
17
- this.process = process || global.process;
18
- this.env = env || this.process.env;
17
+ this.process = proc || global.process;
18
+ this.config = config || require('../../config'); // fallback to global require if not passed
19
19
 
20
- if (!apiKey) {
21
- throw new Error('Missing OpenAI API key. Configure one with `sage config ai set --provider openai --key sk-...` or set OPENAI_API_KEY.');
20
+ // Initialize the ModelClient using the factory
21
+ try {
22
+ this.client = createModelClient(this.config, { provider, model, apiKey });
23
+ } catch (error) {
24
+ // If createModelClient fails (no keys), re-throw with the helpful message
25
+ throw error;
22
26
  }
23
27
 
24
28
  this.title = title || 'Untitled Metaprompt';
25
29
  this.goal = goal || 'Design a high quality system prompt.';
26
- this.targetModel = targetModel;
27
- this.openAiModel = openAiModel;
28
30
  this.interviewStyle = interviewStyle;
29
- this.apiKey = apiKey;
30
31
  this.temperature = temperature;
31
32
 
32
33
  this.messages = [
@@ -48,7 +49,6 @@ class MetapromptSession {
48
49
  'Before emitting FINAL_PROMPT, summarise the agreed requirements and confirm readiness.',
49
50
  'If the user types `/finish` or explicitly asks you to finalise, produce the FINAL_PROMPT immediately.',
50
51
  `Primary goal: ${this.goal}.`,
51
- `Target downstream model: ${this.targetModel}.`,
52
52
  `Interview cadence preference: ${this.interviewStyle}.`,
53
53
  'Guardrails: capture constraints, desired behaviours, failure modes, tone, tooling, fallback defaults, and evaluation hooks.',
54
54
  'When providing FINAL_PROMPT, include only the polished system prompt inside the fenced block (no commentary).',
@@ -66,55 +66,6 @@ class MetapromptSession {
66
66
  this.transcript.push({ role, content, timestamp: new Date().toISOString() });
67
67
  }
68
68
 
69
- async requestAssistantMessage(onToken) {
70
- const response = await this.axios.post(
71
- 'https://api.openai.com/v1/chat/completions',
72
- {
73
- model: this.openAiModel,
74
- messages: this.messages,
75
- temperature: this.temperature,
76
- stream: true,
77
- },
78
- {
79
- headers: {
80
- Authorization: `Bearer ${this.apiKey}`,
81
- 'Content-Type': 'application/json',
82
- },
83
- responseType: 'stream',
84
- }
85
- );
86
-
87
- return new Promise((resolve, reject) => {
88
- let buffer = '';
89
- let fullText = '';
90
- response.data.on('data', (chunk) => {
91
- buffer += chunk.toString('utf8');
92
- const lines = buffer.split('\n');
93
- buffer = lines.pop() || '';
94
- for (const line of lines) {
95
- const trimmed = line.trim();
96
- if (!trimmed.startsWith('data:')) continue;
97
- const payload = trimmed.slice(5).trim();
98
- if (!payload || payload === '[DONE]') continue;
99
- try {
100
- const parsed = JSON.parse(payload);
101
- const delta = parsed.choices?.[0]?.delta || {};
102
- if (typeof delta.content === 'string' && delta.content.length) {
103
- fullText += delta.content;
104
- if (typeof onToken === 'function') onToken(delta.content);
105
- }
106
- if (delta.role === 'assistant' && typeof onToken === 'function') {
107
- onToken('');
108
- }
109
- } catch (_) {}
110
- }
111
- });
112
-
113
- response.data.on('end', () => resolve(fullText.trim()));
114
- response.data.on('error', (error) => reject(error));
115
- });
116
- }
117
-
118
69
  extractFinalPrompt(text) {
119
70
  if (!text) return null;
120
71
  const match = /FINAL_PROMPT\s*```(?:[a-zA-Z0-9_-]+\n)?([\s\S]*?)```/i.exec(text);
@@ -131,24 +82,13 @@ class MetapromptSession {
131
82
  this.#appendTranscript('user', this.messages[1].content);
132
83
 
133
84
  while (!this.finalPrompt) {
134
- let assistantMessage;
85
+ let assistantMessage = '';
135
86
  try {
136
- assistantMessage = await this.requestAssistantMessage((token) => {
87
+ assistantMessage = await this.client.streamChat(this.messages, (token) => {
137
88
  if (token) printAssistant(token);
138
89
  });
139
90
  } catch (error) {
140
- const status = error?.response?.status;
141
- let detail = error?.response?.data?.error?.message;
142
- if (!detail && error?.response?.data) {
143
- try {
144
- detail = JSON.stringify(error.response.data);
145
- } catch (_) {}
146
- }
147
- if (!detail) detail = error.message;
148
- const hint = detail && detail.includes('model')
149
- ? ' (hint: set SAGE_METAPROMPT_MODEL or choose a supported OpenAI model)'
150
- : '';
151
- throw new Error(status ? `OpenAI error (${status}): ${detail}${hint}` : `OpenAI error: ${detail}${hint}`);
91
+ throw new Error(`AI Model error: ${error.message}`);
152
92
  }
153
93
 
154
94
  if (assistantMessage) {
@@ -192,8 +132,6 @@ class MetapromptSession {
192
132
  finalAssistantMessage: this.finalAssistantMessage,
193
133
  transcript: this.transcript,
194
134
  messages: this.messages,
195
- targetModel: this.targetModel,
196
- openAiModel: this.openAiModel,
197
135
  };
198
136
  }
199
137
  }
@@ -0,0 +1,115 @@
1
+ const { createModelClient } = require('./model-client');
2
+ const templates = require('./templates.json');
3
+
4
+ class SlotPlanner {
5
+ /**
6
+ * @param {object} config CLI config object
7
+ * @param {object} [options]
8
+ * @param {object} [options.client] optional shared ModelClient instance
9
+ * @param {string} [options.provider]
10
+ * @param {string} [options.model]
11
+ * @param {string} [options.apiKey]
12
+ */
13
+ constructor(config, options = {}) {
14
+ this.config = config;
15
+ this.overrides = {
16
+ provider: options.provider,
17
+ model: options.model,
18
+ apiKey: options.apiKey,
19
+ };
20
+ this.client = options.client || null;
21
+ }
22
+
23
+ /**
24
+ * Generates a plan of slots (information requirements) for the persona.
25
+ * @param {string} templateKey - The key of the template (e.g., 'coding-assistant').
26
+ * @param {string} initialDescription - The user's initial input/goal.
27
+ * @param {string[]} [contextTags] - Optional tags to guide planning.
28
+ * @returns {Promise<Array>} Array of slot objects.
29
+ */
30
+ async planSlots(templateKey, initialDescription, contextTags = []) {
31
+ const template = templates[templateKey] || templates['custom'];
32
+ const recommended = template.recommended_slots || [];
33
+
34
+ // For standard personas, default to static recommended slots to avoid unnecessary LLM calls.
35
+ // Use the planner primarily for the "custom" template (or when explicitly configured).
36
+ if (templateKey !== 'custom') {
37
+ return recommended.slice().sort((a, b) => a.priority - b.priority);
38
+ }
39
+
40
+ // System prompt for the planner LLM
41
+ const systemPrompt = `
42
+ You are an expert Metaprompt Architect. Your goal is to design a structured "Slot Plan" for a new AI Persona.
43
+ A "Slot" is a specific piece of information we need to collect from the user to build a high-quality system prompt.
44
+
45
+ Template Context: ${template.name} - ${template.description}
46
+ Planner Instructions: ${template.planner_instructions}
47
+
48
+ Existing Recommended Slots:
49
+ ${JSON.stringify(recommended, null, 2)}
50
+
51
+ User's Initial Description: "${initialDescription}"
52
+ Context Tags: ${contextTags.join(', ')}
53
+
54
+ Your Task:
55
+ 1. Analyze the User's Description and the Template Instructions.
56
+ 2. Determine if the Recommended Slots are sufficient, or if new custom slots are needed.
57
+ 3. If the user has *already* provided information for a slot in their description, you should still list the slot but mark it as potentially filled (this logic happens later, just define the schema now).
58
+ 4. Return a JSON array of Slot objects.
59
+
60
+ Slot Schema:
61
+ {
62
+ "key": "snake_case_key",
63
+ "label": "Human Readable Label",
64
+ "description": "Question/Prompt to ask the user to fill this slot",
65
+ "required": boolean,
66
+ "priority": number (1=Critical, 5=Nice to have),
67
+ "group": "identity" | "context" | "constraints" | "tools" | "output"
68
+ }
69
+
70
+ CRITICAL: Return ONLY the JSON array. No markdown formatting, no code blocks.
71
+ `;
72
+
73
+ const messages = [
74
+ { role: 'system', content: systemPrompt.trim() },
75
+ { role: 'user', content: 'Generate the slot plan.' }
76
+ ];
77
+
78
+ try {
79
+ if (!this.client) {
80
+ this.client = createModelClient(this.config, this.overrides);
81
+ }
82
+ const response = await this.client.complete(messages);
83
+ let plan = [];
84
+
85
+ // clean potential markdown blocks
86
+ const cleanJson = response.replace(/```json/g, '').replace(/```/g, '').trim();
87
+
88
+ try {
89
+ plan = JSON.parse(cleanJson);
90
+ } catch (e) {
91
+ // Fallback: if JSON parse fails, just use recommended slots
92
+ console.warn('SlotPlanner: Failed to parse LLM response, using defaults.', e.message);
93
+ return recommended;
94
+ }
95
+
96
+ // Merge recommended slots if they aren't present (or if LLM hallucinated them away)
97
+ // Ideally the LLM includes them, but let's ensure critical ones exist.
98
+ for (const rec of recommended) {
99
+ if (!plan.find(p => p.key === rec.key)) {
100
+ plan.push(rec);
101
+ }
102
+ }
103
+
104
+ // Sort by priority
105
+ return plan.sort((a, b) => a.priority - b.priority);
106
+
107
+ } catch (error) {
108
+ console.error('SlotPlanner Error:', error);
109
+ // Fallback to recommended
110
+ return recommended;
111
+ }
112
+ }
113
+ }
114
+
115
+ module.exports = SlotPlanner;
@@ -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
+ }
@@ -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 = {
@@ -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.3.10",
3
+ "version": "0.4.1",
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"