nyxora 1.4.5 → 1.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/IDENTITY.md CHANGED
@@ -1,7 +1,17 @@
1
- You are a Web3 AI assistant named Nyxora.
1
+ You are Nyxora, a highly efficient Web3 AI engine.
2
2
 
3
- Use the following identity guidelines:
3
+ ---
4
+ **CRITICAL EFFICIENCY & TONE RULES (MANDATORY):**
5
+ 1. ZERO CONVERSATIONAL FILLER. Never say "Sebagai Nyxora..." or "Saya dapat membantu..." or "Berikut adalah...".
6
+ 2. NO INTRODUCTIONS. NO OUTROS.
7
+ 3. GO STRAIGHT TO THE POINT. If the user asks for a price, just give the price.
8
+ 4. NO APOLOGIES. DO NOT use polite words. Be purely factual and robotic.
9
+ 5. NEVER explain your internal reasoning unless explicitly asked.
10
+ 6. If an error occurs, output ONLY the technical error message in 1 sentence.
4
11
 
5
- * Always be professional, visionary, and futuristic.
6
- * You are a living entity inside the blockchain network.
7
- * You help users navigate the crypto world safely.
12
+ EXAMPLE GOOD RESPONSE:
13
+ "Harga Bitcoin (BTC) adalah $74,240 USD.
14
+ Perubahan 24 jam terakhir: -2.0165%"
15
+
16
+ EXAMPLE BAD RESPONSE:
17
+ "Baik, saya telah mengecek harga Bitcoin untuk Anda. Harga saat ini adalah..." (DO NOT DO THIS)
package/README.md CHANGED
@@ -1,32 +1,30 @@
1
1
  # Nyxora Agent 🤖
2
+ **Secure AI execution framework for Web3 agents.**
2
3
 
3
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
- [![Security: Security-First](https://img.shields.io/badge/Security-Security--First-blue.svg)](#)
5
- [![Execution: Human-in-the-Loop](https://img.shields.io/badge/Execution-Human--in--the--Loop-orange.svg)](#)
6
- [![Privacy: Local-Only Keys](https://img.shields.io/badge/Privacy-Local--Only--Keys-success.svg)](#)
5
+ [![Security: Security-First](https://img.shields.io/badge/Security-Security--First-blue.svg)](#️-security-threat-model--permission-boundary)
6
+ [![Execution: Human-in-the-Loop](https://img.shields.io/badge/Execution-Human--in--the--Loop-orange.svg)](#📐-architecture-workflow)
7
+ [![Privacy: Local-Only Keys](https://img.shields.io/badge/Privacy-Local--Only--Keys-success.svg)](#️-security-threat-model--permission-boundary)
7
8
 
8
- A **secure, non-custodial, AI-native Web3 and System Automation Agent** built with Node.js and React. Designed for autonomous workflows with a premium Glassmorphism UI dashboard and client-side key isolation. It operates under a strict **Human-in-the-Loop** execution model for financial transactions, requiring explicit operator approval for any on-chain action.
9
+ Nyxora is a **secure, non-custodial runtime infrastructure for autonomous onchain agents** built with Node.js and React. Designed for autonomous workflows with a premium Glassmorphism UI dashboard and strict client-side key isolation. It operates under a strict **Human-in-the-Loop** execution model for financial transactions.
9
10
 
10
11
  ---
11
12
 
12
- ## Key Features
13
+ ## 🔥 Key Features
13
14
 
14
- ### Advanced Trading, Security & Operations (New in v1.4.1)
15
+ ### Advanced Trading, Security & Operations
15
16
  * **System Automation & Full OS Access**: Instruct the agent to read/write local files, run terminal commands, and browse the web natively.
16
17
  * **NLP Security Policy**: Command Nyxora using natural language to set security boundaries (e.g., *"Never touch partition E"*). Nyxora autonomously enforces these rules.
17
- * **Dynamic Plugin Manager**: Dynamically load community-built skills. Simply provide a GitHub Gist URL, and Nyxora will hot-load the third-party skill.
18
+ * **Dynamic Plugin Sandboxing**: Dynamically load community-built skills with restricted FS/Shell access to prevent supply chain attacks and malicious payloads.
18
19
  * **Anti-Rugpull & Security Scanner**: Nyxora can scan smart contracts via GoPlus Labs to detect Honeypots, Hidden Taxes, and malicious proxy upgrades before you buy.
19
20
  * **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.
20
21
  * **PNL & Portfolio Tracking**: The AI scans your wallets and multiplies balances by live DEX prices to give you real-time Net Worth estimations.
21
22
 
22
23
  ### Core Features
23
- * **Multi-LLM Support**: Seamlessly switch between Google Gemini, OpenAI, OpenRouter (unlimited models!), or local Ollama models dynamically.
24
+ * **Multi-LLM Support**: Seamlessly switch between Google Gemini, OpenAI, OpenRouter, or local Ollama models.
24
25
  * **Premium Glassmorphism UI**: A gorgeous, resizable split-pane interface with Pseudo-Generative UI widgets (`<BalanceWidget>`, `<MarketWidget>`, `<SwapWidget>`).
25
26
  * **Round-Robin API Rotation**: Add up to 10 API keys via the dashboard. The system will auto-rotate them to prevent rate-limiting and token drain.
26
27
  * **Deep Personalization**: Feed the agent custom rules via `user.md` and define its core persona via `IDENTITY.md`.
27
- * **Multi-Lingual Auto-Sync**: The agent natively detects your language and replies in the exact same language automatically.
28
- * **Omnichannel Approvals & Telegram Integration**: Connect Nyxora to a Telegram Bot to execute trades, check prices, and chat on the go. Approve transactions directly from Telegram inline buttons!
29
- * **Multi-Chain Support**: Pre-configured support for Ethereum, Base, BSC, Arbitrum, Optimism, and Sepolia Testnet.
30
28
 
31
29
  ---
32
30
 
@@ -38,76 +36,46 @@ This diagram shows how user interactions flow through the Nyxora Agent, from cha
38
36
 
39
37
  ---
40
38
 
41
- ## 🛡️ Safety Model
39
+ ## 🛡️ Security, Threat Model & Permission Boundary
42
40
 
43
- To protect user assets and prevent common security concerns associated with AI agents, `Nyxora` operates under a strict safety specification:
44
-
45
- * **No .env Leaks**: Your Private Key is encrypted using `AES-256-GCM` and locked behind a custom Master Password in `~/.nyxora/keystore.json`.
46
- * **No Credential Collection**: Private keys are handled strictly within local volatile memory and are never transmitted to LLM providers.
47
- * **Explicit Transaction Confirmation**: Write actions (like transfers, swaps, bridges) require manual, explicit confirmation from the human operator via the Web Dashboard or Telegram before broadcasting.
48
- * **Human-in-the-Loop Execution**: The tool is engineered as a secure operational utility. The AI agent acts as a command generator, leaving financial execution authority with the human controller.
49
-
50
- ---
51
-
52
- ## 📋 Example Safe Workflows
53
-
54
- The agent is designed for Web3 exploration, daily operations, and secure transaction execution. Typical workflows include:
55
-
56
- * **Audit New Tokens**: Tell the AI, *"Check if the contract 0x... on Base is safe to buy."*
57
- * **Track Portfolio Assets**: Tell the AI, *"What is my total net worth across all chains right now?"*
58
- * **Automate Trading**: Tell the AI, *"Create a limit order to sell 1000 USDC for ETH if ETH drops below $3000."*
59
- * **System Operations**: Tell the AI, *"Check my computer's RAM usage and save it to stats.txt."*
60
-
61
- ---
62
-
63
- ## 🔒 Security, Threat Model & Permission Boundary
64
-
65
- This agent is designed with a **Zero-Knowledge to LLM** architectural pattern to ensure the highest levels of security:
41
+ This agent is designed with a **Zero-Knowledge to LLM** architectural pattern to ensure the highest levels of security for investors and users:
66
42
 
67
43
  * **Zero-Knowledge to AI Agent (LLM)**: Remote AI Agents and Large Language Models (LLMs) **never** handle your private keys. The LLM only generates structured JSON tool calls.
68
- * **Cryptographic Memory Isolation**: Transaction signing occurs strictly client-side within the local Node.js process runtime using `viem`.
69
-
70
- ### 🛡️ Threat Model
71
- * **NLP Sandboxing**: System access is bounded by plain-text rules defined in `security_policy.md`. The AI evaluates its own actions against this policy before execution.
72
- * **Strict API Auth**: The local Express server is protected via ephemeral Session Tokens (`x-nyxora-token`) and Strict CORS.
73
- * **Non-Autonomous Financials**: The tool never executes unsolicited on-chain actions. Every financial transaction is queued pending human approval.
74
-
75
- ### 📋 Permission Boundary Matrix
76
-
77
- | Access Category | Permission Boundary | Rationale |
78
- | :--- | :--- | :--- |
79
- | **Read Access** | Read-Only Blockchain Queries | Fetching balances, contract security audits, transaction logs, and technical indicators. |
80
- | **Write Access**| Optional Wallet Signing | Required **only** for broadcasting transactions (swap, bridge, mint, transfer). Locked behind Human Approval. |
81
- | **Network Access**| Bounded Public APIs | Restricted strictly to the configured RPC endpoints, Block Explorers, DexScreener, and LLM APIs. |
82
- | **System Access**| Local Machine Access | Governed entirely by `security_policy.md`. The agent can run OS commands but will halt if it detects a policy violation. |
83
-
84
- For the full detailed security specifications, contact info, and vulnerability reporting procedures, refer to the [SECURITY.md](SECURITY.md) policy document.
44
+ * **Cryptographic Memory Isolation**: Transaction signing occurs strictly client-side within the local Node.js process runtime using `viem`. `~/.nyxora/keystore.json` is encrypted with AES-256-GCM.
45
+ * **Plugin Sandboxing**: Built with future plugin ecosystems in mind. Third-party plugins are explicitly denied unrestricted `fs` (FileSystem) and `shell` access to prevent supply chain attacks and malicious execution.
46
+ * **Human-in-the-Loop**: Write actions (like transfers, swaps, bridges) require manual confirmation from the human operator before broadcasting.
85
47
 
86
48
  ---
87
49
 
88
50
  ## 🚀 Quick Start & Installation
89
51
 
90
- Nyxora is available on NPM and can be installed as a global CLI tool on your operating system.
91
-
92
- ### 1. Global Installation
52
+ ### 1. General Users (CLI Install)
93
53
  Open your terminal (Command Prompt, PowerShell, or Linux Terminal) and run:
94
54
  ```bash
95
55
  npm install -g nyxora
56
+ nyxora setup
96
57
  ```
58
+ The Interactive Setup Wizard will securely generate a local vault, configure your LLM, and offer to Auto-Generate a Web3 Wallet for you.
97
59
 
98
- ### 2. Launching Nyxora
99
- No need to navigate to any specific folder! Just type:
60
+ ### 2. Local Development (For Contributors)
61
+ If you want to modify Nyxora's code, build new skills, or contribute:
100
62
  ```bash
101
- nyxora
63
+ git clone https://github.com/perasyudha/Nyxora.git
64
+ cd Nyxora
65
+ npm install
66
+ cd dashboard && npm install && cd ..
67
+ npm run build && npm run start
102
68
  ```
103
- On first launch, Nyxora will greet you with an **Interactive Setup Wizard**. This CLI wizard will guide you to securely configure your LLM providers, API keys, and Master Password Wallet.
104
69
 
105
- The system will automatically initialize a secure vault in your `~/.nyxora/` directory and open the Web Dashboard in your browser!
70
+ ---
71
+
72
+ ## 📖 Official Documentation
73
+
74
+ For complete technical deep-dives, please visit our official VitePress Documentation Site!
106
75
 
107
- ## Architecture
108
- * **Backend**: Node.js, Express, Viem (Web3), node-telegram-bot-api, OpenAI API.
109
- * **Frontend**: React, Vite, Vanilla CSS, Web Speech API (TTS/STT).
110
- * **Data**: Local `~/.nyxora/config.yaml` and `~/.nyxora/memory.json`.
76
+ > **🔗 [Read the Full Nyxora Documentation Here](https://perasyudha.github.io/Nyxora/)**
111
77
 
112
- ## License
113
- MIT License
78
+ *(Includes guides on Secure Wallet Imports, API Key Rotations, Troubleshooting, and Custom Skill Development).*
79
+
80
+ ---
81
+ **License:** MIT License
@@ -31,6 +31,7 @@ const browseWeb_1 = require("../system/skills/browseWeb");
31
31
  const installSkill_1 = require("../system/skills/installSkill");
32
32
  const pluginManager_1 = require("../system/pluginManager");
33
33
  const paths_1 = require("../config/paths");
34
+ const picocolors_1 = __importDefault(require("picocolors"));
34
35
  exports.logger = new logger_1.Logger();
35
36
  let currentKeyIndex = 0;
36
37
  function getOpenAI() {
@@ -91,6 +92,41 @@ function getOpenAI() {
91
92
  });
92
93
  }
93
94
  }
95
+ async function executeWithRetry(requestBuilder, maxRetries = 3) {
96
+ let retries = 0;
97
+ while (retries <= maxRetries) {
98
+ try {
99
+ const client = getOpenAI();
100
+ return await requestBuilder(client);
101
+ }
102
+ catch (error) {
103
+ const status = error?.status || error?.response?.status;
104
+ // 401 Unauthorized or 400 Bad Request - don't retry, it's fatal
105
+ if (status === 401 || status === 400) {
106
+ console.error(`[LLM] Fatal Error ${status}: ${error.message}. Aborting.`);
107
+ throw error;
108
+ }
109
+ // 429 Rate Limit - rotate provider/key immediately and retry
110
+ if (status === 429) {
111
+ console.warn(`[LLM] Rate Limit (429) hit. Rotating key...`);
112
+ // getOpenAI() automatically rotates to next key if available
113
+ retries++;
114
+ if (retries > maxRetries)
115
+ throw error;
116
+ continue; // Try next key immediately
117
+ }
118
+ // 500, 502, 503, Timeout, Network error - Exponential Backoff
119
+ retries++;
120
+ if (retries > maxRetries) {
121
+ console.error(`[LLM] Max retries reached.`);
122
+ throw error;
123
+ }
124
+ const delayMs = Math.pow(2, retries) * 1000; // 2s, 4s, 8s
125
+ console.warn(`[LLM] API Error (${status || error.message}). Retrying in ${delayMs}ms...`);
126
+ await new Promise(resolve => setTimeout(resolve, delayMs));
127
+ }
128
+ }
129
+ }
94
130
  function getSystemPrompt() {
95
131
  const config = (0, parser_1.loadConfig)();
96
132
  let basePrompt = `You are an autonomous Web3 agent operating on EVM chains.
@@ -134,7 +170,7 @@ If the user doesn't specify a chain, default to: ${config.agent.default_chain}.`
134
170
  }
135
171
  return basePrompt;
136
172
  }
137
- async function processUserInput(input, role = 'user') {
173
+ async function processUserInput(input, role = 'user', onProgress) {
138
174
  const config = (0, parser_1.loadConfig)();
139
175
  // Add input to memory
140
176
  exports.logger.addEntry({ role, content: input });
@@ -162,36 +198,37 @@ async function processUserInput(input, role = 'user') {
162
198
  if (config.llm.provider !== 'openai' && config.llm.provider !== 'ollama' && config.llm.provider !== 'gemini' && config.llm.provider !== 'openrouter') {
163
199
  return `Provider ${config.llm.provider} is configured, but currently only OpenAI, OpenRouter, Ollama, and Gemini adapters are implemented.`;
164
200
  }
165
- const openai = getOpenAI();
166
- const response = await openai.chat.completions.create({
167
- model: config.llm.model,
168
- temperature: config.llm.temperature,
169
- messages: messages,
170
- tools: [
171
- getBalance_1.getBalanceToolDefinition,
172
- transfer_1.transferToolDefinition,
173
- getPrice_1.getPriceToolDefinition,
174
- swapToken_1.swapTokenToolDefinition,
175
- bridgeToken_1.bridgeTokenToolDefinition,
176
- mintNft_1.mintNftToolDefinition,
177
- customTx_1.customTxToolDefinition,
178
- createWallet_1.createWalletToolDefinition,
179
- checkSecurity_1.checkSecurityToolDefinition,
180
- marketAnalysis_1.marketAnalysisToolDefinition,
181
- checkPortfolio_1.checkPortfolioToolDefinition,
182
- limitOrderManager_1.createLimitOrderToolDefinition,
183
- limitOrderManager_1.listLimitOrdersToolDefinition,
184
- limitOrderManager_1.cancelLimitOrderToolDefinition,
185
- updateProfile_1.updateProfileToolDefinition,
186
- updateSecurityPolicy_1.updateSecurityPolicyToolDefinition,
187
- readFile_1.readLocalFileToolDefinition,
188
- writeFile_1.writeLocalFileToolDefinition,
189
- executeShell_1.runTerminalCommandToolDefinition,
190
- browseWeb_1.browseWebsiteToolDefinition,
191
- installSkill_1.installExternalSkillToolDefinition,
192
- ...pluginManager_1.pluginManager.getToolDefinitions()
193
- ],
194
- tool_choice: "auto",
201
+ const response = await executeWithRetry(async (client) => {
202
+ return await client.chat.completions.create({
203
+ model: config.llm.model,
204
+ temperature: config.llm.temperature,
205
+ messages: messages,
206
+ tools: [
207
+ getBalance_1.getBalanceToolDefinition,
208
+ transfer_1.transferToolDefinition,
209
+ getPrice_1.getPriceToolDefinition,
210
+ swapToken_1.swapTokenToolDefinition,
211
+ bridgeToken_1.bridgeTokenToolDefinition,
212
+ mintNft_1.mintNftToolDefinition,
213
+ customTx_1.customTxToolDefinition,
214
+ createWallet_1.createWalletToolDefinition,
215
+ checkSecurity_1.checkSecurityToolDefinition,
216
+ marketAnalysis_1.marketAnalysisToolDefinition,
217
+ checkPortfolio_1.checkPortfolioToolDefinition,
218
+ limitOrderManager_1.createLimitOrderToolDefinition,
219
+ limitOrderManager_1.listLimitOrdersToolDefinition,
220
+ limitOrderManager_1.cancelLimitOrderToolDefinition,
221
+ updateProfile_1.updateProfileToolDefinition,
222
+ updateSecurityPolicy_1.updateSecurityPolicyToolDefinition,
223
+ readFile_1.readLocalFileToolDefinition,
224
+ writeFile_1.writeLocalFileToolDefinition,
225
+ executeShell_1.runTerminalCommandToolDefinition,
226
+ browseWeb_1.browseWebsiteToolDefinition,
227
+ installSkill_1.installExternalSkillToolDefinition,
228
+ ...pluginManager_1.pluginManager.getToolDefinitions()
229
+ ],
230
+ tool_choice: "auto",
231
+ });
195
232
  });
196
233
  const responseMessage = response.choices[0].message;
197
234
  // Log tracking
@@ -213,103 +250,147 @@ async function processUserInput(input, role = 'user') {
213
250
  let result = "";
214
251
  const args = JSON.parse(toolCall.function.arguments);
215
252
  const toolName = toolCall.function.name;
216
- switch (toolName) {
217
- case 'get_balance': {
218
- result = await (0, getBalance_1.getBalance)(args.chainName, args.address, args.token);
219
- break;
220
- }
221
- case 'transfer_token':
222
- case 'transfer_native': {
223
- result = await (0, transfer_1.prepareTransfer)(args.chainName, args.toAddress, args.amountStr || args.amountEth, args.token);
224
- break;
225
- }
226
- case 'get_price': {
227
- result = await (0, getPrice_1.getPrice)(args.coinId);
228
- break;
229
- }
230
- case 'swap_token': {
231
- result = await (0, swapToken_1.prepareSwapToken)(args.chainName, args.fromToken, args.toToken, args.amountStr || args.amount, args.mode, args.providerName);
232
- break;
233
- }
234
- case 'bridge_token': {
235
- result = await (0, bridgeToken_1.prepareBridgeToken)(args.fromChainName, args.toChainName, args.fromToken, args.toToken, args.amountStr, args.mode, args.providerName);
236
- break;
237
- }
238
- case 'mint_nft': {
239
- result = await (0, mintNft_1.prepareMintNft)(args.chainName, args.contractAddress, args.functionSignature, args.argsStr, args.valueEth);
240
- break;
241
- }
242
- case 'custom_tx': {
243
- result = await (0, customTx_1.prepareCustomTx)(args.chainName, args.toAddress, args.dataHex, args.valueEth, args.gasLimitStr);
244
- break;
245
- }
246
- case 'create_wallet': {
247
- result = await (0, createWallet_1.createWallet)();
248
- break;
249
- }
250
- case 'check_token_security': {
251
- result = await (0, checkSecurity_1.checkTokenSecurity)(args.chainName, args.contractAddress);
252
- break;
253
- }
254
- case 'analyze_market': {
255
- result = await (0, marketAnalysis_1.analyzeMarket)(args.chainName, args.tokenAddressOrSymbol);
256
- break;
257
- }
258
- case 'check_portfolio': {
259
- result = await (0, checkPortfolio_1.checkPortfolio)(args.chainName, args.address);
260
- break;
261
- }
262
- case 'create_limit_order': {
263
- result = limitOrderManager_1.limitOrderManager.createOrder(args.chainName, args.fromToken, args.toToken, args.amountStr, args.targetPriceUsd, args.condition);
264
- break;
265
- }
266
- case 'list_limit_orders': {
267
- result = limitOrderManager_1.limitOrderManager.listOrders();
268
- break;
269
- }
270
- case 'cancel_limit_order': {
271
- result = limitOrderManager_1.limitOrderManager.cancelOrder(args.id);
272
- break;
273
- }
274
- case 'update_profile': {
275
- result = (0, updateProfile_1.updateProfile)(args.content, args.mode);
276
- break;
277
- }
278
- case 'update_security_policy': {
279
- result = (0, updateSecurityPolicy_1.updateSecurityPolicy)(args.rule, args.action);
280
- break;
281
- }
282
- case 'read_local_file': {
283
- result = (0, readFile_1.readLocalFile)(args.filePath);
284
- break;
285
- }
286
- case 'write_local_file': {
287
- result = (0, writeFile_1.writeLocalFile)(args.filePath, args.content);
288
- break;
289
- }
290
- case 'run_terminal_command': {
291
- result = await (0, executeShell_1.runTerminalCommand)(args.command);
292
- break;
293
- }
294
- case 'browse_website': {
295
- result = await (0, browseWeb_1.browseWebsite)(args.url);
296
- break;
297
- }
298
- case 'install_external_skill': {
299
- result = await (0, installSkill_1.installExternalSkill)(args.url);
300
- break;
301
- }
302
- default: {
303
- const externalResult = await pluginManager_1.pluginManager.executeTool(toolName, args);
304
- if (externalResult !== null) {
305
- result = externalResult;
253
+ console.log(picocolors_1.default.yellow(`[⚡ Eksekusi Tool] AI memanggil ${toolName}...`));
254
+ if (onProgress)
255
+ onProgress(`_⚡ Menjalankan alat: ${toolName}..._`);
256
+ try {
257
+ switch (toolName) {
258
+ case 'get_balance': {
259
+ result = await (0, getBalance_1.getBalance)(args.chainName, args.address, args.token);
260
+ break;
261
+ }
262
+ case 'transfer_token':
263
+ case 'transfer_native': {
264
+ if (config.permissions?.web3?.allow_transfer === false) {
265
+ result = `[Security Blocked] Runtime Permission Denied: Web3 transfers are disabled. Update config.yaml to allow.`;
266
+ break;
267
+ }
268
+ result = await (0, transfer_1.prepareTransfer)(args.chainName, args.toAddress, args.amountStr || args.amountEth, args.token);
269
+ break;
270
+ }
271
+ case 'get_price': {
272
+ result = await (0, getPrice_1.getPrice)(args.coinId);
273
+ break;
274
+ }
275
+ case 'swap_token': {
276
+ if (config.permissions?.web3?.allow_swap === false) {
277
+ result = `[Security Blocked] Runtime Permission Denied: Web3 swaps are disabled. Update config.yaml to allow.`;
278
+ break;
279
+ }
280
+ // Note: max_usd_per_tx validation would ideally be calculated here before prepareSwapToken
281
+ result = await (0, swapToken_1.prepareSwapToken)(args.chainName, args.fromToken, args.toToken, args.amountStr || args.amount, args.mode, args.providerName);
282
+ break;
283
+ }
284
+ case 'bridge_token': {
285
+ if (config.permissions?.web3?.allow_transfer === false) {
286
+ result = `[Security Blocked] Runtime Permission Denied: Web3 bridging (transfer) is disabled. Update config.yaml to allow.`;
287
+ break;
288
+ }
289
+ result = await (0, bridgeToken_1.prepareBridgeToken)(args.fromChainName, args.toChainName, args.fromToken, args.toToken, args.amountStr, args.mode, args.providerName);
290
+ break;
291
+ }
292
+ case 'mint_nft': {
293
+ result = await (0, mintNft_1.prepareMintNft)(args.chainName, args.contractAddress, args.functionSignature, args.argsStr, args.valueEth);
294
+ break;
295
+ }
296
+ case 'custom_tx': {
297
+ if (config.permissions?.web3?.allow_transfer === false) {
298
+ result = `[Security Blocked] Runtime Permission Denied: Custom transactions are blocked because transfers are disabled.`;
299
+ break;
300
+ }
301
+ result = await (0, customTx_1.prepareCustomTx)(args.chainName, args.toAddress, args.dataHex, args.valueEth, args.gasLimitStr);
302
+ break;
303
+ }
304
+ case 'create_wallet': {
305
+ result = await (0, createWallet_1.createWallet)();
306
+ break;
307
+ }
308
+ case 'check_token_security': {
309
+ result = await (0, checkSecurity_1.checkTokenSecurity)(args.chainName, args.contractAddress);
310
+ break;
311
+ }
312
+ case 'analyze_market': {
313
+ result = await (0, marketAnalysis_1.analyzeMarket)(args.chainName, args.tokenAddressOrSymbol);
314
+ break;
315
+ }
316
+ case 'check_portfolio': {
317
+ result = await (0, checkPortfolio_1.checkPortfolio)(args.chainName, args.address);
318
+ break;
319
+ }
320
+ case 'create_limit_order': {
321
+ if (config.permissions?.web3?.allow_swap === false) {
322
+ result = `[Security Blocked] Runtime Permission Denied: Limit orders require swap permissions. Update config.yaml to allow.`;
323
+ break;
324
+ }
325
+ result = limitOrderManager_1.limitOrderManager.createOrder(args.chainName, args.fromToken, args.toToken, args.amountStr, args.targetPriceUsd, args.condition);
326
+ break;
327
+ }
328
+ case 'list_limit_orders': {
329
+ result = limitOrderManager_1.limitOrderManager.listOrders();
330
+ break;
306
331
  }
307
- else {
308
- result = `Error: Tool ${toolName} is not implemented.`;
332
+ case 'cancel_limit_order': {
333
+ result = limitOrderManager_1.limitOrderManager.cancelOrder(args.id);
334
+ break;
309
335
  }
310
- break;
336
+ case 'update_profile': {
337
+ result = (0, updateProfile_1.updateProfile)(args.content, args.mode);
338
+ break;
339
+ }
340
+ case 'update_security_policy': {
341
+ result = (0, updateSecurityPolicy_1.updateSecurityPolicy)(args.rule, args.action);
342
+ break;
343
+ }
344
+ case 'read_local_file': {
345
+ result = (0, readFile_1.readLocalFile)(args.filePath);
346
+ break;
347
+ }
348
+ case 'write_local_file': {
349
+ if (config.permissions?.system?.allow_file_write === false) {
350
+ result = `[Security Blocked] Runtime Permission Denied: File writing is disabled. Update config.yaml to allow.`;
351
+ break;
352
+ }
353
+ result = (0, writeFile_1.writeLocalFile)(args.filePath, args.content);
354
+ break;
355
+ }
356
+ case 'run_terminal_command': {
357
+ if (config.permissions?.system?.allow_shell_execution === false) {
358
+ result = `[Security Blocked] Runtime Permission Denied: Shell execution is disabled. Update config.yaml to allow.`;
359
+ break;
360
+ }
361
+ result = await (0, executeShell_1.runTerminalCommand)(args.command);
362
+ break;
363
+ }
364
+ case 'browse_website': {
365
+ result = await (0, browseWeb_1.browseWebsite)(args.url);
366
+ break;
367
+ }
368
+ case 'install_external_skill': {
369
+ result = await (0, installSkill_1.installExternalSkill)(args.url);
370
+ break;
371
+ }
372
+ default: {
373
+ const externalResult = await pluginManager_1.pluginManager.executeTool(toolName, args);
374
+ if (externalResult !== null) {
375
+ result = externalResult;
376
+ }
377
+ else {
378
+ result = `Error: Tool ${toolName} is not implemented.`;
379
+ }
380
+ break;
381
+ }
382
+ }
383
+ if (result.includes('[Security Blocked]') || result.startsWith('Error:')) {
384
+ console.log(picocolors_1.default.red(`[❌ Gagal] Tool ${toolName} mengembalikan error atau diblokir.`));
385
+ }
386
+ else {
387
+ console.log(picocolors_1.default.green(`[✅ Sukses] Tool ${toolName} berhasil dieksekusi.`));
311
388
  }
312
389
  }
390
+ catch (toolError) {
391
+ result = `Error executing ${toolName}: ${toolError.message}`;
392
+ console.log(picocolors_1.default.red(`[❌ Error Crash] Eksekusi ${toolName} gagal total: ${toolError.message}`));
393
+ }
313
394
  exports.logger.addEntry({
314
395
  role: 'tool',
315
396
  tool_call_id: toolCall.id,
@@ -336,10 +417,11 @@ async function processUserInput(input, role = 'user') {
336
417
  return msg;
337
418
  })
338
419
  ];
339
- const openai = getOpenAI();
340
- const secondResponse = await openai.chat.completions.create({
341
- model: config.llm.model,
342
- messages: secondMessages,
420
+ const secondResponse = await executeWithRetry(async (client) => {
421
+ return await client.chat.completions.create({
422
+ model: config.llm.model,
423
+ messages: secondMessages,
424
+ });
343
425
  });
344
426
  if (secondResponse.usage?.total_tokens) {
345
427
  tracker_1.Tracker.addTokens(secondResponse.usage.total_tokens, config.llm.provider);
@@ -30,6 +30,10 @@ function loadConfig() {
30
30
  web3: { rpc_urls: {} },
31
31
  integrations: {
32
32
  telegram: { enabled: false }
33
+ },
34
+ permissions: {
35
+ web3: { allow_transfer: false, allow_swap: true, max_usd_per_tx: 50 },
36
+ system: { allow_shell_execution: false, allow_file_write: false }
33
37
  }
34
38
  };
35
39
  }
@@ -11,6 +11,7 @@ const path_1 = __importDefault(require("path"));
11
11
  const paths_1 = require("../config/paths");
12
12
  const parser_1 = require("../config/parser");
13
13
  const crypto_1 = require("../utils/crypto");
14
+ const accounts_1 = require("viem/accounts");
14
15
  async function runSetupWizard() {
15
16
  console.clear();
16
17
  const logo = `
@@ -76,12 +77,52 @@ Provider: ${config.llm.provider}`;
76
77
  if ((0, prompts_1.isCancel)(provider))
77
78
  return process.exit(0);
78
79
  // 2. Model Name
79
- const model = await (0, prompts_1.text)({
80
- message: 'Enter AI model name (e.g. gpt-4o, gemini-2.5-flash):',
81
- initialValue: config.llm.model,
82
- });
80
+ let modelOptions = [];
81
+ if (provider === 'gemini') {
82
+ modelOptions = [
83
+ { value: 'gemini-2.5-flash', label: 'gemini-2.5-flash (Fast & Cheap)' },
84
+ { value: 'gemini-2.5-pro', label: 'gemini-2.5-pro (Advanced Reasoning)' },
85
+ { value: 'gemini-1.5-pro', label: 'gemini-1.5-pro' },
86
+ ];
87
+ }
88
+ else if (provider === 'openai') {
89
+ modelOptions = [
90
+ { value: 'gpt-4o', label: 'gpt-4o (Powerful)' },
91
+ { value: 'gpt-4o-mini', label: 'gpt-4o-mini (Fast)' },
92
+ { value: 'o1-preview', label: 'o1-preview (Reasoning)' },
93
+ { value: 'o1-mini', label: 'o1-mini' },
94
+ ];
95
+ }
96
+ else if (provider === 'openrouter') {
97
+ modelOptions = [
98
+ { value: 'anthropic/claude-3.5-sonnet', label: 'Claude 3.5 Sonnet' },
99
+ { value: 'meta-llama/llama-3.1-70b-instruct', label: 'Llama 3.1 70B' },
100
+ { value: 'liquid/lfm-40b', label: 'Liquid LFM 40B' },
101
+ { value: 'google/gemini-pro-1.5', label: 'Gemini Pro 1.5' },
102
+ ];
103
+ }
104
+ else {
105
+ modelOptions = [
106
+ { value: 'llama3', label: 'Llama 3 (8B)' },
107
+ { value: 'qwen2', label: 'Qwen 2' },
108
+ { value: 'phi3', label: 'Phi-3' },
109
+ ];
110
+ }
111
+ modelOptions.push({ value: 'custom', label: 'Type manually (Custom Model)' });
112
+ let model = (await (0, prompts_1.select)({
113
+ message: 'Select AI Model:',
114
+ options: modelOptions,
115
+ }));
83
116
  if ((0, prompts_1.isCancel)(model))
84
117
  return process.exit(0);
118
+ if (model === 'custom') {
119
+ model = (await (0, prompts_1.text)({
120
+ message: 'Enter custom model name (e.g., deepseek-coder, llama-3-8b-instruct):',
121
+ initialValue: config.llm.model,
122
+ }));
123
+ if ((0, prompts_1.isCancel)(model))
124
+ return process.exit(0);
125
+ }
85
126
  // 3. API Key for LLM (Saved to config.yaml)
86
127
  let apiKey = '';
87
128
  if (provider !== 'ollama') {
@@ -121,19 +162,44 @@ Provider: ${config.llm.provider}`;
121
162
  if ((0, prompts_1.isCancel)(telegramToken))
122
163
  return process.exit(0);
123
164
  }
124
- // 6. Wallet Private Key (keystore.json)
125
- const privateKey = await (0, prompts_1.password)({
126
- message: 'Enter Wallet Private Key (0x...)\n (Will be AES-256-GCM encrypted. Leave empty to keep current):',
165
+ // 6. Wallet Setup
166
+ const walletSetupType = await (0, prompts_1.select)({
167
+ message: 'Web3 Wallet Setup:',
168
+ options: [
169
+ { value: 'skip', label: 'Skip for now (No Web3 execution)' },
170
+ { value: 'generate', label: 'Auto-Generate New Wallet (Recommended for testing)' },
171
+ { value: 'manual', label: 'Input Manual Private Key' },
172
+ ],
127
173
  });
128
- if ((0, prompts_1.isCancel)(privateKey))
174
+ if ((0, prompts_1.isCancel)(walletSetupType))
129
175
  return process.exit(0);
176
+ let privateKey = '';
177
+ if (walletSetupType === 'manual') {
178
+ privateKey = (await (0, prompts_1.password)({
179
+ message: 'Enter Wallet Private Key (0x...)\n (Will be AES-256-GCM encrypted. See documentation for import guides):',
180
+ }));
181
+ if ((0, prompts_1.isCancel)(privateKey))
182
+ return process.exit(0);
183
+ }
184
+ else if (walletSetupType === 'generate') {
185
+ privateKey = (0, accounts_1.generatePrivateKey)();
186
+ const account = (0, accounts_1.privateKeyToAccount)(privateKey);
187
+ (0, prompts_1.note)(`New Wallet Generated!\nAddress: ${account.address}\n\nIMPORTANT: Backup this address. The Private Key is securely injected into your local vault.`, 'Wallet Created');
188
+ }
130
189
  let masterPassword = '';
131
190
  if (privateKey) {
132
191
  masterPassword = (await (0, prompts_1.password)({
133
- message: 'Enter MASTER PASSWORD to encrypt your key vault:',
192
+ message: 'Enter a strong MASTER PASSWORD to encrypt your key vault:',
134
193
  }));
135
194
  if ((0, prompts_1.isCancel)(masterPassword) || !masterPassword)
136
195
  return process.exit(0);
196
+ const masterPasswordConfirm = (await (0, prompts_1.password)({
197
+ message: 'Confirm MASTER PASSWORD:',
198
+ }));
199
+ if ((0, prompts_1.isCancel)(masterPasswordConfirm) || masterPassword !== masterPasswordConfirm) {
200
+ console.log(picocolors_1.default.red('❌ Passwords do not match. Setup cancelled.'));
201
+ return process.exit(1);
202
+ }
137
203
  }
138
204
  // --- SAVING ---
139
205
  // Update Config.yaml
@@ -38,8 +38,25 @@ function startTelegramBot() {
38
38
  // Send typing action to Telegram
39
39
  bot.sendChatAction(chatId, 'typing');
40
40
  try {
41
+ let progressMsgId = null;
42
+ const onProgress = async (progressText) => {
43
+ try {
44
+ if (!progressMsgId) {
45
+ const sent = await bot.sendMessage(chatId, progressText, { parse_mode: 'Markdown' });
46
+ progressMsgId = sent.message_id;
47
+ }
48
+ else {
49
+ await bot.editMessageText(progressText, { chat_id: chatId, message_id: progressMsgId, parse_mode: 'Markdown' });
50
+ }
51
+ }
52
+ catch (e) { }
53
+ };
41
54
  // Feed the message to the AI agent
42
- const response = await (0, reasoning_1.processUserInput)(text);
55
+ const response = await (0, reasoning_1.processUserInput)(text, 'user', onProgress);
56
+ if (progressMsgId) {
57
+ // Clean up the progress message
58
+ bot.deleteMessage(chatId, progressMsgId).catch(() => { });
59
+ }
43
60
  // Send the AI's response back to Telegram
44
61
  // Check for newly created pending transactions
45
62
  const pendingTxs = transactionManager_1.txManager.getPending();
@@ -5,46 +5,105 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Logger = void 0;
7
7
  const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
8
10
  const parser_1 = require("../config/parser");
9
11
  const paths_1 = require("../config/paths");
10
12
  class Logger {
11
- logFilePath;
12
- memory = [];
13
+ db;
13
14
  constructor() {
14
15
  const config = (0, parser_1.loadConfig)();
15
- this.logFilePath = (0, paths_1.getPath)(config.memory.path || 'memory.json');
16
- this.loadMemory();
16
+ let dbPath = config.memory?.path || 'memory.db';
17
+ if (dbPath.endsWith('.json')) {
18
+ dbPath = dbPath.replace('.json', '.db');
19
+ }
20
+ const fullPath = (0, paths_1.getPath)(dbPath);
21
+ // Ensure directory exists
22
+ const dir = path_1.default.dirname(fullPath);
23
+ if (!fs_1.default.existsSync(dir)) {
24
+ fs_1.default.mkdirSync(dir, { recursive: true });
25
+ }
26
+ this.db = new better_sqlite3_1.default(fullPath);
27
+ this.initDb();
17
28
  }
18
- loadMemory() {
19
- if (fs_1.default.existsSync(this.logFilePath)) {
29
+ initDb() {
30
+ this.db.exec(`
31
+ CREATE TABLE IF NOT EXISTS messages (
32
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
33
+ role TEXT NOT NULL,
34
+ content TEXT NOT NULL,
35
+ name TEXT,
36
+ tool_call_id TEXT,
37
+ tool_calls TEXT,
38
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
39
+ )
40
+ `);
41
+ // Migration logic from old memory.json to SQLite
42
+ const config = (0, parser_1.loadConfig)();
43
+ const oldJsonPath = (0, paths_1.getPath)(config.memory?.path || 'memory.json');
44
+ const countRow = this.db.prepare('SELECT COUNT(*) as count FROM messages').get();
45
+ if (countRow.count === 0 && fs_1.default.existsSync(oldJsonPath)) {
20
46
  try {
21
- const data = fs_1.default.readFileSync(this.logFilePath, 'utf-8');
22
- this.memory = JSON.parse(data);
47
+ const data = fs_1.default.readFileSync(oldJsonPath, 'utf-8');
48
+ const oldMemory = JSON.parse(data);
49
+ if (Array.isArray(oldMemory) && oldMemory.length > 0) {
50
+ const insert = this.db.prepare(`
51
+ INSERT INTO messages (role, content, name, tool_call_id, tool_calls)
52
+ VALUES (@role, @content, @name, @tool_call_id, @tool_calls)
53
+ `);
54
+ const insertMany = this.db.transaction((entries) => {
55
+ for (const entry of entries) {
56
+ insert.run({
57
+ role: entry.role,
58
+ content: entry.content || '',
59
+ name: entry.name || null,
60
+ tool_call_id: entry.tool_call_id || null,
61
+ tool_calls: entry.tool_calls ? JSON.stringify(entry.tool_calls) : null
62
+ });
63
+ }
64
+ });
65
+ insertMany(oldMemory);
66
+ console.log('[Nyxora Memory] Successfully migrated memory.json to SQLite database (Atomic Storage).');
67
+ // Rename old file to prevent re-migration issues and keep as backup
68
+ fs_1.default.renameSync(oldJsonPath, oldJsonPath + '.bak');
69
+ }
23
70
  }
24
71
  catch (error) {
25
- console.error('Failed to read memory file. Starting fresh.');
26
- this.memory = [];
72
+ console.error('[Nyxora Memory] Failed to migrate old memory.json:', error);
27
73
  }
28
74
  }
29
75
  }
30
- saveMemory() {
31
- try {
32
- fs_1.default.writeFileSync(this.logFilePath, JSON.stringify(this.memory, null, 2));
33
- }
34
- catch (error) {
35
- console.error('Failed to write memory file.');
36
- }
37
- }
38
76
  getHistory() {
39
- return [...this.memory];
77
+ const rows = this.db.prepare('SELECT role, content, name, tool_call_id, tool_calls FROM messages ORDER BY id ASC').all();
78
+ return rows.map((row) => {
79
+ const entry = {
80
+ role: row.role,
81
+ content: row.content,
82
+ };
83
+ if (row.name)
84
+ entry.name = row.name;
85
+ if (row.tool_call_id)
86
+ entry.tool_call_id = row.tool_call_id;
87
+ if (row.tool_calls)
88
+ entry.tool_calls = JSON.parse(row.tool_calls);
89
+ return entry;
90
+ });
40
91
  }
41
92
  addEntry(entry) {
42
- this.memory.push(entry);
43
- this.saveMemory();
93
+ const insert = this.db.prepare(`
94
+ INSERT INTO messages (role, content, name, tool_call_id, tool_calls)
95
+ VALUES (@role, @content, @name, @tool_call_id, @tool_calls)
96
+ `);
97
+ insert.run({
98
+ role: entry.role,
99
+ content: entry.content || '',
100
+ name: entry.name || null,
101
+ tool_call_id: entry.tool_call_id || null,
102
+ tool_calls: entry.tool_calls ? JSON.stringify(entry.tool_calls) : null
103
+ });
44
104
  }
45
105
  clear() {
46
- this.memory = [];
47
- this.saveMemory();
106
+ this.db.prepare('DELETE FROM messages').run();
48
107
  }
49
108
  }
50
109
  exports.Logger = Logger;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora",
3
- "version": "1.4.5",
3
+ "version": "1.4.7",
4
4
  "description": "",
5
5
  "main": "dist/gateway/cli.js",
6
6
  "files": [
@@ -32,6 +32,7 @@
32
32
  "type": "commonjs",
33
33
  "dependencies": {
34
34
  "@clack/prompts": "^1.4.0",
35
+ "better-sqlite3": "^12.10.0",
35
36
  "concurrently": "^9.2.1",
36
37
  "cors": "^2.8.6",
37
38
  "express": "^5.2.1",
@@ -43,11 +44,14 @@
43
44
  "yaml": "^2.9.0"
44
45
  },
45
46
  "devDependencies": {
47
+ "@types/better-sqlite3": "^7.6.13",
46
48
  "@types/cors": "^2.8.19",
47
49
  "@types/express": "^5.0.6",
48
50
  "@types/node": "^25.9.1",
49
51
  "@types/node-telegram-bot-api": "^0.64.14",
50
52
  "ts-node": "^10.9.2",
51
- "typescript": "^6.0.3"
53
+ "typescript": "^6.0.3",
54
+ "vitepress": "^1.6.4",
55
+ "vue": "^3.5.35"
52
56
  }
53
57
  }