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.
Files changed (80) hide show
  1. package/README.md +18 -1
  2. package/bin/nyxora.mjs +32 -0
  3. package/dist/packages/core/src/agent/reasoning.js +10 -0
  4. package/dist/packages/core/src/config/parser.js +121 -7
  5. package/dist/packages/core/src/gateway/chat.js +82 -0
  6. package/dist/packages/core/src/gateway/cli.js +63 -0
  7. package/dist/packages/core/src/gateway/server.js +100 -56
  8. package/dist/packages/core/src/gateway/setup.js +39 -22
  9. package/dist/packages/core/src/utils/formatter.test.js +40 -0
  10. package/dist/packages/core/src/utils/skillManager.js +91 -0
  11. package/dist/packages/core/src/utils/userWhitelistManager.js +41 -36
  12. package/dist/packages/core/src/web3/aggregator/aggregatorMainnet.js +2 -2
  13. package/dist/packages/core/src/web3/aggregator/defiRouter.js +3 -0
  14. package/dist/packages/core/src/web3/skills/bridgeToken.js +4 -0
  15. package/dist/packages/core/src/web3/skills/checkRegistryStatus.js +13 -0
  16. package/dist/packages/core/src/web3/skills/customTx.js +2 -0
  17. package/dist/packages/core/src/web3/skills/defiLending.js +2 -0
  18. package/dist/packages/core/src/web3/skills/manageCustomTokens.js +18 -32
  19. package/dist/packages/core/src/web3/skills/marketAnalysis.js +3 -1
  20. package/dist/packages/core/src/web3/skills/mintNft.js +2 -0
  21. package/dist/packages/core/src/web3/skills/provideLiquidity.js +2 -0
  22. package/dist/packages/core/src/web3/skills/revokeApprovals.js +2 -0
  23. package/dist/packages/core/src/web3/skills/swapToken.js +4 -2
  24. package/dist/packages/core/src/web3/skills/transfer.js +2 -0
  25. package/dist/packages/core/src/web3/skills/yieldVault.js +2 -0
  26. package/dist/packages/core/src/web3/utils/tokens.js +9 -1
  27. package/package.json +2 -1
  28. package/packages/core/package.json +1 -1
  29. package/packages/core/src/agent/reasoning.ts +11 -0
  30. package/packages/core/src/config/parser.ts +119 -9
  31. package/packages/core/src/gateway/chat.ts +85 -0
  32. package/packages/core/src/gateway/cli.ts +63 -0
  33. package/packages/core/src/gateway/server.ts +115 -60
  34. package/packages/core/src/gateway/setup.ts +39 -27
  35. package/packages/core/src/utils/formatter.test.ts +41 -0
  36. package/packages/core/src/utils/skillManager.ts +98 -0
  37. package/packages/core/src/utils/userWhitelistManager.ts +48 -39
  38. package/packages/core/src/web3/aggregator/aggregatorMainnet.ts +2 -2
  39. package/packages/core/src/web3/aggregator/defiRouter.ts +4 -0
  40. package/packages/core/src/web3/skills/bridgeToken.ts +3 -0
  41. package/packages/core/src/web3/skills/checkRegistryStatus.ts +13 -0
  42. package/packages/core/src/web3/skills/customTx.ts +1 -0
  43. package/packages/core/src/web3/skills/defiLending.ts +1 -0
  44. package/packages/core/src/web3/skills/manageCustomTokens.ts +18 -29
  45. package/packages/core/src/web3/skills/marketAnalysis.ts +2 -1
  46. package/packages/core/src/web3/skills/mintNft.ts +1 -0
  47. package/packages/core/src/web3/skills/provideLiquidity.ts +1 -0
  48. package/packages/core/src/web3/skills/revokeApprovals.ts +1 -0
  49. package/packages/core/src/web3/skills/swapToken.ts +3 -2
  50. package/packages/core/src/web3/skills/transfer.ts +1 -0
  51. package/packages/core/src/web3/skills/yieldVault.ts +1 -0
  52. package/packages/core/src/web3/utils/tokens.ts +9 -1
  53. package/packages/dashboard/dist/assets/index-Di9x08yk.js +16 -0
  54. package/packages/dashboard/dist/index.html +1 -1
  55. package/packages/dashboard/package.json +1 -1
  56. package/packages/mcp-server/package.json +1 -1
  57. package/packages/policy/package.json +1 -1
  58. package/packages/signer/package.json +1 -1
  59. package/dist/packages/core/src/agent/limitOrderManager.js +0 -124
  60. package/dist/packages/core/src/system/pluginManager.js +0 -91
  61. package/dist/packages/core/src/system/skills/installSkill.js +0 -52
  62. package/dist/packages/core/src/test-all-routers.js +0 -81
  63. package/dist/packages/core/src/test-router.js +0 -38
  64. package/dist/packages/core/src/web3/skills/autonomousDefi.js +0 -191
  65. package/dist/packages/core/src/web3/skills/createWallet.js +0 -34
  66. package/dist/packages/core/src/web3/skills/limitOrder.js +0 -106
  67. package/dist/packages/core/src/web3/utils/protocolRegistry.js +0 -46
  68. package/dist/tsconfig.tsbuildinfo +0 -1
  69. package/packages/core/src/__tests__/reasoning.test.ts +0 -81
  70. package/packages/core/src/__tests__/tokens.test.ts +0 -55
  71. package/packages/core/src/__tests__/web3.test.ts +0 -50
  72. package/packages/core/src/agent/reasoning.d.ts.map +0 -1
  73. package/packages/core/src/config/parser.d.ts.map +0 -1
  74. package/packages/core/src/gateway/cli.d.ts.map +0 -1
  75. package/packages/core/src/memory/logger.d.ts.map +0 -1
  76. package/packages/core/src/test-all-routers.ts +0 -59
  77. package/packages/core/src/test-router.ts +0 -49
  78. package/packages/core/src/web3/config.d.ts.map +0 -1
  79. package/packages/core/src/web3/skills/getBalance.d.ts.map +0 -1
  80. 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. Only required for "add" action.'
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 (!config_1.SUPPORTED_CHAIN_NAMES.includes(chain_name)) {
45
- return `Error: Unsupported chain ${chain_name}.`;
41
+ if (!chain_name) {
42
+ throw new Error("Chain name is required.");
46
43
  }
47
- const customTokensPath = (0, paths_1.getPath)('custom_tokens.json');
48
- let customTokens = {};
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 (!customTokens[chain_name]) {
59
- customTokens[chain_name] = {};
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
- customTokens[chain_name][upperSymbol] = contract_address;
67
- fs_1.default.writeFileSync(customTokensPath, JSON.stringify(customTokens, null, 2));
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 (customTokens[chain_name][upperSymbol]) {
72
- delete customTokens[chain_name][upperSymbol];
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.toLowerCase() === chainName.toLowerCase());
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: "0xf55BEC9cbd4732f1F4143f647652e924540d9d64"
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.20",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora-agent-core",
3
- "version": "26.6.20",
3
+ "version": "26.6.21",
4
4
  "private": true,
5
5
  "main": "src/gateway/server.ts",
6
6
  "dependencies": {
@@ -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 = `max_usd_per_tx: ${(parsed as any).permissions?.web3?.max_usd_per_tx || 999999999}\nwhitelist_only: false\nrequire_approval: true\n`;
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
- const yamlStr = yaml.stringify(parsed);
136
- fs.writeFileSync(configPath, yamlStr, 'utf8');
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
- return {
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
- } as NyxoraConfig;
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) {