@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.
- package/dist/cli/browser-wallet-integration.js +0 -1
- package/dist/cli/cast-wallet-manager.js +0 -1
- package/dist/cli/commands/interview.js +149 -0
- package/dist/cli/commands/personal.js +234 -89
- package/dist/cli/commands/stake-status.js +0 -2
- package/dist/cli/config.js +28 -8
- package/dist/cli/governance-manager.js +28 -19
- package/dist/cli/index.js +32 -8
- package/dist/cli/library-manager.js +16 -6
- package/dist/cli/mcp-server-stdio.js +549 -0
- package/dist/cli/mcp-server.js +4 -30
- package/dist/cli/mcp-setup.md +35 -34
- package/dist/cli/metamask-integration.js +0 -1
- package/dist/cli/privy-wallet-manager.js +2 -2
- package/dist/cli/prompt-manager.js +0 -1
- package/dist/cli/services/doctor/fixers.js +1 -1
- package/dist/cli/services/mcp/env-loader.js +2 -0
- package/dist/cli/services/mcp/quick-start.js +14 -15
- package/dist/cli/services/mcp/sage-tool-registry.js +330 -0
- package/dist/cli/services/mcp/tool-args-validator.js +31 -0
- package/dist/cli/services/metaprompt/anthropic-client.js +87 -0
- package/dist/cli/services/metaprompt/interview-driver.js +161 -0
- package/dist/cli/services/metaprompt/model-client.js +49 -0
- package/dist/cli/services/metaprompt/openai-client.js +67 -0
- package/dist/cli/services/metaprompt/persistence.js +86 -0
- package/dist/cli/services/metaprompt/prompt-builder.js +186 -0
- package/dist/cli/services/metaprompt/session.js +18 -80
- package/dist/cli/services/metaprompt/slot-planner.js +115 -0
- package/dist/cli/services/metaprompt/templates.json +130 -0
- package/dist/cli/subdao.js +0 -3
- package/dist/cli/sxxx-manager.js +0 -1
- package/dist/cli/utils/tx-wait.js +0 -3
- package/dist/cli/wallet-manager.js +18 -19
- package/dist/cli/walletconnect-integration.js +0 -1
- package/dist/cli/wizard-manager.js +0 -1
- 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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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.
|
|
17
|
-
this.
|
|
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
|
-
|
|
21
|
-
|
|
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.
|
|
87
|
+
assistantMessage = await this.client.streamChat(this.messages, (token) => {
|
|
137
88
|
if (token) printAssistant(token);
|
|
138
89
|
});
|
|
139
90
|
} catch (error) {
|
|
140
|
-
|
|
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
|
+
}
|
package/dist/cli/subdao.js
CHANGED
package/dist/cli/sxxx-manager.js
CHANGED
|
@@ -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
|
+
"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"
|