nyxora 1.6.2 → 1.6.4
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/CHANGELOG.md +23 -0
- package/README.md +22 -12
- package/SECURITY.md +25 -21
- package/assets/raw-diagram.png +0 -0
- package/assets/security-flow.png +0 -0
- package/bin/nyxora.mjs +236 -0
- package/launcher.js +8 -3
- package/launcher.ts +28 -1
- package/package.json +11 -8
- package/packages/core/package.json +4 -4
- package/packages/core/src/agent/reasoning.ts +10 -8
- package/packages/core/src/config/parser.ts +2 -1
- package/packages/core/src/gateway/cli.ts +2 -64
- package/packages/core/src/gateway/server.ts +89 -8
- package/packages/core/src/gateway/setup-cli.ts +7 -0
- package/packages/core/src/gateway/setup.ts +52 -28
- package/packages/core/src/gateway/telegram.ts +147 -89
- package/packages/core/src/memory/logger.ts +83 -20
- package/packages/core/src/system/pluginManager.ts +48 -34
- package/packages/core/src/utils/state.ts +15 -2
- package/packages/core/src/web3/config.ts +18 -3
- package/packages/core/src/web3/skills/marketAnalysis.ts +43 -17
- package/packages/core/src/web3/skills/swapToken.ts +9 -1
- package/packages/dashboard/dist/assets/index-CfIids2e.js +170 -0
- package/packages/dashboard/dist/assets/index-POJM-7Fd.css +1 -0
- package/packages/dashboard/dist/favicon.svg +1 -1
- package/packages/dashboard/dist/index.html +2 -2
- package/packages/dashboard/package.json +7 -7
- package/packages/dashboard/public/favicon.svg +1 -1
- package/packages/dashboard/src/App.tsx +224 -167
- package/packages/dashboard/src/Settings.tsx +55 -0
- package/packages/dashboard/src/Skills.tsx +8 -1
- package/packages/dashboard/src/index.css +146 -35
- package/packages/policy/package.json +1 -1
- package/packages/policy/src/server.ts +21 -28
- package/packages/signer/package.json +1 -1
- package/packages/signer/src/server.ts +40 -13
- package/test-db.ts +3 -0
- package/bin/nyxora.js +0 -13
- package/packages/dashboard/dist/assets/index-BK4qmIy6.js +0 -200
- package/packages/dashboard/dist/assets/index-C1m4ohce.css +0 -1
- package/packages/dashboard/package-lock.json +0 -2748
- package/packages/dashboard/src/Memory.tsx +0 -110
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.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
|
+
## [1.6.4]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Node.js Native Database Engine**: Migrated the core `logger.ts` memory subsystem to use the built-in `node:sqlite` engine (Node 22+), maintaining ultra-fast 100% synchronous operations while dramatically reducing dependency bloat.
|
|
12
|
+
- **Next-Gen OS Keyring (N-API)**: Migrated `keytar` to `@napi-rs/keyring`. This replaces legacy C++ bindings with modern Rust/NAPI-RS, retaining identical OS-level security (Mac Keychain, Windows Credential Manager) while eliminating the `prebuild-install` deprecation warning and streamlining global installation.
|
|
13
|
+
|
|
14
|
+
### Removed
|
|
15
|
+
- `better-sqlite3` and `keytar` dependencies entirely removed from the monorepo architecture.
|
|
16
|
+
|
|
17
|
+
## [1.6.3]
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- Implemented **Zero-Click Multi-Session** for instantaneous chat creation and switching.
|
|
21
|
+
- Introduced **Smart Auto-Naming** for automatic contextual session titles.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- **Redesigned Sidebar Architecture**: enhanced utility-centric design, significantly reducing gaps for a compact, elegant look.
|
|
25
|
+
- Integrated **OS-Native Keyring**, replacing legacy AES-256-GCM and Master Password mechanics.
|
|
26
|
+
- Updated and cleaned up legacy cryptography references in VitePress guides and README.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- Resolved deeply-nested monorepo CI/CD deployment failures by isolating `package-lock.json` and mitigating peer-dependency conflicts.
|
|
30
|
+
|
|
8
31
|
## [1.4.5]
|
|
9
32
|
|
|
10
33
|
### Fixed
|
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Nyxora Agent 🤖
|
|
2
2
|
**Production-Grade Secure AI Execution Framework for Web3 Agents.**
|
|
3
3
|
|
|
4
|
-
[](https://github.com/perasyudha/Nyxora)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
[](#️-advanced-security-threat-model)
|
|
7
7
|
[](#️-advanced-security-threat-model)
|
|
8
8
|
[](#️-advanced-security-threat-model)
|
|
9
9
|
|
|
10
|
-
Nyxora (v1.6.
|
|
10
|
+
Nyxora (v1.6.3) 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 Glassmorphism UI dashboard and strict client-side key isolation.
|
|
11
11
|
|
|
12
12
|
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.
|
|
13
13
|
|
|
@@ -15,20 +15,23 @@ It operates under an institutional-grade **Cryptographically Bound Human-in-the-
|
|
|
15
15
|
|
|
16
16
|
## 🔥 Key Features
|
|
17
17
|
|
|
18
|
-
### Advanced Security Architecture (v1.6.
|
|
18
|
+
### Advanced Security Architecture (v1.6.3)
|
|
19
19
|
* **3-Tier IPC Architecture**: Nyxora is split into isolated processes: **Core** (LLM Runtime), **Policy Engine** (Guardrails on port 3001), and **Signer Vault** (Isolated Key Manager on Unix Sockets).
|
|
20
20
|
* **Cryptographically Bound Approval**: Policy changes and transactions requested by the AI are drafted as hashes (`sha256`). Approval via the UI requires a challenge nonce, preventing Man-in-the-Middle (MITM) attacks.
|
|
21
21
|
* **Immutable Policy Guardrails**: Transaction limits (e.g. `max_usd_per_tx`) are strictly enforced by the Policy Engine. The LLM has zero write-access to bypass these rules.
|
|
22
|
+
* **Plugin Sandbox VM**: Execute community-built external skills securely inside an airtight Node.js `vm` chamber with zero access to your file system or terminal processes.
|
|
22
23
|
|
|
23
24
|
### Core Operations & Web3 Execution
|
|
24
25
|
* **System Automation & Full OS Access**: Instruct the agent to read/write local files, run terminal commands, and browse the web natively.
|
|
25
26
|
* **Anti-Rugpull & Security Scanner**: Nyxora can scan smart contracts via GoPlus Labs to detect Honeypots, Hidden Taxes, and malicious proxy upgrades before you buy.
|
|
26
27
|
* **Automated Limit Orders**: Set natural language rules (e.g., "Sell my PEPE if price drops below $0.001"). Nyxora runs a background cron monitor and executes the swap while you sleep (Auto-Approve Bypass configured safely).
|
|
28
|
+
* **Cross-Chain Hybrid Market Scanner**: Real-time asset tracking combining CoinGecko global data with DexScreener on-chain metrics across Ethereum, Base, Solana, BSC, and more.
|
|
27
29
|
* **PNL & Portfolio Tracking**: The AI scans your wallets and multiplies balances by live DEX prices to give you real-time Net Worth estimations.
|
|
28
30
|
|
|
29
31
|
### AI & UI Customization
|
|
30
|
-
* **Multi-
|
|
31
|
-
* **
|
|
32
|
+
* **Zero-Click Multi-Session**: Instantly create isolated chat sessions with smart auto-naming triggered by your first prompt, exactly like ChatGPT.
|
|
33
|
+
* **Dynamic Trending Tokens**: Live top 5 crypto assets feed directly injected into the dashboard, completely clickable for instant AI market analysis.
|
|
34
|
+
* **Premium Utility-Centric UI**: A sleek, dark-themed dashboard built for high readability and professional Web3 execution, featuring Pseudo-Generative UI widgets (`<BalanceWidget>`, `<MarketWidget>`, `<SwapWidget>`).
|
|
32
35
|
* **Deep Personalization**: Feed the agent custom rules via `user.md` and define its core persona via `IDENTITY.md`.
|
|
33
36
|
|
|
34
37
|
---
|
|
@@ -42,7 +45,7 @@ The following diagram illustrates Nyxora's **3-Tier Monorepo Architecture**, sho
|
|
|
42
45
|
*Nyxora separates its duties into 3 independent layers for absolute security:*
|
|
43
46
|
1. **🧠 Core (The AI Brain)**: The intelligent assistant that strategizes and plans transactions, but **never** holds your funds.
|
|
44
47
|
2. **🛡️ Policy Engine (The Guard)**: The security guard that verifies the Brain's plans. If the AI attempts to send funds exceeding your set limits, this engine automatically blocks it.
|
|
45
|
-
3. **🔒 Signer Vault (The Safe)**: The offline vault where your Private Keys are securely locked. It only signs transactions after they pass all rigorous security checks.
|
|
48
|
+
3. **🔒 Signer Vault (The Safe)**: The offline vault where your Private Keys are securely locked natively in your OS Keyring (GNOME Keyring / macOS Keychain / Windows Credential Manager). It only signs transactions after they pass all rigorous security checks.
|
|
46
49
|
|
|
47
50
|
*(Note: Despite the multi-layered security process appearing lengthy, the internal system validation and cryptographic signing occurs in **milliseconds**, ensuring zero latency bottlenecks).*
|
|
48
51
|
|
|
@@ -63,12 +66,16 @@ The easiest and fastest way to use Nyxora is to install it globally via NPM. Thi
|
|
|
63
66
|
# 1. Install Nyxora globally
|
|
64
67
|
npm install -g nyxora@latest
|
|
65
68
|
|
|
66
|
-
# 2. Run the Interactive Setup Wizard (API Keys, Wallet,
|
|
69
|
+
# 2. Run the Interactive Setup Wizard (API Keys, Wallet, Telegram)
|
|
67
70
|
nyxora setup
|
|
68
71
|
|
|
69
|
-
# 3. Start the Nyxora
|
|
70
|
-
nyxora
|
|
72
|
+
# 3. Start the Nyxora background daemon
|
|
73
|
+
nyxora start
|
|
74
|
+
|
|
75
|
+
# 4. Open the Web Dashboard
|
|
76
|
+
nyxora dashboard
|
|
71
77
|
```
|
|
78
|
+
> **⚠️ 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.
|
|
72
79
|
|
|
73
80
|
### Local Development (From Source)
|
|
74
81
|
If you wish to modify the code or run from source, you can use the Monorepo architecture.
|
|
@@ -83,13 +90,16 @@ npm install
|
|
|
83
90
|
# 2. Build the Dashboard UI
|
|
84
91
|
npm run build
|
|
85
92
|
|
|
86
|
-
# 3. Interactive Setup Wizard (API Keys, Wallet,
|
|
93
|
+
# 3. Interactive Setup Wizard (API Keys, Wallet, Telegram)
|
|
87
94
|
npm run setup
|
|
88
95
|
|
|
89
|
-
# 4. Start the Nyxora
|
|
96
|
+
# 4. Start the Nyxora background daemon
|
|
90
97
|
npm start
|
|
98
|
+
|
|
99
|
+
# 5. Open the Web Dashboard
|
|
100
|
+
npm run dashboard
|
|
91
101
|
```
|
|
92
|
-
|
|
102
|
+
> **⚠️ IMPORTANT:** Whenever you re-run `npm run setup` or manually edit the config files, you **must restart the daemon** by running `npm run restart` for the changes to take effect.
|
|
93
103
|
|
|
94
104
|
---
|
|
95
105
|
|
package/SECURITY.md
CHANGED
|
@@ -19,21 +19,7 @@ To achieve this, Nyxora uses a **3-Tier Monorepo IPC (Inter-Process Communicatio
|
|
|
19
19
|
### The Security Flow
|
|
20
20
|
When the LLM processes a transaction instruction (e.g., swapping tokens), the lifecycle is as follows:
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
[1] User (Dashboard/Telegram) ──> Sends prompt "Please swap ETH to USDC"
|
|
24
|
-
│
|
|
25
|
-
[2] Core Runtime (LLM) <── Understands context & generates JSON Tool Call
|
|
26
|
-
│
|
|
27
|
-
[3] Policy Engine <── Receives payload, evaluates rules & limits
|
|
28
|
-
│
|
|
29
|
-
[4] User (Dashboard/Telegram) <── (If Auth required) Requests Approval (Challenge Nonce)
|
|
30
|
-
│
|
|
31
|
-
[5] Signer Vault <── Receives certified instruction from Policy
|
|
32
|
-
│
|
|
33
|
-
[6] Blockchain RPC <── Signer Vault signs & broadcasts to RPC
|
|
34
|
-
│
|
|
35
|
-
[7] User (Dashboard/Telegram) <── Success status returned to chat interface
|
|
36
|
-
```
|
|
22
|
+

|
|
37
23
|
|
|
38
24
|
The diagram above illustrates the lifecycle of a transaction initiated from the user interface. Due to Nyxora's layered architecture, the LLM in the Core Runtime acts solely as a planner generating transaction data structures. The actual cryptographic execution and signing are strictly locked and fully controlled by the Policy Engine and Signer Vault after you provide authorization.
|
|
39
25
|
|
|
@@ -41,7 +27,22 @@ The diagram above illustrates the lifecycle of a transaction initiated from the
|
|
|
41
27
|
|
|
42
28
|
---
|
|
43
29
|
|
|
44
|
-
## 2.
|
|
30
|
+
## 2. OS-Native Keyring Integration
|
|
31
|
+
|
|
32
|
+
Nyxora completely eliminates the need for manual "Master Passwords" or custom AES-GCM keystore files by delegating Private Key encryption directly to your Operating System's trusted Keyring.
|
|
33
|
+
|
|
34
|
+
* **Linux:** Integrates seamlessly with `Secret Service API / GNOME Keyring` via `libsecret`.
|
|
35
|
+
* **macOS:** Utilizes the native `Keychain Access`.
|
|
36
|
+
* **Windows:** Uses `Windows Credential Manager`.
|
|
37
|
+
|
|
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
|
+
|
|
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.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 3. Advanced Cryptographic Security
|
|
45
46
|
|
|
46
47
|
To prevent advanced Man-in-the-Middle (MITM) attacks and UI compromises, Nyxora is adopting the following cryptographic standards:
|
|
47
48
|
|
|
@@ -68,13 +69,16 @@ Every approval UI prompt utilizes a **Single-Use Challenge Nonce** with a strict
|
|
|
68
69
|
|
|
69
70
|
---
|
|
70
71
|
|
|
71
|
-
##
|
|
72
|
+
## 4. Plugin Sandboxing (Node.js VM Isolation)
|
|
73
|
+
|
|
74
|
+
Community plugins and custom skills are NEVER executed directly at the OS level. Instead, Nyxora creates an airtight **Virtual Machine (VM) Sandbox** in memory.
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
+
To prevent Supply Chain Attacks, the sandbox **permanently blacklists** critical native modules:
|
|
77
|
+
* `fs` (File System): Plugins cannot read or steal local keystore files.
|
|
78
|
+
* `child_process`: Plugins cannot spawn silent background terminals or malicious `curl | bash` supply chain payloads.
|
|
79
|
+
* `os`, `net`, `cluster`: Blocked to prevent network-level exploitation.
|
|
76
80
|
|
|
77
|
-
##
|
|
81
|
+
## 5. Reporting Vulnerabilities
|
|
78
82
|
|
|
79
83
|
If you discover a vulnerability in the Nyxora architecture, please DO NOT open a public issue.
|
|
80
84
|
Instead, email the core maintainer directly at **ainyxor@gmail.com**.
|
|
Binary file
|
|
Binary file
|
package/bin/nyxora.mjs
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
const projectRoot = path.join(__dirname, '..');
|
|
12
|
+
|
|
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');
|
|
17
|
+
|
|
18
|
+
if (!fs.existsSync(appDir)) {
|
|
19
|
+
fs.mkdirSync(appDir, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const command = process.argv[2];
|
|
23
|
+
|
|
24
|
+
function isDaemonRunning(pidStr) {
|
|
25
|
+
if (!pidStr) return false;
|
|
26
|
+
try {
|
|
27
|
+
process.kill(parseInt(pidStr, 10), 0);
|
|
28
|
+
return true;
|
|
29
|
+
} catch (e) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function getDaemonPid() {
|
|
35
|
+
if (fs.existsSync(pidFile)) {
|
|
36
|
+
const pidStr = fs.readFileSync(pidFile, 'utf8').trim();
|
|
37
|
+
if (isDaemonRunning(pidStr)) {
|
|
38
|
+
return parseInt(pidStr, 10);
|
|
39
|
+
} else {
|
|
40
|
+
fs.unlinkSync(pidFile);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function start() {
|
|
47
|
+
const pid = await getDaemonPid();
|
|
48
|
+
if (pid) {
|
|
49
|
+
console.log(`Nyxora is already running (PID: ${pid}). Use 'nyxora restart' to restart.`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log('Starting Nyxora daemon...');
|
|
54
|
+
|
|
55
|
+
const out = fs.openSync(logFile, 'a');
|
|
56
|
+
const err = fs.openSync(logFile, 'a');
|
|
57
|
+
|
|
58
|
+
// Clean up any stale sockets first
|
|
59
|
+
const socketPath = '/tmp/nyxora-signer.sock';
|
|
60
|
+
if (fs.existsSync(socketPath)) {
|
|
61
|
+
try {
|
|
62
|
+
fs.unlinkSync(socketPath);
|
|
63
|
+
} catch(e) {}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const child = spawn('npx', ['ts-node', '-T', 'launcher.ts'], {
|
|
67
|
+
cwd: projectRoot,
|
|
68
|
+
detached: true,
|
|
69
|
+
stdio: ['ignore', out, err],
|
|
70
|
+
env: { ...process.env, TS_NODE_CACHE: 'false' }
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
child.unref();
|
|
74
|
+
|
|
75
|
+
if (child.pid) {
|
|
76
|
+
fs.writeFileSync(pidFile, child.pid.toString());
|
|
77
|
+
console.log(`Nyxora daemon started (PID: ${child.pid}).`);
|
|
78
|
+
console.log(`Logs are available at: ${logFile}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function stop() {
|
|
83
|
+
const pid = await getDaemonPid();
|
|
84
|
+
if (pid) {
|
|
85
|
+
console.log(`Stopping Nyxora daemon (PID: ${pid})...`);
|
|
86
|
+
try {
|
|
87
|
+
process.kill(-pid, 'SIGTERM');
|
|
88
|
+
console.log('Nyxora stopped gracefully.');
|
|
89
|
+
} catch (e) {
|
|
90
|
+
console.error('Failed to kill process:', e.message);
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
fs.unlinkSync(pidFile);
|
|
94
|
+
} catch(e) {}
|
|
95
|
+
} else {
|
|
96
|
+
console.log('Nyxora is not running.');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function restart() {
|
|
101
|
+
await stop();
|
|
102
|
+
setTimeout(start, 1000);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function dashboard() {
|
|
106
|
+
const pid = await getDaemonPid();
|
|
107
|
+
if (!pid) {
|
|
108
|
+
console.log("Nyxora is not running. Start it first with 'nyxora start'.");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (fs.existsSync(tokenFile)) {
|
|
113
|
+
const token = fs.readFileSync(tokenFile, 'utf8').trim();
|
|
114
|
+
const url = `http://localhost:3000?token=${token}`;
|
|
115
|
+
console.log(`Opening Dashboard at ${url}`);
|
|
116
|
+
try {
|
|
117
|
+
const { default: open } = await import('open');
|
|
118
|
+
await open(url);
|
|
119
|
+
} catch(e) {
|
|
120
|
+
console.error("Failed to open browser. Please manually open:", url);
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
console.log("Dashboard token not found. The daemon might still be starting.");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function cleanLogs() {
|
|
128
|
+
if (fs.existsSync(logFile)) {
|
|
129
|
+
fs.writeFileSync(logFile, '');
|
|
130
|
+
console.log('Logs have been cleared.');
|
|
131
|
+
} else {
|
|
132
|
+
console.log('No logs found to clear.');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function autostart(action) {
|
|
137
|
+
const isLinux = process.platform === 'linux';
|
|
138
|
+
const isMac = process.platform === 'darwin';
|
|
139
|
+
const isWin = process.platform === 'win32';
|
|
140
|
+
|
|
141
|
+
if (isLinux) {
|
|
142
|
+
const autostartDir = path.join(os.homedir(), '.config', 'autostart');
|
|
143
|
+
const desktopFile = path.join(autostartDir, 'nyxora.desktop');
|
|
144
|
+
|
|
145
|
+
if (action === 'enable') {
|
|
146
|
+
if (!fs.existsSync(autostartDir)) fs.mkdirSync(autostartDir, { recursive: true });
|
|
147
|
+
const binPath = path.resolve(projectRoot, 'bin', 'nyxora.mjs');
|
|
148
|
+
const desktopContent = `[Desktop Entry]
|
|
149
|
+
Type=Application
|
|
150
|
+
Exec=node ${binPath} start
|
|
151
|
+
Hidden=false
|
|
152
|
+
NoDisplay=false
|
|
153
|
+
X-GNOME-Autostart-enabled=true
|
|
154
|
+
Name=Nyxora Daemon
|
|
155
|
+
Comment=Start Nyxora in the background
|
|
156
|
+
`;
|
|
157
|
+
fs.writeFileSync(desktopFile, desktopContent);
|
|
158
|
+
console.log('Autostart enabled for Linux (XDG Autostart).');
|
|
159
|
+
} else if (action === 'disable') {
|
|
160
|
+
if (fs.existsSync(desktopFile)) fs.unlinkSync(desktopFile);
|
|
161
|
+
console.log('Autostart disabled.');
|
|
162
|
+
} else {
|
|
163
|
+
console.log("Usage: nyxora autostart [enable|disable]");
|
|
164
|
+
}
|
|
165
|
+
} else if (isMac) {
|
|
166
|
+
const plistDir = path.join(os.homedir(), 'Library', 'LaunchAgents');
|
|
167
|
+
const plistFile = path.join(plistDir, 'com.nyxora.gateway.plist');
|
|
168
|
+
if (action === 'enable') {
|
|
169
|
+
if (!fs.existsSync(plistDir)) fs.mkdirSync(plistDir, { recursive: true });
|
|
170
|
+
const binPath = path.resolve(projectRoot, 'bin', 'nyxora.mjs');
|
|
171
|
+
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
172
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
173
|
+
<plist version="1.0">
|
|
174
|
+
<dict>
|
|
175
|
+
<key>Label</key>
|
|
176
|
+
<string>com.nyxora.gateway</string>
|
|
177
|
+
<key>ProgramArguments</key>
|
|
178
|
+
<array>
|
|
179
|
+
<string>${process.execPath}</string>
|
|
180
|
+
<string>${binPath}</string>
|
|
181
|
+
<string>start</string>
|
|
182
|
+
</array>
|
|
183
|
+
<key>RunAtLoad</key>
|
|
184
|
+
<true/>
|
|
185
|
+
</dict>
|
|
186
|
+
</plist>`;
|
|
187
|
+
fs.writeFileSync(plistFile, plistContent);
|
|
188
|
+
console.log('Autostart enabled for macOS (LaunchAgent).');
|
|
189
|
+
} else if (action === 'disable') {
|
|
190
|
+
if (fs.existsSync(plistFile)) fs.unlinkSync(plistFile);
|
|
191
|
+
console.log('Autostart disabled.');
|
|
192
|
+
}
|
|
193
|
+
} else if (isWin) {
|
|
194
|
+
console.log("For Windows, please use Task Scheduler or add a shortcut to the Startup folder.");
|
|
195
|
+
} else {
|
|
196
|
+
console.log("Unsupported OS for autostart script.");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function setup() {
|
|
201
|
+
console.log("Running Nyxora Setup Wizard...");
|
|
202
|
+
const child = spawn('npx', ['ts-node', '-T', 'packages/core/src/gateway/setup-cli.ts'], {
|
|
203
|
+
cwd: projectRoot,
|
|
204
|
+
stdio: 'inherit',
|
|
205
|
+
env: { ...process.env, TS_NODE_CACHE: 'false' }
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
await new Promise(resolve => child.on('close', resolve));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function main() {
|
|
212
|
+
switch (command) {
|
|
213
|
+
case 'setup': await setup(); break;
|
|
214
|
+
case 'start': await start(); break;
|
|
215
|
+
case 'stop': await stop(); break;
|
|
216
|
+
case 'restart': await restart(); break;
|
|
217
|
+
case 'dashboard': await dashboard(); break;
|
|
218
|
+
case 'clean-logs': await cleanLogs(); break;
|
|
219
|
+
case 'autostart': await autostart(process.argv[3]); break;
|
|
220
|
+
default:
|
|
221
|
+
console.log(`
|
|
222
|
+
Nyxora CLI Manager
|
|
223
|
+
Usage: nyxora <command>
|
|
224
|
+
|
|
225
|
+
Commands:
|
|
226
|
+
start Start the Nyxora background daemon
|
|
227
|
+
stop Stop the running daemon
|
|
228
|
+
restart Restart the daemon
|
|
229
|
+
dashboard Open the dashboard in your browser
|
|
230
|
+
clean-logs Clear the daemon logs
|
|
231
|
+
autostart Enable/disable autostart on boot (usage: nyxora autostart enable)
|
|
232
|
+
`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
main().catch(console.error);
|
package/launcher.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const child_process_1 = require("child_process");
|
|
7
7
|
const crypto_1 = __importDefault(require("crypto"));
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
9
10
|
const INTERNAL_AUTH_TOKEN = crypto_1.default.randomBytes(64).toString('hex');
|
|
10
11
|
console.log(`[Launcher] Generated Internal Auth Token: ${INTERNAL_AUTH_TOKEN.substring(0, 8)}...`);
|
|
11
12
|
const env = {
|
|
@@ -34,10 +35,14 @@ if (fs_1.default.existsSync(socketPath)) {
|
|
|
34
35
|
console.log(`[Launcher] Removing stale unix socket at ${socketPath}`);
|
|
35
36
|
fs_1.default.unlinkSync(socketPath);
|
|
36
37
|
}
|
|
37
|
-
const
|
|
38
|
+
const signerPath = path_1.default.join(__dirname, 'packages/signer/src/server.ts');
|
|
39
|
+
const signer = spawnService('Signer', 'npx', ['ts-node', '-T', signerPath], env);
|
|
38
40
|
setTimeout(() => {
|
|
39
|
-
const
|
|
41
|
+
const policyPath = path_1.default.join(__dirname, 'packages/policy/src/server.ts');
|
|
42
|
+
const policy = spawnService('Policy', 'npx', ['ts-node', '-T', policyPath], env);
|
|
40
43
|
setTimeout(() => {
|
|
41
|
-
const
|
|
44
|
+
const corePath = path_1.default.join(__dirname, 'packages/core/src/gateway/cli.ts');
|
|
45
|
+
const args = process.argv.slice(2);
|
|
46
|
+
const core = spawnService('Core', 'npx', ['ts-node', '-T', corePath, ...args], env, true);
|
|
42
47
|
}, 1000);
|
|
43
48
|
}, 1000);
|
package/launcher.ts
CHANGED
|
@@ -9,7 +9,8 @@ console.log(`[Launcher] Generated Internal Auth Token: ${INTERNAL_AUTH_TOKEN.sub
|
|
|
9
9
|
const env = {
|
|
10
10
|
...process.env,
|
|
11
11
|
INTERNAL_AUTH_TOKEN,
|
|
12
|
-
SIGNER_SOCKET_PATH: '/tmp/nyxora-signer.sock'
|
|
12
|
+
SIGNER_SOCKET_PATH: '/tmp/nyxora-signer.sock',
|
|
13
|
+
TS_NODE_CACHE: 'false'
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
const spawnService = (name: string, command: string, args: string[], env: any, inheritStdio: boolean = false) => {
|
|
@@ -40,16 +41,42 @@ if (fs.existsSync(socketPath)) {
|
|
|
40
41
|
fs.unlinkSync(socketPath);
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
const children: ReturnType<typeof spawn>[] = [];
|
|
45
|
+
|
|
43
46
|
const signerPath = path.join(__dirname, 'packages/signer/src/server.ts');
|
|
44
47
|
const signer = spawnService('Signer', 'npx', ['ts-node', '-T', signerPath], env);
|
|
48
|
+
children.push(signer);
|
|
45
49
|
|
|
46
50
|
setTimeout(() => {
|
|
47
51
|
const policyPath = path.join(__dirname, 'packages/policy/src/server.ts');
|
|
48
52
|
const policy = spawnService('Policy', 'npx', ['ts-node', '-T', policyPath], env);
|
|
53
|
+
children.push(policy);
|
|
49
54
|
|
|
50
55
|
setTimeout(() => {
|
|
51
56
|
const corePath = path.join(__dirname, 'packages/core/src/gateway/cli.ts');
|
|
52
57
|
const args = process.argv.slice(2);
|
|
53
58
|
const core = spawnService('Core', 'npx', ['ts-node', '-T', corePath, ...args], env, true);
|
|
59
|
+
children.push(core);
|
|
54
60
|
}, 1000);
|
|
55
61
|
}, 1000);
|
|
62
|
+
|
|
63
|
+
// Ensure all child processes are killed when launcher exits
|
|
64
|
+
const cleanup = () => {
|
|
65
|
+
console.log('\n[Launcher] Shutting down all services...');
|
|
66
|
+
children.forEach(child => {
|
|
67
|
+
if (!child.killed && child.pid) {
|
|
68
|
+
try {
|
|
69
|
+
process.kill(child.pid, 'SIGKILL');
|
|
70
|
+
} catch (e) {}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
// Kill any stray ts-node processes left behind by npx
|
|
74
|
+
try {
|
|
75
|
+
require('child_process').execSync('pkill -f ts-node');
|
|
76
|
+
} catch (e) {}
|
|
77
|
+
process.exit(0);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
process.on('SIGINT', cleanup);
|
|
81
|
+
process.on('SIGTERM', cleanup);
|
|
82
|
+
process.on('exit', cleanup);
|
package/package.json
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nyxora",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.4",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"workspaces": [
|
|
5
6
|
"packages/*"
|
|
6
7
|
],
|
|
8
|
+
"bin": {
|
|
9
|
+
"nyxora": "bin/nyxora.mjs"
|
|
10
|
+
},
|
|
7
11
|
"scripts": {
|
|
8
|
-
"start": "
|
|
12
|
+
"start": "node ./bin/nyxora.mjs start",
|
|
13
|
+
"stop": "node ./bin/nyxora.mjs stop",
|
|
14
|
+
"restart": "node ./bin/nyxora.mjs restart",
|
|
15
|
+
"dashboard": "node ./bin/nyxora.mjs dashboard",
|
|
16
|
+
"setup": "node ./bin/nyxora.mjs setup",
|
|
9
17
|
"build": "npm run build --workspace=dashboard",
|
|
10
|
-
"setup": "ts-node -T packages/core/src/gateway/cli.ts setup",
|
|
11
18
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
19
|
},
|
|
13
20
|
"dependencies": {
|
|
14
21
|
"@clack/prompts": "^1.4.0",
|
|
15
|
-
"better-sqlite3": "^12.10.0",
|
|
16
22
|
"concurrently": "^9.2.1",
|
|
17
23
|
"cors": "^2.8.6",
|
|
18
24
|
"dotenv": "^17.4.2",
|
|
@@ -20,7 +26,7 @@
|
|
|
20
26
|
"express-rate-limit": "^7.5.0",
|
|
21
27
|
"helmet": "^8.0.0",
|
|
22
28
|
"jsonwebtoken": "^9.0.2",
|
|
23
|
-
"
|
|
29
|
+
"@napi-rs/keyring": "^1.3.0",
|
|
24
30
|
"open": "^11.0.0",
|
|
25
31
|
"openai": "^6.39.0",
|
|
26
32
|
"picocolors": "^1.1.1",
|
|
@@ -34,8 +40,5 @@
|
|
|
34
40
|
"@types/jsonwebtoken": "^9.0.5",
|
|
35
41
|
"@types/node": "^25.9.1",
|
|
36
42
|
"vitepress": "^1.6.4"
|
|
37
|
-
},
|
|
38
|
-
"bin": {
|
|
39
|
-
"nyxora": "./bin/nyxora.js"
|
|
40
43
|
}
|
|
41
44
|
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nyxora/core",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.4",
|
|
4
4
|
"private": true,
|
|
5
5
|
"main": "src/gateway/server.ts",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@clack/prompts": "^1.4.0",
|
|
8
|
-
"better-sqlite3": "^12.10.0",
|
|
9
8
|
"cors": "^2.8.6",
|
|
10
9
|
"express": "^5.2.1",
|
|
11
|
-
"helmet": "^8.0.0",
|
|
12
10
|
"express-rate-limit": "^7.5.0",
|
|
13
|
-
"
|
|
11
|
+
"helmet": "^8.0.0",
|
|
12
|
+
"isolated-vm": "^6.1.2",
|
|
14
13
|
"open": "^11.0.0",
|
|
15
14
|
"openai": "^6.39.0",
|
|
15
|
+
"telegraf": "^4.16.3",
|
|
16
16
|
"yaml": "^2.9.0"
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -181,12 +181,12 @@ If the user doesn't specify a chain, default to: ${config.agent.default_chain}.`
|
|
|
181
181
|
return basePrompt;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
export async function processUserInput(input: string, role: 'user' | 'system' = 'user', onProgress?: (msg: string) => void): Promise<string> {
|
|
184
|
+
export async function processUserInput(input: string, role: 'user' | 'system' = 'user', onProgress?: (msg: string) => void, sessionId?: string): Promise<string> {
|
|
185
185
|
const config = loadConfig();
|
|
186
186
|
// Add input to memory
|
|
187
|
-
logger.addEntry({ role, content: input });
|
|
187
|
+
logger.addEntry({ role, content: input }, sessionId);
|
|
188
188
|
|
|
189
|
-
const history = logger.getHistory();
|
|
189
|
+
const history = logger.getHistory(sessionId);
|
|
190
190
|
|
|
191
191
|
// Format messages for OpenAI
|
|
192
192
|
const messages: any[] = [
|
|
@@ -258,7 +258,7 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
|
|
|
258
258
|
role: 'assistant',
|
|
259
259
|
content: responseMessage.content || "",
|
|
260
260
|
tool_calls: responseMessage.tool_calls,
|
|
261
|
-
});
|
|
261
|
+
}, sessionId);
|
|
262
262
|
|
|
263
263
|
// Check if the model wants to call a tool
|
|
264
264
|
if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {
|
|
@@ -422,13 +422,13 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
|
|
|
422
422
|
tool_call_id: toolCall.id,
|
|
423
423
|
name: toolName,
|
|
424
424
|
content: result,
|
|
425
|
-
});
|
|
425
|
+
}, sessionId);
|
|
426
426
|
}
|
|
427
427
|
|
|
428
428
|
// Second call to get the final answer after tool execution
|
|
429
429
|
const secondMessages = [
|
|
430
430
|
{ role: 'system', content: getSystemPrompt() },
|
|
431
|
-
...logger.getHistory()
|
|
431
|
+
...logger.getHistory(sessionId)
|
|
432
432
|
.filter(m => !(m.role === 'tool' && !m.tool_call_id))
|
|
433
433
|
.map(m => {
|
|
434
434
|
let role = m.role;
|
|
@@ -454,13 +454,15 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
|
|
|
454
454
|
Tracker.addEvent('llm.final_response', { provider: config.llm.provider });
|
|
455
455
|
|
|
456
456
|
const finalContent = secondResponse.choices[0].message.content || "";
|
|
457
|
-
logger.addEntry({ role: 'assistant', content: finalContent });
|
|
457
|
+
logger.addEntry({ role: 'assistant', content: finalContent }, sessionId);
|
|
458
458
|
return finalContent;
|
|
459
459
|
}
|
|
460
460
|
|
|
461
461
|
return responseMessage.content || "No response generated.";
|
|
462
462
|
} catch (error: any) {
|
|
463
463
|
console.error("LLM Error:", error);
|
|
464
|
-
|
|
464
|
+
const errorMsg = '⚠️ All models are temporarily rate-limited. Please try again in a few minutes.';
|
|
465
|
+
logger.addEntry({ role: 'assistant', content: errorMsg }, sessionId);
|
|
466
|
+
return errorMsg;
|
|
465
467
|
}
|
|
466
468
|
}
|
|
@@ -24,12 +24,13 @@ export interface NyxoraConfig {
|
|
|
24
24
|
path: string;
|
|
25
25
|
};
|
|
26
26
|
web3?: {
|
|
27
|
-
rpc_urls?: Record<string, string>;
|
|
27
|
+
rpc_urls?: Record<string, string | string[]>;
|
|
28
28
|
};
|
|
29
29
|
integrations?: {
|
|
30
30
|
telegram?: {
|
|
31
31
|
enabled: boolean;
|
|
32
32
|
bot_token?: string;
|
|
33
|
+
authorized_chat_id?: number;
|
|
33
34
|
};
|
|
34
35
|
};
|
|
35
36
|
permissions?: {
|