nyxora 26.6.13 → 26.6.18

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 +18 -7
  4. package/dist/packages/core/src/agent/reasoning.js +21 -52
  5. package/dist/packages/core/src/config/parser.js +48 -11
  6. package/dist/packages/core/src/gateway/server.js +71 -9
  7. package/dist/packages/core/src/gateway/setup.js +5 -4
  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 +18 -7
  18. package/package.json +5 -7
  19. package/packages/core/package.json +1 -2
  20. package/packages/core/src/agent/reasoning.ts +24 -50
  21. package/packages/core/src/config/parser.ts +49 -22
  22. package/packages/core/src/gateway/server.ts +76 -10
  23. package/packages/core/src/gateway/setup.ts +6 -5
  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-BhKhEfi_.js +0 -13
@@ -171,7 +171,7 @@ app.post('/sign-transaction', async (req, res) => {
171
171
  const rpcNonce = await client.getTransactionCount({ address: account.address, blockTag: 'pending' });
172
172
  let nextNonce = Math.max(rpcNonce, nonceCache[chainId] || 0);
173
173
  const txRequest = txPayload.details?.txRequest || txPayload.details?.txData || txPayload;
174
- // Phase 2: Transaction Simulation (Dry-Run / Anti-Gagal)
174
+ // Phase 2: Transaction Simulation (Dry-Run / Anti-Fail)
175
175
  try {
176
176
  await client.estimateGas({
177
177
  account,
@@ -225,7 +225,7 @@ app.listen(SOCKET_PATH, () => {
225
225
  console.error('Failed to chmod socket:', err);
226
226
  }
227
227
  });
228
- // Phase 3: Graceful Shutdown (Keamanan Keyring Lokal)
228
+ // Phase 3: Graceful Shutdown (Local Keyring Security)
229
229
  const gracefulShutdown = () => {
230
230
  console.log('[Signer Vault] Received shutdown signal. Locking vault...');
231
231
  // @ts-ignore
package/launcher.ts CHANGED
@@ -1,3 +1,4 @@
1
+ // @ts-ignore
1
2
  import { initSafeLogger } from './packages/core/src/utils/safeLogger';
2
3
  initSafeLogger();
3
4
 
@@ -38,7 +39,14 @@ const spawnService = (name: string, command: string, args: string[], env: any, i
38
39
 
39
40
  if (!inheritStdio) {
40
41
  child.stdout?.on('data', (data) => process.stdout.write(`[${name}] ${data}`));
41
- child.stderr?.on('data', (data) => process.stderr.write(`[${name}] ERROR: ${data}`));
42
+ child.stderr?.on('data', (data) => {
43
+ const msg = data.toString();
44
+ if (msg.toLowerCase().includes('warn')) {
45
+ process.stderr.write(`[${name}] ${msg}`);
46
+ } else {
47
+ process.stderr.write(`[${name}] ERROR: ${msg}`);
48
+ }
49
+ });
42
50
  }
43
51
 
44
52
  child.on('close', async (code) => {
@@ -109,22 +117,25 @@ if (fs.existsSync(socketPath)) {
109
117
 
110
118
  const children: { kill: () => void }[] = [];
111
119
 
112
- const isCompiled = __filename.endsWith('.js');
120
+ const __filenameResolved = __filename;
121
+ const __dirnameResolved = __dirname;
122
+
123
+ const isCompiled = __filenameResolved.endsWith('.js');
113
124
  const ext = isCompiled ? '.js' : '.ts';
114
- const cmd = isCompiled ? 'node' : 'npx';
115
- const baseArgs = isCompiled ? [] : ['ts-node', '-T'];
125
+ const cmd = isCompiled ? 'node' : path.join(__dirnameResolved, 'node_modules', '.bin', 'ts-node');
126
+ const baseArgs = isCompiled ? [] : ['-T'];
116
127
 
117
- const signerPath = path.join(__dirname, `packages/signer/src/server${ext}`);
128
+ const signerPath = path.join(__dirnameResolved, `packages/signer/src/server${ext}`);
118
129
  const signer = spawnService('Signer', cmd, [...baseArgs, signerPath], env);
119
130
  children.push(signer);
120
131
 
121
132
  setTimeout(() => {
122
- const policyPath = path.join(__dirname, `packages/policy/src/server${ext}`);
133
+ const policyPath = path.join(__dirnameResolved, `packages/policy/src/server${ext}`);
123
134
  const policy = spawnService('Policy', cmd, [...baseArgs, policyPath], env);
124
135
  children.push(policy);
125
136
 
126
137
  setTimeout(() => {
127
- const corePath = path.join(__dirname, `packages/core/src/gateway/cli${ext}`);
138
+ const corePath = path.join(__dirnameResolved, `packages/core/src/gateway/cli${ext}`);
128
139
  const args = process.argv.slice(2);
129
140
  const core = spawnService('Core', cmd, [...baseArgs, corePath, ...args], env, true);
130
141
  children.push(core);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora",
3
- "version": "26.6.13",
3
+ "version": "26.6.18",
4
4
  "description": "Your Personal Web3 Assistant",
5
5
  "keywords": [
6
6
  "web3",
@@ -27,6 +27,7 @@
27
27
  "packages/signer/src",
28
28
  "packages/signer/package.json",
29
29
  "packages/mcp-server/src",
30
+ "packages/mcp-server/dist",
30
31
  "packages/mcp-server/package.json",
31
32
  "packages/dashboard/dist",
32
33
  "packages/dashboard/package.json"
@@ -35,8 +36,8 @@
35
36
  "nyxora": "bin/nyxora.mjs"
36
37
  },
37
38
  "scripts": {
38
- "prepare": "npm run build --workspace=nyxora-dashboard || echo '⚠️ Build failed – dashboard UI may be missing'",
39
- "dev": "concurrently -n \"BACKEND,FRONTEND\" -c \"blue,green\" \"npx ts-node -T launcher.ts\" \"npm run dev --workspace=nyxora-dashboard\"",
39
+ "prepare": "npm run build",
40
+ "dev": "concurrently -n \"BACKEND,FRONTEND\" -c \"blue,green\" \"NODE_NO_WARNINGS=1 ./node_modules/.bin/ts-node -T launcher.ts\" \"npm run dev --workspace=nyxora-dashboard\"",
40
41
  "start": "node ./bin/nyxora.mjs start",
41
42
  "stop": "node ./bin/nyxora.mjs stop",
42
43
  "restart": "node ./bin/nyxora.mjs restart",
@@ -44,7 +45,7 @@
44
45
  "setup": "node ./bin/nyxora.mjs setup",
45
46
  "set-key": "node ./bin/nyxora.mjs set-key",
46
47
  "doctor": "node ./bin/nyxora.mjs doctor",
47
- "build": "tsc && npm run build --workspace=nyxora-dashboard",
48
+ "build": "tsc && npm run build --workspace=nyxora-mcp-server && npm run build --workspace=nyxora-dashboard",
48
49
  "test": "npm run test --workspace=packages/core"
49
50
  },
50
51
  "dependencies": {
@@ -98,8 +99,5 @@
98
99
  "concurrently": "^10.0.3",
99
100
  "oxc-minify": "^0.135.0",
100
101
  "vitepress": "^2.0.0-alpha.17"
101
- },
102
- "allowScripts": {
103
- "isolated-vm": "true"
104
102
  }
105
103
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora-agent-core",
3
- "version": "26.6.13",
3
+ "version": "26.6.17",
4
4
  "private": true,
5
5
  "main": "src/gateway/server.ts",
6
6
  "dependencies": {
@@ -13,7 +13,6 @@
13
13
  "express": "^5.2.1",
14
14
  "express-rate-limit": "^7.5.0",
15
15
  "helmet": "^8.0.0",
16
- "isolated-vm": "^6.1.2",
17
16
  "mammoth": "^1.12.0",
18
17
  "open": "^11.0.0",
19
18
  "openai": "^6.39.0",
@@ -16,6 +16,7 @@ import { customTxToolDefinition, prepareCustomTx } from '../web3/skills/customTx
16
16
 
17
17
  import { checkSecurityToolDefinition, checkTokenSecurity } from '../web3/skills/checkSecurity';
18
18
  import { marketAnalysisToolDefinition, analyzeMarket } from '../web3/skills/marketAnalysis';
19
+ import { createMarketWatchAgentToolDefinition, createMarketWatchAgent } from '../web3/skills/createMarketWatchAgent';
19
20
  import { checkPortfolioToolDefinition, checkPortfolio } from '../web3/skills/checkPortfolio';
20
21
  import { checkAddressToolDefinition, checkAddress } from '../web3/skills/checkAddress';
21
22
  import { getMyAddressToolDefinition, getMyAddress } from '../web3/skills/getMyAddress';
@@ -36,7 +37,7 @@ import { generateExcelToolDefinition, generateExcelFile } from '../system/skills
36
37
  import { runTerminalCommandToolDefinition, runTerminalCommand } from '../system/skills/executeShell';
37
38
  import { browseWebsiteToolDefinition, browseWebsite } from '../system/skills/browseWeb';
38
39
  import { searchWebToolDefinition, searchWeb } from '../system/skills/searchWeb';
39
- import { installExternalSkillToolDefinition, installExternalSkill } from '../system/skills/installSkill';
40
+
40
41
  import { editLocalFileToolDefinition, editLocalFile } from '../system/skills/editFile';
41
42
  import { gitManagerToolDefinition, executeGitCommand } from '../system/skills/gitManager';
42
43
  import { xManagerToolDefinition, manageTwitter } from '../system/skills/xManager';
@@ -55,7 +56,7 @@ import {
55
56
  readGoogleDocsToolDefinition,
56
57
  readGoogleFormResponsesToolDefinition
57
58
  } from '../system/skills/googleWorkspace';
58
- import { pluginManager } from '../system/pluginManager';
59
+
59
60
  import { getPath } from '../config/paths';
60
61
  import pc from 'picocolors';
61
62
 
@@ -156,7 +157,7 @@ IMPORTANT: The <think> block is strictly for your internal hidden monologue. NEV
156
157
 
157
158
  [EXECUTION WORKFLOW]
158
159
  CRITICAL RULE 1: NEVER expose internal JSON tool calls to the user. Always parse them and explain the outcome naturally.
159
- CRITICAL RULE 2: STRICT LANGUAGE MATCHING. You MUST strictly reply in the exact same language as the user's LATEST prompt.
160
+ 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.
160
161
  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.
161
162
  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.
162
163
  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}.
@@ -197,15 +198,19 @@ CRITICAL RULE 19: MARKET CONFIDENCE SCORE. When analyzing market data, token sec
197
198
  console.error('Failed to read user.md:', error);
198
199
  }
199
200
 
200
- // Read security_policy.md for NLP security constraints
201
+ // Read policy.yaml for NLP security constraints
201
202
  try {
202
- const policyPath = getPath('security_policy.md');
203
+ const policyPath = getPath('policy.yaml');
203
204
  if (fs.existsSync(policyPath)) {
204
- const securityInstructions = fs.readFileSync(policyPath, 'utf8');
205
- 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.`;
205
+ const yaml = require('yaml'); // lazily import if not imported
206
+ const file = fs.readFileSync(policyPath, 'utf8');
207
+ const parsed = yaml.parse(file) || {};
208
+ if (parsed.custom_llm_rules && Array.isArray(parsed.custom_llm_rules) && parsed.custom_llm_rules.length > 0) {
209
+ basePrompt += `\n\n--- SECURITY POLICY (MANDATORY RULES) ---\n${parsed.custom_llm_rules.map((r: string) => `* ${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.`;
210
+ }
206
211
  }
207
212
  } catch (error) {
208
- console.error('Failed to read security_policy.md:', error);
213
+ console.error('Failed to read policy.yaml:', error);
209
214
  }
210
215
 
211
216
  // Inject Episodic Memories (Smart Suggestions Context)
@@ -285,6 +290,7 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
285
290
  customTxToolDefinition,
286
291
  checkSecurityToolDefinition,
287
292
  marketAnalysisToolDefinition,
293
+ createMarketWatchAgentToolDefinition,
288
294
  checkPortfolioToolDefinition,
289
295
  checkAddressToolDefinition,
290
296
  getMyAddressToolDefinition,
@@ -297,16 +303,16 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
297
303
  createLimitOrderToolDefinition
298
304
  );
299
305
  }
300
- const SYSTEM_TOOLS = [updateProfileToolDefinition, updateSecurityPolicyToolDefinition, analyzeDocumentToolDefinition, readLocalFileToolDefinition, writeLocalFileToolDefinition, generateExcelToolDefinition, runTerminalCommandToolDefinition, browseWebsiteToolDefinition, searchWebToolDefinition, installExternalSkillToolDefinition, editLocalFileToolDefinition, gitManagerToolDefinition, xManagerToolDefinition, notionWorkspaceToolDefinition, audioTranscribeToolDefinition, summarizeTextToolDefinition];
306
+ const SYSTEM_TOOLS = [updateProfileToolDefinition, updateSecurityPolicyToolDefinition, analyzeDocumentToolDefinition, readLocalFileToolDefinition, writeLocalFileToolDefinition, generateExcelToolDefinition, runTerminalCommandToolDefinition, browseWebsiteToolDefinition, searchWebToolDefinition, editLocalFileToolDefinition, gitManagerToolDefinition, xManagerToolDefinition, notionWorkspaceToolDefinition, audioTranscribeToolDefinition, summarizeTextToolDefinition];
301
307
  const GOOGLE_TOOLS = [readGmailInboxToolDefinition, listCalendarEventsToolDefinition, appendRowToSheetsToolDefinition, readGoogleDocsToolDefinition, readGoogleFormResponsesToolDefinition];
302
308
 
303
309
  let activeTools: any[] = [];
304
310
  if (hasGoogleKeyword && !hasWeb3Keyword) {
305
- activeTools = [...GOOGLE_TOOLS, ...SYSTEM_TOOLS, ...pluginManager.getToolDefinitions()];
311
+ activeTools = [...GOOGLE_TOOLS, ...SYSTEM_TOOLS];
306
312
  } else if (hasWeb3Keyword && !hasGoogleKeyword) {
307
- activeTools = [...tools, ...SYSTEM_TOOLS, ...pluginManager.getToolDefinitions()];
313
+ activeTools = [...tools, ...SYSTEM_TOOLS];
308
314
  } else {
309
- activeTools = [...tools, ...SYSTEM_TOOLS, ...GOOGLE_TOOLS, ...pluginManager.getToolDefinitions()];
315
+ activeTools = [...tools, ...SYSTEM_TOOLS, ...GOOGLE_TOOLS];
310
316
  }
311
317
  activeTools = activeTools.filter(t => isSkillActive(t.function.name));
312
318
 
@@ -376,10 +382,6 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
376
382
  }
377
383
  case 'transfer_token':
378
384
  case 'transfer_native': {
379
- if (config.permissions?.web3?.allow_transfer === false) {
380
- result = `[Security Blocked] Runtime Permission Denied: Web3 transfers are disabled. Update config.yaml to allow.`;
381
- break;
382
- }
383
385
  result = await prepareTransfer(args.chainName, args.toAddress, args.amountStr || args.amountEth, args.token);
384
386
  break;
385
387
  }
@@ -388,18 +390,10 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
388
390
  break;
389
391
  }
390
392
  case 'swap_token': {
391
- if (config.permissions?.web3?.allow_swap === false) {
392
- result = `[Security Blocked] Runtime Permission Denied: Web3 swaps are disabled. Update config.yaml to allow.`;
393
- break;
394
- }
395
393
  result = await prepareSwapToken(args.chainName, args.fromToken, args.toToken, args.amountStr || args.amount, args.mode, args.providerName);
396
394
  break;
397
395
  }
398
396
  case 'bridge_token': {
399
- if (config.permissions?.web3?.allow_transfer === false) {
400
- result = `[Security Blocked] Runtime Permission Denied: Web3 bridging (transfer) is disabled. Update config.yaml to allow.`;
401
- break;
402
- }
403
397
  result = await prepareBridgeToken(args.fromChain, args.toChain, args.tokenSymbol, args.amountStr, args.mode, args.providerName);
404
398
  break;
405
399
  }
@@ -408,10 +402,6 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
408
402
  break;
409
403
  }
410
404
  case 'custom_tx': {
411
- if (config.permissions?.web3?.allow_transfer === false) {
412
- result = `[Security Blocked] Runtime Permission Denied: Custom transactions are blocked because transfers are disabled.`;
413
- break;
414
- }
415
405
  result = await prepareCustomTx(args.chainName, args.toAddress, args.dataHex, args.valueEth, args.gasLimitStr);
416
406
  break;
417
407
  }
@@ -423,6 +413,10 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
423
413
  result = await analyzeMarket(args.chainName, args.tokenAddressOrSymbol);
424
414
  break;
425
415
  }
416
+ case 'create_market_watch_agent': {
417
+ result = await createMarketWatchAgent(args.chainName, args.contractAddress, args.rules, args.durationDays);
418
+ break;
419
+ }
426
420
  case 'check_portfolio': {
427
421
  result = await checkPortfolio(args.chainName, args.address);
428
422
  break;
@@ -535,26 +529,14 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
535
529
  break;
536
530
  }
537
531
  case 'write_local_file': {
538
- if (config.permissions?.system?.allow_file_write === false) {
539
- result = `[Security Blocked] Runtime Permission Denied: File writing is disabled. Update config.yaml to allow.`;
540
- break;
541
- }
542
532
  result = writeLocalFile(args.filePath, args.content);
543
533
  break;
544
534
  }
545
535
  case 'generate_excel_file': {
546
- if (config.permissions?.system?.allow_file_write === false) {
547
- result = `[Security Blocked] Runtime Permission Denied: File writing is disabled. Update config.yaml to allow.`;
548
- break;
549
- }
550
536
  result = await generateExcelFile(args.data, args.filePath);
551
537
  break;
552
538
  }
553
539
  case 'run_terminal_command': {
554
- if (config.permissions?.system?.allow_shell_execution === false) {
555
- result = `[Security Blocked] Runtime Permission Denied: Shell execution is disabled. Update config.yaml to allow.`;
556
- break;
557
- }
558
540
  result = await runTerminalCommand(args.command);
559
541
  break;
560
542
  }
@@ -566,10 +548,7 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
566
548
  result = await searchWeb(args.query, args.depth);
567
549
  break;
568
550
  }
569
- case 'install_external_skill': {
570
- result = await installExternalSkill(args.url);
571
- break;
572
- }
551
+
573
552
  case 'read_gmail_inbox': {
574
553
  result = await readGmailInbox(args.maxResults);
575
554
  break;
@@ -591,12 +570,7 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
591
570
  break;
592
571
  }
593
572
  default: {
594
- const externalResult = await pluginManager.executeTool(toolName, args);
595
- if (externalResult !== null) {
596
- result = externalResult;
597
- } else {
598
- result = `Error: Tool ${toolName} is not implemented.`;
599
- }
573
+ result = `Error: Tool ${toolName} is not implemented.`;
600
574
  break;
601
575
  }
602
576
  }
@@ -3,6 +3,27 @@ import yaml from 'yaml';
3
3
  import path from 'path';
4
4
  import { getPath } from './paths';
5
5
 
6
+ export function loadRpcConfig(): Record<string, string | string[]> {
7
+ const rpcPath = getPath('rpc_key.yaml');
8
+ if (fs.existsSync(rpcPath)) {
9
+ try {
10
+ return yaml.parse(fs.readFileSync(rpcPath, 'utf8')) || {};
11
+ } catch (e) {
12
+ console.error('[Config] Failed to parse rpc_key.yaml', e);
13
+ }
14
+ }
15
+ return {};
16
+ }
17
+
18
+ export function saveRpcConfig(rpcUrls: Record<string, string | string[]>): void {
19
+ const rpcPath = getPath('rpc_key.yaml');
20
+ try {
21
+ fs.writeFileSync(rpcPath, yaml.stringify(rpcUrls), 'utf8');
22
+ } catch (error) {
23
+ console.error('Failed to save rpc_key.yaml', error);
24
+ }
25
+ }
26
+
6
27
  export async function loadApiKeys(): Promise<Record<string, string>> {
7
28
  const config = loadConfig();
8
29
  return config.credentials || {};
@@ -64,21 +85,13 @@ export interface NyxoraConfig {
64
85
  channels?: {
65
86
  active: string[];
66
87
  };
67
- permissions?: {
68
- web3?: {
69
- allow_transfer?: boolean;
70
- allow_swap?: boolean;
71
- max_usd_per_tx?: number;
72
- };
73
- system?: {
74
- allow_shell_execution?: boolean;
75
- allow_file_write?: boolean;
76
- };
77
- };
78
88
  }
79
89
 
80
90
  export function loadConfig(): NyxoraConfig {
81
91
  const configPath = getPath('config.yaml');
92
+ const rpcPath = getPath('rpc_key.yaml');
93
+ let rpcUrls = loadRpcConfig();
94
+
82
95
  try {
83
96
  const file = fs.readFileSync(configPath, 'utf8');
84
97
  const parsed = yaml.parse(file) as Partial<NyxoraConfig>;
@@ -99,6 +112,24 @@ export function loadConfig(): NyxoraConfig {
99
112
  needsSave = true;
100
113
  }
101
114
 
115
+ // Ensure we don't accidentally overwrite rpc_key.yaml with old config.yaml data.
116
+ if (parsed.web3 && parsed.web3.rpc_urls) {
117
+ delete parsed.web3.rpc_urls;
118
+ needsSave = true;
119
+ }
120
+
121
+ // Auto-migration logic: move permissions to policy.yaml
122
+ const policyPath = getPath('policy.yaml');
123
+ if (!fs.existsSync(policyPath)) {
124
+ const defaultPolicy = `max_usd_per_tx: ${(parsed as any).permissions?.web3?.max_usd_per_tx || 999999999}\nwhitelist_only: false\nrequire_approval: true\n`;
125
+ fs.writeFileSync(policyPath, defaultPolicy, 'utf8');
126
+ console.log('[Config] Created default policy.yaml.');
127
+ }
128
+ if ((parsed as any).permissions) {
129
+ delete (parsed as any).permissions;
130
+ needsSave = true;
131
+ }
132
+
102
133
  if (needsSave) {
103
134
  try {
104
135
  const yamlStr = yaml.stringify(parsed);
@@ -125,13 +156,9 @@ export function loadConfig(): NyxoraConfig {
125
156
  },
126
157
  credentials: parsed.credentials || {},
127
158
  memory: parsed.memory || { type: 'file', path: './memory.json' },
128
- web3: parsed.web3 || { rpc_urls: {} },
159
+ web3: { ...parsed.web3, rpc_urls: rpcUrls },
129
160
  integrations: parsed.integrations || {
130
161
  telegram: { enabled: false }
131
- },
132
- permissions: parsed.permissions || {
133
- web3: { allow_transfer: true, allow_swap: true, max_usd_per_tx: 999999999 },
134
- system: { allow_shell_execution: true, allow_file_write: true }
135
162
  }
136
163
  } as NyxoraConfig;
137
164
  } catch (error: any) {
@@ -160,13 +187,9 @@ export function loadConfig(): NyxoraConfig {
160
187
  },
161
188
  credentials: {},
162
189
  memory: { type: 'file', path: './memory.json' },
163
- web3: { rpc_urls: {} },
190
+ web3: { rpc_urls: rpcUrls },
164
191
  integrations: {
165
192
  telegram: { enabled: false }
166
- },
167
- permissions: {
168
- web3: { allow_transfer: true, allow_swap: true, max_usd_per_tx: 999999999 },
169
- system: { allow_shell_execution: true, allow_file_write: true }
170
193
  }
171
194
  };
172
195
  }
@@ -175,7 +198,11 @@ export function loadConfig(): NyxoraConfig {
175
198
  export function saveConfig(newConfig: NyxoraConfig): void {
176
199
  const configPath = getPath('config.yaml');
177
200
  try {
178
- const yamlStr = yaml.stringify(newConfig);
201
+ const configToSave = JSON.parse(JSON.stringify(newConfig));
202
+ if (configToSave.web3 && configToSave.web3.rpc_urls) {
203
+ delete configToSave.web3.rpc_urls;
204
+ }
205
+ const yamlStr = yaml.stringify(configToSave);
179
206
  fs.writeFileSync(configPath, yamlStr, 'utf8');
180
207
  } catch (error) {
181
208
  console.error('Failed to save config.yaml', error);
@@ -21,14 +21,15 @@ import { getPath } from '../config/paths';
21
21
  import { validateToken, getSessionToken } from '../utils/state';
22
22
 
23
23
  import fs from 'fs';
24
+ import yaml from 'yaml';
24
25
  import { processUserInput, logger } from '../agent/reasoning';
25
- import { loadConfig, saveConfig } from '../config/parser';
26
+ import { loadConfig, saveConfig, loadRpcConfig, saveRpcConfig } from '../config/parser';
26
27
  import { loadDefiKeys, saveDefiKeys } from '../config/defiConfigManager';
27
28
  import { getPublicClient, SUPPORTED_CHAIN_NAMES, getAddress } from '../web3/config';
28
29
  import { TOKEN_MAP, ERC20_ABI } from '../web3/utils/tokens';
29
30
  import { Tracker } from './tracker';
30
31
  import { txManager } from '../agent/transactionManager';
31
- import { pluginManager } from '../system/pluginManager';
32
+
32
33
  import { executeTransfer, transferToolDefinition } from '../web3/skills/transfer';
33
34
  import { executeSwap, swapTokenToolDefinition } from '../web3/skills/swapToken';
34
35
  import { getBalanceToolDefinition } from '../web3/skills/getBalance';
@@ -56,7 +57,7 @@ import { createLimitOrderToolDefinition } from '../web3/skills/createLimitOrder'
56
57
  // System Skills
57
58
  import { browseWebsiteToolDefinition } from '../system/skills/browseWeb';
58
59
  import { runTerminalCommandToolDefinition } from '../system/skills/executeShell';
59
- import { installExternalSkillToolDefinition } from '../system/skills/installSkill';
60
+
60
61
  import { readLocalFileToolDefinition } from '../system/skills/readFile';
61
62
  import { editLocalFileToolDefinition } from '../system/skills/editFile';
62
63
  import { gitManagerToolDefinition } from '../system/skills/gitManager';
@@ -287,6 +288,26 @@ app.post('/api/config', (req, res) => {
287
288
  }
288
289
  });
289
290
 
291
+ app.get('/api/rpc', (req, res) => {
292
+ try {
293
+ const rpcConfig = loadRpcConfig();
294
+ res.json(rpcConfig);
295
+ } catch (error: any) {
296
+ res.status(500).json({ error: error.message });
297
+ }
298
+ });
299
+
300
+ app.post('/api/rpc', (req, res) => {
301
+ try {
302
+ const currentRpc = loadRpcConfig();
303
+ const newRpc = { ...currentRpc, ...req.body };
304
+ saveRpcConfig(newRpc);
305
+ res.json({ success: true });
306
+ } catch (error: any) {
307
+ res.status(500).json({ error: error.message });
308
+ }
309
+ });
310
+
290
311
  app.get('/api/defi-keys', (req, res) => {
291
312
  try {
292
313
  const keys = loadDefiKeys();
@@ -356,7 +377,7 @@ app.get('/api/skills/system', (req, res) => {
356
377
  generateExcelToolDefinition,
357
378
  browseWebsiteToolDefinition,
358
379
  updateSecurityPolicyToolDefinition,
359
- installExternalSkillToolDefinition,
380
+
360
381
  analyzeDocumentToolDefinition,
361
382
  searchWebToolDefinition,
362
383
  readGmailInboxToolDefinition,
@@ -829,6 +850,45 @@ app.delete('/api/memory/:id', (req, res) => {
829
850
  }
830
851
  });
831
852
 
853
+ // --- Policy Engine Endpoints ---
854
+ app.get('/api/policy', (req, res) => {
855
+ try {
856
+ const policyPath = getPath('policy.yaml');
857
+ if (!fs.existsSync(policyPath)) {
858
+ return res.json({
859
+ max_usd_per_tx: 999999999,
860
+ whitelist_only: false,
861
+ require_approval: true,
862
+ custom_llm_rules: []
863
+ });
864
+ }
865
+ const file = fs.readFileSync(policyPath, 'utf8');
866
+ const parsed = yaml.parse(file) || {};
867
+ res.json({
868
+ max_usd_per_tx: parsed.max_usd_per_tx ?? 999999999,
869
+ whitelist_only: parsed.whitelist_only ?? false,
870
+ require_approval: parsed.require_approval ?? true,
871
+ custom_llm_rules: parsed.custom_llm_rules || []
872
+ });
873
+ } catch (error: any) {
874
+ res.status(500).json({ error: error.message });
875
+ }
876
+ });
877
+
878
+ app.post('/api/policy', (req, res) => {
879
+ try {
880
+ const policyPath = getPath('policy.yaml');
881
+ let current = {};
882
+ if (fs.existsSync(policyPath)) {
883
+ current = yaml.parse(fs.readFileSync(policyPath, 'utf8')) || {};
884
+ }
885
+ const updated = { ...current, ...req.body };
886
+ fs.writeFileSync(policyPath, yaml.stringify(updated), 'utf8');
887
+ res.json({ success: true });
888
+ } catch (error: any) {
889
+ res.status(500).json({ error: error.message });
890
+ }
891
+ });
832
892
 
833
893
  // --- User Persona / Risk Profile Endpoints (V3) ---
834
894
  app.get('/api/profile', (req, res) => {
@@ -893,9 +953,7 @@ export async function autoMigrateKeys() {
893
953
  export function startServer() {
894
954
  autoMigrateKeys().catch(e => console.error('[Auto-Migrate] Error:', e));
895
955
 
896
- pluginManager.loadPlugins().then(() => {
897
- console.log(`[PluginManager] Finished loading external skills.`);
898
- });
956
+
899
957
 
900
958
  const PORT = Number(process.env.PORT || 3000);
901
959
  const server = app.listen(PORT, '127.0.0.1', () => {
@@ -921,19 +979,27 @@ export function startServer() {
921
979
  }
922
980
  });
923
981
 
982
+ let isShuttingDown = false;
924
983
  const gracefulShutdown = () => {
984
+ if (isShuttingDown) return;
985
+ isShuttingDown = true;
925
986
  console.log('[Nyxora Gateway] Received shutdown signal. Closing server...');
987
+
988
+ if (server.closeAllConnections) {
989
+ server.closeAllConnections();
990
+ }
991
+
926
992
  server.close(() => {
927
993
  console.log('[Nyxora Gateway] HTTP server closed.');
928
994
  logger.close();
929
995
  process.exit(0);
930
996
  });
931
997
 
932
- // Force exit after 10s if stuck
998
+ // Force exit after 3s if stuck
933
999
  setTimeout(() => {
934
- console.error('[Nyxora Gateway] Forced shutdown after 10s.');
1000
+ console.error('[Nyxora Gateway] Forced shutdown.');
935
1001
  process.exit(1);
936
- }, 10000);
1002
+ }, 3000).unref();
937
1003
  };
938
1004
 
939
1005
  process.on('SIGTERM', gracefulShutdown);
@@ -4,7 +4,7 @@ import pc from 'picocolors';
4
4
  import fs from 'fs';
5
5
  import path from 'path';
6
6
  import { getAppDir, getPath } from '../config/paths';
7
- import { loadConfig, saveConfig, saveApiKeys } from '../config/parser';
7
+ import { loadConfig, saveConfig, saveApiKeys, saveRpcConfig } from '../config/parser';
8
8
  import crypto from 'crypto';
9
9
 
10
10
  function encryptKey(privateKey: string, password: string) {
@@ -302,9 +302,8 @@ Provider: ${config.llm.provider}`;
302
302
  { value: 'generateExcel', label: 'Generate Excel Reports' },
303
303
  { value: 'analyzeDocument', label: 'Analyze Docs (PDF/Word)' },
304
304
  { value: 'run_terminal', label: 'Run Terminal Command', hint: '⚠️ UNSAFE' },
305
- { value: 'installSkill', label: 'Install External Skills (Plugins)' },
306
305
  { value: 'gitManager', label: 'Git Operations (Commit/Push/Pull)' },
307
- { value: 'updateSecurityPolicy', label: 'Update security_policy.md', hint: 'safeguard' },
306
+ { value: 'updateSecurityPolicy', label: 'Update policy.yaml rules', hint: 'safeguard' },
308
307
  { value: 'browseWeb', label: 'Browse & Scrape Webpages' },
309
308
  { value: 'searchWeb', label: 'Smart Web Search (Tavily/Brave)', hint: 'Requires API Key' },
310
309
  { value: 'googleWorkspace', label: 'Google Workspace (Gmail, Docs, Sheets, Forms)', hint: 'Requires OAuth' },
@@ -429,7 +428,8 @@ Provider: ${config.llm.provider}`;
429
428
  }
430
429
 
431
430
  if (Object.keys(newApiKeys).length > 0) {
432
- await saveApiKeys(newApiKeys);
431
+ if (!config.credentials) config.credentials = {};
432
+ config.credentials = { ...config.credentials, ...newApiKeys };
433
433
  }
434
434
 
435
435
  if (!config.integrations) config.integrations = {};
@@ -446,6 +446,7 @@ Provider: ${config.llm.provider}`;
446
446
 
447
447
  saveConfig(config);
448
448
 
449
+
449
450
  // Sync disabled_skills.json based on user selection
450
451
  const allWeb3Skills = [
451
452
  'transfer', 'swapToken', 'bridgeToken', 'customTx', 'mintNft',
@@ -456,7 +457,7 @@ Provider: ${config.llm.provider}`;
456
457
 
457
458
  const allOsSkills = [
458
459
  'readFile', 'writeFile', 'editFile', 'generateExcel', 'analyzeDocument',
459
- 'run_terminal', 'installSkill', 'gitManager', 'updateSecurityPolicy',
460
+ 'run_terminal', 'gitManager', 'updateSecurityPolicy',
460
461
  'browseWeb', 'searchWeb', 'googleWorkspace', 'notionWorkspace', 'xManager',
461
462
  'audioTranscribe', 'summarizeText'
462
463
  ];