@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.
- 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 +138 -79
- package/dist/cli/commands/prompts.js +242 -87
- 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 +759 -156
- package/dist/cli/mcp-server.js +4 -30
- 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/artifact-manager.js +198 -0
- package/dist/cli/services/doctor/fixers.js +1 -1
- package/dist/cli/services/mcp/env-loader.js +2 -0
- package/dist/cli/services/mcp/prompt-result-formatter.js +8 -1
- package/dist/cli/services/mcp/quick-start.js +14 -15
- package/dist/cli/services/mcp/sage-tool-registry.js +322 -0
- package/dist/cli/services/mcp/tool-args-validator.js +43 -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/services/project-context.js +98 -0
- package/dist/cli/subdao.js +0 -3
- package/dist/cli/sxxx-manager.js +0 -1
- package/dist/cli/utils/aliases.js +0 -6
- 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
- package/dist/cli/commands/prompt-test.js +0 -176
- 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;
|
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 = {
|
|
@@ -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.
|
|
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;
|