nyxora 26.6.14 → 26.6.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +4 -3
  2. package/bin/nyxora.mjs +3 -4
  3. package/dist/launcher.js +8 -5
  4. package/dist/packages/core/src/agent/reasoning.js +21 -55
  5. package/dist/packages/core/src/config/parser.js +13 -15
  6. package/dist/packages/core/src/gateway/server.js +53 -10
  7. package/dist/packages/core/src/gateway/setup.js +63 -11
  8. package/dist/packages/core/src/gateway/telegram.js +9 -19
  9. package/dist/packages/core/src/system/skills/updateSecurityPolicy.js +17 -11
  10. package/dist/packages/core/src/utils/formatter.js +13 -18
  11. package/dist/packages/core/src/web3/skills/createMarketWatchAgent.js +51 -0
  12. package/dist/packages/core/src/web3/skills/getPrice.js +1 -1
  13. package/dist/packages/core/src/web3/skills/marketAnalysis.js +208 -47
  14. package/dist/packages/core/src/web3/utils/riskIntelligence.js +110 -0
  15. package/dist/packages/policy/src/server.js +38 -3
  16. package/dist/packages/signer/src/server.js +2 -2
  17. package/launcher.ts +9 -5
  18. package/package.json +5 -7
  19. package/packages/core/package.json +1 -2
  20. package/packages/core/src/agent/reasoning.ts +24 -54
  21. package/packages/core/src/config/parser.ts +14 -26
  22. package/packages/core/src/gateway/server.ts +57 -10
  23. package/packages/core/src/gateway/setup.ts +65 -11
  24. package/packages/core/src/gateway/telegram.ts +9 -19
  25. package/packages/core/src/system/skills/updateSecurityPolicy.ts +18 -11
  26. package/packages/core/src/utils/formatter.ts +13 -17
  27. package/packages/core/src/web3/skills/createMarketWatchAgent.ts +59 -0
  28. package/packages/core/src/web3/skills/getPrice.ts +1 -1
  29. package/packages/core/src/web3/skills/marketAnalysis.ts +209 -49
  30. package/packages/core/src/web3/utils/riskIntelligence.ts +118 -0
  31. package/packages/dashboard/dist/assets/index-BAXifdMN.js +16 -0
  32. package/packages/dashboard/dist/index.html +1 -1
  33. package/packages/dashboard/package.json +1 -1
  34. package/packages/mcp-server/dist/server.js +110 -0
  35. package/packages/mcp-server/package.json +1 -1
  36. package/packages/policy/package.json +1 -1
  37. package/packages/policy/src/server.ts +41 -4
  38. package/packages/signer/package.json +1 -1
  39. package/packages/signer/src/server.ts +2 -2
  40. package/packages/core/src/system/pluginManager.ts +0 -106
  41. package/packages/core/src/system/skills/installSkill.ts +0 -51
  42. package/packages/dashboard/dist/assets/index-BKkezv4e.js +0 -13
package/README.md CHANGED
@@ -25,7 +25,7 @@ It operates under a **Zero-Trust, Defense-in-Depth Cryptographically Bound Human
25
25
  * **DeFi Configuration BYOK & UI Masking**: All aggregator and provider API keys are strictly isolated via a Bring Your Own Keys (BYOK) architecture into a heavily guarded `~/.nyxora/defi_keys.yaml` file. The local web Dashboard masks these injected secrets using `***********` and `IS_SET` censorship, completely neutralizing malicious browser extensions from exfiltrating your keys.
26
26
  * **Approval Replay Protection (Nonce Guard)**: Transactions requested by the AI are drafted as hashes and signed with a randomized 16-byte Nonce. The `/api/transactions/:id/approve` endpoint strictly enforces Nonce matching to completely eliminate double-spending and Replay Attacks.
27
27
  * **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.
28
- * **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.
28
+
29
29
  * **Graceful SQLite WAL Shutdown**: Integrated `SIGTERM`/`SIGINT` interceptors ensure that when the daemon stops, active requests are safely terminated and SQLite Write-Ahead Logs (WAL) are securely flushed, preventing database corruption.
30
30
 
31
31
  ### 🌐 Web3 Skills (On-Chain)
@@ -34,7 +34,8 @@ It operates under a **Zero-Trust, Defense-in-Depth Cryptographically Bound Human
34
34
  * **6-Engine Meta-Aggregator & Anti-MEV**: The core engine interfaces with a powerful 6-Engine Meta-Aggregator (**1inch, 0x, LI.FI, Relay, OpenOcean, and KyberSwap**) to route tokens cross-chain, ensuring absolute maximum liquidity depth.
35
35
  * **Adaptive Auto Slippage Protection**: Nyxora enforces a dynamic and adaptive **'auto' slippage** by default to leverage dynamic MEV-protection from these industry-standard aggregators. However, the user retains absolute control to override this dynamically—either globally via the Dashboard Settings or on a per-transaction basis through NLP chat commands (e.g., *"Swap 1 ETH to PEPE with 10% slippage"*).
36
36
 
37
- * **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.
37
+ * **Dual-Routing Market Intelligence Engine**: Real-time asset tracking utilizing a sophisticated API Waterfall. Symbol queries are routed to CoinGecko/CEXs for global FDV, while Contract Addresses trigger DexScreener for live on-chain liquidity metrics across all networks.
38
+ * **Asynchronous Watchdog Agents**: Seamlessly spawn detached background instances for long-running monitoring tasks (e.g., *"Notify me when $ETH drops below $2500"*), leaving your primary chat session free for other operations.
38
39
  * **"Lean Degen" Auto-Whitelist**: Automatically intercepts Contract Addresses (CAs) whenever you check balances or swap tokens, saving them to your localized `user_whitelist.json` for future tracking.
39
40
  * **Dynamic Portfolio Engine**: Merges standard tokens, your custom Degen CAs, and CoinGecko's daily trending list into a single hyper-fast Multicall scan to deliver a clean, spam-free PnL portfolio report in under 1 second.
40
41
  * **Deep Transaction History**: Accurately fetch your 30-day (or custom timeframe) Native and ERC-20 transaction history across all supported EVM chains. Powered by the Unified Etherscan API V2, enabling seamless cross-chain fetching (Mainnets & Testnets) using a single API key.
@@ -171,7 +172,7 @@ For complete technical deep-dives into our Cryptographic Architecture, please vi
171
172
  **❤️ Support the Project**
172
173
 
173
174
  Building and maintaining a highly secure, zero-trust architecture takes significant time and resources. If you love what we are building, you can help us keep Nyxora open, secure, and constantly evolving by sending a coffee our way:
174
- - **EVM:** `0x18a30d5db50d287dba669c5672cd71246cc4c4c6`
175
+ - **EVM (Multi-Sig Safe):** `0x490717E50D6434C348AA0D2bD5fe682392823708`
175
176
 
176
177
  ---
177
178
  **License:** MIT License
package/bin/nyxora.mjs CHANGED
@@ -15,9 +15,9 @@ const pidFile = path.join(appDir, 'run', 'daemon.pid');
15
15
  const logFile = path.join(appDir, 'run', 'gateway.log');
16
16
  const tokenFile = path.join(appDir, 'auth', 'auth.token');
17
17
 
18
- if (!fs.existsSync(appDir)) {
19
- fs.mkdirSync(appDir, { recursive: true });
20
- }
18
+ [path.join(appDir, 'run'), path.join(appDir, 'auth')].forEach(dir => {
19
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
20
+ });
21
21
 
22
22
  const command = process.argv[2];
23
23
 
@@ -291,7 +291,6 @@ async function unlock() {
291
291
  } catch (e) {}
292
292
  }
293
293
  try {
294
- const fetch = (await import('node-fetch')).default;
295
294
  const res = await fetch('http://localhost:3000/api/status/unlock', {
296
295
  method: 'POST',
297
296
  headers: {
package/dist/launcher.js CHANGED
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ // @ts-ignore
6
7
  const safeLogger_1 = require("./packages/core/src/utils/safeLogger");
7
8
  (0, safeLogger_1.initSafeLogger)();
8
9
  const child_process_1 = require("child_process");
@@ -107,19 +108,21 @@ if (fs_1.default.existsSync(socketPath)) {
107
108
  fs_1.default.unlinkSync(socketPath);
108
109
  }
109
110
  const children = [];
110
- const isCompiled = __filename.endsWith('.js');
111
+ const __filenameResolved = __filename;
112
+ const __dirnameResolved = __dirname;
113
+ const isCompiled = __filenameResolved.endsWith('.js');
111
114
  const ext = isCompiled ? '.js' : '.ts';
112
- const cmd = isCompiled ? 'node' : path_1.default.join(__dirname, 'node_modules', '.bin', 'ts-node');
115
+ const cmd = isCompiled ? 'node' : path_1.default.join(__dirnameResolved, 'node_modules', '.bin', 'ts-node');
113
116
  const baseArgs = isCompiled ? [] : ['-T'];
114
- const signerPath = path_1.default.join(__dirname, `packages/signer/src/server${ext}`);
117
+ const signerPath = path_1.default.join(__dirnameResolved, `packages/signer/src/server${ext}`);
115
118
  const signer = spawnService('Signer', cmd, [...baseArgs, signerPath], env);
116
119
  children.push(signer);
117
120
  setTimeout(() => {
118
- const policyPath = path_1.default.join(__dirname, `packages/policy/src/server${ext}`);
121
+ const policyPath = path_1.default.join(__dirnameResolved, `packages/policy/src/server${ext}`);
119
122
  const policy = spawnService('Policy', cmd, [...baseArgs, policyPath], env);
120
123
  children.push(policy);
121
124
  setTimeout(() => {
122
- const corePath = path_1.default.join(__dirname, `packages/core/src/gateway/cli${ext}`);
125
+ const corePath = path_1.default.join(__dirnameResolved, `packages/core/src/gateway/cli${ext}`);
123
126
  const args = process.argv.slice(2);
124
127
  const core = spawnService('Core', cmd, [...baseArgs, corePath, ...args], env, true);
125
128
  children.push(core);
@@ -22,6 +22,7 @@ const mintNft_1 = require("../web3/skills/mintNft");
22
22
  const customTx_1 = require("../web3/skills/customTx");
23
23
  const checkSecurity_1 = require("../web3/skills/checkSecurity");
24
24
  const marketAnalysis_1 = require("../web3/skills/marketAnalysis");
25
+ const createMarketWatchAgent_1 = require("../web3/skills/createMarketWatchAgent");
25
26
  const checkPortfolio_1 = require("../web3/skills/checkPortfolio");
26
27
  const checkAddress_1 = require("../web3/skills/checkAddress");
27
28
  const getMyAddress_1 = require("../web3/skills/getMyAddress");
@@ -41,7 +42,6 @@ const generateExcel_1 = require("../system/skills/generateExcel");
41
42
  const executeShell_1 = require("../system/skills/executeShell");
42
43
  const browseWeb_1 = require("../system/skills/browseWeb");
43
44
  const searchWeb_1 = require("../system/skills/searchWeb");
44
- const installSkill_1 = require("../system/skills/installSkill");
45
45
  const editFile_1 = require("../system/skills/editFile");
46
46
  const gitManager_1 = require("../system/skills/gitManager");
47
47
  const xManager_1 = require("../system/skills/xManager");
@@ -49,7 +49,6 @@ const notionWorkspace_1 = require("../system/skills/notionWorkspace");
49
49
  const audioTranscribe_1 = require("../system/skills/audioTranscribe");
50
50
  const summarizeText_1 = require("../system/skills/summarizeText");
51
51
  const googleWorkspace_1 = require("../system/skills/googleWorkspace");
52
- const pluginManager_1 = require("../system/pluginManager");
53
52
  const paths_1 = require("../config/paths");
54
53
  const picocolors_1 = __importDefault(require("picocolors"));
55
54
  exports.logger = new logger_1.Logger();
@@ -134,7 +133,7 @@ IMPORTANT: The <think> block is strictly for your internal hidden monologue. NEV
134
133
 
135
134
  [EXECUTION WORKFLOW]
136
135
  CRITICAL RULE 1: NEVER expose internal JSON tool calls to the user. Always parse them and explain the outcome naturally.
137
- CRITICAL RULE 2: STRICT LANGUAGE MATCHING. You MUST strictly reply in the exact same language as the user's LATEST prompt.
136
+ CRITICAL RULE 2: STRICT LANGUAGE MATCHING. You MUST strictly reply in the exact same language as the user's LATEST prompt. Render all output, metric labels, and suggested actions entirely in the language the user initiated the prompt with, while strictly preserving the visual structure of the progress bars.
138
137
  CRITICAL RULE 3: FORMATTING & CONCISENESS. Provide concise analytical summaries of data rather than just dumping raw markdown tables. Be analytical but brief. Use commas for thousands.
139
138
  CRITICAL RULE 4: TOOL PRIORITIZATION. Web3 tasks must use Web3 Skills exclusively. OS Skills (search, browse) are fallbacks only. Use get_my_address to show wallet address, and check_portfolio to show balances.
140
139
  CRITICAL RULE 5: DEFAULT CHAIN HANDLING. Default to: ${config.agent.default_chain} unless specified. If overridden, confirm the chain politely. For 2-chain txs (bridge), default source to ${config.agent.default_chain}.
@@ -174,16 +173,20 @@ CRITICAL RULE 19: MARKET CONFIDENCE SCORE. When analyzing market data, token sec
174
173
  catch (error) {
175
174
  console.error('Failed to read user.md:', error);
176
175
  }
177
- // Read security_policy.md for NLP security constraints
176
+ // Read policy.yaml for NLP security constraints
178
177
  try {
179
- const policyPath = (0, paths_1.getPath)('security_policy.md');
178
+ const policyPath = (0, paths_1.getPath)('policy.yaml');
180
179
  if (fs_1.default.existsSync(policyPath)) {
181
- const securityInstructions = fs_1.default.readFileSync(policyPath, 'utf8');
182
- basePrompt += `\n\n--- SECURITY POLICY (MANDATORY RULES) ---\n${securityInstructions}\n\nCRITICAL: If the user asks you to perform an action that violates the Security Policy above, YOU MUST NOT EXECUTE IT DIRECTLY. Instead, ask for their explicit permission first.`;
180
+ const yaml = require('yaml'); // lazily import if not imported
181
+ const file = fs_1.default.readFileSync(policyPath, 'utf8');
182
+ const parsed = yaml.parse(file) || {};
183
+ if (parsed.custom_llm_rules && Array.isArray(parsed.custom_llm_rules) && parsed.custom_llm_rules.length > 0) {
184
+ basePrompt += `\n\n--- SECURITY POLICY (MANDATORY RULES) ---\n${parsed.custom_llm_rules.map((r) => `* ${r}`).join('\n')}\n\nCRITICAL: If the user asks you to perform an action that violates the Security Policy above, YOU MUST NOT EXECUTE IT DIRECTLY. Instead, ask for their explicit permission first.`;
185
+ }
183
186
  }
184
187
  }
185
188
  catch (error) {
186
- console.error('Failed to read security_policy.md:', error);
189
+ console.error('Failed to read policy.yaml:', error);
187
190
  }
188
191
  // Inject Episodic Memories (Smart Suggestions Context)
189
192
  try {
@@ -242,27 +245,24 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
242
245
  })
243
246
  ];
244
247
  try {
245
- if (config.llm.provider !== 'openai' && config.llm.provider !== 'ollama' && config.llm.provider !== 'gemini' && config.llm.provider !== 'openrouter') {
246
- return `Provider ${config.llm.provider} is configured, but currently only OpenAI, OpenRouter, Ollama, and Gemini adapters are implemented.`;
247
- }
248
248
  const lowerInput = input.toLowerCase();
249
249
  const hasWeb3Keyword = /swap|transfer|price|token|crypto|bridge|wallet|balance|portfolio|buy|sell|send|receive|address|market|limit|mint|nft/i.test(lowerInput);
250
250
  const hasGoogleKeyword = /email|gmail|calendar|sheet|doc|form|event/i.test(lowerInput);
251
251
  let tools = [];
252
252
  if ((0, skillManager_1.isSkillActive)('web3')) {
253
- tools.push(getBalance_1.getBalanceToolDefinition, transfer_1.transferToolDefinition, getPrice_1.getPriceToolDefinition, swapToken_1.swapTokenToolDefinition, bridgeToken_1.bridgeTokenToolDefinition, mintNft_1.mintNftToolDefinition, customTx_1.customTxToolDefinition, checkSecurity_1.checkSecurityToolDefinition, marketAnalysis_1.marketAnalysisToolDefinition, checkPortfolio_1.checkPortfolioToolDefinition, checkAddress_1.checkAddressToolDefinition, getMyAddress_1.getMyAddressToolDefinition, manageCustomTokens_1.manageCustomTokensDefinition, revokeApprovals_1.revokeApprovalToolDefinition, defiLending_1.aaveSupplyToolDefinition, yieldVault_1.vaultDepositToolDefinition, provideLiquidity_1.provideLiquidityToolDefinition, getTxHistory_1.getTxHistoryToolDefinition, createLimitOrder_1.createLimitOrderToolDefinition);
253
+ tools.push(getBalance_1.getBalanceToolDefinition, transfer_1.transferToolDefinition, getPrice_1.getPriceToolDefinition, swapToken_1.swapTokenToolDefinition, bridgeToken_1.bridgeTokenToolDefinition, mintNft_1.mintNftToolDefinition, customTx_1.customTxToolDefinition, checkSecurity_1.checkSecurityToolDefinition, marketAnalysis_1.marketAnalysisToolDefinition, createMarketWatchAgent_1.createMarketWatchAgentToolDefinition, checkPortfolio_1.checkPortfolioToolDefinition, checkAddress_1.checkAddressToolDefinition, getMyAddress_1.getMyAddressToolDefinition, manageCustomTokens_1.manageCustomTokensDefinition, revokeApprovals_1.revokeApprovalToolDefinition, defiLending_1.aaveSupplyToolDefinition, yieldVault_1.vaultDepositToolDefinition, provideLiquidity_1.provideLiquidityToolDefinition, getTxHistory_1.getTxHistoryToolDefinition, createLimitOrder_1.createLimitOrderToolDefinition);
254
254
  }
255
- const SYSTEM_TOOLS = [updateProfile_1.updateProfileToolDefinition, updateSecurityPolicy_1.updateSecurityPolicyToolDefinition, analyzeDocument_1.analyzeDocumentToolDefinition, readFile_1.readLocalFileToolDefinition, writeFile_1.writeLocalFileToolDefinition, generateExcel_1.generateExcelToolDefinition, executeShell_1.runTerminalCommandToolDefinition, browseWeb_1.browseWebsiteToolDefinition, searchWeb_1.searchWebToolDefinition, installSkill_1.installExternalSkillToolDefinition, editFile_1.editLocalFileToolDefinition, gitManager_1.gitManagerToolDefinition, xManager_1.xManagerToolDefinition, notionWorkspace_1.notionWorkspaceToolDefinition, audioTranscribe_1.audioTranscribeToolDefinition, summarizeText_1.summarizeTextToolDefinition];
255
+ const SYSTEM_TOOLS = [updateProfile_1.updateProfileToolDefinition, updateSecurityPolicy_1.updateSecurityPolicyToolDefinition, analyzeDocument_1.analyzeDocumentToolDefinition, readFile_1.readLocalFileToolDefinition, writeFile_1.writeLocalFileToolDefinition, generateExcel_1.generateExcelToolDefinition, executeShell_1.runTerminalCommandToolDefinition, browseWeb_1.browseWebsiteToolDefinition, searchWeb_1.searchWebToolDefinition, editFile_1.editLocalFileToolDefinition, gitManager_1.gitManagerToolDefinition, xManager_1.xManagerToolDefinition, notionWorkspace_1.notionWorkspaceToolDefinition, audioTranscribe_1.audioTranscribeToolDefinition, summarizeText_1.summarizeTextToolDefinition];
256
256
  const GOOGLE_TOOLS = [googleWorkspace_1.readGmailInboxToolDefinition, googleWorkspace_1.listCalendarEventsToolDefinition, googleWorkspace_1.appendRowToSheetsToolDefinition, googleWorkspace_1.readGoogleDocsToolDefinition, googleWorkspace_1.readGoogleFormResponsesToolDefinition];
257
257
  let activeTools = [];
258
258
  if (hasGoogleKeyword && !hasWeb3Keyword) {
259
- activeTools = [...GOOGLE_TOOLS, ...SYSTEM_TOOLS, ...pluginManager_1.pluginManager.getToolDefinitions()];
259
+ activeTools = [...GOOGLE_TOOLS, ...SYSTEM_TOOLS];
260
260
  }
261
261
  else if (hasWeb3Keyword && !hasGoogleKeyword) {
262
- activeTools = [...tools, ...SYSTEM_TOOLS, ...pluginManager_1.pluginManager.getToolDefinitions()];
262
+ activeTools = [...tools, ...SYSTEM_TOOLS];
263
263
  }
264
264
  else {
265
- activeTools = [...tools, ...SYSTEM_TOOLS, ...GOOGLE_TOOLS, ...pluginManager_1.pluginManager.getToolDefinitions()];
265
+ activeTools = [...tools, ...SYSTEM_TOOLS, ...GOOGLE_TOOLS];
266
266
  }
267
267
  activeTools = activeTools.filter(t => (0, skillManager_1.isSkillActive)(t.function.name));
268
268
  const response = await executeWithRetry(async (client) => {
@@ -323,10 +323,6 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
323
323
  }
324
324
  case 'transfer_token':
325
325
  case 'transfer_native': {
326
- if (config.permissions?.web3?.allow_transfer === false) {
327
- result = `[Security Blocked] Runtime Permission Denied: Web3 transfers are disabled. Update config.yaml to allow.`;
328
- break;
329
- }
330
326
  result = await (0, transfer_1.prepareTransfer)(args.chainName, args.toAddress, args.amountStr || args.amountEth, args.token);
331
327
  break;
332
328
  }
@@ -335,18 +331,10 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
335
331
  break;
336
332
  }
337
333
  case 'swap_token': {
338
- if (config.permissions?.web3?.allow_swap === false) {
339
- result = `[Security Blocked] Runtime Permission Denied: Web3 swaps are disabled. Update config.yaml to allow.`;
340
- break;
341
- }
342
334
  result = await (0, swapToken_1.prepareSwapToken)(args.chainName, args.fromToken, args.toToken, args.amountStr || args.amount, args.mode, args.providerName);
343
335
  break;
344
336
  }
345
337
  case 'bridge_token': {
346
- if (config.permissions?.web3?.allow_transfer === false) {
347
- result = `[Security Blocked] Runtime Permission Denied: Web3 bridging (transfer) is disabled. Update config.yaml to allow.`;
348
- break;
349
- }
350
338
  result = await (0, bridgeToken_1.prepareBridgeToken)(args.fromChain, args.toChain, args.tokenSymbol, args.amountStr, args.mode, args.providerName);
351
339
  break;
352
340
  }
@@ -355,10 +343,6 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
355
343
  break;
356
344
  }
357
345
  case 'custom_tx': {
358
- if (config.permissions?.web3?.allow_transfer === false) {
359
- result = `[Security Blocked] Runtime Permission Denied: Custom transactions are blocked because transfers are disabled.`;
360
- break;
361
- }
362
346
  result = await (0, customTx_1.prepareCustomTx)(args.chainName, args.toAddress, args.dataHex, args.valueEth, args.gasLimitStr);
363
347
  break;
364
348
  }
@@ -370,6 +354,10 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
370
354
  result = await (0, marketAnalysis_1.analyzeMarket)(args.chainName, args.tokenAddressOrSymbol);
371
355
  break;
372
356
  }
357
+ case 'create_market_watch_agent': {
358
+ result = await (0, createMarketWatchAgent_1.createMarketWatchAgent)(args.chainName, args.contractAddress, args.rules, args.durationDays);
359
+ break;
360
+ }
373
361
  case 'check_portfolio': {
374
362
  result = await (0, checkPortfolio_1.checkPortfolio)(args.chainName, args.address);
375
363
  break;
@@ -451,26 +439,14 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
451
439
  break;
452
440
  }
453
441
  case 'write_local_file': {
454
- if (config.permissions?.system?.allow_file_write === false) {
455
- result = `[Security Blocked] Runtime Permission Denied: File writing is disabled. Update config.yaml to allow.`;
456
- break;
457
- }
458
442
  result = (0, writeFile_1.writeLocalFile)(args.filePath, args.content);
459
443
  break;
460
444
  }
461
445
  case 'generate_excel_file': {
462
- if (config.permissions?.system?.allow_file_write === false) {
463
- result = `[Security Blocked] Runtime Permission Denied: File writing is disabled. Update config.yaml to allow.`;
464
- break;
465
- }
466
446
  result = await (0, generateExcel_1.generateExcelFile)(args.data, args.filePath);
467
447
  break;
468
448
  }
469
449
  case 'run_terminal_command': {
470
- if (config.permissions?.system?.allow_shell_execution === false) {
471
- result = `[Security Blocked] Runtime Permission Denied: Shell execution is disabled. Update config.yaml to allow.`;
472
- break;
473
- }
474
450
  result = await (0, executeShell_1.runTerminalCommand)(args.command);
475
451
  break;
476
452
  }
@@ -482,10 +458,6 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
482
458
  result = await (0, searchWeb_1.searchWeb)(args.query, args.depth);
483
459
  break;
484
460
  }
485
- case 'install_external_skill': {
486
- result = await (0, installSkill_1.installExternalSkill)(args.url);
487
- break;
488
- }
489
461
  case 'read_gmail_inbox': {
490
462
  result = await (0, googleWorkspace_1.readGmailInbox)(args.maxResults);
491
463
  break;
@@ -507,13 +479,7 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
507
479
  break;
508
480
  }
509
481
  default: {
510
- const externalResult = await pluginManager_1.pluginManager.executeTool(toolName, args);
511
- if (externalResult !== null) {
512
- result = externalResult;
513
- }
514
- else {
515
- result = `Error: Tool ${toolName} is not implemented.`;
516
- }
482
+ result = `Error: Tool ${toolName} is not implemented.`;
517
483
  break;
518
484
  }
519
485
  }
@@ -66,16 +66,22 @@ function loadConfig() {
66
66
  delete parsed.llm.credentials;
67
67
  needsSave = true;
68
68
  }
69
- // Auto-migration logic: move web3.rpc_urls to rpc_key.yaml
70
- if (parsed.web3 && parsed.web3.rpc_urls && Object.keys(parsed.web3.rpc_urls).length > 0) {
71
- if (!fs_1.default.existsSync(rpcPath)) {
72
- rpcUrls = parsed.web3.rpc_urls;
73
- saveRpcConfig(rpcUrls);
74
- console.log('[Config] Auto-migrated web3.rpc_urls to rpc_key.yaml.');
75
- }
69
+ // Ensure we don't accidentally overwrite rpc_key.yaml with old config.yaml data.
70
+ if (parsed.web3 && parsed.web3.rpc_urls) {
76
71
  delete parsed.web3.rpc_urls;
77
72
  needsSave = true;
78
73
  }
74
+ // Auto-migration logic: move permissions to policy.yaml
75
+ const policyPath = (0, paths_1.getPath)('policy.yaml');
76
+ if (!fs_1.default.existsSync(policyPath)) {
77
+ const defaultPolicy = `max_usd_per_tx: ${parsed.permissions?.web3?.max_usd_per_tx || 999999999}\nwhitelist_only: false\nrequire_approval: true\n`;
78
+ fs_1.default.writeFileSync(policyPath, defaultPolicy, 'utf8');
79
+ console.log('[Config] Created default policy.yaml.');
80
+ }
81
+ if (parsed.permissions) {
82
+ delete parsed.permissions;
83
+ needsSave = true;
84
+ }
79
85
  if (needsSave) {
80
86
  try {
81
87
  const yamlStr = yaml_1.default.stringify(parsed);
@@ -103,10 +109,6 @@ function loadConfig() {
103
109
  web3: { ...parsed.web3, rpc_urls: rpcUrls },
104
110
  integrations: parsed.integrations || {
105
111
  telegram: { enabled: false }
106
- },
107
- permissions: parsed.permissions || {
108
- web3: { allow_transfer: true, allow_swap: true, max_usd_per_tx: 999999999 },
109
- system: { allow_shell_execution: true, allow_file_write: true }
110
112
  }
111
113
  };
112
114
  }
@@ -140,10 +142,6 @@ function loadConfig() {
140
142
  web3: { rpc_urls: rpcUrls },
141
143
  integrations: {
142
144
  telegram: { enabled: false }
143
- },
144
- permissions: {
145
- web3: { allow_transfer: true, allow_swap: true, max_usd_per_tx: 999999999 },
146
- system: { allow_shell_execution: true, allow_file_write: true }
147
145
  }
148
146
  };
149
147
  }
@@ -21,6 +21,7 @@ const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
21
21
  const paths_1 = require("../config/paths");
22
22
  const state_1 = require("../utils/state");
23
23
  const fs_1 = __importDefault(require("fs"));
24
+ const yaml_1 = __importDefault(require("yaml"));
24
25
  const reasoning_1 = require("../agent/reasoning");
25
26
  const parser_1 = require("../config/parser");
26
27
  const defiConfigManager_1 = require("../config/defiConfigManager");
@@ -28,7 +29,6 @@ const config_1 = require("../web3/config");
28
29
  const tokens_1 = require("../web3/utils/tokens");
29
30
  const tracker_1 = require("./tracker");
30
31
  const transactionManager_1 = require("../agent/transactionManager");
31
- const pluginManager_1 = require("../system/pluginManager");
32
32
  const transfer_1 = require("../web3/skills/transfer");
33
33
  const swapToken_1 = require("../web3/skills/swapToken");
34
34
  const getBalance_1 = require("../web3/skills/getBalance");
@@ -55,7 +55,6 @@ const createLimitOrder_1 = require("../web3/skills/createLimitOrder");
55
55
  // System Skills
56
56
  const browseWeb_1 = require("../system/skills/browseWeb");
57
57
  const executeShell_1 = require("../system/skills/executeShell");
58
- const installSkill_1 = require("../system/skills/installSkill");
59
58
  const readFile_1 = require("../system/skills/readFile");
60
59
  const editFile_1 = require("../system/skills/editFile");
61
60
  const gitManager_1 = require("../system/skills/gitManager");
@@ -125,7 +124,8 @@ app.use('/api/', apiLimiter);
125
124
  app.use('/api', (req, res, next) => {
126
125
  // Bypass auth for Google OAuth callback and URLs since they are handled externally or by the browser
127
126
  const allowedPaths = ['/api/auth/google/url', '/api/auth/google/callback', '/api/auth/google/status', '/api/auth/google'];
128
- if (allowedPaths.includes(req.path) || allowedPaths.includes(req.path.replace(/\/$/, ''))) {
127
+ const currentPath = req.originalUrl.split('?')[0];
128
+ if (allowedPaths.includes(currentPath) || allowedPaths.includes(currentPath.replace(/\/$/, ''))) {
129
129
  return next();
130
130
  }
131
131
  const token = req.headers['x-nyxora-token'];
@@ -349,7 +349,6 @@ app.get('/api/skills/system', (req, res) => {
349
349
  generateExcel_1.generateExcelToolDefinition,
350
350
  browseWeb_1.browseWebsiteToolDefinition,
351
351
  updateSecurityPolicy_1.updateSecurityPolicyToolDefinition,
352
- installSkill_1.installExternalSkillToolDefinition,
353
352
  analyzeDocument_1.analyzeDocumentToolDefinition,
354
353
  searchWeb_1.searchWebToolDefinition,
355
354
  googleWorkspace_1.readGmailInboxToolDefinition,
@@ -802,6 +801,46 @@ app.delete('/api/memory/:id', (req, res) => {
802
801
  res.status(500).json({ error: error.message });
803
802
  }
804
803
  });
804
+ // --- Policy Engine Endpoints ---
805
+ app.get('/api/policy', (req, res) => {
806
+ try {
807
+ const policyPath = (0, paths_1.getPath)('policy.yaml');
808
+ if (!fs_1.default.existsSync(policyPath)) {
809
+ return res.json({
810
+ max_usd_per_tx: 999999999,
811
+ whitelist_only: false,
812
+ require_approval: true,
813
+ custom_llm_rules: []
814
+ });
815
+ }
816
+ const file = fs_1.default.readFileSync(policyPath, 'utf8');
817
+ const parsed = yaml_1.default.parse(file) || {};
818
+ res.json({
819
+ max_usd_per_tx: parsed.max_usd_per_tx ?? 999999999,
820
+ whitelist_only: parsed.whitelist_only ?? false,
821
+ require_approval: parsed.require_approval ?? true,
822
+ custom_llm_rules: parsed.custom_llm_rules || []
823
+ });
824
+ }
825
+ catch (error) {
826
+ res.status(500).json({ error: error.message });
827
+ }
828
+ });
829
+ app.post('/api/policy', (req, res) => {
830
+ try {
831
+ const policyPath = (0, paths_1.getPath)('policy.yaml');
832
+ let current = {};
833
+ if (fs_1.default.existsSync(policyPath)) {
834
+ current = yaml_1.default.parse(fs_1.default.readFileSync(policyPath, 'utf8')) || {};
835
+ }
836
+ const updated = { ...current, ...req.body };
837
+ fs_1.default.writeFileSync(policyPath, yaml_1.default.stringify(updated), 'utf8');
838
+ res.json({ success: true });
839
+ }
840
+ catch (error) {
841
+ res.status(500).json({ error: error.message });
842
+ }
843
+ });
805
844
  // --- User Persona / Risk Profile Endpoints (V3) ---
806
845
  app.get('/api/profile', (req, res) => {
807
846
  try {
@@ -862,9 +901,6 @@ async function autoMigrateKeys() {
862
901
  }
863
902
  function startServer() {
864
903
  autoMigrateKeys().catch(e => console.error('[Auto-Migrate] Error:', e));
865
- pluginManager_1.pluginManager.loadPlugins().then(() => {
866
- console.log(`[PluginManager] Finished loading external skills.`);
867
- });
868
904
  const PORT = Number(process.env.PORT || 3000);
869
905
  const server = app.listen(PORT, '127.0.0.1', () => {
870
906
  console.log(`🤖 Nyxora API Server running on port ${PORT}`);
@@ -885,18 +921,25 @@ function startServer() {
885
921
  process.exit(1);
886
922
  }
887
923
  });
924
+ let isShuttingDown = false;
888
925
  const gracefulShutdown = () => {
926
+ if (isShuttingDown)
927
+ return;
928
+ isShuttingDown = true;
889
929
  console.log('[Nyxora Gateway] Received shutdown signal. Closing server...');
930
+ if (server.closeAllConnections) {
931
+ server.closeAllConnections();
932
+ }
890
933
  server.close(() => {
891
934
  console.log('[Nyxora Gateway] HTTP server closed.');
892
935
  reasoning_1.logger.close();
893
936
  process.exit(0);
894
937
  });
895
- // Force exit after 10s if stuck
938
+ // Force exit after 3s if stuck
896
939
  setTimeout(() => {
897
- console.error('[Nyxora Gateway] Forced shutdown after 10s.');
940
+ console.error('[Nyxora Gateway] Forced shutdown.');
898
941
  process.exit(1);
899
- }, 10000);
942
+ }, 3000).unref();
900
943
  };
901
944
  process.on('SIGTERM', gracefulShutdown);
902
945
  process.on('SIGINT', gracefulShutdown);
@@ -289,9 +289,8 @@ Provider: ${config.llm.provider}`;
289
289
  { value: 'generateExcel', label: 'Generate Excel Reports' },
290
290
  { value: 'analyzeDocument', label: 'Analyze Docs (PDF/Word)' },
291
291
  { value: 'run_terminal', label: 'Run Terminal Command', hint: '⚠️ UNSAFE' },
292
- { value: 'installSkill', label: 'Install External Skills (Plugins)' },
293
292
  { value: 'gitManager', label: 'Git Operations (Commit/Push/Pull)' },
294
- { value: 'updateSecurityPolicy', label: 'Update security_policy.md', hint: 'safeguard' },
293
+ { value: 'updateSecurityPolicy', label: 'Update policy.yaml rules', hint: 'safeguard' },
295
294
  { value: 'browseWeb', label: 'Browse & Scrape Webpages' },
296
295
  { value: 'searchWeb', label: 'Smart Web Search (Tavily/Brave)', hint: 'Requires API Key' },
297
296
  { value: 'googleWorkspace', label: 'Google Workspace (Gmail, Docs, Sheets, Forms)', hint: 'Requires OAuth' },
@@ -410,7 +409,9 @@ Provider: ${config.llm.provider}`;
410
409
  config.web_search.enabled = false;
411
410
  }
412
411
  if (Object.keys(newApiKeys).length > 0) {
413
- await (0, parser_1.saveApiKeys)(newApiKeys);
412
+ if (!config.credentials)
413
+ config.credentials = {};
414
+ config.credentials = { ...config.credentials, ...newApiKeys };
414
415
  }
415
416
  if (!config.integrations)
416
417
  config.integrations = {};
@@ -424,7 +425,6 @@ Provider: ${config.llm.provider}`;
424
425
  config.integrations.telegram.authorized_chat_id = authorizedChatId;
425
426
  }
426
427
  (0, parser_1.saveConfig)(config);
427
- (0, parser_1.saveRpcConfig)({});
428
428
  // Sync disabled_skills.json based on user selection
429
429
  const allWeb3Skills = [
430
430
  'transfer', 'swapToken', 'bridgeToken', 'customTx', 'mintNft',
@@ -434,23 +434,75 @@ Provider: ${config.llm.provider}`;
434
434
  ];
435
435
  const allOsSkills = [
436
436
  'readFile', 'writeFile', 'editFile', 'generateExcel', 'analyzeDocument',
437
- 'run_terminal', 'installSkill', 'gitManager', 'updateSecurityPolicy',
437
+ 'run_terminal', 'gitManager', 'updateSecurityPolicy',
438
438
  'browseWeb', 'searchWeb', 'googleWorkspace', 'notionWorkspace', 'xManager',
439
439
  'audioTranscribe', 'summarizeText'
440
440
  ];
441
441
  const disabledSkills = [];
442
+ const skillMapping = {
443
+ // OS Skills
444
+ readFile: 'read_local_file',
445
+ writeFile: 'write_local_file',
446
+ editFile: 'edit_local_file',
447
+ generateExcel: 'generate_excel_file',
448
+ analyzeDocument: 'analyze_document',
449
+ run_terminal: 'run_terminal_command',
450
+ gitManager: 'execute_git_command',
451
+ updateSecurityPolicy: 'update_security_policy',
452
+ browseWeb: 'browse_website',
453
+ searchWeb: 'search_web',
454
+ googleWorkspace: [
455
+ 'read_gmail_inbox',
456
+ 'list_calendar_events',
457
+ 'append_row_to_sheets',
458
+ 'read_google_docs',
459
+ 'read_google_form_responses'
460
+ ],
461
+ notionWorkspace: 'manage_notion',
462
+ xManager: 'manage_twitter',
463
+ audioTranscribe: 'transcribe_audio',
464
+ summarizeText: 'summarize_text',
465
+ // Web3 Skills
466
+ transfer: 'transfer_token',
467
+ swapToken: 'swap_token',
468
+ bridgeToken: 'bridge_token',
469
+ customTx: 'custom_tx',
470
+ mintNft: 'mint_nft',
471
+ defiLending: 'supply_aave',
472
+ provideLiquidity: 'provide_liquidity_v3',
473
+ yieldVault: 'deposit_yield_vault',
474
+ revokeApprovals: 'revoke_approval',
475
+ getBalance: 'get_balance',
476
+ getMyAddress: 'get_my_address',
477
+ checkPortfolio: 'check_portfolio',
478
+ getPrice: 'get_price',
479
+ marketAnalysis: 'analyze_market',
480
+ getTxHistory: 'get_tx_history',
481
+ checkSecurity: 'check_token_security',
482
+ checkAddress: 'check_address',
483
+ checkRegistryStatus: 'check_registry_status',
484
+ manageCustomTokens: 'manage_custom_tokens'
485
+ };
486
+ const processDisabledSkill = (skill) => {
487
+ const mapped = skillMapping[skill];
488
+ if (Array.isArray(mapped)) {
489
+ disabledSkills.push(...mapped);
490
+ }
491
+ else if (mapped) {
492
+ disabledSkills.push(mapped);
493
+ }
494
+ else {
495
+ disabledSkills.push(skill);
496
+ }
497
+ };
442
498
  allWeb3Skills.forEach(skill => {
443
499
  if (!activeWeb3Skills.includes(skill))
444
- disabledSkills.push(skill);
500
+ processDisabledSkill(skill);
445
501
  });
446
502
  allOsSkills.forEach(skill => {
447
503
  if (!activeOsSkills.includes(skill))
448
- disabledSkills.push(skill);
504
+ processDisabledSkill(skill);
449
505
  });
450
- // Note: the backend uses 'run_terminal_command', but the UI/wizard used 'run_terminal'
451
- // I need to map it just in case:
452
- if (!activeOsSkills.includes('run_terminal'))
453
- disabledSkills.push('run_terminal_command');
454
506
  fs_1.default.writeFileSync((0, paths_1.getPath)('disabled_skills.json'), JSON.stringify(disabledSkills, null, 2));
455
507
  // Save Private Key to OS Keyring or fallback to .env
456
508
  if (privateKey) {
@@ -64,13 +64,13 @@ function startTelegramBot() {
64
64
  generatedPin = Math.floor(100000 + Math.random() * 900000).toString();
65
65
  pinExpiry = Date.now() + 5 * 60 * 1000; // 5 minutes TTL
66
66
  console.log(picocolors_1.default.yellow('\n==================================================='));
67
- console.log(picocolors_1.default.yellow('🔐 OTORISASI BOT TELEGRAM DIBUTUHKAN'));
67
+ console.log(picocolors_1.default.yellow('🔐 TELEGRAM BOT AUTHORIZATION REQUIRED'));
68
68
  console.log(picocolors_1.default.yellow('==================================================='));
69
- console.log('Bot Telegram Anda saat ini terkunci demi keamanan.');
70
- console.log('Buka Telegram Anda, dan kirimkan perintah berikut ke bot Anda:\n');
69
+ console.log('Your Telegram Bot is currently locked for security.');
70
+ console.log('Open your Telegram app, and send the following command to your bot:\n');
71
71
  console.log(picocolors_1.default.cyan(` /auth ${generatedPin}\n`));
72
- console.log(picocolors_1.default.gray('(Kode OTP ini akan kedaluwarsa dalam 5 menit)\n'));
73
- console.log('⏳ Menunggu pesan masuk...');
72
+ console.log(picocolors_1.default.gray('(This OTP code will expire in 5 minutes)\n'));
73
+ console.log('⏳ Waiting for incoming message...');
74
74
  }
75
75
  // Security Middleware (OTP & Authorization)
76
76
  bot.use(async (ctx, next) => {
@@ -100,12 +100,12 @@ function startTelegramBot() {
100
100
  currentConfig.integrations.telegram = { enabled: true, bot_token: token };
101
101
  currentConfig.integrations.telegram.authorized_chat_id = ctx.chat?.id;
102
102
  (0, parser_1.saveConfig)(currentConfig);
103
- await ctx.reply('✅ Otorisasi Berhasil! Agen Nyxora kini hanya akan mematuhi perintah Anda. Koneksi diamankan.');
103
+ await ctx.reply('✅ Authorization Successful! Nyxora Agent will now only obey your commands. Connection secured.');
104
104
  console.log(picocolors_1.default.green(`\n[Telegram] Successfully paired with Chat ID: ${ctx.chat?.id}`));
105
105
  return; // Done parsing auth, ignore this specific message for further logic
106
106
  }
107
107
  else {
108
- await ctx.reply('❌ PIN salah.');
108
+ await ctx.reply('❌ Incorrect PIN.');
109
109
  return;
110
110
  }
111
111
  }
@@ -115,7 +115,7 @@ function startTelegramBot() {
115
115
  });
116
116
  bot.command('clear', async (ctx) => {
117
117
  reasoning_1.logger.clear(ctx.chat?.id.toString());
118
- await ctx.reply('Memori AI telah dihapus. Mari kita mulai obrolan baru!');
118
+ await ctx.reply("✅ AI memory has been cleared. Let's start a new chat!");
119
119
  });
120
120
  bot.on('text', async (ctx) => {
121
121
  const text = ctx.message.text;
@@ -216,17 +216,7 @@ function startTelegramBot() {
216
216
  }
217
217
  }
218
218
  transactionManager_1.txManager.updateStatus(txId, 'executed', result);
219
- // Pass session history to formatTransactionSuccess to detect language
220
- const sessionId = ctx.chat?.id.toString() || 'default';
221
- const history = reasoning_1.logger.getHistory(sessionId);
222
- let isIndonesian = false;
223
- if (history.length > 0) {
224
- const lastMsg = history[history.length - 1].content.toLowerCase();
225
- const idWords = ['saya', 'kamu', 'aku', 'apa', 'bagaimana', 'kenapa', 'bisa', 'tolong', 'ke', 'di', 'dari', 'yang', 'ini', 'itu', 'buat', 'cek', 'saldo'];
226
- if (idWords.some(w => lastMsg.includes(w)))
227
- isIndonesian = true;
228
- }
229
- const prettyMsg = (0, formatter_1.formatTransactionSuccess)(tx, result, isIndonesian);
219
+ const prettyMsg = (0, formatter_1.formatTransactionSuccess)(tx, result);
230
220
  await ctx.reply(formatToTelegramHTML(`✅ **Transaction processed: Success**\n\n${prettyMsg}`), { parse_mode: 'HTML' });
231
221
  (0, reasoning_1.processUserInput)(`Transaction ${txId} was APPROVED via Telegram. Result: ${result}`, 'system', undefined, ctx.chat?.id.toString()).catch(() => { });
232
222
  }