nyxora 26.6.7 → 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 (47) hide show
  1. package/.dockerignore +9 -0
  2. package/CHANGELOG.md +15 -3
  3. package/README.md +14 -7
  4. package/SECURITY.md +3 -1
  5. package/bin/nyxora.mjs +3 -3
  6. package/dist/launcher.js +137 -0
  7. package/dist/packages/core/src/agent/reasoning.js +4 -20
  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 +5 -5
  11. package/dist/packages/core/src/gateway/doctor.js +3 -5
  12. package/dist/packages/core/src/gateway/googleAuthModule.js +13 -32
  13. package/dist/packages/core/src/gateway/legalGenerator.js +88 -0
  14. package/dist/packages/core/src/gateway/server.js +65 -0
  15. package/dist/packages/core/src/gateway/setup.js +1 -2
  16. package/dist/packages/core/src/utils/state.js +2 -3
  17. package/dist/packages/core/src/web3/skills/manageCustomTokens.js +1 -2
  18. package/dist/packages/policy/src/server.js +7 -0
  19. package/dist/packages/signer/src/server.js +7 -0
  20. package/package.json +4 -3
  21. package/packages/core/package.json +1 -1
  22. package/packages/core/src/agent/reasoning.ts +6 -23
  23. package/packages/core/src/config/parser.ts +7 -41
  24. package/packages/core/src/config/paths.ts +42 -1
  25. package/packages/core/src/gateway/cli.ts +21 -21
  26. package/packages/core/src/gateway/doctor.ts +4 -5
  27. package/packages/core/src/gateway/googleAuthModule.ts +12 -27
  28. package/packages/core/src/gateway/legalGenerator.ts +85 -0
  29. package/packages/core/src/gateway/server.ts +77 -0
  30. package/packages/core/src/gateway/setup.ts +2 -2
  31. package/packages/core/src/utils/state.ts +3 -1
  32. package/packages/core/src/web3/skills/manageCustomTokens.ts +2 -2
  33. package/packages/dashboard/dist/assets/{index-whRRjJKK.js → index-D50q-_33.js} +67 -62
  34. package/packages/dashboard/dist/index.html +1 -1
  35. package/packages/dashboard/package.json +1 -1
  36. package/packages/mcp-server/package.json +1 -1
  37. package/packages/policy/package.json +1 -1
  38. package/packages/policy/src/server.ts +9 -0
  39. package/packages/signer/package.json +1 -1
  40. package/packages/signer/src/server.ts +9 -0
  41. package/DOCKER.md +0 -68
  42. package/Dockerfile +0 -43
  43. package/assets/architecture.png +0 -0
  44. package/assets/architecture.svg +0 -1
  45. package/assets/raw-diagram.png +0 -0
  46. package/assets/security-flow.png +0 -0
  47. package/launcher.ts +0 -98
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,7 +5,20 @@ 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
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
9
22
  ### Enterprise Features & Web3 Enhancements
10
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.
11
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.
@@ -121,8 +134,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
121
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.
122
135
 
123
136
  ### AI Engine Optimizations
124
- - **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.
125
- - **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
+
126
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.
127
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.
128
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
@@ -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
 
@@ -93,16 +93,23 @@ Alternatively, you can install it manually on any operating system using NPM:
93
93
  npm install -g nyxora@latest
94
94
  ```
95
95
 
96
- # 2. Run the Interactive Setup Wizard (API Keys, Wallet, Telegram)
96
+ ### 2. Run the Interactive Setup Wizard (API Keys, Wallet, Telegram)
97
+ ```bash
97
98
  nyxora setup
99
+ ```
98
100
 
99
- # 3. Start the Nyxora background daemon
101
+ ### 3. Start the Nyxora background daemon
102
+ ```bash
100
103
  nyxora start
104
+ ```
101
105
 
102
- # 4. Open the Web Dashboard
106
+ ### 4. Open the Web Dashboard
107
+ ```bash
103
108
  nyxora dashboard
109
+ ```
104
110
 
105
- # 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
106
113
  nyxora clear --force
107
114
  ```
108
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.
@@ -135,7 +142,7 @@ npm start
135
142
 
136
143
  For complete technical deep-dives into our Cryptographic Architecture, please visit our official VitePress Documentation Site!
137
144
 
138
- > **🔗 [Read the Full Nyxora Documentation Here](https://nyxoraAI.github.io/Nyxora/)**
145
+ > **🔗 [Read the Full Nyxora Documentation Here](https://nyxoraai.github.io/Nyxora/)**
139
146
 
140
147
  *(Includes guides on Secure Wallet Imports, Architecture Blueprints, Troubleshooting, and Custom Skill Development).*
141
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 });
@@ -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);
@@ -40,7 +40,6 @@ const pluginManager_1 = require("../system/pluginManager");
40
40
  const paths_1 = require("../config/paths");
41
41
  const picocolors_1 = __importDefault(require("picocolors"));
42
42
  exports.logger = new logger_1.Logger();
43
- let currentKeyIndex = 0;
44
43
  const PROVIDER_CONFIGS = {
45
44
  ollama: { baseURL: process.env.OLLAMA_BASE_URL ? `${process.env.OLLAMA_BASE_URL}/v1` : 'http://localhost:11434/v1', requiresApiKey: false },
46
45
  gemini: { baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai/', requiresApiKey: true },
@@ -59,27 +58,12 @@ async function getOpenAI() {
59
58
  let apiKey = 'local';
60
59
  if (providerConf.requiresApiKey) {
61
60
  apiKey = '';
62
- let configuredKeys = config.llm.api_keys;
63
- if (typeof configuredKeys === 'string') {
64
- configuredKeys = [configuredKeys];
65
- }
66
- if (Array.isArray(configuredKeys) && configuredKeys.length > 0) {
67
- const keys = configuredKeys.filter(k => typeof k === 'string' && k.trim() !== '');
68
- if (keys.length > 0) {
69
- currentKeyIndex = currentKeyIndex % keys.length;
70
- apiKey = keys[currentKeyIndex];
71
- console.log(`[LLM] Using rotated API Key (${currentKeyIndex + 1}/${keys.length}): ${apiKey.substring(0, 4)}...`);
72
- currentKeyIndex++;
73
- }
74
- }
61
+ const keyName = `${providerName}_key`;
62
+ apiKey = vaultKeys[keyName] || config.credentials?.[keyName] || '';
75
63
  if (!apiKey) {
76
- const fallbackKeyName = `${providerName}_key`;
77
- apiKey = vaultKeys[fallbackKeyName] || config.credentials?.[fallbackKeyName] || '';
78
- if (!apiKey) {
79
- throw new Error(`No API Key found for ${providerName}. Please run 'nyxora setup' to configure it.`);
80
- }
81
- 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'.`);
82
65
  }
66
+ console.log(`[LLM] Using API Key securely unlocked from OS Keyring vault.`);
83
67
  }
84
68
  return new openai_1.OpenAI({
85
69
  baseURL: providerConf.baseURL,
@@ -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
  }
@@ -106,7 +106,7 @@ async function main() {
106
106
  };
107
107
  const mappedKey = keyMap[provider.toLowerCase()] || `${provider.toLowerCase()}_key`;
108
108
  await (0, parser_1.saveApiKeys)({ [mappedKey]: key });
109
- console.log(picocolors_1.default.green(`✅ API Key for ${provider} saved securely to vault.`));
109
+ console.log(picocolors_1.default.green(`✅ API Key for ${provider} saved successfully.`));
110
110
  process.exit(0);
111
111
  }
112
112
  // Check for wallet command
@@ -146,9 +146,9 @@ async function main() {
146
146
  // 2. Setup boilerplate files if in global mode and they don't exist
147
147
  let isFirstBoot = false;
148
148
  if (isGlobalMode) {
149
- const globalConfigPath = path_1.default.join(appDir, 'config.yaml');
150
- const globalUserMdPath = path_1.default.join(appDir, 'user.md');
151
- const globalIdentityMdPath = path_1.default.join(appDir, 'IDENTITY.md');
149
+ const globalConfigPath = (0, paths_1.getPath)('config.yaml');
150
+ const globalUserMdPath = (0, paths_1.getPath)('user.md');
151
+ const globalIdentityMdPath = (0, paths_1.getPath)('IDENTITY.md');
152
152
  // Copy default config.yaml
153
153
  if (!fs_1.default.existsSync(globalConfigPath)) {
154
154
  isFirstBoot = true;
@@ -157,7 +157,7 @@ async function main() {
157
157
  fs_1.default.copyFileSync(exampleConfigPath, globalConfigPath);
158
158
  }
159
159
  else {
160
- fs_1.default.writeFileSync(globalConfigPath, 'agent:\n name: Nyxora-Agent\n default_chain: base\nllm:\n provider: openai\n model: gpt-4o-mini\n temperature: 0.2\n api_keys: []\nmemory:\n type: file\n path: memory.json\n');
160
+ fs_1.default.writeFileSync(globalConfigPath, 'agent:\n name: Nyxora-Agent\n default_chain: base\nllm:\n provider: openai\n model: gpt-4\n temperature: 0.7\nmemory:\n type: file\n path: memory.json\n');
161
161
  }
162
162
  }
163
163
  if (!fs_1.default.existsSync(globalUserMdPath)) {
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.runDoctor = runDoctor;
7
7
  const fs_1 = __importDefault(require("fs"));
8
- const path_1 = __importDefault(require("path"));
9
8
  const net_1 = __importDefault(require("net"));
10
9
  const picocolors_1 = __importDefault(require("picocolors"));
11
10
  const paths_1 = require("../config/paths");
@@ -29,8 +28,7 @@ async function runDoctor() {
29
28
  const majorVer = parseInt(nodeVer.split('.')[0], 10);
30
29
  printStatus(`Node.js Version (${nodeVer})`, majorVer >= 22, `Please upgrade to Node.js v22 or higher.`);
31
30
  // 2. Check App Directory & Config
32
- const appDir = (0, paths_1.getAppDir)();
33
- const configPath = path_1.default.join(appDir, 'config.yaml');
31
+ const configPath = (0, paths_1.getPath)('config.yaml');
34
32
  let configOk = false;
35
33
  let configErr = '';
36
34
  if (fs_1.default.existsSync(configPath)) {
@@ -47,7 +45,7 @@ async function runDoctor() {
47
45
  }
48
46
  printStatus(`Configuration File`, configOk, configErr);
49
47
  // 3. Check SQLite DB access
50
- const dbPath = path_1.default.join(appDir, 'memory.db');
48
+ const dbPath = (0, paths_1.getPath)('memory.db');
51
49
  let dbOk = false;
52
50
  let dbErr = '';
53
51
  try {
@@ -96,7 +94,7 @@ async function runDoctor() {
96
94
  const port3000Free = await checkPort(3000);
97
95
  const port3001Free = await checkPort(3001);
98
96
  let isDaemonRunning = false;
99
- const pidPath = path_1.default.join(appDir, 'daemon.pid');
97
+ const pidPath = (0, paths_1.getPath)('daemon.pid');
100
98
  if (fs_1.default.existsSync(pidPath)) {
101
99
  try {
102
100
  const pidStr = fs_1.default.readFileSync(pidPath, 'utf8').trim();
@@ -10,10 +10,9 @@ exports.isAuthenticated = isAuthenticated;
10
10
  exports.getAccessToken = getAccessToken;
11
11
  exports.logoutGoogle = logoutGoogle;
12
12
  const fs_1 = __importDefault(require("fs"));
13
- const path_1 = __importDefault(require("path"));
14
13
  const paths_1 = require("../config/paths");
15
- const CREDENTIALS_PATH = path_1.default.join((0, paths_1.getAppDir)(), 'google-credentials.json');
16
- const FALLBACK_TOKEN_PATH = path_1.default.join((0, paths_1.getAppDir)(), 'google-tokens.json');
14
+ const CREDENTIALS_PATH = (0, paths_1.getPath)('google-credentials.json');
15
+ const FALLBACK_TOKEN_PATH = (0, paths_1.getPath)('google-tokens.json');
17
16
  const SCOPES = [
18
17
  'https://www.googleapis.com/auth/gmail.readonly',
19
18
  'https://www.googleapis.com/auth/calendar.readonly',
@@ -154,37 +153,19 @@ async function logoutGoogle() {
154
153
  }
155
154
  // ---- Secure Storage for Refresh Token ----
156
155
  async function saveRefreshToken(token) {
157
- try {
158
- const { Entry } = require('@napi-rs/keyring');
159
- const entry = new Entry('nyxora', 'google_refresh_token');
160
- await entry.setPassword(token);
161
- console.log('[Google Auth] Refresh token saved securely to OS Keyring.');
162
- }
163
- catch (error) {
164
- console.warn('[Google Auth] Keyring failed, falling back to local tokens.json');
165
- fs_1.default.writeFileSync(FALLBACK_TOKEN_PATH, JSON.stringify({ refresh_token: token }), { mode: 0o600 });
166
- }
156
+ fs_1.default.writeFileSync(FALLBACK_TOKEN_PATH, JSON.stringify({ refresh_token: token }), { mode: 0o600 });
157
+ console.log('[Google Auth] Refresh token saved to local tokens.json');
167
158
  }
168
159
  async function getRefreshToken() {
169
- try {
170
- const { Entry } = require('@napi-rs/keyring');
171
- const entry = new Entry('nyxora', 'google_refresh_token');
172
- const password = await entry.getPassword();
173
- if (password)
174
- return password;
175
- }
176
- catch (error) {
177
- // Fallback to file
178
- if (fs_1.default.existsSync(FALLBACK_TOKEN_PATH)) {
179
- try {
180
- const content = fs_1.default.readFileSync(FALLBACK_TOKEN_PATH, 'utf8');
181
- const parsed = JSON.parse(content);
182
- if (parsed.refresh_token)
183
- return parsed.refresh_token;
184
- }
185
- catch (e) {
186
- return null;
187
- }
160
+ if (fs_1.default.existsSync(FALLBACK_TOKEN_PATH)) {
161
+ try {
162
+ const content = fs_1.default.readFileSync(FALLBACK_TOKEN_PATH, 'utf8');
163
+ const parsed = JSON.parse(content);
164
+ if (parsed.refresh_token)
165
+ return parsed.refresh_token;
166
+ }
167
+ catch (e) {
168
+ return null;
188
169
  }
189
170
  }
190
171
  return null;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generatePrivacyPolicyHtml = generatePrivacyPolicyHtml;
4
+ exports.generateTosHtml = generateTosHtml;
5
+ function generatePrivacyPolicyHtml() {
6
+ return `
7
+ <!DOCTYPE html>
8
+ <html lang="en">
9
+ <head>
10
+ <meta charset="UTF-8">
11
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
12
+ <title>Nyxora Local Agent - Privacy Policy</title>
13
+ <style>
14
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #eceff4; background-color: #2e3440; max-width: 800px; margin: 0 auto; padding: 40px 20px; }
15
+ h1, h2 { color: #88c0d0; }
16
+ a { color: #81a1c1; text-decoration: none; }
17
+ a:hover { text-decoration: underline; }
18
+ .container { background-color: #3b4252; padding: 32px; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
19
+ </style>
20
+ </head>
21
+ <body>
22
+ <div class="container">
23
+ <h1>Privacy Policy for Nyxora Local Agent</h1>
24
+ <p><em>Last Updated: June 2026</em></p>
25
+
26
+ <h2>1. Introduction</h2>
27
+ <p>Nyxora ("we", "our", or "us") is a self-hosted, local-first automation software. This Privacy Policy explains how your information is handled when you use the Nyxora software and its Google Workspace integrations.</p>
28
+
29
+ <h2>2. Local-First Data Processing</h2>
30
+ <p>Nyxora is designed with absolute privacy in mind. Unlike traditional cloud services, <strong>Nyxora does not operate a centralized backend server that collects your data.</strong> All data fetched from your connected Google accounts (including Gmail, Google Drive, and Google Sheets) is processed and stored strictly on your local machine.</p>
31
+
32
+ <h2>3. Google Workspace APIs Usage</h2>
33
+ <p>Nyxora's use and transfer of information received from Google APIs to any other app will adhere to <a href="https://developers.google.com/terms/api-services-user-data-policy" target="_blank">Google API Services User Data Policy</a>, including the Limited Use requirements.</p>
34
+ <ul>
35
+ <li><strong>What we access:</strong> We access your emails (<code>gmail.readonly</code>) and spreadsheets (<code>spreadsheets</code>, <code>drive.file</code>) solely to perform automations you explicitly request (e.g., summarizing emails or logging data to sheets).</li>
36
+ <li><strong>What we store:</strong> OAuth tokens are stored locally on your device in secure encrypted vaults. We do not store your data on any external servers.</li>
37
+ <li><strong>Third-Party Sharing:</strong> We <strong>do not</strong> share, sell, or transmit your Google user data to any third-party services, advertisers, or external LLM training databases.</li>
38
+ </ul>
39
+
40
+ <h2>4. Data Retention and Deletion</h2>
41
+ <p>Because your data is stored locally on your own hardware, you maintain 100% control over it. You can delete all your data, including Google OAuth tokens, at any time by running the <code>nyxora clear --force</code> command or by deleting the <code>~/.nyxora</code> directory on your device.</p>
42
+
43
+ <h2>5. Contact Us</h2>
44
+ <p>If you have any questions regarding this Privacy Policy, please refer to the <a href="https://github.com/nyxoraAI/Nyxora">Nyxora GitHub Repository</a>.</p>
45
+ </div>
46
+ </body>
47
+ </html>
48
+ `;
49
+ }
50
+ function generateTosHtml() {
51
+ return `
52
+ <!DOCTYPE html>
53
+ <html lang="en">
54
+ <head>
55
+ <meta charset="UTF-8">
56
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
57
+ <title>Nyxora Local Agent - Terms of Service</title>
58
+ <style>
59
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #eceff4; background-color: #2e3440; max-width: 800px; margin: 0 auto; padding: 40px 20px; }
60
+ h1, h2 { color: #88c0d0; }
61
+ a { color: #81a1c1; text-decoration: none; }
62
+ .container { background-color: #3b4252; padding: 32px; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
63
+ </style>
64
+ </head>
65
+ <body>
66
+ <div class="container">
67
+ <h1>Terms of Service for Nyxora Local Agent</h1>
68
+ <p><em>Last Updated: June 2026</em></p>
69
+
70
+ <h2>1. Acceptance of Terms</h2>
71
+ <p>By downloading, installing, and using the Nyxora software, you agree to be bound by these Terms of Service. If you do not agree to these terms, do not use the software.</p>
72
+
73
+ <h2>2. Nature of the Software (Self-Hosted)</h2>
74
+ <p>Nyxora is provided as a self-hosted, local-first software application. You are solely responsible for the hardware, environment, and security of the device where Nyxora is installed.</p>
75
+
76
+ <h2>3. Google Workspace Integrations</h2>
77
+ <p>Nyxora allows you to connect your personal Google Workspace accounts to automate tasks. By utilizing these features, you authorize your local instance of Nyxora to access your data. You acknowledge that Nyxora does not control Google's services and is not liable for any changes, outages, or data loss occurring on Google's end.</p>
78
+
79
+ <h2>4. Limitation of Liability</h2>
80
+ <p>Nyxora is provided "AS IS" without any warranties of any kind. Under no circumstances shall the creators of Nyxora be liable for any direct, indirect, incidental, or consequential damages (including but not limited to financial losses, data loss, or unauthorized access) arising from your use of the software.</p>
81
+
82
+ <h2>5. User Responsibilities</h2>
83
+ <p>You are strictly responsible for maintaining the security of your local machine, your private keys, and your API credentials. You agree not to use Nyxora for any illegal activities or to violate the terms of service of any integrated third-party platforms.</p>
84
+ </div>
85
+ </body>
86
+ </html>
87
+ `;
88
+ }