nyxora 26.6.6 → 26.6.8-2

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 (56) hide show
  1. package/.dockerignore +9 -0
  2. package/CHANGELOG.md +36 -2
  3. package/README.md +30 -9
  4. package/SECURITY.md +3 -1
  5. package/bin/nyxora.mjs +35 -3
  6. package/dist/launcher.js +137 -0
  7. package/dist/packages/core/src/agent/reasoning.js +15 -26
  8. package/dist/packages/core/src/config/parser.js +7 -45
  9. package/dist/packages/core/src/config/paths.js +39 -1
  10. package/dist/packages/core/src/gateway/cli.js +80 -5
  11. package/dist/packages/core/src/gateway/doctor.js +129 -0
  12. package/dist/packages/core/src/gateway/googleAuthModule.js +14 -33
  13. package/dist/packages/core/src/gateway/legalGenerator.js +88 -0
  14. package/dist/packages/core/src/gateway/server.js +236 -1
  15. package/dist/packages/core/src/gateway/setup.js +9 -5
  16. package/dist/packages/core/src/utils/state.js +2 -3
  17. package/dist/packages/core/src/web3/config.js +3 -3
  18. package/dist/packages/core/src/web3/skills/bridgeToken.js +3 -1
  19. package/dist/packages/core/src/web3/skills/manageCustomTokens.js +81 -0
  20. package/dist/packages/core/src/web3/skills/swapToken.js +5 -2
  21. package/dist/packages/core/src/web3/utils/tokens.js +1 -1
  22. package/dist/packages/policy/src/server.js +7 -0
  23. package/dist/packages/signer/src/server.js +7 -0
  24. package/package.json +16 -4
  25. package/packages/core/package.json +2 -1
  26. package/packages/core/src/agent/reasoning.ts +17 -29
  27. package/packages/core/src/config/parser.ts +7 -41
  28. package/packages/core/src/config/paths.ts +42 -1
  29. package/packages/core/src/gateway/cli.ts +63 -22
  30. package/packages/core/src/gateway/doctor.ts +125 -0
  31. package/packages/core/src/gateway/googleAuthModule.ts +13 -28
  32. package/packages/core/src/gateway/legalGenerator.ts +85 -0
  33. package/packages/core/src/gateway/server.ts +256 -1
  34. package/packages/core/src/gateway/setup.ts +12 -7
  35. package/packages/core/src/utils/state.ts +3 -1
  36. package/packages/core/src/web3/config.ts +4 -3
  37. package/packages/core/src/web3/skills/bridgeToken.ts +3 -1
  38. package/packages/core/src/web3/skills/manageCustomTokens.ts +81 -0
  39. package/packages/core/src/web3/skills/swapToken.ts +5 -2
  40. package/packages/core/src/web3/utils/tokens.ts +1 -1
  41. package/packages/dashboard/dist/assets/index-D50q-_33.js +311 -0
  42. package/packages/dashboard/dist/index.html +1 -1
  43. package/packages/dashboard/package.json +2 -2
  44. package/packages/mcp-server/package.json +1 -1
  45. package/packages/policy/package.json +1 -1
  46. package/packages/policy/src/server.ts +9 -0
  47. package/packages/signer/package.json +1 -1
  48. package/packages/signer/src/server.ts +9 -0
  49. package/DOCKER.md +0 -68
  50. package/Dockerfile +0 -43
  51. package/assets/architecture.png +0 -0
  52. package/assets/architecture.svg +0 -1
  53. package/assets/raw-diagram.png +0 -0
  54. package/assets/security-flow.png +0 -0
  55. package/launcher.ts +0 -98
  56. package/packages/dashboard/dist/assets/index-DWxWzOS7.js +0 -306
package/.dockerignore CHANGED
@@ -10,3 +10,12 @@ build
10
10
  .nyxora
11
11
  vault.key
12
12
  api_vault.key
13
+ keystore.json
14
+ memory.json
15
+ memory.db*
16
+ credentials.json
17
+ google-*.json
18
+ *.token
19
+ config.yaml
20
+ custom_tokens.json
21
+ dynamic_tokens.json
package/CHANGELOG.md CHANGED
@@ -5,6 +5,41 @@ 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.8-1] - Unreleased
9
+ ### Enterprise Features & Web3 Enhancements
10
+ - **Zero-Downtime Directory Migration**: Restructured the root `~/.nyxora` local data directory into a strict `config/`, `data/`, `auth/`, and `run/` subdirectory architecture. Implemented a Lazy Auto-Migration Engine (`getPath()`) that seamlessly relocates legacy files to their new secure zones instantly upon access, ensuring zero-downtime and zero-data-loss upgrades for existing users.
11
+ ### Security & UX Updates
12
+ - **Proactive Anti-Crash Engine**: Implemented `Max Retry` & `Exponential Backoff` auto-respawn logic inside the Monorepo Launcher. The daemon now features robust global `unhandledRejection` and `uncaughtException` guards across the Core, Policy, and Signer subsystems, ensuring sporadic Web3 network timeouts can no longer crash the main reasoning engine.
13
+ - **Death Loop Telegram Alerts**: Integrated a critical emergency monitoring system into the Launcher. If a core microservice crashes catastrophically (5 times within 1 minute), the daemon will autonomously initiate an emergency lock-down to protect the system state and immediately broadcast a high-priority alert directly to the administrator's Telegram.
14
+ - **Unix Socket Anti-EADDRINUSE Guard**: Implemented aggressive pre-flight cleanup routines for the `nyxora-signer.sock` IPC channel. The system now guarantees secure file unlinking before rebinding, eliminating recurring `EADDRINUSE` daemon failures upon rapid restart commands.
15
+
16
+ ### Bug Fixes & Optimizations
17
+ - **NPM Package Optimization**: Added `assets/` to `.npmignore` to exclude large architectural diagrams and images from the NPM registry tarball, reducing the total package download size by over 11 MB.
18
+ - **Docker Multi-Stage Build**: Radically refactored `Dockerfile` to a Multi-Stage architecture. The production image now exclusively installs runtime dependencies (`--omit=dev`) and leaves behind heavy build tools (`python3`, `make`, `g++`), dramatically shrinking the final container image size.
19
+ - **Docker Security Patch**: Hardened `.dockerignore` to explicitly block local keystores (`keystore.json`), persistent memory (`memory.db`), and local credentials from accidentally leaking into Docker image layers during local builds.
20
+
21
+ ## [26.6.7] - 2026-06-07
22
+ ### Enterprise Features & Web3 Enhancements
23
+ - **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.
24
+ - **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.
25
+ - **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.
26
+ - **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.
27
+ - **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.
28
+ - **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.
29
+
30
+ ### Security & UX Updates
31
+ - **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.
32
+ - **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.
33
+ - **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.
34
+ - **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.
35
+ - **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.
36
+ - **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.
37
+
38
+ ### Bug Fixes & Optimizations
39
+ - **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).
40
+ - **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.
41
+ - **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.
42
+
8
43
  ## [26.6.6] - 2026-06-05
9
44
  ### Enterprise Stability Upgrades
10
45
  - **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.
@@ -99,8 +134,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
134
  - **Fast CLI Shortcuts**: Added the `nyxora set-key <provider> <key>` global command shortcut allowing developers to quickly inject or override any API Key (OpenAI, Gemini, OpenRouter, Tavily, Brave) directly into the secure vault without traversing the wizard.
100
135
 
101
136
  ### AI Engine Optimizations
102
- - **Hybrid API Vault (Security)**: API Keys are no longer stored in plain text inside `config.yaml`. Nyxora now encrypts and stores them via `@napi-rs/keyring` utilizing OS-native credential management. For Headless/VPS Linux environments lacking DBUS/Secret Service, it automatically falls back to an isolated `api_vault.key` with strict `0600` permissions.
103
- - **Root-Level Config Auto-Migration**: Restructured `config.yaml` to move all API keys out of the nested `llm.credentials` into a logical, root-level `credentials` object. Implemented a silent auto-migration routine in `parser.ts` that safely upgrades legacy config files on boot without breaking existing setups.
137
+
104
138
  - **Web Search Smart Memory Cache**: Embedded a local Memory Cache (`Map`) into the `searchWeb` skill with a 5-minute (300,000ms) TTL. Exact duplicate queries now execute in 0ms and consume 0 API quota, dramatically improving conversation flow.
105
139
  - **Deep Research Mode**: The `search_web` tool definition now accepts a dynamic `depth` parameter (1 to 3). If users instruct the AI to conduct comprehensive research, Nyxora will automatically trigger `advanced` API payloads and extract up to 15 top web snippets simultaneously.
106
140
  - **Strict Skill Prioritization**: Added CRITICAL RULE 7 to the core NLP System Prompt. The AI is now hard-coded to prioritize native Web3 Skills (e.g. `get_price`, `analyze_market`, `check_security`) for all crypto-related queries, using `search_web` exclusively as a fallback mechanism.
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
 
@@ -10,7 +10,7 @@
10
10
 
11
11
  Nyxora is a **secure, non-custodial runtime infrastructure for autonomous onchain agents** built with a robust Monorepo architecture (Node.js & React). Designed for autonomous workflows with a premium Utility-Centric dark-themed UI and strict client-side key isolation.
12
12
 
13
- **Nyxora now natively supports the Model Context Protocol (MCP)**. You can transform your external AI agents (like Claude Desktop and Cursor) into secure Web3 actors that execute swaps and fetch balances using Nyxora's secure signer vault. [View the MCP Integration Guide](https://nyxoraAI.github.io/Nyxora/guide/mcp-integration)
13
+ **Nyxora now natively supports the Model Context Protocol (MCP)**. You can transform your external AI agents (like Claude Desktop and Cursor) into secure Web3 actors that execute swaps and fetch balances using Nyxora's secure signer vault. [View the MCP Integration Guide](https://nyxoraai.github.io/Nyxora/guide/mcp-integration)
14
14
 
15
15
  It operates under an institutional-grade **Cryptographically Bound Human-in-the-Loop** execution model, ensuring that Remote AIs (LLMs) never have unilateral access to your funds.
16
16
 
@@ -66,7 +66,7 @@ The following diagram illustrates Nyxora's **3-Tier Monorepo Architecture**, sho
66
66
 
67
67
  ## 🛡️ Advanced Security & Threat Model
68
68
 
69
- To dive deeper into the technical details of our Zero-Knowledge security architecture, please visit the [Nyxora Security Blueprint](https://nyxoraAI.github.io/Nyxora/).
69
+ To dive deeper into the technical details of our Zero-Knowledge security architecture, please visit the [Nyxora Security Blueprint](https://nyxoraai.github.io/Nyxora/).
70
70
 
71
71
  ---
72
72
 
@@ -75,20 +75,41 @@ 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
- # 2. Run the Interactive Setup Wizard (API Keys, Wallet, Telegram)
96
+ ### 2. Run the Interactive Setup Wizard (API Keys, Wallet, Telegram)
97
+ ```bash
83
98
  nyxora setup
99
+ ```
84
100
 
85
- # 3. Start the Nyxora background daemon
101
+ ### 3. Start the Nyxora background daemon
102
+ ```bash
86
103
  nyxora start
104
+ ```
87
105
 
88
- # 4. Open the Web Dashboard
106
+ ### 4. Open the Web Dashboard
107
+ ```bash
89
108
  nyxora dashboard
109
+ ```
90
110
 
91
- # Utility: Atomically clear the AI's short-term and long-term memory
111
+ ### Utility: Atomically clear the AI's short-term and long-term memory
112
+ ```bash
92
113
  nyxora clear --force
93
114
  ```
94
115
  > **⚠️ IMPORTANT:** Whenever you re-run `nyxora setup` or manually edit the config files, you **must restart the daemon** by running `nyxora restart` for the changes to take effect.
@@ -121,7 +142,7 @@ npm start
121
142
 
122
143
  For complete technical deep-dives into our Cryptographic Architecture, please visit our official VitePress Documentation Site!
123
144
 
124
- > **🔗 [Read the Full Nyxora Documentation Here](https://nyxoraAI.github.io/Nyxora/)**
145
+ > **🔗 [Read the Full Nyxora Documentation Here](https://nyxoraai.github.io/Nyxora/)**
125
146
 
126
147
  *(Includes guides on Secure Wallet Imports, Architecture Blueprints, Troubleshooting, and Custom Skill Development).*
127
148
 
package/SECURITY.md CHANGED
@@ -38,7 +38,9 @@ Nyxora completely eliminates the need for manual "Master Passwords" or custom AE
38
38
  When the background daemon boots via `nyxora start`, the Signer Vault process reads the Private Key directly from the OS Keyring without requiring human intervention. This ensures the daemon can safely persist across reboots while maintaining institutional-grade encryption at rest.
39
39
 
40
40
  ### Secure Fallback Storage
41
- In headless server environments (e.g., VPS, Docker) where a GUI Keyring is unavailable, Nyxora gracefully falls back to a strictly permissioned `.env` / `vault.key` file mechanism. This file is programmatically enforced with `chmod 0600` permissions (Read/Write for owner only), preventing access by other system users.
41
+ In headless server environments (e.g., VPS, Docker) where a GUI Keyring is unavailable, Nyxora gracefully falls back to a strictly permissioned `vault.key` file mechanism. This file is programmatically enforced with `chmod 0600` permissions (Read/Write for owner only), preventing access by other system users.
42
+
43
+ > **Note:** This OS-level keyring protection is strictly reserved for your Web3 Wallet Private Keys. Standard integration credentials (like LLM API Keys or Telegram tokens) are managed separately via the `config.yaml` file for transparent developer access.
42
44
 
43
45
  ---
44
46
 
package/bin/nyxora.mjs CHANGED
@@ -11,9 +11,9 @@ const __dirname = path.dirname(__filename);
11
11
  const projectRoot = path.join(__dirname, '..');
12
12
 
13
13
  const appDir = path.join(os.homedir(), '.nyxora');
14
- const pidFile = path.join(appDir, 'daemon.pid');
15
- const logFile = path.join(appDir, 'gateway.log');
16
- const tokenFile = path.join(appDir, 'auth.token');
14
+ const pidFile = path.join(appDir, 'run', 'daemon.pid');
15
+ const logFile = path.join(appDir, 'run', 'gateway.log');
16
+ const tokenFile = path.join(appDir, 'auth', 'auth.token');
17
17
 
18
18
  if (!fs.existsSync(appDir)) {
19
19
  fs.mkdirSync(appDir, { recursive: true });
@@ -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
@@ -0,0 +1,137 @@
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
+ const safeLogger_1 = require("./packages/core/src/utils/safeLogger");
7
+ (0, safeLogger_1.initSafeLogger)();
8
+ const child_process_1 = require("child_process");
9
+ const crypto_1 = __importDefault(require("crypto"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const INTERNAL_AUTH_TOKEN = crypto_1.default.randomBytes(64).toString('hex');
13
+ console.log(`[Launcher] Generated Internal Auth Token: ${INTERNAL_AUTH_TOKEN.substring(0, 8)}...`);
14
+ const nyxoraDir = path_1.default.join(process.env.HOME || process.env.USERPROFILE || '', '.nyxora');
15
+ const authDir = path_1.default.join(nyxoraDir, 'auth');
16
+ if (!fs_1.default.existsSync(authDir))
17
+ fs_1.default.mkdirSync(authDir, { recursive: true, mode: 0o700 });
18
+ const tokenPath = path_1.default.join(authDir, 'runtime.token');
19
+ fs_1.default.writeFileSync(tokenPath, INTERNAL_AUTH_TOKEN, { mode: 0o600 });
20
+ console.log(`[Launcher] Secured runtime token at ${tokenPath} (0600)`);
21
+ const env = {
22
+ ...process.env,
23
+ INTERNAL_AUTH_TOKEN,
24
+ SIGNER_SOCKET_PATH: '/tmp/nyxora-signer.sock',
25
+ TS_NODE_CACHE: 'false'
26
+ };
27
+ const spawnService = (name, command, args, env, inheritStdio = false) => {
28
+ let child;
29
+ let crashCount = 0;
30
+ let crashWindowStart = Date.now();
31
+ let isShuttingDown = false;
32
+ const startProcess = () => {
33
+ child = (0, child_process_1.spawn)(command, args, { env, stdio: inheritStdio ? 'inherit' : 'pipe' });
34
+ if (!inheritStdio) {
35
+ child.stdout?.on('data', (data) => process.stdout.write(`[${name}] ${data}`));
36
+ child.stderr?.on('data', (data) => process.stderr.write(`[${name}] ERROR: ${data}`));
37
+ }
38
+ child.on('close', async (code) => {
39
+ console.log(`[${name}] Exited with code ${code}`);
40
+ if (isShuttingDown)
41
+ return;
42
+ const now = Date.now();
43
+ if (now - crashWindowStart > 60000) {
44
+ crashCount = 0;
45
+ crashWindowStart = now;
46
+ }
47
+ crashCount++;
48
+ if (crashCount > 5) {
49
+ console.error(`[Launcher] FATAL: ${name} crashed 5 times in 1 minute. Initiating emergency shutdown.`);
50
+ isShuttingDown = true;
51
+ try {
52
+ const yaml = require('yaml');
53
+ const configPath = path_1.default.join(process.env.HOME || process.env.USERPROFILE || '', '.nyxora', 'config', 'config.yaml');
54
+ // Fallback check just in case it hasn't migrated yet
55
+ const actualConfigPath = fs_1.default.existsSync(configPath) ? configPath : path_1.default.join(process.env.HOME || process.env.USERPROFILE || '', '.nyxora', 'config.yaml');
56
+ if (fs_1.default.existsSync(actualConfigPath)) {
57
+ const configStr = fs_1.default.readFileSync(actualConfigPath, 'utf8');
58
+ const config = yaml.parse(configStr);
59
+ const tgToken = config?.telegram?.bot_token;
60
+ const tgChatId = config?.telegram?.admin_chat_id;
61
+ if (tgToken && tgChatId) {
62
+ const alertText = config?.alerts?.emergency_text || "🚨 FATAL ERROR: A critical process has crashed repeatedly. Nyxora is executing an emergency auto-shutdown to protect your system. Please check the server logs.";
63
+ await fetch(`https://api.telegram.org/bot${tgToken}/sendMessage`, {
64
+ method: 'POST',
65
+ headers: { 'Content-Type': 'application/json' },
66
+ body: JSON.stringify({ chat_id: tgChatId, text: alertText })
67
+ }).catch(() => { });
68
+ }
69
+ }
70
+ }
71
+ catch (e) { }
72
+ process.exit(1);
73
+ return;
74
+ }
75
+ console.log(`[Launcher] Restarting ${name} in 3 seconds... (Attempt ${crashCount}/5)`);
76
+ setTimeout(startProcess, 3000);
77
+ });
78
+ };
79
+ startProcess();
80
+ return {
81
+ kill: () => {
82
+ isShuttingDown = true;
83
+ if (child && !child.killed && child.pid) {
84
+ try {
85
+ process.kill(child.pid, 'SIGTERM');
86
+ }
87
+ catch (e) { }
88
+ }
89
+ }
90
+ };
91
+ };
92
+ console.log('[Launcher] Starting Monorepo Services...');
93
+ const socketPath = env.SIGNER_SOCKET_PATH;
94
+ if (fs_1.default.existsSync(socketPath)) {
95
+ console.log(`[Launcher] Removing stale unix socket at ${socketPath}`);
96
+ fs_1.default.unlinkSync(socketPath);
97
+ }
98
+ const children = [];
99
+ const isCompiled = __filename.endsWith('.js');
100
+ const ext = isCompiled ? '.js' : '.ts';
101
+ const cmd = isCompiled ? 'node' : 'npx';
102
+ const baseArgs = isCompiled ? [] : ['ts-node', '-T'];
103
+ const signerPath = path_1.default.join(__dirname, `packages/signer/src/server${ext}`);
104
+ const signer = spawnService('Signer', cmd, [...baseArgs, signerPath], env);
105
+ children.push(signer);
106
+ setTimeout(() => {
107
+ const policyPath = path_1.default.join(__dirname, `packages/policy/src/server${ext}`);
108
+ const policy = spawnService('Policy', cmd, [...baseArgs, policyPath], env);
109
+ children.push(policy);
110
+ setTimeout(() => {
111
+ const corePath = path_1.default.join(__dirname, `packages/core/src/gateway/cli${ext}`);
112
+ const args = process.argv.slice(2);
113
+ const core = spawnService('Core', cmd, [...baseArgs, corePath, ...args], env, true);
114
+ children.push(core);
115
+ }, 1000);
116
+ }, 1000);
117
+ // Ensure all child processes are killed when launcher exits
118
+ let isCleaningUp = false;
119
+ const cleanup = () => {
120
+ if (isCleaningUp)
121
+ return;
122
+ isCleaningUp = true;
123
+ console.log('\n[Launcher] Shutting down all services...');
124
+ children.forEach(c => c.kill());
125
+ // Give them a moment to cleanup
126
+ setTimeout(() => {
127
+ try {
128
+ require('child_process').execSync('pkill -f ts-node');
129
+ }
130
+ catch (e) { }
131
+ process.exit(0);
132
+ }, 1000);
133
+ };
134
+ process.on('SIGINT', cleanup);
135
+ process.on('SIGTERM', cleanup);
136
+ process.on('SIGTERM', cleanup);
137
+ process.on('exit', cleanup);
@@ -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");
@@ -39,7 +40,6 @@ const pluginManager_1 = require("../system/pluginManager");
39
40
  const paths_1 = require("../config/paths");
40
41
  const picocolors_1 = __importDefault(require("picocolors"));
41
42
  exports.logger = new logger_1.Logger();
42
- let currentKeyIndex = 0;
43
43
  const PROVIDER_CONFIGS = {
44
44
  ollama: { baseURL: process.env.OLLAMA_BASE_URL ? `${process.env.OLLAMA_BASE_URL}/v1` : 'http://localhost:11434/v1', requiresApiKey: false },
45
45
  gemini: { baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai/', requiresApiKey: true },
@@ -58,27 +58,12 @@ async function getOpenAI() {
58
58
  let apiKey = 'local';
59
59
  if (providerConf.requiresApiKey) {
60
60
  apiKey = '';
61
- let configuredKeys = config.llm.api_keys;
62
- if (typeof configuredKeys === 'string') {
63
- configuredKeys = [configuredKeys];
64
- }
65
- if (Array.isArray(configuredKeys) && configuredKeys.length > 0) {
66
- const keys = configuredKeys.filter(k => typeof k === 'string' && k.trim() !== '');
67
- if (keys.length > 0) {
68
- currentKeyIndex = currentKeyIndex % keys.length;
69
- apiKey = keys[currentKeyIndex];
70
- console.log(`[LLM] Using rotated API Key (${currentKeyIndex + 1}/${keys.length}): ${apiKey.substring(0, 4)}...`);
71
- currentKeyIndex++;
72
- }
73
- }
61
+ const keyName = `${providerName}_key`;
62
+ apiKey = vaultKeys[keyName] || config.credentials?.[keyName] || '';
74
63
  if (!apiKey) {
75
- const fallbackKeyName = `${providerName}_key`;
76
- apiKey = vaultKeys[fallbackKeyName] || config.credentials?.[fallbackKeyName] || '';
77
- if (!apiKey) {
78
- throw new Error(`No API Key found for ${providerName}. Please run 'nyxora setup' to configure it.`);
79
- }
80
- console.log(`[LLM] Using API Key from secure vault`);
64
+ throw new Error(`[Security] No API Key found for ${providerName} in OS Keyring. Please run 'nyxora set-key ${providerName} <key>' or 'nyxora setup'.`);
81
65
  }
66
+ console.log(`[LLM] Using API Key securely unlocked from OS Keyring vault.`);
82
67
  }
83
68
  return new openai_1.OpenAI({
84
69
  baseURL: providerConf.baseURL,
@@ -207,7 +192,7 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
207
192
  const lowerInput = input.toLowerCase();
208
193
  const hasWeb3Keyword = /swap|transfer|price|token|crypto|bridge|wallet|balance|portfolio|buy|sell|send|receive|address|market|limit|mint|nft/i.test(lowerInput);
209
194
  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];
195
+ 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
196
  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
197
  const GOOGLE_TOOLS = [googleWorkspace_1.readGmailInboxToolDefinition, googleWorkspace_1.listCalendarEventsToolDefinition, googleWorkspace_1.appendRowToSheetsToolDefinition, googleWorkspace_1.readGoogleDocsToolDefinition, googleWorkspace_1.readGoogleFormResponsesToolDefinition];
213
198
  let activeTools = [];
@@ -251,9 +236,9 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
251
236
  let result = "";
252
237
  let args = {};
253
238
  const toolName = toolCall.function.name;
254
- console.log(picocolors_1.default.yellow(`[⚡ Eksekusi Tool] AI memanggil ${toolName}...`));
239
+ console.log(picocolors_1.default.yellow(`[⚡ Tool Execution] AI is calling ${toolName}...`));
255
240
  if (onProgress)
256
- onProgress(`_⚡ Menjalankan alat: ${toolName}..._`);
241
+ onProgress(`_⚡ Running tool: ${toolName}..._`);
257
242
  // Phase 1: LLM Output Validation (Anti-Halusinasi)
258
243
  try {
259
244
  args = JSON.parse(toolCall.function.arguments);
@@ -342,6 +327,10 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
342
327
  result = await (0, getMyAddress_1.getMyAddress)();
343
328
  break;
344
329
  }
330
+ case 'manage_custom_tokens': {
331
+ result = await (0, manageCustomTokens_1.executeManageCustomTokens)(args);
332
+ break;
333
+ }
345
334
  case 'create_limit_order': {
346
335
  if (config.permissions?.web3?.allow_swap === false) {
347
336
  result = `[Security Blocked] Runtime Permission Denied: Limit orders require swap permissions. Update config.yaml to allow.`;
@@ -434,15 +423,15 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
434
423
  }
435
424
  }
436
425
  if (result.includes('[Security Blocked]') || result.startsWith('Error:')) {
437
- console.log(picocolors_1.default.red(`[❌ Gagal] Tool ${toolName} mengembalikan error atau diblokir.`));
426
+ console.log(picocolors_1.default.red(`[❌ Failed] Tool ${toolName} returned an error or was blocked.`));
438
427
  }
439
428
  else {
440
- console.log(picocolors_1.default.green(`[✅ Sukses] Tool ${toolName} berhasil dieksekusi.`));
429
+ console.log(picocolors_1.default.green(`[✅ Success] Tool ${toolName} executed successfully.`));
441
430
  }
442
431
  }
443
432
  catch (toolError) {
444
433
  result = `Error executing ${toolName}: ${toolError.message}`;
445
- console.log(picocolors_1.default.red(`[❌ Error Crash] Eksekusi ${toolName} gagal total: ${toolError.message}`));
434
+ console.error(picocolors_1.default.red(`[❌ Error Crash] Execution of ${toolName} failed completely: ${toolError.message}`));
446
435
  }
447
436
  exports.logger.addEntry({
448
437
  role: 'tool',
@@ -11,38 +11,15 @@ const fs_1 = __importDefault(require("fs"));
11
11
  const yaml_1 = __importDefault(require("yaml"));
12
12
  const paths_1 = require("./paths");
13
13
  async function loadApiKeys() {
14
- const vaultPath = (0, paths_1.getPath)('api_vault.key');
15
- try {
16
- const { Entry } = require('@napi-rs/keyring');
17
- const entry = new Entry('nyxora', 'api_keys');
18
- const data = await entry.getPassword();
19
- if (data)
20
- return JSON.parse(data);
21
- }
22
- catch (e) {
23
- if (fs_1.default.existsSync(vaultPath)) {
24
- try {
25
- const file = fs_1.default.readFileSync(vaultPath, 'utf8');
26
- return JSON.parse(file);
27
- }
28
- catch (err) { }
29
- }
30
- }
31
- return {};
14
+ const config = loadConfig();
15
+ return config.credentials || {};
32
16
  }
33
17
  async function saveApiKeys(newKeys) {
34
- const vaultPath = (0, paths_1.getPath)('api_vault.key');
35
- const currentKeys = await loadApiKeys();
36
- const mergedKeys = { ...currentKeys, ...newKeys };
37
- const dataString = JSON.stringify(mergedKeys);
38
- try {
39
- const { Entry } = require('@napi-rs/keyring');
40
- const entry = new Entry('nyxora', 'api_keys');
41
- await entry.setPassword(dataString);
42
- }
43
- catch (e) {
44
- fs_1.default.writeFileSync(vaultPath, dataString, { mode: 0o600 });
45
- }
18
+ const config = loadConfig();
19
+ if (!config.credentials)
20
+ config.credentials = {};
21
+ config.credentials = { ...config.credentials, ...newKeys };
22
+ saveConfig(config);
46
23
  }
47
24
  function loadConfig() {
48
25
  const configPath = (0, paths_1.getPath)('config.yaml');
@@ -74,21 +51,6 @@ function loadConfig() {
74
51
  console.error('[Config] Failed to auto-migrate config file', e);
75
52
  }
76
53
  }
77
- // Auto-migrate from config.yaml to Keyring/Vault
78
- if (parsed.credentials && Object.keys(parsed.credentials).length > 0) {
79
- const credsToMigrate = { ...parsed.credentials };
80
- saveApiKeys(credsToMigrate).then(() => {
81
- console.log('[Config] Auto-migrated API keys to secure vault.');
82
- delete parsed.credentials;
83
- try {
84
- const yamlStr = yaml_1.default.stringify(parsed);
85
- fs_1.default.writeFileSync(configPath, yamlStr, 'utf8');
86
- }
87
- catch (e) { }
88
- }).catch(e => {
89
- console.error('[Config] Failed to migrate API keys to secure vault', e);
90
- });
91
- }
92
54
  return {
93
55
  agent: parsed.agent || { name: 'Nyxora-Default', description: 'Your Personal Web3 Assistant.', default_chain: 'base', default_router: 'auto', default_slippage: 0.5 },
94
56
  llm: parsed.llm || {
@@ -30,6 +30,44 @@ function getAppDir() {
30
30
  }
31
31
  return process.cwd();
32
32
  }
33
+ function ensureDir(dir) {
34
+ if (!fs_1.default.existsSync(dir)) {
35
+ fs_1.default.mkdirSync(dir, { recursive: true });
36
+ }
37
+ }
33
38
  function getPath(filename) {
34
- return path_1.default.join(getAppDir(), filename);
39
+ const baseDir = getAppDir();
40
+ // Determine subdirectory based on filename
41
+ let subDir = '';
42
+ const lowerFile = filename.toLowerCase();
43
+ if (lowerFile.endsWith('.db') || lowerFile.endsWith('.db-wal') || lowerFile.endsWith('.db-shm') || lowerFile.endsWith('.json') && lowerFile.includes('memory') || lowerFile.endsWith('.md') || lowerFile.includes('orders')) {
44
+ subDir = 'data';
45
+ }
46
+ else if (lowerFile.endsWith('.yaml') || lowerFile.includes('config') || lowerFile.includes('skills') || lowerFile.includes('whitelist') || lowerFile.includes('tokens')) {
47
+ subDir = 'config';
48
+ }
49
+ else if (lowerFile.endsWith('.token') || lowerFile.includes('vault') || lowerFile.includes('credentials')) {
50
+ subDir = 'auth';
51
+ }
52
+ else if (lowerFile.endsWith('.log') || lowerFile.includes('pid')) {
53
+ subDir = 'run';
54
+ }
55
+ const targetDir = path_1.default.join(baseDir, subDir);
56
+ ensureDir(targetDir);
57
+ const fullPath = path_1.default.join(targetDir, filename);
58
+ // AUTO-MIGRATION: If file exists in root but not in subdir, move it
59
+ const oldRootPath = path_1.default.join(baseDir, filename);
60
+ if (subDir !== '') {
61
+ if (fs_1.default.existsSync(oldRootPath) && !fs_1.default.existsSync(fullPath)) {
62
+ try {
63
+ fs_1.default.renameSync(oldRootPath, fullPath);
64
+ console.log(`[Migration] Moved ${filename} to ${subDir}/ directory.`);
65
+ }
66
+ catch (err) {
67
+ console.warn(`[Migration] Failed to move ${filename} to ${subDir}/`, err);
68
+ return oldRootPath; // fallback to root if migration fails
69
+ }
70
+ }
71
+ }
72
+ return fullPath;
35
73
  }