nyxora 26.6.20 → 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 +10 -0
- 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 +100 -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/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 +11 -0
- 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 +115 -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/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-Di9x08yk.js +16 -0
- package/packages/dashboard/dist/index.html +1 -1
- 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-O2m42q4p.js +0 -16
|
@@ -8,6 +8,8 @@ const transactionManager_1 = require("../../agent/transactionManager");
|
|
|
8
8
|
const vaultClient_1 = require("../utils/vaultClient");
|
|
9
9
|
async function prepareCustomTx(chainName, toAddress, data, valueWei = "0", description = "Custom transaction") {
|
|
10
10
|
try {
|
|
11
|
+
if (!chainName || !toAddress || !data)
|
|
12
|
+
throw new Error("Missing required parameters for custom transaction.");
|
|
11
13
|
const tx = transactionManager_1.txManager.createPendingTransaction('custom', chainName, {
|
|
12
14
|
toAddress,
|
|
13
15
|
data,
|
|
@@ -32,6 +32,8 @@ const AAVE_V3_POOLS = {
|
|
|
32
32
|
};
|
|
33
33
|
async function prepareAaveSupply(chainName, tokenAddressOrSymbol, amountStr) {
|
|
34
34
|
try {
|
|
35
|
+
if (!chainName || !tokenAddressOrSymbol || !amountStr)
|
|
36
|
+
throw new Error("Missing protocol/chain/token parameters for DeFi operation.");
|
|
35
37
|
const publicClient = (0, config_1.getPublicClient)(chainName);
|
|
36
38
|
const userAddress = await (0, config_1.getAddress)();
|
|
37
39
|
const account = userAddress;
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.manageCustomTokensDefinition = void 0;
|
|
7
4
|
exports.executeManageCustomTokens = executeManageCustomTokens;
|
|
8
|
-
const fs_1 = __importDefault(require("fs"));
|
|
9
|
-
const paths_1 = require("../../config/paths");
|
|
10
5
|
const config_1 = require("../config");
|
|
6
|
+
const userWhitelistManager_1 = require("../../utils/userWhitelistManager");
|
|
7
|
+
const vaultClient_1 = require("../utils/vaultClient");
|
|
11
8
|
exports.manageCustomTokensDefinition = {
|
|
12
9
|
type: 'function',
|
|
13
10
|
function: {
|
|
14
11
|
name: 'manage_custom_tokens',
|
|
15
|
-
description: 'Add or remove a custom ERC-20 token (like a memecoin or specific token) from the user\'s local portfolio watcher.',
|
|
12
|
+
description: 'Add or remove a custom ERC-20 token (like a memecoin or specific token) from the user\'s local portfolio watcher and swap whitelist.',
|
|
16
13
|
parameters: {
|
|
17
14
|
type: 'object',
|
|
18
15
|
properties: {
|
|
@@ -32,50 +29,39 @@ exports.manageCustomTokensDefinition = {
|
|
|
32
29
|
},
|
|
33
30
|
contract_address: {
|
|
34
31
|
type: 'string',
|
|
35
|
-
description: 'The smart contract address of the token.
|
|
32
|
+
description: 'The smart contract address of the token. Required for "add" action or "remove" action.'
|
|
36
33
|
}
|
|
37
34
|
},
|
|
38
|
-
required: ['action', 'chain_name', 'symbol']
|
|
35
|
+
required: ['action', 'chain_name', 'symbol', 'contract_address']
|
|
39
36
|
}
|
|
40
37
|
}
|
|
41
38
|
};
|
|
42
39
|
async function executeManageCustomTokens(args) {
|
|
43
40
|
const { action, chain_name, symbol, contract_address } = args;
|
|
44
|
-
if (!
|
|
45
|
-
|
|
41
|
+
if (!chain_name) {
|
|
42
|
+
throw new Error("Chain name is required.");
|
|
46
43
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (fs_1.default.existsSync(customTokensPath)) {
|
|
50
|
-
try {
|
|
51
|
-
const data = fs_1.default.readFileSync(customTokensPath, 'utf8');
|
|
52
|
-
customTokens = JSON.parse(data);
|
|
53
|
-
}
|
|
54
|
-
catch (e) {
|
|
55
|
-
console.error('Error parsing custom_tokens.json', e);
|
|
56
|
-
}
|
|
44
|
+
if (!symbol) {
|
|
45
|
+
throw new Error("Token symbol is required.");
|
|
57
46
|
}
|
|
58
|
-
if (!
|
|
59
|
-
|
|
47
|
+
if (!config_1.SUPPORTED_CHAIN_NAMES.includes(chain_name)) {
|
|
48
|
+
return `Error: Unsupported chain ${chain_name}.`;
|
|
60
49
|
}
|
|
61
50
|
const upperSymbol = symbol.toUpperCase();
|
|
51
|
+
const userAddress = await (0, vaultClient_1.getAddress)();
|
|
62
52
|
if (action === 'add') {
|
|
63
53
|
if (!contract_address || !contract_address.startsWith('0x')) {
|
|
64
54
|
return `Error: Invalid or missing contract_address.`;
|
|
65
55
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return `Successfully added custom token ${upperSymbol} to the ${chain_name} portfolio tracker.`;
|
|
56
|
+
await (0, userWhitelistManager_1.saveTokenToWhitelist)(userAddress, chain_name, contract_address, 'manual', upperSymbol);
|
|
57
|
+
return `Successfully added custom token ${upperSymbol} to the ${chain_name} portfolio tracker and swap whitelist.`;
|
|
69
58
|
}
|
|
70
59
|
else if (action === 'remove') {
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
fs_1.default.writeFileSync(customTokensPath, JSON.stringify(customTokens, null, 2));
|
|
74
|
-
return `Successfully removed custom token ${upperSymbol} from the ${chain_name} portfolio tracker.`;
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
return `Warning: Token ${upperSymbol} was not found in the custom portfolio tracker for ${chain_name}.`;
|
|
60
|
+
if (!contract_address || !contract_address.startsWith('0x')) {
|
|
61
|
+
return `Error: Invalid or missing contract_address for removal.`;
|
|
78
62
|
}
|
|
63
|
+
(0, userWhitelistManager_1.removeTokenFromWhitelist)(userAddress, chain_name, contract_address);
|
|
64
|
+
return `Successfully removed custom token ${upperSymbol} from the ${chain_name} whitelist.`;
|
|
79
65
|
}
|
|
80
66
|
return `Error: Invalid action.`;
|
|
81
67
|
}
|
|
@@ -56,7 +56,7 @@ async function fetchDexData(query, isCa, chainName) {
|
|
|
56
56
|
if (data && data.pairs && data.pairs.length > 0) {
|
|
57
57
|
let pairs = data.pairs;
|
|
58
58
|
if (chainName) {
|
|
59
|
-
pairs = pairs.filter((p) => p.chainId
|
|
59
|
+
pairs = pairs.filter((p) => p.chainId?.toLowerCase() === chainName?.toLowerCase());
|
|
60
60
|
if (pairs.length === 0)
|
|
61
61
|
pairs = data.pairs;
|
|
62
62
|
}
|
|
@@ -95,6 +95,8 @@ async function fetchCexMomentum(symbol, currentP) {
|
|
|
95
95
|
}
|
|
96
96
|
async function analyzeMarket(chainName, tokenAddressOrSymbol) {
|
|
97
97
|
try {
|
|
98
|
+
if (!tokenAddressOrSymbol)
|
|
99
|
+
throw new Error("Token symbol is invalid.");
|
|
98
100
|
const cleanInput = tokenAddressOrSymbol.replace('$', '').toLowerCase();
|
|
99
101
|
const isAddress = cleanInput.startsWith('0x') && cleanInput.length === 42;
|
|
100
102
|
let officialSymbol = cleanInput.toUpperCase();
|
|
@@ -8,6 +8,8 @@ const config_1 = require("../config");
|
|
|
8
8
|
const transactionManager_1 = require("../../agent/transactionManager");
|
|
9
9
|
async function prepareMintNft(chainName, contractAddress, functionSignature, argsStr, valueEth = "0") {
|
|
10
10
|
try {
|
|
11
|
+
if (!chainName || !contractAddress || !functionSignature)
|
|
12
|
+
throw new Error("Missing required parameters to mint NFT.");
|
|
11
13
|
const publicClient = (0, config_1.getPublicClient)(chainName);
|
|
12
14
|
const userAddress = await (0, config_1.getAddress)();
|
|
13
15
|
const account = userAddress;
|
|
@@ -52,6 +52,8 @@ const POSITION_MANAGERS = {
|
|
|
52
52
|
const parser_1 = require("../../config/parser");
|
|
53
53
|
async function prepareProvideLiquidity(chainName, token0AddressOrSymbol, token1AddressOrSymbol, amount0Str, amount1Str, feeTier, tickLower, tickUpper, slippagePercent) {
|
|
54
54
|
try {
|
|
55
|
+
if (!chainName || !token0AddressOrSymbol || !token1AddressOrSymbol || !amount0Str || !amount1Str)
|
|
56
|
+
throw new Error("Missing protocol/chain/token parameters for DeFi operation.");
|
|
55
57
|
let actualSlippage = slippagePercent;
|
|
56
58
|
if (actualSlippage === undefined || actualSlippage === null || actualSlippage === "auto") {
|
|
57
59
|
try {
|
|
@@ -8,6 +8,8 @@ const transactionManager_1 = require("../../agent/transactionManager");
|
|
|
8
8
|
const tokens_1 = require("../utils/tokens");
|
|
9
9
|
async function prepareRevokeApproval(chainName, tokenAddressOrSymbol, spenderAddress) {
|
|
10
10
|
try {
|
|
11
|
+
if (!chainName || !tokenAddressOrSymbol || !spenderAddress)
|
|
12
|
+
throw new Error("Missing required parameters for revoking approval.");
|
|
11
13
|
const publicClient = (0, config_1.getPublicClient)(chainName);
|
|
12
14
|
const userAddress = await (0, config_1.getAddress)();
|
|
13
15
|
const account = userAddress;
|
|
@@ -12,15 +12,17 @@ const userWhitelistManager_1 = require("../../utils/userWhitelistManager");
|
|
|
12
12
|
const defiRouter_1 = require("../aggregator/defiRouter");
|
|
13
13
|
async function prepareSwapToken(chainName, fromToken, toToken, amountStr, mode = "auto", providerName = "auto", slippagePercent) {
|
|
14
14
|
try {
|
|
15
|
+
if (!chainName || !fromToken || !toToken || !amountStr)
|
|
16
|
+
throw new Error("Missing required parameters for swap (chain, tokens, or amount).");
|
|
15
17
|
const userAddress = await (0, vaultClient_1.getAddress)();
|
|
16
18
|
const fromTokenAddress = (0, tokens_1.resolveToken)(fromToken, chainName);
|
|
17
19
|
const toTokenAddress = (0, tokens_1.resolveToken)(toToken, chainName);
|
|
18
20
|
const isNativeIn = fromTokenAddress === "0x0000000000000000000000000000000000000000";
|
|
19
21
|
// Auto-save to Degen Whitelist
|
|
20
22
|
if (!isNativeIn)
|
|
21
|
-
(0, userWhitelistManager_1.saveTokenToWhitelist)(userAddress, chainName, fromTokenAddress, 'swap');
|
|
23
|
+
await (0, userWhitelistManager_1.saveTokenToWhitelist)(userAddress, chainName, fromTokenAddress, 'swap');
|
|
22
24
|
if (toTokenAddress !== "0x0000000000000000000000000000000000000000") {
|
|
23
|
-
(0, userWhitelistManager_1.saveTokenToWhitelist)(userAddress, chainName, toTokenAddress, 'swap');
|
|
25
|
+
await (0, userWhitelistManager_1.saveTokenToWhitelist)(userAddress, chainName, toTokenAddress, 'swap');
|
|
24
26
|
}
|
|
25
27
|
// Default to 18 decimals for formatting input, though Aggregator handles this if we pass raw
|
|
26
28
|
const amountWei = (0, viem_1.parseUnits)(amountStr, 18).toString();
|
|
@@ -10,6 +10,8 @@ const tokens_1 = require("../utils/tokens");
|
|
|
10
10
|
const vaultClient_1 = require("../utils/vaultClient");
|
|
11
11
|
async function prepareTransfer(chainName, toAddress, amountStr, token) {
|
|
12
12
|
try {
|
|
13
|
+
if (!chainName || !toAddress || !amountStr)
|
|
14
|
+
throw new Error("Missing required parameters for transfer (chain, recipient, or amount).");
|
|
13
15
|
const publicClient = (0, config_1.getPublicClient)(chainName);
|
|
14
16
|
const userAddress = await (0, config_1.getAddress)();
|
|
15
17
|
const account = userAddress;
|
|
@@ -25,6 +25,8 @@ const VAULT_ABI = [
|
|
|
25
25
|
];
|
|
26
26
|
async function prepareVaultDeposit(chainName, protocol, vaultAddress, tokenAddressOrSymbol, amountStr) {
|
|
27
27
|
try {
|
|
28
|
+
if (!chainName || !vaultAddress || !tokenAddressOrSymbol || !amountStr)
|
|
29
|
+
throw new Error("Missing protocol/chain/token parameters for DeFi operation.");
|
|
28
30
|
const publicClient = (0, config_1.getPublicClient)(chainName);
|
|
29
31
|
const userAddress = await (0, config_1.getAddress)();
|
|
30
32
|
const account = userAddress;
|
|
@@ -5,6 +5,7 @@ exports.resolveToken = resolveToken;
|
|
|
5
5
|
exports.getTokenMetadata = getTokenMetadata;
|
|
6
6
|
exports.invalidateTokenCache = invalidateTokenCache;
|
|
7
7
|
exports.preloadTokenMetadataAndAllowance = preloadTokenMetadataAndAllowance;
|
|
8
|
+
const userWhitelistManager_1 = require("../../utils/userWhitelistManager");
|
|
8
9
|
exports.ERC20_ABI = [
|
|
9
10
|
{
|
|
10
11
|
type: 'function',
|
|
@@ -76,7 +77,7 @@ exports.TOKEN_MAP = {
|
|
|
76
77
|
ETH: "0x0000000000000000000000000000000000000000",
|
|
77
78
|
WETH: "0x4200000000000000000000000000000000000006",
|
|
78
79
|
USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
79
|
-
USDT: "
|
|
80
|
+
USDT: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2"
|
|
80
81
|
},
|
|
81
82
|
optimism: {
|
|
82
83
|
ETH: "0x0000000000000000000000000000000000000000",
|
|
@@ -126,6 +127,13 @@ function resolveToken(tokenSymbolOrAddress, chainName) {
|
|
|
126
127
|
if (chainTokens && chainTokens[symbolUpper]) {
|
|
127
128
|
return chainTokens[symbolUpper];
|
|
128
129
|
}
|
|
130
|
+
const whitelistData = (0, userWhitelistManager_1.getUserWhitelist)();
|
|
131
|
+
for (const addr in whitelistData) {
|
|
132
|
+
const tokens = whitelistData[addr];
|
|
133
|
+
const match = tokens.find(t => t.chainName === chainName && t.symbol === symbolUpper);
|
|
134
|
+
if (match)
|
|
135
|
+
return match.address;
|
|
136
|
+
}
|
|
129
137
|
throw new Error(`Token "${tokenSymbolOrAddress}" pada chain ${chainName} tidak ditemukan. Silakan gunakan alamat kontrak langsung (0x...).`);
|
|
130
138
|
}
|
|
131
139
|
// Bounded LRU Cache to prevent RAM bloat from fake tokens (OOM protection)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nyxora",
|
|
3
|
-
"version": "26.6.
|
|
3
|
+
"version": "26.6.21",
|
|
4
4
|
"description": "Your Personal Web3 Assistant",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web3",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"stop": "node ./bin/nyxora.mjs stop",
|
|
43
43
|
"restart": "node ./bin/nyxora.mjs restart",
|
|
44
44
|
"dashboard": "node ./bin/nyxora.mjs dashboard",
|
|
45
|
+
"chat": "node ./bin/nyxora.mjs chat",
|
|
45
46
|
"setup": "node ./bin/nyxora.mjs setup",
|
|
46
47
|
"set-key": "node ./bin/nyxora.mjs set-key",
|
|
47
48
|
"doctor": "node ./bin/nyxora.mjs doctor",
|
|
@@ -370,6 +370,17 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
|
|
|
370
370
|
continue;
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
+
if (!isSkillActive(toolName)) {
|
|
374
|
+
console.warn(pc.red(`[Security] Blocked illegal execution of disabled skill: ${toolName}`));
|
|
375
|
+
result = `[System Error] Access denied: Skill '${toolName}' is currently disabled by the user.`;
|
|
376
|
+
logger.addEntry({
|
|
377
|
+
role: "tool",
|
|
378
|
+
tool_call_id: toolCall.id,
|
|
379
|
+
content: result
|
|
380
|
+
}, sessionId);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
|
|
373
384
|
try {
|
|
374
385
|
switch (toolName) {
|
|
375
386
|
case 'get_balance': {
|
|
@@ -1,7 +1,70 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import yaml from 'yaml';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import crypto from 'crypto';
|
|
4
6
|
import { getPath } from './paths';
|
|
7
|
+
let cachedEncryptionKey: Buffer | null = null;
|
|
8
|
+
function getEncryptionKeySync(): Buffer {
|
|
9
|
+
if (cachedEncryptionKey) return cachedEncryptionKey;
|
|
10
|
+
let masterKeyRaw = process.env.NYXORA_MASTER_KEY;
|
|
11
|
+
if (!masterKeyRaw) {
|
|
12
|
+
try {
|
|
13
|
+
const { execSync } = require('child_process');
|
|
14
|
+
const output = execSync(`node -e "require('@napi-rs/keyring').Entry.prototype.getPassword.call(new (require('@napi-rs/keyring').Entry)('nyxora', 'config_master')).then(console.log).catch(()=>console.log(''))"`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
|
|
15
|
+
const pk = output?.trim();
|
|
16
|
+
if (pk) masterKeyRaw = pk;
|
|
17
|
+
} catch (e) {
|
|
18
|
+
// Ignore
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (!masterKeyRaw) {
|
|
22
|
+
try {
|
|
23
|
+
const masterKeyPath = path.join(os.homedir(), '.nyxora', 'auth', 'master.key');
|
|
24
|
+
if (fs.existsSync(masterKeyPath)) {
|
|
25
|
+
masterKeyRaw = fs.readFileSync(masterKeyPath, 'utf8').trim();
|
|
26
|
+
} else {
|
|
27
|
+
masterKeyRaw = crypto.randomBytes(32).toString('hex');
|
|
28
|
+
try { fs.writeFileSync(masterKeyPath, masterKeyRaw, { mode: 0o600 }); } catch (e) {}
|
|
29
|
+
}
|
|
30
|
+
} catch (e) {
|
|
31
|
+
masterKeyRaw = 'default_fallback_nyxora_key';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
cachedEncryptionKey = crypto.createHash('sha256').update(masterKeyRaw).digest();
|
|
35
|
+
return cachedEncryptionKey;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function encryptDataSync(text: string): string {
|
|
39
|
+
if (!text || text.startsWith('ENC:')) return text;
|
|
40
|
+
const iv = crypto.randomBytes(12);
|
|
41
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', getEncryptionKeySync(), iv);
|
|
42
|
+
let encrypted = cipher.update(text, 'utf8', 'hex');
|
|
43
|
+
encrypted += cipher.final('hex');
|
|
44
|
+
const authTag = cipher.getAuthTag().toString('hex');
|
|
45
|
+
return `ENC:${iv.toString('hex')}:${authTag}:${encrypted}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function decryptDataSync(encryptedText: string): string {
|
|
49
|
+
if (!encryptedText) return encryptedText;
|
|
50
|
+
if (!encryptedText.startsWith('ENC:')) {
|
|
51
|
+
return encryptedText;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const parts = encryptedText.split(':');
|
|
55
|
+
if (parts.length < 4) return encryptedText;
|
|
56
|
+
const iv = Buffer.from(parts[1], 'hex');
|
|
57
|
+
const authTag = Buffer.from(parts[2], 'hex');
|
|
58
|
+
const encrypted = parts.slice(3).join(':');
|
|
59
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', getEncryptionKeySync(), iv);
|
|
60
|
+
decipher.setAuthTag(authTag);
|
|
61
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
62
|
+
decrypted += decipher.final('utf8');
|
|
63
|
+
return decrypted;
|
|
64
|
+
} catch (e) {
|
|
65
|
+
return encryptedText; // return raw if decryption fails
|
|
66
|
+
}
|
|
67
|
+
}
|
|
5
68
|
|
|
6
69
|
export function loadRpcConfig(): Record<string, string | string[]> {
|
|
7
70
|
const rpcPath = getPath('rpc_key.yaml');
|
|
@@ -26,7 +89,7 @@ export function saveRpcConfig(rpcUrls: Record<string, string | string[]>): void
|
|
|
26
89
|
|
|
27
90
|
export async function loadApiKeys(): Promise<Record<string, string>> {
|
|
28
91
|
const config = loadConfig();
|
|
29
|
-
return config.credentials || {};
|
|
92
|
+
return (config.credentials as Record<string, string>) || {};
|
|
30
93
|
}
|
|
31
94
|
|
|
32
95
|
export async function saveApiKeys(newKeys: Record<string, string>): Promise<void> {
|
|
@@ -96,8 +159,27 @@ export function loadConfig(): NyxoraConfig {
|
|
|
96
159
|
const file = fs.readFileSync(configPath, 'utf8');
|
|
97
160
|
const parsed = yaml.parse(file) as Partial<NyxoraConfig>;
|
|
98
161
|
|
|
99
|
-
// Auto-migration logic: move llm.credentials to root credentials
|
|
100
162
|
let needsSave = false;
|
|
163
|
+
|
|
164
|
+
// Decrypt credentials
|
|
165
|
+
if (parsed.credentials) {
|
|
166
|
+
for (const key in parsed.credentials) {
|
|
167
|
+
if (parsed.credentials[key]) {
|
|
168
|
+
if (parsed.credentials[key]!.startsWith('ENC:')) needsSave = true;
|
|
169
|
+
parsed.credentials[key] = decryptDataSync(parsed.credentials[key]!);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (parsed.integrations?.telegram?.bot_token) {
|
|
174
|
+
if (parsed.integrations.telegram.bot_token.startsWith('ENC:')) needsSave = true;
|
|
175
|
+
parsed.integrations.telegram.bot_token = decryptDataSync(parsed.integrations.telegram.bot_token);
|
|
176
|
+
}
|
|
177
|
+
if (parsed.web3?.explorer_api_key) {
|
|
178
|
+
if (parsed.web3.explorer_api_key.startsWith('ENC:')) needsSave = true;
|
|
179
|
+
parsed.web3.explorer_api_key = decryptDataSync(parsed.web3.explorer_api_key);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Auto-migration logic: move llm.credentials to root credentials
|
|
101
183
|
if (parsed.llm && (parsed.llm as any).credentials) {
|
|
102
184
|
if (!parsed.credentials) {
|
|
103
185
|
parsed.credentials = {};
|
|
@@ -121,7 +203,7 @@ export function loadConfig(): NyxoraConfig {
|
|
|
121
203
|
// Auto-migration logic: move permissions to policy.yaml
|
|
122
204
|
const policyPath = getPath('policy.yaml');
|
|
123
205
|
if (!fs.existsSync(policyPath)) {
|
|
124
|
-
const defaultPolicy = `
|
|
206
|
+
const defaultPolicy = `auto_approve_limit_usd: 0\ncustom_llm_rules: []\n`;
|
|
125
207
|
fs.writeFileSync(policyPath, defaultPolicy, 'utf8');
|
|
126
208
|
console.log('[Config] Created default policy.yaml.');
|
|
127
209
|
}
|
|
@@ -132,9 +214,8 @@ export function loadConfig(): NyxoraConfig {
|
|
|
132
214
|
|
|
133
215
|
if (needsSave) {
|
|
134
216
|
try {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
console.log('[Config] Auto-migrated llm.credentials to root credentials.');
|
|
217
|
+
saveConfig(parsed as NyxoraConfig);
|
|
218
|
+
console.log('[Config] Auto-migrated config file safely.');
|
|
138
219
|
} catch (e) {
|
|
139
220
|
console.error('[Config] Failed to auto-migrate config file', e);
|
|
140
221
|
}
|
|
@@ -142,7 +223,8 @@ export function loadConfig(): NyxoraConfig {
|
|
|
142
223
|
|
|
143
224
|
|
|
144
225
|
|
|
145
|
-
|
|
226
|
+
|
|
227
|
+
const validatedConfig: NyxoraConfig = {
|
|
146
228
|
agent: parsed.agent || { name: 'Nyxora-Default', description: 'Your Personal Web3 Assistant.', default_chain: 'base', default_router: 'auto', default_slippage: 'auto' },
|
|
147
229
|
llm: parsed.llm || {
|
|
148
230
|
provider: 'openai',
|
|
@@ -159,11 +241,16 @@ export function loadConfig(): NyxoraConfig {
|
|
|
159
241
|
web3: { ...parsed.web3, rpc_urls: rpcUrls },
|
|
160
242
|
integrations: parsed.integrations || {
|
|
161
243
|
telegram: { enabled: false }
|
|
162
|
-
}
|
|
163
|
-
|
|
244
|
+
},
|
|
245
|
+
skills: parsed.skills
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
return validatedConfig;
|
|
164
249
|
} catch (error: any) {
|
|
165
250
|
if (error.code === 'ENOENT') {
|
|
166
251
|
console.log('[Config] No config.yaml found. Using default configuration.');
|
|
252
|
+
} else if (error.name === 'YAMLError' || error.message?.includes('YAML')) {
|
|
253
|
+
console.warn('[Parser] YAML Parse Error:', error.message);
|
|
167
254
|
} else {
|
|
168
255
|
console.error('[Config] Failed to load config.yaml. Using default configuration.', error);
|
|
169
256
|
}
|
|
@@ -202,9 +289,32 @@ export function saveConfig(newConfig: NyxoraConfig): void {
|
|
|
202
289
|
if (configToSave.web3 && configToSave.web3.rpc_urls) {
|
|
203
290
|
delete configToSave.web3.rpc_urls;
|
|
204
291
|
}
|
|
292
|
+
|
|
293
|
+
// Keys are no longer encrypted before saving. They are stored in plain text.
|
|
294
|
+
|
|
205
295
|
const yamlStr = yaml.stringify(configToSave);
|
|
206
296
|
fs.writeFileSync(configPath, yamlStr, 'utf8');
|
|
207
297
|
} catch (error) {
|
|
208
298
|
console.error('Failed to save config.yaml', error);
|
|
209
299
|
}
|
|
210
300
|
}
|
|
301
|
+
|
|
302
|
+
export interface PolicyConfig {
|
|
303
|
+
max_usd_per_tx?: number;
|
|
304
|
+
whitelist_only?: boolean;
|
|
305
|
+
require_approval?: boolean;
|
|
306
|
+
auto_approve_limit_usd?: number;
|
|
307
|
+
custom_llm_rules?: string[];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function loadPolicyConfig(): PolicyConfig {
|
|
311
|
+
const policyPath = getPath('policy.yaml');
|
|
312
|
+
if (fs.existsSync(policyPath)) {
|
|
313
|
+
try {
|
|
314
|
+
return yaml.parse(fs.readFileSync(policyPath, 'utf8')) || {};
|
|
315
|
+
} catch (e) {
|
|
316
|
+
console.error('[Config] Failed to parse policy.yaml', e);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return {};
|
|
320
|
+
}
|
|
@@ -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) {
|