nyxora 26.6.19 → 26.6.21
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 +18 -1
- package/bin/nyxora.mjs +32 -0
- package/dist/packages/core/src/agent/reasoning.js +11 -1
- package/dist/packages/core/src/config/parser.js +121 -7
- package/dist/packages/core/src/gateway/chat.js +82 -0
- package/dist/packages/core/src/gateway/cli.js +63 -0
- package/dist/packages/core/src/gateway/server.js +117 -56
- package/dist/packages/core/src/gateway/setup.js +39 -22
- package/dist/packages/core/src/utils/formatter.test.js +40 -0
- package/dist/packages/core/src/utils/skillManager.js +91 -0
- package/dist/packages/core/src/utils/userWhitelistManager.js +41 -36
- package/dist/packages/core/src/web3/aggregator/aggregatorMainnet.js +2 -2
- package/dist/packages/core/src/web3/aggregator/defiRouter.js +3 -0
- package/dist/packages/core/src/web3/skills/bridgeToken.js +4 -0
- package/dist/packages/core/src/web3/skills/checkRegistryStatus.js +13 -0
- package/dist/packages/core/src/web3/skills/customTx.js +2 -0
- package/dist/packages/core/src/web3/skills/defiLending.js +2 -0
- package/dist/packages/core/src/web3/skills/getPrice.js +11 -6
- package/dist/packages/core/src/web3/skills/manageCustomTokens.js +18 -32
- package/dist/packages/core/src/web3/skills/marketAnalysis.js +3 -1
- package/dist/packages/core/src/web3/skills/mintNft.js +2 -0
- package/dist/packages/core/src/web3/skills/provideLiquidity.js +2 -0
- package/dist/packages/core/src/web3/skills/revokeApprovals.js +2 -0
- package/dist/packages/core/src/web3/skills/swapToken.js +4 -2
- package/dist/packages/core/src/web3/skills/transfer.js +2 -0
- package/dist/packages/core/src/web3/skills/yieldVault.js +2 -0
- package/dist/packages/core/src/web3/utils/tokens.js +9 -1
- package/package.json +2 -1
- package/packages/core/package.json +1 -1
- package/packages/core/src/agent/reasoning.ts +12 -1
- package/packages/core/src/config/parser.ts +119 -9
- package/packages/core/src/gateway/chat.ts +85 -0
- package/packages/core/src/gateway/cli.ts +63 -0
- package/packages/core/src/gateway/server.ts +132 -60
- package/packages/core/src/gateway/setup.ts +39 -27
- package/packages/core/src/utils/formatter.test.ts +41 -0
- package/packages/core/src/utils/skillManager.ts +98 -0
- package/packages/core/src/utils/userWhitelistManager.ts +48 -39
- package/packages/core/src/web3/aggregator/aggregatorMainnet.ts +2 -2
- package/packages/core/src/web3/aggregator/defiRouter.ts +4 -0
- package/packages/core/src/web3/skills/bridgeToken.ts +3 -0
- package/packages/core/src/web3/skills/checkRegistryStatus.ts +13 -0
- package/packages/core/src/web3/skills/customTx.ts +1 -0
- package/packages/core/src/web3/skills/defiLending.ts +1 -0
- package/packages/core/src/web3/skills/getPrice.ts +11 -6
- package/packages/core/src/web3/skills/manageCustomTokens.ts +18 -29
- package/packages/core/src/web3/skills/marketAnalysis.ts +2 -1
- package/packages/core/src/web3/skills/mintNft.ts +1 -0
- package/packages/core/src/web3/skills/provideLiquidity.ts +1 -0
- package/packages/core/src/web3/skills/revokeApprovals.ts +1 -0
- package/packages/core/src/web3/skills/swapToken.ts +3 -2
- package/packages/core/src/web3/skills/transfer.ts +1 -0
- package/packages/core/src/web3/skills/yieldVault.ts +1 -0
- package/packages/core/src/web3/utils/tokens.ts +9 -1
- package/packages/dashboard/dist/assets/{index-DnQrbB4c.css → index-CQNHWZtN.css} +1 -1
- package/packages/dashboard/dist/assets/index-Di9x08yk.js +16 -0
- package/packages/dashboard/dist/index.html +2 -2
- package/packages/dashboard/package.json +1 -1
- package/packages/mcp-server/package.json +1 -1
- package/packages/policy/package.json +1 -1
- package/packages/signer/package.json +1 -1
- package/dist/packages/core/src/agent/limitOrderManager.js +0 -124
- package/dist/packages/core/src/system/pluginManager.js +0 -91
- package/dist/packages/core/src/system/skills/installSkill.js +0 -52
- package/dist/packages/core/src/test-all-routers.js +0 -81
- package/dist/packages/core/src/test-router.js +0 -38
- package/dist/packages/core/src/web3/skills/autonomousDefi.js +0 -191
- package/dist/packages/core/src/web3/skills/createWallet.js +0 -34
- package/dist/packages/core/src/web3/skills/limitOrder.js +0 -106
- package/dist/packages/core/src/web3/utils/protocolRegistry.js +0 -46
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/packages/core/src/__tests__/reasoning.test.ts +0 -81
- package/packages/core/src/__tests__/tokens.test.ts +0 -55
- package/packages/core/src/__tests__/web3.test.ts +0 -50
- package/packages/core/src/agent/reasoning.d.ts.map +0 -1
- package/packages/core/src/config/parser.d.ts.map +0 -1
- package/packages/core/src/gateway/cli.d.ts.map +0 -1
- package/packages/core/src/memory/logger.d.ts.map +0 -1
- package/packages/core/src/test-all-routers.ts +0 -59
- package/packages/core/src/test-router.ts +0 -49
- package/packages/core/src/web3/config.d.ts.map +0 -1
- package/packages/core/src/web3/skills/getBalance.d.ts.map +0 -1
- package/packages/dashboard/dist/assets/index-BAXifdMN.js +0 -16
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { intro, text, spinner, isCancel, cancel } from '@clack/prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { getPath } from '../config/paths';
|
|
5
|
+
|
|
6
|
+
export async function chatInteractive() {
|
|
7
|
+
const tokenFile = getPath('auth.token');
|
|
8
|
+
if (!fs.existsSync(tokenFile)) {
|
|
9
|
+
console.log(pc.red('❌ Nyxora daemon is not running. Please start it with `nyxora start`.'));
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let token = fs.readFileSync(tokenFile, 'utf8').trim();
|
|
14
|
+
if (token.startsWith('{')) {
|
|
15
|
+
try {
|
|
16
|
+
const parsed = JSON.parse(token);
|
|
17
|
+
token = parsed.token;
|
|
18
|
+
} catch (e) {}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const logo = `
|
|
22
|
+
███╗ ██╗██╗ ██╗██╗ ██╗ ██████╗ ██████╗ █████╗
|
|
23
|
+
████╗ ██║╚██╗ ██╔╝╚██╗██╔╝██╔═══██╗██╔══██╗██╔══██╗
|
|
24
|
+
██╔██╗ ██║ ╚████╔╝ ╚███╔╝ ██║ ██║██████╔╝███████║
|
|
25
|
+
██║╚██╗██║ ╚██╔╝ ██╔██╗ ██║ ██║██╔══██╗██╔══██║
|
|
26
|
+
██║ ╚████║ ██║ ██╔╝ ██╗╚██████╔╝██║ ██║██║ ██║
|
|
27
|
+
╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
console.log(pc.cyan(logo));
|
|
31
|
+
intro(pc.inverse(' Nyxora Interactive Shell '));
|
|
32
|
+
console.log(pc.gray('Type your message and press Enter. Type "exit" or press Ctrl+C to quit.\n'));
|
|
33
|
+
|
|
34
|
+
while (true) {
|
|
35
|
+
const input = await text({
|
|
36
|
+
message: pc.cyan('You:'),
|
|
37
|
+
placeholder: 'Send a message...',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (isCancel(input) || input.toString().trim().toLowerCase() === 'exit' || input.toString().trim().toLowerCase() === 'quit') {
|
|
41
|
+
cancel('Chat session ended.');
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const messageStr = input.toString().trim();
|
|
46
|
+
if (!messageStr) continue;
|
|
47
|
+
|
|
48
|
+
const s = spinner();
|
|
49
|
+
s.start('Thinking...');
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const response = await fetch('http://localhost:3000/api/chat', {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: {
|
|
55
|
+
'Content-Type': 'application/json',
|
|
56
|
+
'x-nyxora-token': token,
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify({ message: messageStr, session_id: 'cli-chat' })
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
s.stop(pc.red('API Error.'));
|
|
63
|
+
if (response.status === 401) {
|
|
64
|
+
console.log(pc.red('Unauthorized: Token is invalid. Please restart the daemon.'));
|
|
65
|
+
process.exit(1);
|
|
66
|
+
} else {
|
|
67
|
+
console.log(pc.red(`Gateway returned status ${response.status}`));
|
|
68
|
+
}
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const data = await response.json();
|
|
73
|
+
s.stop(pc.green('Nyxora:'));
|
|
74
|
+
|
|
75
|
+
let finalReply = data.response || '';
|
|
76
|
+
// Strip <think> tags for clean UI
|
|
77
|
+
finalReply = finalReply.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
|
78
|
+
|
|
79
|
+
console.log(finalReply + '\n');
|
|
80
|
+
} catch (error) {
|
|
81
|
+
s.stop(pc.red('Connection failed.'));
|
|
82
|
+
console.log(pc.red(`Is the daemon running? (http://localhost:3000)`));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -128,6 +128,69 @@ console.log(`================================`);
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
// Check for chat command
|
|
132
|
+
if (process.argv.includes('chat')) {
|
|
133
|
+
const { chatInteractive } = await import('./chat');
|
|
134
|
+
await chatInteractive();
|
|
135
|
+
process.exit(0);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Check for uninstall command
|
|
139
|
+
if (process.argv.includes('uninstall')) {
|
|
140
|
+
console.log(pc.cyan('\n🗑️ Nyxora Uninstallation Wizard'));
|
|
141
|
+
|
|
142
|
+
let shouldProceed = process.argv.includes('--force') || process.argv.includes('-y');
|
|
143
|
+
|
|
144
|
+
if (!shouldProceed) {
|
|
145
|
+
const proceed = await confirm({
|
|
146
|
+
message: pc.bgRed(pc.white(' ⚠️ WARNING ')) + pc.yellow(' This will PERMANENTLY WIPE the AI\'s local memory, securely delete your Private Key and Master Key from the OS Keyring, and remove all configuration.\n\nAre you absolutely sure you want to proceed?'),
|
|
147
|
+
});
|
|
148
|
+
if (isCancel(proceed) || !proceed) process.exit(0);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log(pc.gray('\nStarting cleanup process...'));
|
|
152
|
+
|
|
153
|
+
// 1. Wipe AI Memory
|
|
154
|
+
try {
|
|
155
|
+
const { Logger } = require('../memory/logger');
|
|
156
|
+
const logger = new Logger();
|
|
157
|
+
logger.clear();
|
|
158
|
+
console.log(pc.green('✅ AI memory wiped successfully.'));
|
|
159
|
+
} catch (e: any) {
|
|
160
|
+
console.log(pc.gray('⚠️ Could not clear AI memory (may not exist).'));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 2. Delete OS Keyring entries
|
|
164
|
+
try {
|
|
165
|
+
const { Entry } = await import('@napi-rs/keyring');
|
|
166
|
+
const walletEntry = new Entry('nyxora', 'wallet');
|
|
167
|
+
try { await walletEntry.deletePassword(); } catch(e) {}
|
|
168
|
+
console.log(pc.green('✅ Wallet key removed from OS Keyring.'));
|
|
169
|
+
|
|
170
|
+
const masterEntry = new Entry('nyxora', 'config_master');
|
|
171
|
+
try { await masterEntry.deletePassword(); } catch(e) {}
|
|
172
|
+
console.log(pc.green('✅ Master key removed from OS Keyring.'));
|
|
173
|
+
} catch (e: any) {
|
|
174
|
+
console.log(pc.gray('⚠️ Could not access OS Keyring.'));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 3. Delete ~/.nyxora directory
|
|
178
|
+
try {
|
|
179
|
+
const targetDir = path.join(os.homedir(), '.nyxora');
|
|
180
|
+
if (fs.existsSync(targetDir)) {
|
|
181
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
182
|
+
console.log(pc.green('✅ Configuration directory (~/.nyxora) deleted.'));
|
|
183
|
+
}
|
|
184
|
+
} catch (e: any) {
|
|
185
|
+
console.log(pc.red(`❌ Failed to delete ~/.nyxora: ${e.message}`));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
console.log(pc.cyan('\n✨ Nyxora data has been completely removed.'));
|
|
189
|
+
console.log(pc.white('To complete the uninstallation, run:'));
|
|
190
|
+
console.log(pc.green(' npm uninstall -g nyxora\n'));
|
|
191
|
+
process.exit(0);
|
|
192
|
+
}
|
|
193
|
+
|
|
131
194
|
// 2. Setup boilerplate files if in global mode and they don't exist
|
|
132
195
|
let isFirstBoot = false;
|
|
133
196
|
if (isGlobalMode) {
|
|
@@ -42,7 +42,7 @@ import { checkPortfolioToolDefinition } from '../web3/skills/checkPortfolio';
|
|
|
42
42
|
import { marketAnalysisToolDefinition } from '../web3/skills/marketAnalysis';
|
|
43
43
|
import { executeApprove, executeAaveSupply, executeVaultDeposit, executeUniv3Mint } from '../web3/skills/executeDefi';
|
|
44
44
|
import { executeRevokeApproval } from '../web3/skills/revokeApprovals';
|
|
45
|
-
import { isSkillActive, toggleSkill } from '../utils/skillManager';
|
|
45
|
+
import { isSkillActive, toggleSkill, syncAllSkillsToConfig } from '../utils/skillManager';
|
|
46
46
|
import { executeBridge, bridgeTokenToolDefinition } from '../web3/skills/bridgeToken';
|
|
47
47
|
import { executeMintNft, mintNftToolDefinition } from '../web3/skills/mintNft';
|
|
48
48
|
import { executeCustomTx, customTxToolDefinition } from '../web3/skills/customTx';
|
|
@@ -51,8 +51,11 @@ import { revokeApprovalToolDefinition } from '../web3/skills/revokeApprovals';
|
|
|
51
51
|
import { vaultDepositToolDefinition } from '../web3/skills/yieldVault';
|
|
52
52
|
import { provideLiquidityToolDefinition } from '../web3/skills/provideLiquidity';
|
|
53
53
|
import { getTxHistoryToolDefinition } from '../web3/skills/getTxHistory';
|
|
54
|
-
import { checkRegistryStatus } from '../web3/skills/checkRegistryStatus';
|
|
54
|
+
import { checkRegistryStatus, checkRegistryStatusToolDefinition } from '../web3/skills/checkRegistryStatus';
|
|
55
55
|
import { createLimitOrderToolDefinition } from '../web3/skills/createLimitOrder';
|
|
56
|
+
import { getUserWhitelist, saveTokenToWhitelist, removeTokenFromWhitelist } from '../utils/userWhitelistManager';
|
|
57
|
+
import { getTokenMetadata } from '../web3/utils/tokens';
|
|
58
|
+
import { ChainName } from '../web3/config';
|
|
56
59
|
|
|
57
60
|
// System Skills
|
|
58
61
|
import { browseWebsiteToolDefinition } from '../system/skills/browseWeb';
|
|
@@ -84,6 +87,9 @@ import { ReflectionEngine } from '../memory/reflection';
|
|
|
84
87
|
// Initialize Google Auth
|
|
85
88
|
initGoogleAuth();
|
|
86
89
|
|
|
90
|
+
// Synchronize all active skills to config.yaml on startup
|
|
91
|
+
syncAllSkillsToConfig();
|
|
92
|
+
|
|
87
93
|
import util from 'util';
|
|
88
94
|
|
|
89
95
|
// Intercept console.log and console.error
|
|
@@ -331,8 +337,63 @@ app.post('/api/defi-keys', (req, res) => {
|
|
|
331
337
|
}
|
|
332
338
|
});
|
|
333
339
|
|
|
340
|
+
const allSkills = [
|
|
341
|
+
getBalanceToolDefinition,
|
|
342
|
+
transferToolDefinition,
|
|
343
|
+
getPriceToolDefinition,
|
|
344
|
+
swapTokenToolDefinition,
|
|
345
|
+
bridgeTokenToolDefinition,
|
|
346
|
+
mintNftToolDefinition,
|
|
347
|
+
customTxToolDefinition,
|
|
348
|
+
checkAddressToolDefinition,
|
|
349
|
+
getMyAddressToolDefinition,
|
|
350
|
+
checkSecurityToolDefinition,
|
|
351
|
+
checkPortfolioToolDefinition,
|
|
352
|
+
marketAnalysisToolDefinition,
|
|
353
|
+
manageCustomTokensDefinition,
|
|
354
|
+
aaveSupplyToolDefinition,
|
|
355
|
+
revokeApprovalToolDefinition,
|
|
356
|
+
vaultDepositToolDefinition,
|
|
357
|
+
provideLiquidityToolDefinition,
|
|
358
|
+
getTxHistoryToolDefinition,
|
|
359
|
+
createLimitOrderToolDefinition,
|
|
360
|
+
checkRegistryStatusToolDefinition
|
|
361
|
+
];
|
|
362
|
+
|
|
363
|
+
const systemSkills = [
|
|
364
|
+
runTerminalCommandToolDefinition,
|
|
365
|
+
readLocalFileToolDefinition,
|
|
366
|
+
writeLocalFileToolDefinition,
|
|
367
|
+
generateExcelToolDefinition,
|
|
368
|
+
browseWebsiteToolDefinition,
|
|
369
|
+
updateSecurityPolicyToolDefinition,
|
|
370
|
+
|
|
371
|
+
analyzeDocumentToolDefinition,
|
|
372
|
+
searchWebToolDefinition,
|
|
373
|
+
readGmailInboxToolDefinition,
|
|
374
|
+
listCalendarEventsToolDefinition,
|
|
375
|
+
appendRowToSheetsToolDefinition,
|
|
376
|
+
readGoogleDocsToolDefinition,
|
|
377
|
+
readGoogleFormResponsesToolDefinition,
|
|
378
|
+
editLocalFileToolDefinition,
|
|
379
|
+
gitManagerToolDefinition,
|
|
380
|
+
xManagerToolDefinition,
|
|
381
|
+
notionWorkspaceToolDefinition,
|
|
382
|
+
audioTranscribeToolDefinition,
|
|
383
|
+
summarizeTextToolDefinition
|
|
384
|
+
];
|
|
385
|
+
|
|
334
386
|
app.get('/api/stats', (req, res) => {
|
|
335
|
-
|
|
387
|
+
const stats = Tracker.getStats();
|
|
388
|
+
const dbPath = getPath('memory.db');
|
|
389
|
+
|
|
390
|
+
const activeWeb3 = allSkills.filter(s => isSkillActive(s.function.name)).length;
|
|
391
|
+
const activeSystem = systemSkills.filter(s => isSkillActive(s.function.name)).length;
|
|
392
|
+
|
|
393
|
+
const totalSkills = allSkills.length + systemSkills.length;
|
|
394
|
+
const activeSkills = activeWeb3 + activeSystem;
|
|
395
|
+
|
|
396
|
+
res.json({ ...stats, memoryPath: dbPath, totalSkills, activeSkills });
|
|
336
397
|
});
|
|
337
398
|
|
|
338
399
|
app.get('/api/logs', (req, res) => {
|
|
@@ -340,28 +401,6 @@ app.get('/api/logs', (req, res) => {
|
|
|
340
401
|
});
|
|
341
402
|
|
|
342
403
|
app.get('/api/skills', (req, res) => {
|
|
343
|
-
const allSkills = [
|
|
344
|
-
getBalanceToolDefinition,
|
|
345
|
-
transferToolDefinition,
|
|
346
|
-
getPriceToolDefinition,
|
|
347
|
-
swapTokenToolDefinition,
|
|
348
|
-
bridgeTokenToolDefinition,
|
|
349
|
-
mintNftToolDefinition,
|
|
350
|
-
customTxToolDefinition,
|
|
351
|
-
checkAddressToolDefinition,
|
|
352
|
-
getMyAddressToolDefinition,
|
|
353
|
-
checkSecurityToolDefinition,
|
|
354
|
-
checkPortfolioToolDefinition,
|
|
355
|
-
marketAnalysisToolDefinition,
|
|
356
|
-
manageCustomTokensDefinition,
|
|
357
|
-
aaveSupplyToolDefinition,
|
|
358
|
-
revokeApprovalToolDefinition,
|
|
359
|
-
vaultDepositToolDefinition,
|
|
360
|
-
provideLiquidityToolDefinition,
|
|
361
|
-
getTxHistoryToolDefinition,
|
|
362
|
-
createLimitOrderToolDefinition
|
|
363
|
-
];
|
|
364
|
-
|
|
365
404
|
const skillsWithStatus = allSkills.map(skill => ({
|
|
366
405
|
...skill,
|
|
367
406
|
isActive: isSkillActive(skill.function.name)
|
|
@@ -371,29 +410,6 @@ app.get('/api/skills', (req, res) => {
|
|
|
371
410
|
});
|
|
372
411
|
|
|
373
412
|
app.get('/api/skills/system', (req, res) => {
|
|
374
|
-
const systemSkills = [
|
|
375
|
-
runTerminalCommandToolDefinition,
|
|
376
|
-
readLocalFileToolDefinition,
|
|
377
|
-
writeLocalFileToolDefinition,
|
|
378
|
-
generateExcelToolDefinition,
|
|
379
|
-
browseWebsiteToolDefinition,
|
|
380
|
-
updateSecurityPolicyToolDefinition,
|
|
381
|
-
|
|
382
|
-
analyzeDocumentToolDefinition,
|
|
383
|
-
searchWebToolDefinition,
|
|
384
|
-
readGmailInboxToolDefinition,
|
|
385
|
-
listCalendarEventsToolDefinition,
|
|
386
|
-
appendRowToSheetsToolDefinition,
|
|
387
|
-
readGoogleDocsToolDefinition,
|
|
388
|
-
readGoogleFormResponsesToolDefinition,
|
|
389
|
-
editLocalFileToolDefinition,
|
|
390
|
-
gitManagerToolDefinition,
|
|
391
|
-
xManagerToolDefinition,
|
|
392
|
-
notionWorkspaceToolDefinition,
|
|
393
|
-
audioTranscribeToolDefinition,
|
|
394
|
-
summarizeTextToolDefinition
|
|
395
|
-
];
|
|
396
|
-
|
|
397
413
|
const skillsWithStatus = systemSkills.map(skill => ({
|
|
398
414
|
...skill,
|
|
399
415
|
isActive: isSkillActive(skill.function.name)
|
|
@@ -411,6 +427,44 @@ app.post('/api/skills/toggle', (req, res) => {
|
|
|
411
427
|
res.json({ success: true, skillName, active });
|
|
412
428
|
});
|
|
413
429
|
|
|
430
|
+
// Portfolio Whitelist Routes
|
|
431
|
+
app.get('/api/portfolio/whitelist', async (req, res) => {
|
|
432
|
+
const whitelist = getUserWhitelist();
|
|
433
|
+
res.json(whitelist);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
app.post('/api/portfolio/whitelist', async (req, res) => {
|
|
437
|
+
const { walletAddress, chainName, tokenAddress, symbol, decimals } = req.body;
|
|
438
|
+
if (!walletAddress || !chainName || !tokenAddress) {
|
|
439
|
+
return res.status(400).json({ error: 'Missing required fields' });
|
|
440
|
+
}
|
|
441
|
+
await saveTokenToWhitelist(walletAddress, chainName, tokenAddress, 'manual', symbol, decimals);
|
|
442
|
+
res.json({ success: true });
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
app.delete('/api/portfolio/whitelist', (req, res) => {
|
|
446
|
+
const { walletAddress, chainName, tokenAddress } = req.body;
|
|
447
|
+
if (!walletAddress || !chainName || !tokenAddress) {
|
|
448
|
+
return res.status(400).json({ error: 'Missing required fields' });
|
|
449
|
+
}
|
|
450
|
+
removeTokenFromWhitelist(walletAddress, chainName, tokenAddress);
|
|
451
|
+
res.json({ success: true });
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
app.get('/api/portfolio/token-metadata', async (req, res) => {
|
|
455
|
+
const { chain, address } = req.query;
|
|
456
|
+
if (!chain || !address || typeof address !== 'string') {
|
|
457
|
+
return res.status(400).json({ error: 'Missing chain or address' });
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
const client = getPublicClient(chain as ChainName);
|
|
461
|
+
const metadata = await getTokenMetadata(client, address as `0x${string}`);
|
|
462
|
+
res.json(metadata);
|
|
463
|
+
} catch (err: any) {
|
|
464
|
+
res.status(500).json({ error: err.message });
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
|
|
414
468
|
// Google Workspace Auth Routes
|
|
415
469
|
app.get('/api/auth/google/url', (req, res) => {
|
|
416
470
|
const url = getAuthUrl();
|
|
@@ -603,6 +657,7 @@ let cachedTrending: string[] | null = null;
|
|
|
603
657
|
let lastTrendingFetch = 0;
|
|
604
658
|
|
|
605
659
|
let cachedPrices: Record<string, number> = {};
|
|
660
|
+
let cachedPriceChanges: Record<string, number> = {};
|
|
606
661
|
let lastPricesFetch = 0;
|
|
607
662
|
|
|
608
663
|
app.get('/api/trending', async (req, res) => {
|
|
@@ -629,16 +684,20 @@ app.get('/api/trending', async (req, res) => {
|
|
|
629
684
|
}
|
|
630
685
|
});
|
|
631
686
|
|
|
687
|
+
app.get('/api/wallet', async (req, res) => {
|
|
688
|
+
try {
|
|
689
|
+
const userAddress = await getAddress();
|
|
690
|
+
res.json({ address: userAddress });
|
|
691
|
+
} catch (error: any) {
|
|
692
|
+
res.status(500).json({ error: error.message });
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
|
|
632
696
|
app.get('/api/portfolio', async (req, res) => {
|
|
633
697
|
try {
|
|
634
698
|
const userAddress = await getAddress();
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
if (fs.existsSync(customTokensPath)) {
|
|
638
|
-
try {
|
|
639
|
-
customTokens = JSON.parse(fs.readFileSync(customTokensPath, 'utf8'));
|
|
640
|
-
} catch (e) {}
|
|
641
|
-
}
|
|
699
|
+
const whitelist = getUserWhitelist();
|
|
700
|
+
const userCustomTokens = whitelist[userAddress.toLowerCase()] || [];
|
|
642
701
|
|
|
643
702
|
const portfolio: Record<string, any[]> = {};
|
|
644
703
|
|
|
@@ -659,11 +718,15 @@ app.get('/api/portfolio', async (req, res) => {
|
|
|
659
718
|
});
|
|
660
719
|
}
|
|
661
720
|
|
|
662
|
-
// 2. Combine TOKEN_MAP and
|
|
721
|
+
// 2. Combine TOKEN_MAP and YAML whitelist for this chain
|
|
663
722
|
const tokensToQuery = { ...((TOKEN_MAP as any)[chainName] || {}) };
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
723
|
+
|
|
724
|
+
// Inject whitelisted tokens
|
|
725
|
+
userCustomTokens.forEach(t => {
|
|
726
|
+
if (t.chainName === chainName && t.symbol && t.address) {
|
|
727
|
+
tokensToQuery[t.symbol.toUpperCase()] = t.address;
|
|
728
|
+
}
|
|
729
|
+
});
|
|
667
730
|
|
|
668
731
|
// 3. Query all ERC-20 balances in parallel
|
|
669
732
|
await Promise.all(Object.entries(tokensToQuery).map(async ([symbol, address]) => {
|
|
@@ -682,7 +745,9 @@ app.get('/api/portfolio', async (req, res) => {
|
|
|
682
745
|
} as any);
|
|
683
746
|
|
|
684
747
|
const [bal, decimals] = await Promise.all([balPromise, decPromise]) as [bigint, number];
|
|
685
|
-
|
|
748
|
+
const isCustom = userCustomTokens.some(t => t.chainName === chainName && t.address === address);
|
|
749
|
+
|
|
750
|
+
if (bal > 0n || isCustom) {
|
|
686
751
|
portfolio[chainName].push({
|
|
687
752
|
symbol,
|
|
688
753
|
address,
|
|
@@ -722,10 +787,12 @@ app.get('/api/portfolio', async (req, res) => {
|
|
|
722
787
|
const uniqueAddrs = Array.from(addressesToFetch);
|
|
723
788
|
const now = Date.now();
|
|
724
789
|
let priceMap = cachedPrices;
|
|
790
|
+
let changeMap = cachedPriceChanges;
|
|
725
791
|
|
|
726
792
|
if (uniqueAddrs.length > 0 && now - lastPricesFetch > 2 * 60 * 1000) {
|
|
727
793
|
try {
|
|
728
794
|
const newPrices: Record<string, number> = {};
|
|
795
|
+
const newChanges: Record<string, number> = {};
|
|
729
796
|
|
|
730
797
|
await Promise.all(uniqueAddrs.map(async (addr) => {
|
|
731
798
|
try {
|
|
@@ -751,8 +818,10 @@ app.get('/api/portfolio', async (req, res) => {
|
|
|
751
818
|
|
|
752
819
|
if (baseAddr === addr) {
|
|
753
820
|
newPrices[addr] = parseFloat(bestPair.priceUsd);
|
|
821
|
+
newChanges[addr] = bestPair.priceChange?.h24 || 0;
|
|
754
822
|
} else if (quoteAddr === addr && bestPair.priceNative && parseFloat(bestPair.priceNative) > 0) {
|
|
755
823
|
newPrices[addr] = parseFloat(bestPair.priceUsd) / parseFloat(bestPair.priceNative);
|
|
824
|
+
newChanges[addr] = bestPair.priceChange?.h24 || 0;
|
|
756
825
|
}
|
|
757
826
|
}
|
|
758
827
|
}
|
|
@@ -765,7 +834,9 @@ app.get('/api/portfolio', async (req, res) => {
|
|
|
765
834
|
console.log('DexScreener Fetched Prices:', newPrices);
|
|
766
835
|
|
|
767
836
|
cachedPrices = { ...cachedPrices, ...newPrices };
|
|
837
|
+
cachedPriceChanges = { ...cachedPriceChanges, ...newChanges };
|
|
768
838
|
priceMap = cachedPrices;
|
|
839
|
+
changeMap = cachedPriceChanges;
|
|
769
840
|
lastPricesFetch = now;
|
|
770
841
|
} catch (e) {
|
|
771
842
|
console.error('DexScreener fetch error:', e);
|
|
@@ -780,6 +851,7 @@ app.get('/api/portfolio', async (req, res) => {
|
|
|
780
851
|
lookupAddr = (((TOKEN_MAP as any)[chain]?.[wToken]) || '').toLowerCase();
|
|
781
852
|
}
|
|
782
853
|
t.priceUsd = priceMap[lookupAddr] || 0;
|
|
854
|
+
t.priceChange24h = changeMap[lookupAddr] || 0;
|
|
783
855
|
}
|
|
784
856
|
}
|
|
785
857
|
|
|
@@ -7,24 +7,7 @@ import { getAppDir, getPath } from '../config/paths';
|
|
|
7
7
|
import { loadConfig, saveConfig, saveApiKeys, saveRpcConfig } from '../config/parser';
|
|
8
8
|
import crypto from 'crypto';
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
const salt = crypto.randomBytes(16);
|
|
12
|
-
const key = crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256');
|
|
13
|
-
const iv = crypto.randomBytes(12);
|
|
14
|
-
|
|
15
|
-
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
16
|
-
let encrypted = cipher.update(privateKey, 'utf8', 'hex');
|
|
17
|
-
encrypted += cipher.final('hex');
|
|
18
|
-
|
|
19
|
-
const authTag = cipher.getAuthTag().toString('hex');
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
salt: salt.toString('hex'),
|
|
23
|
-
iv: iv.toString('hex'),
|
|
24
|
-
authTag,
|
|
25
|
-
encryptedData: encrypted
|
|
26
|
-
};
|
|
27
|
-
}
|
|
10
|
+
|
|
28
11
|
import { generatePrivateKey, privateKeyToAccount, generateMnemonic, mnemonicToAccount, english } from 'viem/accounts';
|
|
29
12
|
|
|
30
13
|
export async function runSetupWizard() {
|
|
@@ -286,9 +269,9 @@ Provider: ${config.llm.provider}`;
|
|
|
286
269
|
privateKey = '0x' + Buffer.from(account.getHdKey().privateKey!).toString('hex');
|
|
287
270
|
log.success('New Wallet Generated!');
|
|
288
271
|
log.info(`Address: ${account.address}`);
|
|
289
|
-
log.info(`Private Key:
|
|
272
|
+
log.info(`Private Key: [REDACTED - Saved securely to vault]`);
|
|
290
273
|
log.info(`Seed Phrase (Mnemonic): ${seedPhrase}`);
|
|
291
|
-
log.warn('IMPORTANT: Write down these 12 words
|
|
274
|
+
log.warn('IMPORTANT: Write down these 12 words NOW! This is your ONLY backup. The credentials have been securely injected into your local OS vault.');
|
|
292
275
|
}
|
|
293
276
|
}
|
|
294
277
|
|
|
@@ -356,19 +339,32 @@ Provider: ${config.llm.provider}`;
|
|
|
356
339
|
})) as string;
|
|
357
340
|
if (isCancel(telegramToken)) return process.exit(0);
|
|
358
341
|
|
|
359
|
-
if (telegramToken &&
|
|
342
|
+
if (telegramToken && telegramToken.trim() !== '') {
|
|
343
|
+
authorizedChatId = undefined;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const activeToken = telegramToken || config.integrations?.telegram?.bot_token;
|
|
347
|
+
|
|
348
|
+
if (activeToken && !authorizedChatId) {
|
|
360
349
|
const s = spinner();
|
|
361
350
|
const pin = Math.floor(100000 + Math.random() * 900000).toString();
|
|
362
351
|
|
|
363
352
|
note(pc.cyan(`1. Open Telegram and search for your Bot.\n2. Send this exact message to your bot:\n\n /auth ${pin}\n\nWaiting for your message...`), 'Telegram Pairing Required');
|
|
364
353
|
s.start(`Waiting for /auth ${pin} on Telegram...`);
|
|
365
354
|
|
|
355
|
+
let bot: any = null;
|
|
366
356
|
try {
|
|
367
357
|
const { Telegraf } = require('telegraf');
|
|
368
|
-
|
|
358
|
+
bot = new Telegraf(activeToken);
|
|
369
359
|
let paired = false;
|
|
370
360
|
|
|
361
|
+
let failedAttempts: Record<string, number> = {};
|
|
371
362
|
bot.command('auth', (ctx: any) => {
|
|
363
|
+
const chatId = ctx.chat.id.toString();
|
|
364
|
+
if (failedAttempts[chatId] >= 5) {
|
|
365
|
+
return ctx.reply('❌ Too many failed attempts. You are locked out.');
|
|
366
|
+
}
|
|
367
|
+
|
|
372
368
|
const text = ctx.message.text.split(' ');
|
|
373
369
|
if (text[1] === pin) {
|
|
374
370
|
authorizedChatId = ctx.chat.id;
|
|
@@ -376,6 +372,7 @@ Provider: ${config.llm.provider}`;
|
|
|
376
372
|
ctx.reply('✅ Bot successfully paired with Nyxora!');
|
|
377
373
|
bot.stop();
|
|
378
374
|
} else {
|
|
375
|
+
failedAttempts[chatId] = (failedAttempts[chatId] || 0) + 1;
|
|
379
376
|
ctx.reply('❌ Invalid PIN.');
|
|
380
377
|
}
|
|
381
378
|
});
|
|
@@ -383,12 +380,25 @@ Provider: ${config.llm.provider}`;
|
|
|
383
380
|
bot.launch();
|
|
384
381
|
|
|
385
382
|
// Wait until paired
|
|
386
|
-
|
|
383
|
+
let attempts = 0;
|
|
384
|
+
while (!paired && attempts < 120) {
|
|
387
385
|
await new Promise(r => setTimeout(r, 1000));
|
|
386
|
+
attempts++;
|
|
387
|
+
}
|
|
388
|
+
if (!paired) {
|
|
389
|
+
s.stop('Timeout waiting for Telegram pairing. Setup will continue, but Telegram integration is disabled.');
|
|
390
|
+
try { bot.stop(); } catch(e) {}
|
|
391
|
+
authorizedChatId = undefined;
|
|
392
|
+
telegramToken = '';
|
|
393
|
+
} else {
|
|
394
|
+
s.stop(`Bot successfully paired with Chat ID: ${authorizedChatId}`);
|
|
388
395
|
}
|
|
389
|
-
s.stop(`Bot successfully paired with Chat ID: ${authorizedChatId}`);
|
|
390
396
|
} catch (err: any) {
|
|
391
397
|
s.stop(`Failed to start bot listener: ${err.message}. You can pair it later.`);
|
|
398
|
+
// Try to stop the bot if it was initialized before the error (L17)
|
|
399
|
+
try {
|
|
400
|
+
if (bot) bot.stop();
|
|
401
|
+
} catch(e) {}
|
|
392
402
|
}
|
|
393
403
|
}
|
|
394
404
|
}
|
|
@@ -403,11 +413,11 @@ Provider: ${config.llm.provider}`;
|
|
|
403
413
|
config.agent.default_chain = defaultChain as string;
|
|
404
414
|
|
|
405
415
|
if (!config.skills) config.skills = { web3: [], os: [] } as any;
|
|
406
|
-
config.skills
|
|
407
|
-
config.skills
|
|
416
|
+
config.skills!.web3 = activeWeb3Skills;
|
|
417
|
+
config.skills!.os = activeOsSkills;
|
|
408
418
|
|
|
409
419
|
if (!config.channels) config.channels = { active: [] } as any;
|
|
410
|
-
config.channels
|
|
420
|
+
config.channels!.active = activeChannels;
|
|
411
421
|
|
|
412
422
|
const newApiKeys: Record<string, string> = {};
|
|
413
423
|
if (apiKey) {
|
|
@@ -442,6 +452,8 @@ Provider: ${config.llm.provider}`;
|
|
|
442
452
|
|
|
443
453
|
if (authorizedChatId) {
|
|
444
454
|
config.integrations.telegram.authorized_chat_id = authorizedChatId;
|
|
455
|
+
} else if (config.integrations.telegram) {
|
|
456
|
+
delete config.integrations.telegram.authorized_chat_id;
|
|
445
457
|
}
|
|
446
458
|
|
|
447
459
|
saveConfig(config);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { formatTransactionSuccess, formatTransactionError } from './formatter';
|
|
3
|
+
import { PendingTransaction } from '../agent/transactionManager';
|
|
4
|
+
|
|
5
|
+
describe('Formatter Utilities', () => {
|
|
6
|
+
it('should format transaction success correctly for swap', () => {
|
|
7
|
+
const tx: PendingTransaction = {
|
|
8
|
+
id: 'test-1',
|
|
9
|
+
type: 'swap',
|
|
10
|
+
chainName: 'ethereum',
|
|
11
|
+
status: 'pending',
|
|
12
|
+
nonce: "0",
|
|
13
|
+
details: {
|
|
14
|
+
fromToken: 'eth',
|
|
15
|
+
toToken: 'usdc',
|
|
16
|
+
amountStr: '1'
|
|
17
|
+
},
|
|
18
|
+
createdAt: Date.now()
|
|
19
|
+
};
|
|
20
|
+
const rawResult = '{"txHash":"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"}';
|
|
21
|
+
const output = formatTransactionSuccess(tx, rawResult);
|
|
22
|
+
expect(output).toContain('Network:** Ethereum');
|
|
23
|
+
expect(output).toContain('Action:** Swapped 1 ETH to USDC');
|
|
24
|
+
expect(output).toContain('Tx Hash:** `0x1234...cdef`');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should format transaction error correctly', () => {
|
|
28
|
+
const tx: PendingTransaction = {
|
|
29
|
+
id: 'test-2',
|
|
30
|
+
type: 'bridge',
|
|
31
|
+
chainName: 'base_sepolia',
|
|
32
|
+
status: 'failed',
|
|
33
|
+
nonce: "0",
|
|
34
|
+
details: {},
|
|
35
|
+
createdAt: Date.now()
|
|
36
|
+
};
|
|
37
|
+
const output = formatTransactionError(tx, 'Insufficient funds');
|
|
38
|
+
expect(output).toContain('Transaction Failed (Base Sepolia)');
|
|
39
|
+
expect(output).toContain('Error: Insufficient funds');
|
|
40
|
+
});
|
|
41
|
+
});
|