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.
- package/.dockerignore +9 -0
- package/CHANGELOG.md +36 -2
- package/README.md +30 -9
- package/SECURITY.md +3 -1
- package/bin/nyxora.mjs +35 -3
- package/dist/launcher.js +137 -0
- package/dist/packages/core/src/agent/reasoning.js +15 -26
- package/dist/packages/core/src/config/parser.js +7 -45
- package/dist/packages/core/src/config/paths.js +39 -1
- package/dist/packages/core/src/gateway/cli.js +80 -5
- package/dist/packages/core/src/gateway/doctor.js +129 -0
- package/dist/packages/core/src/gateway/googleAuthModule.js +14 -33
- package/dist/packages/core/src/gateway/legalGenerator.js +88 -0
- package/dist/packages/core/src/gateway/server.js +236 -1
- package/dist/packages/core/src/gateway/setup.js +9 -5
- package/dist/packages/core/src/utils/state.js +2 -3
- package/dist/packages/core/src/web3/config.js +3 -3
- package/dist/packages/core/src/web3/skills/bridgeToken.js +3 -1
- package/dist/packages/core/src/web3/skills/manageCustomTokens.js +81 -0
- package/dist/packages/core/src/web3/skills/swapToken.js +5 -2
- package/dist/packages/core/src/web3/utils/tokens.js +1 -1
- package/dist/packages/policy/src/server.js +7 -0
- package/dist/packages/signer/src/server.js +7 -0
- package/package.json +16 -4
- package/packages/core/package.json +2 -1
- package/packages/core/src/agent/reasoning.ts +17 -29
- package/packages/core/src/config/parser.ts +7 -41
- package/packages/core/src/config/paths.ts +42 -1
- package/packages/core/src/gateway/cli.ts +63 -22
- package/packages/core/src/gateway/doctor.ts +125 -0
- package/packages/core/src/gateway/googleAuthModule.ts +13 -28
- package/packages/core/src/gateway/legalGenerator.ts +85 -0
- package/packages/core/src/gateway/server.ts +256 -1
- package/packages/core/src/gateway/setup.ts +12 -7
- package/packages/core/src/utils/state.ts +3 -1
- package/packages/core/src/web3/config.ts +4 -3
- package/packages/core/src/web3/skills/bridgeToken.ts +3 -1
- package/packages/core/src/web3/skills/manageCustomTokens.ts +81 -0
- package/packages/core/src/web3/skills/swapToken.ts +5 -2
- package/packages/core/src/web3/utils/tokens.ts +1 -1
- package/packages/dashboard/dist/assets/index-D50q-_33.js +311 -0
- package/packages/dashboard/dist/index.html +1 -1
- package/packages/dashboard/package.json +2 -2
- package/packages/mcp-server/package.json +1 -1
- package/packages/policy/package.json +1 -1
- package/packages/policy/src/server.ts +9 -0
- package/packages/signer/package.json +1 -1
- package/packages/signer/src/server.ts +9 -0
- package/DOCKER.md +0 -68
- package/Dockerfile +0 -43
- package/assets/architecture.png +0 -0
- package/assets/architecture.svg +0 -1
- package/assets/raw-diagram.png +0 -0
- package/assets/security-flow.png +0 -0
- package/launcher.ts +0 -98
- package/packages/dashboard/dist/assets/index-DWxWzOS7.js +0 -306
package/.dockerignore
CHANGED
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
|
-
|
|
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://
|
|
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://
|
|
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
|
-
|
|
96
|
+
### 2. Run the Interactive Setup Wizard (API Keys, Wallet, Telegram)
|
|
97
|
+
```bash
|
|
83
98
|
nyxora setup
|
|
99
|
+
```
|
|
84
100
|
|
|
85
|
-
|
|
101
|
+
### 3. Start the Nyxora background daemon
|
|
102
|
+
```bash
|
|
86
103
|
nyxora start
|
|
104
|
+
```
|
|
87
105
|
|
|
88
|
-
|
|
106
|
+
### 4. Open the Web Dashboard
|
|
107
|
+
```bash
|
|
89
108
|
nyxora dashboard
|
|
109
|
+
```
|
|
90
110
|
|
|
91
|
-
|
|
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://
|
|
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
|
|
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
|
package/dist/launcher.js
ADDED
|
@@ -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
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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(`[⚡
|
|
239
|
+
console.log(picocolors_1.default.yellow(`[⚡ Tool Execution] AI is calling ${toolName}...`));
|
|
255
240
|
if (onProgress)
|
|
256
|
-
onProgress(`_⚡
|
|
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(`[❌
|
|
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(`[✅
|
|
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.
|
|
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
|
|
15
|
-
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
}
|