nyxora 26.6.6 → 26.6.7

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 (31) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +16 -2
  3. package/bin/nyxora.mjs +32 -0
  4. package/dist/packages/core/src/agent/reasoning.js +11 -6
  5. package/dist/packages/core/src/gateway/cli.js +75 -0
  6. package/dist/packages/core/src/gateway/doctor.js +131 -0
  7. package/dist/packages/core/src/gateway/googleAuthModule.js +1 -1
  8. package/dist/packages/core/src/gateway/server.js +171 -1
  9. package/dist/packages/core/src/gateway/setup.js +8 -3
  10. package/dist/packages/core/src/web3/config.js +3 -3
  11. package/dist/packages/core/src/web3/skills/bridgeToken.js +3 -1
  12. package/dist/packages/core/src/web3/skills/manageCustomTokens.js +82 -0
  13. package/dist/packages/core/src/web3/skills/swapToken.js +5 -2
  14. package/dist/packages/core/src/web3/utils/tokens.js +1 -1
  15. package/package.json +13 -2
  16. package/packages/core/package.json +2 -1
  17. package/packages/core/src/agent/reasoning.ts +11 -6
  18. package/packages/core/src/gateway/cli.ts +42 -1
  19. package/packages/core/src/gateway/doctor.ts +126 -0
  20. package/packages/core/src/gateway/googleAuthModule.ts +1 -1
  21. package/packages/core/src/gateway/server.ts +179 -1
  22. package/packages/core/src/gateway/setup.ts +10 -5
  23. package/packages/core/src/web3/config.ts +4 -3
  24. package/packages/core/src/web3/skills/bridgeToken.ts +3 -1
  25. package/packages/core/src/web3/skills/manageCustomTokens.ts +81 -0
  26. package/packages/core/src/web3/skills/swapToken.ts +5 -2
  27. package/packages/core/src/web3/utils/tokens.ts +1 -1
  28. package/packages/dashboard/dist/assets/index-whRRjJKK.js +306 -0
  29. package/packages/dashboard/dist/index.html +1 -1
  30. package/packages/dashboard/package.json +2 -2
  31. package/packages/dashboard/dist/assets/index-DWxWzOS7.js +0 -306
package/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepashangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [26.6.7] - Unreleased
9
+ ### Enterprise Features & Web3 Enhancements
10
+ - **Enterprise Portfolio Scanner**: Integrated a fully decentralized, real-time Dashboard UI (Nord Theme) to scan all native and ERC-20 token balances across 8 EVM chains natively, without relying on centralized third-party APIs.
11
+ - **Real-Time USD Valuation**: Integrated DexScreener API into the Portfolio Scanner backend to actively compute and display USD portfolio values in real-time. Features an adaptive 2-minute memory cache system to ensure complete immunity against API rate-limits and eliminate LLM token consumption.
12
+ - **Official Web3 Branding**: Integrated TrustWallet and CovalentHQ CDNs to automatically resolve and render official Native Chain icons and ERC-20 Token logos with dynamic address casing, delivering an authentic Tier-1 exchange aesthetic.
13
+ - **Custom Token Management (AI Skill)**: Deployed the new `manage_custom_tokens` Web3 skill. The AI agent can now autonomously recognize, store, and manage user-specified custom token addresses (e.g., obscure/degen tokens) to `~/.nyxora/custom_tokens.json`. These are instantly synced with the Portfolio Scanner.
14
+ - **MEV-Blocker Integration**: Upgraded the Ethereum mainnet routing core in `config.ts` to strictly prioritize `rpc.mevblocker.io` and `rpc.flashbots.net` as primary transports. All user transactions are now forcefully routed through Private Mempools, establishing complete immunity against sandwich attacks and front-running bots.
15
+ - **System Diagnosis (`nyxora doctor`)**: Added a new CLI tool `nyxora doctor` to automatically verify OS requirements, node versions, filesystem permissions, SQLite database r/w access, Keyring vault security, and network port availability for seamless troubleshooting.
16
+
17
+ ### Security & UX Updates
18
+ - **CLI Wallet Management (`nyxora wallet update`)**: Added a highly requested sub-command to allow users to securely overwrite their OS Keyring Web3 wallet directly via the CLI without having to re-run the full LLM setup wizard. Features an aggressive visual confirmation step to prevent accidental Private Key destruction.
19
+ - **Terminal UI Resilience**: Replaced dynamic `note()` text rendering with static linear console logs in the `nyxora setup` wizard. This completely eliminates a UI truncation bug where the `clack` prompter would swallow the 12-word mnemonic phrase on small terminal windows.
20
+ - **Helmet CSP Optimization**: Adjusted the Gateway Server's Content Security Policy (CSP) to securely whitelist decentralized image repositories (GitHub raw, CovalentHQ) without compromising strict anti-XSS protection protocols.
21
+ - **BIP-39 Mnemonic Generation**: Upgraded the `nyxora setup` CLI wizard. When auto-generating a new wallet, the system now provides a standard 12-word Seed Phrase (Mnemonic) instead of a raw hex Private Key, vastly improving user security and cross-wallet compatibility (e.g., MetaMask). The private key is still autonomously extracted and locked in the OS Keyring.
22
+ - **One-Liner Install Script**: Added a new hacker-style `curl | bash` installation method at `https://nyxoraai.github.io/Nyxora/install.sh` for Linux/macOS, and a native PowerShell script `install.ps1` for Windows, providing an instant, frictionless setup experience across all major operating systems.
23
+ - **Global Localization Standardization**: Swept and translated the entire AI reasoning log engine (`reasoning.ts`) from Indonesian to standardized English to maintain strict international professional standards across console output and UI feedback.
24
+
25
+ ### Bug Fixes & Optimizations
26
+ - **ERC-20 Decimals Resolution**: Completely eradicated a critical math bug where all custom tokens were assumed to have 18 decimals. The backend now executes parallel `decimals()` on-chain queries alongside `balanceOf()`, guaranteeing 100% mathematical precision for tokens like USDC/USDT (6 decimals).
27
+ - **NPM Monorepo Build Fix**: Fixed the `packages/core` workspace `package.json` to correctly include the `"build": "tsc"` script and aligned its internal versioning (`v26.6.7`). This resolves the NPM workspace lifecycle crash during global build triggers.
28
+ - **NPM Optimization**: Added official keywords (`web3`, `ai`, `agent`, `crypto`, `mcp`, `automation`, `defi`, `zero-trust`) to the root `package.json` to significantly improve Nyxora's discoverability and SEO on the NPM Registry.
29
+
8
30
  ## [26.6.6] - 2026-06-05
9
31
  ### Enterprise Stability Upgrades
10
32
  - **Strict LLM Output Validation**: Added robust try-catch parsing for LLM tool arguments in `reasoning.ts`. If the AI outputs malformed JSON, the error is fed back into the reasoning loop, allowing the model to autonomously self-correct without crashing the agent pipeline.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Nyxora Agent 🤖
1
+ # Nyxora Agent <img src="./packages/dashboard/public/favicon.svg" width="36" align="top" />
2
2
  **Your Personal Web3 Assistant.**
3
3
 
4
4
 
@@ -75,9 +75,23 @@ To dive deeper into the technical details of our Zero-Knowledge security archite
75
75
  ### Global Installation via NPM (Recommended)
76
76
  The easiest and fastest way to use Nyxora is to install it globally via NPM. This ensures you get the latest version and can run Nyxora from anywhere on your machine.
77
77
 
78
+ The fastest way to install Nyxora is via our automated installation script:
79
+
80
+ **For Linux & macOS (Bash):**
81
+ ```bash
82
+ curl -fsSL https://nyxoraai.github.io/Nyxora/install.sh | bash
83
+ ```
84
+
85
+ **For Windows (PowerShell):**
86
+ ```powershell
87
+ iwr https://nyxoraai.github.io/Nyxora/install.ps1 -useb | iex
88
+ ```
89
+
90
+ Alternatively, you can install it manually on any operating system using NPM:
91
+
78
92
  ```bash
79
- # 1. Install Nyxora globally
80
93
  npm install -g nyxora@latest
94
+ ```
81
95
 
82
96
  # 2. Run the Interactive Setup Wizard (API Keys, Wallet, Telegram)
83
97
  nyxora setup
package/bin/nyxora.mjs CHANGED
@@ -247,11 +247,41 @@ async function setKey(cliArgs) {
247
247
  await new Promise(resolve => child.on('close', resolve));
248
248
  }
249
249
 
250
+ async function wallet(cliArgs) {
251
+ const compiledCli = path.join(projectRoot, 'dist', 'packages/core/src/gateway/cli.js');
252
+ const useCompiled = fs.existsSync(compiledCli);
253
+ const cmd = useCompiled ? 'node' : 'npx';
254
+ const args = useCompiled ? [compiledCli, 'wallet', ...cliArgs] : ['ts-node', '-T', 'packages/core/src/gateway/cli.ts', 'wallet', ...cliArgs];
255
+ const child = spawn(cmd, args, {
256
+ cwd: projectRoot,
257
+ stdio: 'inherit',
258
+ env: { ...process.env, TS_NODE_CACHE: 'false' }
259
+ });
260
+
261
+ await new Promise(resolve => child.on('close', resolve));
262
+ }
263
+
264
+ async function runDoctor() {
265
+ const compiledCli = path.join(projectRoot, 'dist', 'packages/core/src/gateway/doctor.js');
266
+ const useCompiled = fs.existsSync(compiledCli);
267
+ const cmd = useCompiled ? 'node' : 'npx';
268
+ const args = useCompiled ? [compiledCli] : ['ts-node', '-T', 'packages/core/src/gateway/doctor.ts'];
269
+ const child = spawn(cmd, args, {
270
+ cwd: projectRoot,
271
+ stdio: 'inherit',
272
+ env: { ...process.env, TS_NODE_CACHE: 'false' }
273
+ });
274
+
275
+ await new Promise(resolve => child.on('close', resolve));
276
+ }
277
+
250
278
  async function main() {
251
279
  switch (command) {
280
+ case 'doctor': await runDoctor(); break;
252
281
  case 'setup': await setup(); break;
253
282
  case 'clear': await clearMemory(process.argv.slice(3)); break;
254
283
  case 'set-key': await setKey(process.argv.slice(3)); break;
284
+ case 'wallet': await wallet(process.argv.slice(3)); break;
255
285
  case 'start': await start(); break;
256
286
  case 'stop': await stop(); break;
257
287
  case 'restart': await restart(); break;
@@ -277,10 +307,12 @@ Commands:
277
307
  restart Restart the daemon
278
308
  setup Run the interactive Setup Wizard
279
309
  dashboard Open the dashboard in your browser
310
+ doctor Run system diagnostics and check requirements
280
311
  clear Atomically clear the AI's short/long-term memory SQLite database
281
312
  clean-logs Clear the daemon logs
282
313
  autostart Enable/disable autostart on boot (usage: nyxora autostart enable)
283
314
  set-key Securely save API Key (usage: nyxora set-key <provider> <key>)
315
+ wallet Manage your Web3 Wallet (usage: nyxora wallet update)
284
316
 
285
317
  Options:
286
318
  -v, --version Show current version
@@ -24,6 +24,7 @@ const marketAnalysis_1 = require("../web3/skills/marketAnalysis");
24
24
  const checkPortfolio_1 = require("../web3/skills/checkPortfolio");
25
25
  const checkAddress_1 = require("../web3/skills/checkAddress");
26
26
  const getMyAddress_1 = require("../web3/skills/getMyAddress");
27
+ const manageCustomTokens_1 = require("../web3/skills/manageCustomTokens");
27
28
  const limitOrderManager_1 = require("./limitOrderManager");
28
29
  const updateProfile_1 = require("./updateProfile");
29
30
  const updateSecurityPolicy_1 = require("../system/skills/updateSecurityPolicy");
@@ -207,7 +208,7 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
207
208
  const lowerInput = input.toLowerCase();
208
209
  const hasWeb3Keyword = /swap|transfer|price|token|crypto|bridge|wallet|balance|portfolio|buy|sell|send|receive|address|market|limit|mint|nft/i.test(lowerInput);
209
210
  const hasGoogleKeyword = /email|gmail|calendar|sheet|doc|form|event/i.test(lowerInput);
210
- const WEB3_TOOLS = [getBalance_1.getBalanceToolDefinition, transfer_1.transferToolDefinition, getPrice_1.getPriceToolDefinition, swapToken_1.swapTokenToolDefinition, bridgeToken_1.bridgeTokenToolDefinition, mintNft_1.mintNftToolDefinition, customTx_1.customTxToolDefinition, createWallet_1.createWalletToolDefinition, checkSecurity_1.checkSecurityToolDefinition, marketAnalysis_1.marketAnalysisToolDefinition, checkPortfolio_1.checkPortfolioToolDefinition, checkAddress_1.checkAddressToolDefinition, getMyAddress_1.getMyAddressToolDefinition, limitOrderManager_1.createLimitOrderToolDefinition, limitOrderManager_1.listLimitOrdersToolDefinition, limitOrderManager_1.cancelLimitOrderToolDefinition];
211
+ const WEB3_TOOLS = [getBalance_1.getBalanceToolDefinition, transfer_1.transferToolDefinition, getPrice_1.getPriceToolDefinition, swapToken_1.swapTokenToolDefinition, bridgeToken_1.bridgeTokenToolDefinition, mintNft_1.mintNftToolDefinition, customTx_1.customTxToolDefinition, createWallet_1.createWalletToolDefinition, checkSecurity_1.checkSecurityToolDefinition, marketAnalysis_1.marketAnalysisToolDefinition, checkPortfolio_1.checkPortfolioToolDefinition, checkAddress_1.checkAddressToolDefinition, getMyAddress_1.getMyAddressToolDefinition, manageCustomTokens_1.manageCustomTokensDefinition, limitOrderManager_1.createLimitOrderToolDefinition, limitOrderManager_1.listLimitOrdersToolDefinition, limitOrderManager_1.cancelLimitOrderToolDefinition];
211
212
  const SYSTEM_TOOLS = [updateProfile_1.updateProfileToolDefinition, updateSecurityPolicy_1.updateSecurityPolicyToolDefinition, analyzeDocument_1.analyzeDocumentToolDefinition, readFile_1.readLocalFileToolDefinition, writeFile_1.writeLocalFileToolDefinition, executeShell_1.runTerminalCommandToolDefinition, browseWeb_1.browseWebsiteToolDefinition, searchWeb_1.searchWebToolDefinition, installSkill_1.installExternalSkillToolDefinition];
212
213
  const GOOGLE_TOOLS = [googleWorkspace_1.readGmailInboxToolDefinition, googleWorkspace_1.listCalendarEventsToolDefinition, googleWorkspace_1.appendRowToSheetsToolDefinition, googleWorkspace_1.readGoogleDocsToolDefinition, googleWorkspace_1.readGoogleFormResponsesToolDefinition];
213
214
  let activeTools = [];
@@ -251,9 +252,9 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
251
252
  let result = "";
252
253
  let args = {};
253
254
  const toolName = toolCall.function.name;
254
- console.log(picocolors_1.default.yellow(`[⚡ Eksekusi Tool] AI memanggil ${toolName}...`));
255
+ console.log(picocolors_1.default.yellow(`[⚡ Tool Execution] AI is calling ${toolName}...`));
255
256
  if (onProgress)
256
- onProgress(`_⚡ Menjalankan alat: ${toolName}..._`);
257
+ onProgress(`_⚡ Running tool: ${toolName}..._`);
257
258
  // Phase 1: LLM Output Validation (Anti-Halusinasi)
258
259
  try {
259
260
  args = JSON.parse(toolCall.function.arguments);
@@ -342,6 +343,10 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
342
343
  result = await (0, getMyAddress_1.getMyAddress)();
343
344
  break;
344
345
  }
346
+ case 'manage_custom_tokens': {
347
+ result = await (0, manageCustomTokens_1.executeManageCustomTokens)(args);
348
+ break;
349
+ }
345
350
  case 'create_limit_order': {
346
351
  if (config.permissions?.web3?.allow_swap === false) {
347
352
  result = `[Security Blocked] Runtime Permission Denied: Limit orders require swap permissions. Update config.yaml to allow.`;
@@ -434,15 +439,15 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
434
439
  }
435
440
  }
436
441
  if (result.includes('[Security Blocked]') || result.startsWith('Error:')) {
437
- console.log(picocolors_1.default.red(`[❌ Gagal] Tool ${toolName} mengembalikan error atau diblokir.`));
442
+ console.log(picocolors_1.default.red(`[❌ Failed] Tool ${toolName} returned an error or was blocked.`));
438
443
  }
439
444
  else {
440
- console.log(picocolors_1.default.green(`[✅ Sukses] Tool ${toolName} berhasil dieksekusi.`));
445
+ console.log(picocolors_1.default.green(`[✅ Success] Tool ${toolName} executed successfully.`));
441
446
  }
442
447
  }
443
448
  catch (toolError) {
444
449
  result = `Error executing ${toolName}: ${toolError.message}`;
445
- console.log(picocolors_1.default.red(`[❌ Error Crash] Eksekusi ${toolName} gagal total: ${toolError.message}`));
450
+ console.error(picocolors_1.default.red(`[❌ Error Crash] Execution of ${toolName} failed completely: ${toolError.message}`));
446
451
  }
447
452
  exports.logger.addEntry({
448
453
  role: 'tool',
@@ -1,5 +1,38 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
3
36
  var __importDefault = (this && this.__importDefault) || function (mod) {
4
37
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
38
  };
@@ -8,9 +41,11 @@ const safeLogger_1 = require("../utils/safeLogger");
8
41
  (0, safeLogger_1.initSafeLogger)();
9
42
  const fs_1 = __importDefault(require("fs"));
10
43
  const path_1 = __importDefault(require("path"));
44
+ const os_1 = __importDefault(require("os"));
11
45
  const paths_1 = require("../config/paths");
12
46
  const server_1 = require("./server");
13
47
  const setup_1 = require("./setup");
48
+ const prompts_1 = require("@clack/prompts");
14
49
  const state_1 = require("../utils/state");
15
50
  const picocolors_1 = __importDefault(require("picocolors"));
16
51
  const parser_1 = require("../config/parser");
@@ -27,6 +62,12 @@ async function main() {
27
62
  await (0, setup_1.runSetupWizard)();
28
63
  process.exit(0);
29
64
  }
65
+ // Check for doctor command
66
+ if (process.argv.includes('doctor')) {
67
+ const { runDoctor } = await Promise.resolve().then(() => __importStar(require('./doctor')));
68
+ await runDoctor();
69
+ process.exit(0);
70
+ }
30
71
  // Check for memory clear command
31
72
  if (process.argv.includes('clear')) {
32
73
  if (process.argv.includes('--force') || process.argv.includes('-y')) {
@@ -68,6 +109,40 @@ async function main() {
68
109
  console.log(picocolors_1.default.green(`✅ API Key for ${provider} saved securely to vault.`));
69
110
  process.exit(0);
70
111
  }
112
+ // Check for wallet command
113
+ if (process.argv.includes('wallet')) {
114
+ if (process.argv.includes('update')) {
115
+ console.log(picocolors_1.default.cyan('\n🔄 Wallet Update Wizard'));
116
+ const proceed = await (0, prompts_1.confirm)({
117
+ message: picocolors_1.default.bgRed(picocolors_1.default.white(' ⚠️ WARNING ')) + picocolors_1.default.yellow(' This will immediately OVERWRITE your existing wallet in the OS Vault.\nIf you have not backed up your current Private Key, you will lose access to its funds forever.\n\nAre you absolutely sure you want to proceed?'),
118
+ });
119
+ if ((0, prompts_1.isCancel)(proceed) || !proceed)
120
+ process.exit(0);
121
+ const pk = await (0, prompts_1.password)({
122
+ message: 'Enter your new Private Key (0x...):',
123
+ });
124
+ if ((0, prompts_1.isCancel)(pk))
125
+ process.exit(0);
126
+ try {
127
+ const { Entry } = await Promise.resolve().then(() => __importStar(require('@napi-rs/keyring')));
128
+ const entry = new Entry('nyxora', 'wallet');
129
+ await entry.setPassword(pk);
130
+ console.log(picocolors_1.default.green('✅ Wallet updated securely in OS Native Vault.'));
131
+ console.log(picocolors_1.default.yellow('⚠️ Please restart your Nyxora agent for the new wallet to take effect.\n'));
132
+ }
133
+ catch (e) {
134
+ const vaultPath = path_1.default.join(os_1.default.homedir(), '.nyxora', 'vault.key');
135
+ fs_1.default.writeFileSync(vaultPath, `PRIVATE_KEY=${pk}\n`, { mode: 0o600 });
136
+ console.log(picocolors_1.default.green('✅ Wallet updated securely in fallback vault.key.'));
137
+ console.log(picocolors_1.default.yellow('⚠️ Please restart your Nyxora agent for the new wallet to take effect.\n'));
138
+ }
139
+ process.exit(0);
140
+ }
141
+ else {
142
+ console.error(picocolors_1.default.red('Usage: nyxora wallet update'));
143
+ process.exit(1);
144
+ }
145
+ }
71
146
  // 2. Setup boilerplate files if in global mode and they don't exist
72
147
  let isFirstBoot = false;
73
148
  if (isGlobalMode) {
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runDoctor = runDoctor;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const net_1 = __importDefault(require("net"));
10
+ const picocolors_1 = __importDefault(require("picocolors"));
11
+ const paths_1 = require("../config/paths");
12
+ const parser_1 = require("../config/parser");
13
+ async function runDoctor() {
14
+ console.log(picocolors_1.default.cyan('\n🔍 Nyxora System Doctor\n'));
15
+ let allGood = true;
16
+ const printStatus = (name, status, errorMsg) => {
17
+ if (status) {
18
+ console.log(`${picocolors_1.default.green('✓')} ${name}`);
19
+ }
20
+ else {
21
+ console.log(`${picocolors_1.default.red('✗')} ${name}`);
22
+ if (errorMsg)
23
+ console.log(` ${picocolors_1.default.gray(errorMsg)}`);
24
+ allGood = false;
25
+ }
26
+ };
27
+ // 1. Check Node Version
28
+ const nodeVer = process.versions.node;
29
+ const majorVer = parseInt(nodeVer.split('.')[0], 10);
30
+ printStatus(`Node.js Version (${nodeVer})`, majorVer >= 22, `Please upgrade to Node.js v22 or higher.`);
31
+ // 2. Check App Directory & Config
32
+ const appDir = (0, paths_1.getAppDir)();
33
+ const configPath = path_1.default.join(appDir, 'config.yaml');
34
+ let configOk = false;
35
+ let configErr = '';
36
+ if (fs_1.default.existsSync(configPath)) {
37
+ try {
38
+ (0, parser_1.loadConfig)();
39
+ configOk = true;
40
+ }
41
+ catch (e) {
42
+ configErr = `Invalid YAML syntax: ${e.message}`;
43
+ }
44
+ }
45
+ else {
46
+ configErr = `config.yaml not found at ${configPath}`;
47
+ }
48
+ printStatus(`Configuration File`, configOk, configErr);
49
+ // 3. Check SQLite DB access
50
+ const dbPath = path_1.default.join(appDir, 'memory.db');
51
+ let dbOk = false;
52
+ let dbErr = '';
53
+ try {
54
+ if (fs_1.default.existsSync(dbPath)) {
55
+ fs_1.default.accessSync(dbPath, fs_1.default.constants.R_OK | fs_1.default.constants.W_OK);
56
+ dbOk = true;
57
+ }
58
+ else {
59
+ dbOk = true; // Not created yet, which is fine
60
+ }
61
+ }
62
+ catch (e) {
63
+ dbErr = `Cannot read/write memory.db: ${e.message}`;
64
+ }
65
+ printStatus(`SQLite Database Permissions`, dbOk, dbErr);
66
+ // 4. Check OS Keyring
67
+ let keyringOk = false;
68
+ let keyringErr = '';
69
+ try {
70
+ const { getPassword } = require('@napi-rs/keyring');
71
+ // Just try to access it. If it throws native error, keyring is inaccessible
72
+ try {
73
+ getPassword('nyxora', 'test_ping_doctor');
74
+ }
75
+ catch (e) {
76
+ // It's normal to throw "The specified item could not be found in the keychain"
77
+ // But if it crashes or throws permission error, it's bad.
78
+ }
79
+ keyringOk = true;
80
+ }
81
+ catch (e) {
82
+ keyringErr = `OS Vault inaccessible: ${e.message}. Ensure libsecret is installed on Linux.`;
83
+ }
84
+ printStatus(`OS Native Vault (Keyring)`, keyringOk, keyringErr);
85
+ // 5. Check Ports
86
+ const checkPort = (port) => {
87
+ return new Promise((resolve) => {
88
+ const server = net_1.default.createServer();
89
+ server.unref();
90
+ server.on('error', () => resolve(false)); // Port in use
91
+ server.listen(port, () => {
92
+ server.close(() => resolve(true)); // Port free
93
+ });
94
+ });
95
+ };
96
+ const port3000Free = await checkPort(3000);
97
+ const port3001Free = await checkPort(3001);
98
+ let isDaemonRunning = false;
99
+ const pidPath = path_1.default.join(appDir, 'daemon.pid');
100
+ if (fs_1.default.existsSync(pidPath)) {
101
+ try {
102
+ const pidStr = fs_1.default.readFileSync(pidPath, 'utf8').trim();
103
+ process.kill(parseInt(pidStr, 10), 0);
104
+ isDaemonRunning = true;
105
+ }
106
+ catch (e) { }
107
+ }
108
+ if (isDaemonRunning && !port3000Free) {
109
+ console.log(`${picocolors_1.default.green('✓')} Port 3000 (Dashboard) ${picocolors_1.default.cyan('[In Use by Nyxora]')}`);
110
+ }
111
+ else {
112
+ printStatus(`Port 3000 (Dashboard)`, port3000Free, `Port is already in use by another application.`);
113
+ }
114
+ if (isDaemonRunning && !port3001Free) {
115
+ console.log(`${picocolors_1.default.green('✓')} Port 3001 (Gateway API) ${picocolors_1.default.cyan('[In Use by Nyxora]')}`);
116
+ }
117
+ else {
118
+ printStatus(`Port 3001 (Gateway API)`, port3001Free, `Port is already in use by another application.`);
119
+ }
120
+ console.log('\n================================');
121
+ if (allGood) {
122
+ console.log(picocolors_1.default.green('🚀 All systems are completely healthy! Nyxora is ready to fly.'));
123
+ }
124
+ else {
125
+ console.log(picocolors_1.default.yellow('⚠️ Some checks failed. Please fix the issues above for optimal performance.'));
126
+ }
127
+ console.log('================================\n');
128
+ }
129
+ if (require.main === module) {
130
+ runDoctor();
131
+ }
@@ -37,7 +37,7 @@ async function initGoogleAuth() {
37
37
  // Check if we already have a refresh token saved
38
38
  const refreshToken = await getRefreshToken();
39
39
  if (refreshToken) {
40
- console.log('[Google Auth] Refresh token found in secure storage.');
40
+ // console.log('[Google Auth] Refresh token found in secure storage.'); // Suppressed to avoid CLI prompt disruption
41
41
  return true;
42
42
  }
43
43
  return false;
@@ -9,9 +9,13 @@ const cors_1 = __importDefault(require("cors"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const helmet_1 = __importDefault(require("helmet"));
11
11
  const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
12
+ const paths_1 = require("../config/paths");
12
13
  const state_1 = require("../utils/state");
14
+ const fs_1 = __importDefault(require("fs"));
13
15
  const reasoning_1 = require("../agent/reasoning");
14
16
  const parser_1 = require("../config/parser");
17
+ const config_1 = require("../web3/config");
18
+ const tokens_1 = require("../web3/utils/tokens");
15
19
  const tracker_1 = require("./tracker");
16
20
  const transactionManager_1 = require("../agent/transactionManager");
17
21
  const limitOrderManager_1 = require("../agent/limitOrderManager");
@@ -21,6 +25,7 @@ const swapToken_1 = require("../web3/skills/swapToken");
21
25
  const getBalance_1 = require("../web3/skills/getBalance");
22
26
  const checkAddress_1 = require("../web3/skills/checkAddress");
23
27
  const getMyAddress_1 = require("../web3/skills/getMyAddress");
28
+ const manageCustomTokens_1 = require("../web3/skills/manageCustomTokens");
24
29
  const getPrice_1 = require("../web3/skills/getPrice");
25
30
  const checkSecurity_1 = require("../web3/skills/checkSecurity");
26
31
  const checkPortfolio_1 = require("../web3/skills/checkPortfolio");
@@ -59,7 +64,17 @@ console.error = function (...args) {
59
64
  originalError.apply(console, args);
60
65
  };
61
66
  const app = (0, express_1.default)();
62
- app.use((0, helmet_1.default)());
67
+ app.use((0, helmet_1.default)({
68
+ contentSecurityPolicy: {
69
+ directives: {
70
+ defaultSrc: ["'self'"],
71
+ imgSrc: ["'self'", 'data:', 'https://raw.githubusercontent.com', 'https://logos.covalenthq.com'],
72
+ scriptSrc: ["'self'", "'unsafe-inline'"],
73
+ styleSrc: ["'self'", "'unsafe-inline'"],
74
+ connectSrc: ["'self'", 'https://*']
75
+ }
76
+ }
77
+ }));
63
78
  app.use((0, cors_1.default)({
64
79
  origin: function (origin, callback) {
65
80
  if (!origin || /^(http:\/\/(localhost|127\.0\.0\.1):\d+)$/.test(origin)) {
@@ -208,6 +223,7 @@ app.get('/api/skills', (req, res) => {
208
223
  checkPortfolio_1.checkPortfolioToolDefinition,
209
224
  marketAnalysis_1.marketAnalysisToolDefinition,
210
225
  createWallet_1.createWalletToolDefinition,
226
+ manageCustomTokens_1.manageCustomTokensDefinition,
211
227
  limitOrderManager_2.createLimitOrderToolDefinition,
212
228
  limitOrderManager_2.listLimitOrdersToolDefinition,
213
229
  limitOrderManager_2.cancelLimitOrderToolDefinition
@@ -361,6 +377,8 @@ app.post('/api/transactions/:id/reject', async (req, res) => {
361
377
  });
362
378
  let cachedTrending = null;
363
379
  let lastTrendingFetch = 0;
380
+ let cachedPrices = {};
381
+ let lastPricesFetch = 0;
364
382
  app.get('/api/trending', async (req, res) => {
365
383
  const now = Date.now();
366
384
  if (cachedTrending && now - lastTrendingFetch < 5 * 60 * 1000) {
@@ -388,6 +406,158 @@ app.get('/api/trending', async (req, res) => {
388
406
  res.status(500).json({ error: err.message });
389
407
  }
390
408
  });
409
+ app.get('/api/portfolio', async (req, res) => {
410
+ try {
411
+ const userAddress = await (0, config_1.getAddress)();
412
+ const customTokensPath = path_1.default.join((0, paths_1.getPath)('custom_tokens.json'));
413
+ let customTokens = {};
414
+ if (fs_1.default.existsSync(customTokensPath)) {
415
+ try {
416
+ customTokens = JSON.parse(fs_1.default.readFileSync(customTokensPath, 'utf8'));
417
+ }
418
+ catch (e) { }
419
+ }
420
+ const portfolio = {};
421
+ await Promise.all(config_1.SUPPORTED_CHAIN_NAMES.map(async (chainName) => {
422
+ portfolio[chainName] = [];
423
+ try {
424
+ const publicClient = (0, config_1.getPublicClient)(chainName);
425
+ // 1. Get Native Balance
426
+ const nativeBal = await publicClient.getBalance({ address: userAddress });
427
+ if (nativeBal > 0n) {
428
+ portfolio[chainName].push({
429
+ symbol: chainName === 'bsc' ? 'BNB' : chainName === 'polygon' ? 'POL' : 'ETH',
430
+ address: 'native',
431
+ balanceRaw: nativeBal.toString(),
432
+ decimals: 18,
433
+ isNative: true
434
+ });
435
+ }
436
+ // 2. Combine TOKEN_MAP and customTokens for this chain
437
+ const tokensToQuery = { ...(tokens_1.TOKEN_MAP[chainName] || {}) };
438
+ if (customTokens[chainName]) {
439
+ Object.assign(tokensToQuery, customTokens[chainName]);
440
+ }
441
+ // 3. Query all ERC-20 balances in parallel
442
+ await Promise.all(Object.entries(tokensToQuery).map(async ([symbol, address]) => {
443
+ if (address === '0x0000000000000000000000000000000000000000')
444
+ return; // Skip native placeholder
445
+ try {
446
+ const balPromise = publicClient.readContract({
447
+ address: address,
448
+ abi: tokens_1.ERC20_ABI,
449
+ functionName: 'balanceOf',
450
+ args: [userAddress]
451
+ });
452
+ const decPromise = publicClient.readContract({
453
+ address: address,
454
+ abi: tokens_1.ERC20_ABI,
455
+ functionName: 'decimals'
456
+ });
457
+ const [bal, decimals] = await Promise.all([balPromise, decPromise]);
458
+ if (bal > 0n) {
459
+ portfolio[chainName].push({
460
+ symbol,
461
+ address,
462
+ balanceRaw: bal.toString(),
463
+ decimals: decimals, // Now using actual on-chain decimals
464
+ isNative: false
465
+ });
466
+ }
467
+ }
468
+ catch (e) {
469
+ // Ignore read errors
470
+ }
471
+ }));
472
+ }
473
+ catch (e) {
474
+ console.error(`Portfolio error on ${chainName}:`, e);
475
+ }
476
+ }));
477
+ // --- DexScreener Price Fetching ---
478
+ const addressesToFetch = new Set();
479
+ const wrapMap = {
480
+ ethereum: 'WETH', arbitrum: 'WETH', base: 'WETH', optimism: 'WETH', sepolia: 'WETH', base_sepolia: 'WETH',
481
+ bsc: 'WBNB', polygon: 'WMATIC'
482
+ };
483
+ for (const chain of Object.keys(portfolio)) {
484
+ for (const t of portfolio[chain]) {
485
+ if (t.isNative) {
486
+ const wToken = wrapMap[chain] || 'WETH';
487
+ const wAddr = (tokens_1.TOKEN_MAP[chain]?.[wToken]) || '';
488
+ if (wAddr)
489
+ addressesToFetch.add(wAddr.toLowerCase());
490
+ }
491
+ else {
492
+ addressesToFetch.add(t.address.toLowerCase());
493
+ }
494
+ }
495
+ }
496
+ const uniqueAddrs = Array.from(addressesToFetch);
497
+ const now = Date.now();
498
+ let priceMap = cachedPrices;
499
+ if (uniqueAddrs.length > 0 && now - lastPricesFetch > 2 * 60 * 1000) {
500
+ try {
501
+ const newPrices = {};
502
+ await Promise.all(uniqueAddrs.map(async (addr) => {
503
+ try {
504
+ const res = await fetch(`https://api.dexscreener.com/latest/dex/tokens/${addr}`);
505
+ if (res.ok) {
506
+ const data = await res.json();
507
+ if (data.pairs && data.pairs.length > 0) {
508
+ // Find the pair with highest liquidity
509
+ let bestPair = data.pairs[0];
510
+ let maxLiq = 0;
511
+ for (const p of data.pairs) {
512
+ const liq = p.liquidity?.usd || 0;
513
+ if (liq > maxLiq) {
514
+ maxLiq = liq;
515
+ bestPair = p;
516
+ }
517
+ }
518
+ // Calculate price from bestPair
519
+ if (bestPair.priceUsd) {
520
+ const baseAddr = bestPair.baseToken.address.toLowerCase();
521
+ const quoteAddr = bestPair.quoteToken?.address?.toLowerCase();
522
+ if (baseAddr === addr) {
523
+ newPrices[addr] = parseFloat(bestPair.priceUsd);
524
+ }
525
+ else if (quoteAddr === addr && bestPair.priceNative && parseFloat(bestPair.priceNative) > 0) {
526
+ newPrices[addr] = parseFloat(bestPair.priceUsd) / parseFloat(bestPair.priceNative);
527
+ }
528
+ }
529
+ }
530
+ }
531
+ }
532
+ catch (e) {
533
+ console.error(`DexScreener error for ${addr}:`, e);
534
+ }
535
+ }));
536
+ console.log('DexScreener Fetched Prices:', newPrices);
537
+ cachedPrices = { ...cachedPrices, ...newPrices };
538
+ priceMap = cachedPrices;
539
+ lastPricesFetch = now;
540
+ }
541
+ catch (e) {
542
+ console.error('DexScreener fetch error:', e);
543
+ }
544
+ }
545
+ for (const chain of Object.keys(portfolio)) {
546
+ for (const t of portfolio[chain]) {
547
+ let lookupAddr = t.address.toLowerCase();
548
+ if (t.isNative) {
549
+ const wToken = wrapMap[chain] || 'WETH';
550
+ lookupAddr = ((tokens_1.TOKEN_MAP[chain]?.[wToken]) || '').toLowerCase();
551
+ }
552
+ t.priceUsd = priceMap[lookupAddr] || 0;
553
+ }
554
+ }
555
+ res.json(portfolio);
556
+ }
557
+ catch (err) {
558
+ res.status(500).json({ error: err.message });
559
+ }
560
+ });
391
561
  app.post('/api/chat', async (req, res) => {
392
562
  try {
393
563
  const { message, session_id } = req.body;
@@ -304,9 +304,14 @@ Provider: ${config.llm.provider}`;
304
304
  return process.exit(0);
305
305
  }
306
306
  else if (walletSetupType === 'generate') {
307
- privateKey = (0, accounts_1.generatePrivateKey)();
308
- const account = (0, accounts_1.privateKeyToAccount)(privateKey);
309
- (0, prompts_1.note)(`New Wallet Generated!\n\nAddress: ${account.address}\nPrivate Key: ${privateKey}\n\nIMPORTANT: Backup this Private Key NOW! It is securely injected into your local vault, but you will need it to import your wallet elsewhere.`, 'Wallet Created');
307
+ const seedPhrase = (0, accounts_1.generateMnemonic)(accounts_1.english);
308
+ const account = (0, accounts_1.mnemonicToAccount)(seedPhrase);
309
+ privateKey = '0x' + Buffer.from(account.getHdKey().privateKey).toString('hex');
310
+ prompts_1.log.success('New Wallet Generated!');
311
+ prompts_1.log.info(`Address: ${account.address}`);
312
+ prompts_1.log.info(`Private Key: ${privateKey}`);
313
+ prompts_1.log.info(`Seed Phrase (Mnemonic): ${seedPhrase}`);
314
+ prompts_1.log.warn('IMPORTANT: Write down these 12 words (or the Private Key) NOW! This is your ONLY backup. The credentials have been securely injected into your local OS vault.');
310
315
  }
311
316
  // --- SAVING ---
312
317
  // Update Config.yaml