@emblemvault/agentwallet 1.3.1 → 3.0.0
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/README.md +157 -44
- package/docs/COMMANDS.md +171 -0
- package/docs/PLUGINS.md +106 -0
- package/docs/SETUP.md +180 -0
- package/emblemai.js +562 -859
- package/package.json +13 -5
- package/src/auth-server.js +300 -0
- package/src/auth.js +816 -0
- package/src/commands.js +754 -0
- package/src/formatter.js +250 -0
- package/src/glow.js +364 -0
- package/src/plugins/loader.js +533 -0
- package/src/session-store.js +94 -0
- package/src/tui.js +171 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emblemvault/agentwallet",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "CLI for EmblemVault Hustle AI - autonomous crypto wallet management",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "CLI for EmblemVault Hustle AI - autonomous crypto wallet management with browser auth, streaming, and plugins",
|
|
5
5
|
"main": "emblemai.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -9,12 +9,17 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node emblemai.js",
|
|
12
|
+
"simple": "node emblemai.js --simple",
|
|
12
13
|
"reset": "node emblemai.js --reset"
|
|
13
14
|
},
|
|
14
15
|
"dependencies": {
|
|
15
|
-
"
|
|
16
|
+
"@dotenvx/dotenvx": "^1.52.0",
|
|
16
17
|
"@emblemvault/auth-sdk": "^2.3.16",
|
|
17
|
-
"
|
|
18
|
+
"blessed": "^0.1.81",
|
|
19
|
+
"blessed-contrib": "^4.11.0",
|
|
20
|
+
"chalk": "^5.3.0",
|
|
21
|
+
"dotenv": "^16.3.1",
|
|
22
|
+
"hustle-incognito": "^1.0.4"
|
|
18
23
|
},
|
|
19
24
|
"keywords": [
|
|
20
25
|
"ai",
|
|
@@ -27,7 +32,8 @@
|
|
|
27
32
|
"ethereum",
|
|
28
33
|
"defi",
|
|
29
34
|
"trading",
|
|
30
|
-
"openclaw"
|
|
35
|
+
"openclaw",
|
|
36
|
+
"tui"
|
|
31
37
|
],
|
|
32
38
|
"author": "EmblemVault",
|
|
33
39
|
"license": "MIT",
|
|
@@ -41,6 +47,8 @@
|
|
|
41
47
|
},
|
|
42
48
|
"files": [
|
|
43
49
|
"emblemai.js",
|
|
50
|
+
"src/",
|
|
51
|
+
"docs/",
|
|
44
52
|
"README.md"
|
|
45
53
|
]
|
|
46
54
|
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Auth Server for EmblemAI CLI
|
|
3
|
+
*
|
|
4
|
+
* Starts a temporary local HTTP server that serves an auth page and receives
|
|
5
|
+
* the session callback. Ported from hustle-v5/src/auth/local-server.ts
|
|
6
|
+
* and hustle-v5/src/auth/auth-page.ts (combined).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import http from 'http';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_PORT = 18247;
|
|
12
|
+
const MAX_PORT_ATTEMPTS = 10;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Start the local auth server.
|
|
16
|
+
*
|
|
17
|
+
* The server:
|
|
18
|
+
* - Binds to 127.0.0.1 only (not network-accessible)
|
|
19
|
+
* - Serves the auth page at GET /auth
|
|
20
|
+
* - Receives session at POST /callback
|
|
21
|
+
* - Auto-closes after successful callback
|
|
22
|
+
*
|
|
23
|
+
* @param {{ appId?: string, authUrl?: string, apiUrl?: string, port?: number }} config
|
|
24
|
+
* @param {{ onSession: (session: object) => void, onError: (err: Error) => void }} callbacks
|
|
25
|
+
* @returns {Promise<{ url: string, port: number, close: () => void }>}
|
|
26
|
+
*/
|
|
27
|
+
export function startAuthServer(config, callbacks) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
let currentPort = config.port || DEFAULT_PORT;
|
|
30
|
+
let attempts = 0;
|
|
31
|
+
|
|
32
|
+
const tryStartServer = () => {
|
|
33
|
+
const server = http.createServer((req, res) => {
|
|
34
|
+
// CORS headers for localhost
|
|
35
|
+
res.setHeader('Access-Control-Allow-Origin', `http://localhost:${currentPort}`);
|
|
36
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
37
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
38
|
+
|
|
39
|
+
if (req.method === 'OPTIONS') {
|
|
40
|
+
res.writeHead(204);
|
|
41
|
+
res.end();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const url = new URL(req.url || '/', `http://localhost:${currentPort}`);
|
|
46
|
+
|
|
47
|
+
// Serve the auth page
|
|
48
|
+
if (req.method === 'GET' && url.pathname === '/auth') {
|
|
49
|
+
const html = generateAuthPage({
|
|
50
|
+
appId: config.appId || 'emblem-agent-wallet',
|
|
51
|
+
authUrl: config.authUrl || 'https://auth.emblemvault.ai',
|
|
52
|
+
apiUrl: config.apiUrl || 'https://api.emblemvault.ai',
|
|
53
|
+
callbackUrl: `http://localhost:${currentPort}/callback`,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
57
|
+
res.end(html);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Handle session callback
|
|
62
|
+
if (req.method === 'POST' && url.pathname === '/callback') {
|
|
63
|
+
let body = '';
|
|
64
|
+
|
|
65
|
+
req.on('data', (chunk) => {
|
|
66
|
+
body += chunk.toString();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
req.on('end', () => {
|
|
70
|
+
try {
|
|
71
|
+
const data = JSON.parse(body);
|
|
72
|
+
const session = data.session;
|
|
73
|
+
|
|
74
|
+
if (!session?.authToken || !session?.user) {
|
|
75
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
76
|
+
res.end(JSON.stringify({ error: 'Invalid session data' }));
|
|
77
|
+
callbacks.onError(new Error('Invalid session data received'));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
82
|
+
res.end(JSON.stringify({ success: true }));
|
|
83
|
+
|
|
84
|
+
callbacks.onSession(session);
|
|
85
|
+
|
|
86
|
+
setTimeout(() => {
|
|
87
|
+
server.close();
|
|
88
|
+
}, 1000);
|
|
89
|
+
} catch {
|
|
90
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
91
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
92
|
+
callbacks.onError(new Error('Invalid JSON in callback request'));
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 404 for unknown routes
|
|
100
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
101
|
+
res.end('Not Found');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
server.on('error', (err) => {
|
|
105
|
+
if (err.code === 'EADDRINUSE' && attempts < MAX_PORT_ATTEMPTS) {
|
|
106
|
+
attempts++;
|
|
107
|
+
currentPort++;
|
|
108
|
+
tryStartServer();
|
|
109
|
+
} else {
|
|
110
|
+
reject(new Error(`Failed to start auth server: ${err.message}`));
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
server.listen(currentPort, '127.0.0.1', () => {
|
|
115
|
+
resolve({
|
|
116
|
+
url: `http://localhost:${currentPort}/auth`,
|
|
117
|
+
port: currentPort,
|
|
118
|
+
close: () => server.close(),
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
tryStartServer();
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Escape HTML special characters to prevent XSS in injected config values.
|
|
129
|
+
* @param {string} str
|
|
130
|
+
* @returns {string}
|
|
131
|
+
*/
|
|
132
|
+
function escapeHtml(str) {
|
|
133
|
+
return String(str)
|
|
134
|
+
.replace(/&/g, '&')
|
|
135
|
+
.replace(/</g, '<')
|
|
136
|
+
.replace(/>/g, '>')
|
|
137
|
+
.replace(/"/g, '"')
|
|
138
|
+
.replace(/'/g, ''');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Generate the HTML auth page.
|
|
143
|
+
*
|
|
144
|
+
* @param {{ appId: string, authUrl: string, apiUrl: string, callbackUrl: string }} config
|
|
145
|
+
* @returns {string}
|
|
146
|
+
*/
|
|
147
|
+
function generateAuthPage(config) {
|
|
148
|
+
return `<!DOCTYPE html>
|
|
149
|
+
<html lang="en">
|
|
150
|
+
<head>
|
|
151
|
+
<meta charset="UTF-8">
|
|
152
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
153
|
+
<title>EmblemAI - Connect Wallet</title>
|
|
154
|
+
<style>
|
|
155
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
156
|
+
body {
|
|
157
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
158
|
+
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 100%);
|
|
159
|
+
color: #fff;
|
|
160
|
+
display: flex; align-items: center; justify-content: center;
|
|
161
|
+
min-height: 100vh; padding: 1rem;
|
|
162
|
+
}
|
|
163
|
+
.container { text-align: center; max-width: 400px; padding: 2rem; }
|
|
164
|
+
.logo { font-size: 3rem; margin-bottom: 1rem; }
|
|
165
|
+
h1 {
|
|
166
|
+
font-size: 1.75rem; font-weight: 600; margin-bottom: 0.5rem;
|
|
167
|
+
background: linear-gradient(90deg, #fff, #22d3ee);
|
|
168
|
+
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
|
169
|
+
background-clip: text;
|
|
170
|
+
}
|
|
171
|
+
.subtitle { color: #888; font-size: 0.9rem; margin-bottom: 2rem; }
|
|
172
|
+
.status {
|
|
173
|
+
display: flex; align-items: center; justify-content: center; gap: 0.75rem;
|
|
174
|
+
padding: 1rem 1.5rem; background: rgba(255,255,255,0.05);
|
|
175
|
+
border-radius: 12px; border: 1px solid rgba(255,255,255,0.1);
|
|
176
|
+
}
|
|
177
|
+
.spinner {
|
|
178
|
+
width: 20px; height: 20px;
|
|
179
|
+
border: 2px solid rgba(255,255,255,0.2); border-top-color: #22d3ee;
|
|
180
|
+
border-radius: 50%; animation: spin 1s linear infinite;
|
|
181
|
+
}
|
|
182
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
183
|
+
.status-text { color: #ccc; }
|
|
184
|
+
.success { background: rgba(74,222,128,0.1); border-color: rgba(74,222,128,0.3); }
|
|
185
|
+
.success .status-text { color: #4ade80; }
|
|
186
|
+
.error { background: rgba(248,113,113,0.1); border-color: rgba(248,113,113,0.3); }
|
|
187
|
+
.error .status-text { color: #f87171; }
|
|
188
|
+
.checkmark { color: #4ade80; font-size: 1.25rem; }
|
|
189
|
+
.error-icon { color: #f87171; font-size: 1.25rem; }
|
|
190
|
+
.retry-btn {
|
|
191
|
+
margin-top: 1rem; padding: 0.75rem 1.5rem;
|
|
192
|
+
background: #22d3ee; color: #000; border: none; border-radius: 8px;
|
|
193
|
+
font-size: 0.9rem; cursor: pointer; font-weight: 600; transition: background 0.2s;
|
|
194
|
+
}
|
|
195
|
+
.retry-btn:hover { background: #06b6d4; }
|
|
196
|
+
.hidden { display: none; }
|
|
197
|
+
</style>
|
|
198
|
+
</head>
|
|
199
|
+
<body>
|
|
200
|
+
<div class="container">
|
|
201
|
+
<div class="logo">⚡</div>
|
|
202
|
+
<h1>EmblemAI</h1>
|
|
203
|
+
<p class="subtitle">Connect your wallet to continue</p>
|
|
204
|
+
<div class="status" id="status">
|
|
205
|
+
<div class="spinner" id="spinner"></div>
|
|
206
|
+
<span class="status-text" id="statusText">Opening wallet connection...</span>
|
|
207
|
+
</div>
|
|
208
|
+
<button class="retry-btn hidden" id="retryBtn" onclick="retryAuth()">Try Again</button>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<script type="module">
|
|
212
|
+
const CONFIG = {
|
|
213
|
+
appId: '${escapeHtml(config.appId)}',
|
|
214
|
+
authUrl: '${escapeHtml(config.authUrl)}',
|
|
215
|
+
apiUrl: '${escapeHtml(config.apiUrl)}',
|
|
216
|
+
callbackUrl: '${escapeHtml(config.callbackUrl)}'
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const statusEl = document.getElementById('status');
|
|
220
|
+
const spinnerEl = document.getElementById('spinner');
|
|
221
|
+
const statusTextEl = document.getElementById('statusText');
|
|
222
|
+
const retryBtn = document.getElementById('retryBtn');
|
|
223
|
+
|
|
224
|
+
function setStatus(text, type = 'loading') {
|
|
225
|
+
statusTextEl.textContent = text;
|
|
226
|
+
statusEl.className = 'status ' + (type === 'success' ? 'success' : type === 'error' ? 'error' : '');
|
|
227
|
+
spinnerEl.className = type === 'loading' ? 'spinner' : 'hidden';
|
|
228
|
+
if (type === 'success') {
|
|
229
|
+
spinnerEl.outerHTML = '<span class="checkmark">✓</span>';
|
|
230
|
+
} else if (type === 'error') {
|
|
231
|
+
spinnerEl.outerHTML = '<span class="error-icon">✗</span>';
|
|
232
|
+
retryBtn.classList.remove('hidden');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function sendSessionToCLI(session) {
|
|
237
|
+
setStatus('Sending to CLI...', 'loading');
|
|
238
|
+
try {
|
|
239
|
+
const response = await fetch(CONFIG.callbackUrl, {
|
|
240
|
+
method: 'POST',
|
|
241
|
+
headers: { 'Content-Type': 'application/json' },
|
|
242
|
+
body: JSON.stringify({ session })
|
|
243
|
+
});
|
|
244
|
+
if (!response.ok) throw new Error('Failed to send session to CLI');
|
|
245
|
+
setStatus('Connected! You can close this window.', 'success');
|
|
246
|
+
} catch (err) {
|
|
247
|
+
setStatus('Failed to connect to CLI: ' + err.message, 'error');
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function startAuth() {
|
|
252
|
+
try {
|
|
253
|
+
let EmblemAuthSDK;
|
|
254
|
+
const cdnUrls = [
|
|
255
|
+
'https://esm.sh/@emblemvault/auth-sdk@2.1.0',
|
|
256
|
+
'https://cdn.skypack.dev/@emblemvault/auth-sdk@2.1.0',
|
|
257
|
+
'https://unpkg.com/@emblemvault/auth-sdk@2.1.0/dist/index.mjs'
|
|
258
|
+
];
|
|
259
|
+
|
|
260
|
+
for (const url of cdnUrls) {
|
|
261
|
+
try {
|
|
262
|
+
const module = await import(url);
|
|
263
|
+
EmblemAuthSDK = module.EmblemAuthSDK || module.default?.EmblemAuthSDK;
|
|
264
|
+
if (EmblemAuthSDK) break;
|
|
265
|
+
} catch (e) {
|
|
266
|
+
console.warn('Failed to load from ' + url + ':', e.message);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!EmblemAuthSDK) throw new Error('Could not load auth SDK from any CDN');
|
|
271
|
+
|
|
272
|
+
const sdk = new EmblemAuthSDK({
|
|
273
|
+
appId: CONFIG.appId,
|
|
274
|
+
authUrl: CONFIG.authUrl,
|
|
275
|
+
apiUrl: CONFIG.apiUrl,
|
|
276
|
+
onSuccess: sendSessionToCLI,
|
|
277
|
+
onError: (err) => {
|
|
278
|
+
setStatus('Authentication failed: ' + (err.message || 'Unknown error'), 'error');
|
|
279
|
+
},
|
|
280
|
+
onCancel: () => {
|
|
281
|
+
setStatus('Authentication cancelled', 'error');
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
sdk.openAuthModal();
|
|
286
|
+
} catch (err) {
|
|
287
|
+
setStatus('Failed to load auth SDK: ' + err.message, 'error');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
window.retryAuth = () => {
|
|
292
|
+
retryBtn.classList.add('hidden');
|
|
293
|
+
location.reload();
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
startAuth();
|
|
297
|
+
</script>
|
|
298
|
+
</body>
|
|
299
|
+
</html>`;
|
|
300
|
+
}
|