nyxora 1.7.0 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.dockerignore +12 -0
  2. package/CHANGELOG.md +21 -0
  3. package/DOCKER.md +68 -0
  4. package/Dockerfile +43 -0
  5. package/bin/nyxora.mjs +8 -0
  6. package/package.json +1 -2
  7. package/packages/core/package.json +1 -1
  8. package/packages/core/src/agent/limitOrderManager.ts +2 -2
  9. package/packages/core/src/agent/reasoning.ts +12 -10
  10. package/packages/core/src/config/parser.ts +105 -11
  11. package/packages/core/src/gateway/cli.ts +28 -2
  12. package/packages/core/src/gateway/googleAuthModule.ts +19 -0
  13. package/packages/core/src/gateway/server.ts +9 -3
  14. package/packages/core/src/gateway/setup.ts +44 -9
  15. package/packages/core/src/gateway/telegram.ts +3 -1
  16. package/packages/core/src/system/skills/searchWeb.ts +187 -21
  17. package/packages/core/src/web3/config.ts +7 -1
  18. package/packages/core/src/web3/skills/bridgeToken.ts +4 -3
  19. package/packages/core/src/web3/skills/checkAddress.ts +2 -2
  20. package/packages/core/src/web3/skills/checkPortfolio.ts +2 -2
  21. package/packages/core/src/web3/skills/checkSecurity.ts +3 -2
  22. package/packages/core/src/web3/skills/customTx.ts +2 -2
  23. package/packages/core/src/web3/skills/getBalance.ts +3 -3
  24. package/packages/core/src/web3/skills/marketAnalysis.ts +2 -2
  25. package/packages/core/src/web3/skills/mintNft.ts +2 -2
  26. package/packages/core/src/web3/skills/swapToken.ts +4 -3
  27. package/packages/core/src/web3/skills/transfer.ts +2 -2
  28. package/packages/core/src/web3/utils/tokens.ts +8 -0
  29. package/packages/dashboard/dist/assets/{index-24OeXn-k.css → index-BSk4CLkG.css} +1 -1
  30. package/packages/dashboard/dist/assets/{index-DQtaOlOl.js → index-cGPka4s1.js} +14 -14
  31. package/packages/dashboard/dist/index.html +2 -2
  32. package/packages/dashboard/package.json +1 -1
  33. package/packages/mcp-server/package.json +2 -2
  34. package/packages/policy/package.json +2 -2
  35. package/packages/policy/src/server.ts +2 -2
  36. package/packages/signer/package.json +2 -2
  37. package/packages/dashboard/public/favicon.svg +0 -10
  38. package/packages/dashboard/public/icons.svg +0 -24
  39. package/packages/dashboard/src/App.css +0 -184
  40. package/packages/dashboard/src/App.tsx +0 -585
  41. package/packages/dashboard/src/BalanceWidget.tsx +0 -65
  42. package/packages/dashboard/src/MarketWidget.tsx +0 -73
  43. package/packages/dashboard/src/NetworkSelector.tsx +0 -64
  44. package/packages/dashboard/src/NyxoraLogo.tsx +0 -25
  45. package/packages/dashboard/src/OsSkills.tsx +0 -352
  46. package/packages/dashboard/src/Overview.tsx +0 -157
  47. package/packages/dashboard/src/PendingTransactions.tsx +0 -75
  48. package/packages/dashboard/src/Settings.tsx +0 -338
  49. package/packages/dashboard/src/Skills.tsx +0 -200
  50. package/packages/dashboard/src/SwapWidget.tsx +0 -141
  51. package/packages/dashboard/src/TransactionWidget.tsx +0 -95
  52. package/packages/dashboard/src/assets/hero.png +0 -0
  53. package/packages/dashboard/src/assets/react.svg +0 -1
  54. package/packages/dashboard/src/assets/vite.svg +0 -1
  55. package/packages/dashboard/src/components/PillSelect.tsx +0 -65
  56. package/packages/dashboard/src/index.css +0 -807
  57. package/packages/dashboard/src/main.tsx +0 -10
  58. package/packages/dashboard/src/overview.css +0 -304
  59. package/packages/dashboard/src/utils/api.ts +0 -31
package/.dockerignore ADDED
@@ -0,0 +1,12 @@
1
+ node_modules
2
+ .git
3
+ .github
4
+ .env
5
+ .env.*
6
+ *.log
7
+ .DS_Store
8
+ dist
9
+ build
10
+ .nyxora
11
+ vault.key
12
+ api_vault.key
package/CHANGELOG.md CHANGED
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.7.2] - Unreleased
9
+
10
+ ### UI/UX Enhancements
11
+ - **Google Workspace Logout**: Users can now easily disconnect their Google Workspace accounts directly from the Dashboard (OS Skills tab). This triggers a secure token purge from both the OS Keyring and local storage, ensuring privacy and seamless account switching.
12
+
13
+ ## [1.7.1]
14
+
15
+ ### CLI Enhancements
16
+ - **Global Version Checker**: Implemented native version checking for the global CLI manager. Users can now run `nyxora -v`, `nyxora --version`, or `nyxora version` to instantly check their installed daemon version without starting the application.
17
+ - **Smart Web Search Setup Wizard**: The `nyxora setup` command now includes an interactive prompt allowing users to choose their preferred Web Search Engine (Tavily, Brave, or Decentralized Mesh) and configure their API keys.
18
+ - **Fast CLI Shortcuts**: Added the `nyxora set-key <provider> <key>` global command shortcut allowing developers to quickly inject or override any API Key (OpenAI, Gemini, OpenRouter, Tavily, Brave) directly into the secure vault without traversing the wizard.
19
+
20
+ ### AI Engine Optimizations
21
+ - **Hybrid API Vault (Security)**: API Keys are no longer stored in plain text inside `config.yaml`. Nyxora now encrypts and stores them via `@napi-rs/keyring` utilizing OS-native credential management. For Headless/VPS Linux environments lacking DBUS/Secret Service, it automatically falls back to an isolated `api_vault.key` with strict `0600` permissions.
22
+ - **Root-Level Config Auto-Migration**: Restructured `config.yaml` to move all API keys out of the nested `llm.credentials` into a logical, root-level `credentials` object. Implemented a silent auto-migration routine in `parser.ts` that safely upgrades legacy config files on boot without breaking existing setups.
23
+ - **Web Search Smart Memory Cache**: Embedded a local Memory Cache (`Map`) into the `searchWeb` skill with a 5-minute (300,000ms) TTL. Exact duplicate queries now execute in 0ms and consume 0 API quota, dramatically improving conversation flow.
24
+ - **Deep Research Mode**: The `search_web` tool definition now accepts a dynamic `depth` parameter (1 to 3). If users instruct the AI to conduct comprehensive research, Nyxora will automatically trigger `advanced` API payloads and extract up to 15 top web snippets simultaneously.
25
+ - **Strict Skill Prioritization**: Added CRITICAL RULE 7 to the core NLP System Prompt. The AI is now hard-coded to prioritize native Web3 Skills (e.g. `get_price`, `analyze_market`, `check_security`) for all crypto-related queries, using `search_web` exclusively as a fallback mechanism.
26
+ - **Dual-Engine Web Search (L3 Failover)**: Completely removed the fragile `duck-duck-scrape` dependency. The `search_web` skill is now powered by a robust L3 Auto-Failover architecture. Users can configure enterprise-grade search providers (Tavily or Brave). If the primary provider hits a rate limit (429) or invalid key (401/403), Nyxora seamlessly falls back to the secondary provider, and ultimately to a Decentralized SearXNG Mesh as a final safety net, guaranteeing 100% uptime.
27
+
28
+
8
29
  ## [1.7.0]
9
30
 
10
31
  ### Bug Fixes & Optimizations
package/DOCKER.md ADDED
@@ -0,0 +1,68 @@
1
+ # 🐳 Nyxora Docker Guide
2
+
3
+ This document provides a comprehensive guide to installing, configuring interactively, and running the Nyxora Agent fully containerized via Docker. This approach ensures Nyxora can run seamlessly across different environments (Linux, macOS, Windows) without requiring a local Node.js installation.
4
+
5
+ ## 🛠 1. Get the Docker Image
6
+
7
+ You have two options to obtain the Nyxora Docker image:
8
+
9
+ ### Option A: Pull Pre-built Image (Recommended & Fastest)
10
+ Nyxora officially publishes Docker images via GitHub Container Registry (GHCR) upon every release.
11
+ ```bash
12
+ docker pull ghcr.io/nyxoraai/nyxora:latest
13
+ ```
14
+
15
+ ### Option B: Build Locally
16
+ If you are developing or prefer to compile it yourself, run this in the root directory:
17
+ ```bash
18
+ docker build -t ghcr.io/nyxoraai/nyxora:latest .
19
+ ```
20
+ > **Note:** The initial build takes time as it compiles C++/Rust system modules (`isolated-vm`, `libsecret`).
21
+
22
+ ---
23
+
24
+ ## ⚙️ 2. Interactive Setup (Secure & Isolated)
25
+ Nyxora requires an initial configuration (API Keys, Web3 Wallet, etc.). Run the command below to launch the **Interactive Setup Wizard** securely inside Docker.
26
+
27
+ This command mounts a volume and saves your configurations safely to a `.nyxora_docker` folder in your computer's Home directory, ensuring your existing local Nyxora installation remains untouched.
28
+ ```bash
29
+ docker run -it --rm -v ~/.nyxora_docker:/root/.nyxora ghcr.io/nyxoraai/nyxora:latest npx ts-node -T packages/core/src/gateway/setup-cli.ts
30
+ ```
31
+ *Complete the setup by answering the prompts in your terminal. Once you see "Setup Successful!", this temporary container will automatically delete itself.*
32
+
33
+ ---
34
+
35
+ ## 🚀 3. Start Nyxora (Background Daemon)
36
+ Now that the setup is complete, it's time to start the main architecture (Core API, Policy Engine, and Signer Vault) as a non-stop background daemon:
37
+ ```bash
38
+ docker run -d --name nyxora-daemon -p 3000:3000 -p 3001:3001 -v ~/.nyxora_docker:/root/.nyxora ghcr.io/nyxoraai/nyxora:latest
39
+ ```
40
+ *(This command will output a long container ID indicating that the daemon is successfully running).*
41
+
42
+ ---
43
+
44
+ ## 🔑 4. Retrieve the Auth Token
45
+ For security reasons, the Web Dashboard is locked behind a randomly generated runtime token upon every boot. Retrieve this token from the Docker logs:
46
+ ```bash
47
+ docker logs nyxora-daemon
48
+ ```
49
+ Look for a line that says: `[Launcher] Generated Internal Auth Token: <LONG_TOKEN_CODE>`. Please copy that token code.
50
+
51
+ ---
52
+
53
+ ## 💻 5. Access the Web Dashboard
54
+ Open your preferred web browser, and access the following URL (replace `<LONG_TOKEN_CODE>` with the token you copied in Step 4):
55
+ ```text
56
+ http://localhost:3000/?token=<LONG_TOKEN_CODE>
57
+ ```
58
+ Congratulations! Your Nyxora Agent is now fully operational.
59
+
60
+ ---
61
+
62
+ ## 🧰 Docker Management Reference
63
+ Here are some useful commands for managing your Nyxora daemon in the future:
64
+
65
+ * **Stop Nyxora:** `docker stop nyxora-daemon`
66
+ * **Start Nyxora again:** `docker start nyxora-daemon`
67
+ * **Monitor real-time AI logs:** `docker logs -f nyxora-daemon` (Press `Ctrl+C` to exit the stream).
68
+ * **Remove the container (e.g., to reset or upgrade):** `docker rm -f nyxora-daemon`
package/Dockerfile ADDED
@@ -0,0 +1,43 @@
1
+ FROM node:22-bookworm-slim
2
+
3
+ # Set working directory
4
+ WORKDIR /app
5
+
6
+ # Set Production Environment
7
+ # Install native dependencies required for isolated-vm, keyring, etc.
8
+ RUN apt-get update && apt-get install -y \
9
+ python3 \
10
+ make \
11
+ g++ \
12
+ libsecret-1-dev \
13
+ && rm -rf /var/lib/apt/lists/*
14
+
15
+ # Copy package metadata first to leverage Docker caching
16
+ COPY package*.json ./
17
+ COPY packages/core/package*.json ./packages/core/
18
+ COPY packages/dashboard/package*.json ./packages/dashboard/
19
+ COPY packages/mcp-server/package*.json ./packages/mcp-server/
20
+ COPY packages/policy/package*.json ./packages/policy/
21
+ COPY packages/signer/package*.json ./packages/signer/
22
+
23
+ # Install dependencies
24
+ RUN npm install
25
+
26
+ # Copy the rest of the application code
27
+ COPY . .
28
+
29
+ # Build the dashboard (frontend)
30
+ RUN npm run build --workspace=dashboard
31
+
32
+ # Set Production Environment now that build is done
33
+ ENV NODE_ENV=production
34
+
35
+ # Expose the ports used by Core/Dashboard and Policy Engine
36
+ EXPOSE 3000
37
+ EXPOSE 3001
38
+
39
+ # PERSISTENT STORAGE: Protect Nyxora's memory and configuration
40
+ VOLUME ["/root/.nyxora"]
41
+
42
+ # Start the daemon in the foreground
43
+ CMD ["npx", "ts-node", "-T", "launcher.ts"]
package/bin/nyxora.mjs CHANGED
@@ -217,6 +217,14 @@ async function main() {
217
217
  case 'dashboard': await dashboard(); break;
218
218
  case 'clean-logs': await cleanLogs(); break;
219
219
  case 'autostart': await autostart(process.argv[3]); break;
220
+ case '-v':
221
+ case '--v':
222
+ case '--version':
223
+ case 'version':
224
+ const pkgPath = path.join(projectRoot, 'package.json');
225
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
226
+ console.log(`Nyxora v${pkg.version}`);
227
+ break;
220
228
  default:
221
229
  console.log(`
222
230
  Nyxora CLI Manager
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora",
3
- "version": "1.7.0",
3
+ "version": "1.7.2",
4
4
  "license": "MIT",
5
5
  "workspaces": [
6
6
  "packages/*"
@@ -26,7 +26,6 @@
26
26
  "concurrently": "^9.2.1",
27
27
  "cors": "^2.8.6",
28
28
  "dotenv": "^17.4.2",
29
- "duck-duck-scrape": "^2.2.7",
30
29
  "express": "^5.2.1",
31
30
  "express-rate-limit": "^7.5.0",
32
31
  "helmet": "^8.0.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora-agent-core",
3
- "version": "1.7.0",
3
+ "version": "1.7.2",
4
4
  "private": true,
5
5
  "main": "src/gateway/server.ts",
6
6
  "dependencies": {
@@ -2,7 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { loadConfig } from '../config/parser';
4
4
  import { getPath } from '../config/paths';
5
- import { ChainName } from '../web3/config';
5
+ import { ChainName, SUPPORTED_CHAIN_NAMES } from '../web3/config';
6
6
  import { resolveToken } from '../web3/utils/tokens';
7
7
  import { prepareSwapToken, executeSwap } from '../web3/skills/swapToken';
8
8
  import { txManager } from './transactionManager';
@@ -158,7 +158,7 @@ export const createLimitOrderToolDefinition = {
158
158
  parameters: {
159
159
  type: "object",
160
160
  properties: {
161
- chainName: { type: "string", enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"] },
161
+ chainName: { type: "string", enum: SUPPORTED_CHAIN_NAMES },
162
162
  fromToken: { type: "string", description: "Token to sell" },
163
163
  toToken: { type: "string", description: "Token to buy" },
164
164
  amountStr: { type: "string", description: "Amount to sell" },
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { OpenAI } from 'openai';
4
- import { loadConfig } from '../config/parser';
4
+ import { loadConfig, loadApiKeys } from '../config/parser';
5
5
  import { Logger } from '../memory/logger';
6
6
  import { Tracker } from '../gateway/tracker';
7
7
  import { getBalanceToolDefinition, getBalance } from '../web3/skills/getBalance';
@@ -48,8 +48,9 @@ export const logger = new Logger();
48
48
 
49
49
  let currentKeyIndex = 0;
50
50
 
51
- function getOpenAI(): OpenAI {
51
+ async function getOpenAI(): Promise<OpenAI> {
52
52
  const config = loadConfig();
53
+ const vaultKeys = await loadApiKeys();
53
54
 
54
55
  if (config.llm.provider === 'ollama') {
55
56
  return new OpenAI({
@@ -80,16 +81,16 @@ function getOpenAI(): OpenAI {
80
81
  // Fallbacks if no valid keys found in config.llm.api_keys
81
82
  if (!apiKey) {
82
83
  if (config.llm.provider === 'gemini') {
83
- apiKey = config.llm.credentials?.gemini_key || '';
84
+ apiKey = vaultKeys.gemini_key || config.credentials?.gemini_key || '';
84
85
  } else if (config.llm.provider === 'openrouter') {
85
- apiKey = config.llm.credentials?.openrouter_key || '';
86
+ apiKey = vaultKeys.openrouter_key || config.credentials?.openrouter_key || '';
86
87
  } else {
87
- apiKey = config.llm.credentials?.openai_key || '';
88
+ apiKey = vaultKeys.openai_key || config.credentials?.openai_key || '';
88
89
  }
89
90
  if (!apiKey) {
90
- throw new Error(`No API Key found for ${config.llm.provider} in config.yaml. Please run 'nyxora setup' to configure it.`);
91
+ throw new Error(`No API Key found for ${config.llm.provider}. Please run 'nyxora setup' to configure it.`);
91
92
  }
92
- console.log(`[LLM] Using default API Key from config.yaml`);
93
+ console.log(`[LLM] Using API Key from secure vault`);
93
94
  }
94
95
 
95
96
  if (config.llm.provider === 'gemini') {
@@ -117,7 +118,7 @@ async function executeWithRetry(
117
118
 
118
119
  while (retries <= maxRetries) {
119
120
  try {
120
- const client = getOpenAI();
121
+ const client = await getOpenAI();
121
122
  return await requestBuilder(client);
122
123
  } catch (error: any) {
123
124
  const status = error?.status || error?.response?.status;
@@ -166,7 +167,8 @@ CRITICAL RULE 3: FORMATTING & CONCISENESS.
166
167
  - When displaying a list of assets, tokens, portfolio, or transaction history, YOU MUST USE MARKDOWN TABLES. Do not use bullet points for financial data.
167
168
  CRITICAL RULE 4: When the user asks to check "my balance", "saldo saya", or anything about their own wallet generally, ALWAYS use the check_portfolio tool to show all assets on the chain that have a USD value greater than 0. LEAVE THE ADDRESS PARAMETER EMPTY. Do NOT use get_balance unless the user explicitly asks for the balance of ONE specific token.
168
169
  CRITICAL RULE 5: If the user doesn't specify a chain, default to: ${config.agent.default_chain}. If the user mentions a specific chain (e.g., "on BNB", "di Base"), you MUST override the default and execute the tool on that specific chain.
169
- CRITICAL RULE 6: If you use the default chain because the user forgot to specify one, you MUST politely confirm which chain you checked in your response (e.g., "I checked your balance on the ${config.agent.default_chain} network..."). Do not issue scary warnings.`;
170
+ CRITICAL RULE 6: If you use the default chain because the user forgot to specify one, you MUST politely confirm which chain you checked in your response (e.g., "I checked your balance on the ${config.agent.default_chain} network..."). Do not issue scary warnings.
171
+ CRITICAL RULE 7: TOOL PRIORITIZATION. When the user asks about crypto prices, market analysis, token security, or blockchain data, YOU MUST prioritize using the dedicated Web3 skills (e.g., get_price, analyze_market, check_security) FIRST. Only if those tools fail or cannot provide the requested information, you may fallback to using search_web.`;
170
172
 
171
173
  // Read IDENTITY.md for core AI persona
172
174
  try {
@@ -426,7 +428,7 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
426
428
  break;
427
429
  }
428
430
  case 'search_web': {
429
- result = await searchWeb(args.query);
431
+ result = await searchWeb(args.query, args.depth);
430
432
  break;
431
433
  }
432
434
  case 'install_external_skill': {
@@ -3,6 +3,39 @@ import yaml from 'yaml';
3
3
  import path from 'path';
4
4
  import { getPath } from './paths';
5
5
 
6
+ export async function loadApiKeys(): Promise<Record<string, string>> {
7
+ const vaultPath = getPath('api_vault.key');
8
+ try {
9
+ const { Entry } = require('@napi-rs/keyring');
10
+ const entry = new Entry('nyxora', 'api_keys');
11
+ const data = await entry.getPassword();
12
+ if (data) return JSON.parse(data);
13
+ } catch (e) {
14
+ if (fs.existsSync(vaultPath)) {
15
+ try {
16
+ const file = fs.readFileSync(vaultPath, 'utf8');
17
+ return JSON.parse(file);
18
+ } catch (err) {}
19
+ }
20
+ }
21
+ return {};
22
+ }
23
+
24
+ export async function saveApiKeys(newKeys: Record<string, string>): Promise<void> {
25
+ const vaultPath = getPath('api_vault.key');
26
+ const currentKeys = await loadApiKeys();
27
+ const mergedKeys = { ...currentKeys, ...newKeys };
28
+ const dataString = JSON.stringify(mergedKeys);
29
+
30
+ try {
31
+ const { Entry } = require('@napi-rs/keyring');
32
+ const entry = new Entry('nyxora', 'api_keys');
33
+ await entry.setPassword(dataString);
34
+ } catch (e) {
35
+ fs.writeFileSync(vaultPath, dataString, { mode: 0o600 });
36
+ }
37
+ }
38
+
6
39
  export interface NyxoraConfig {
7
40
  agent: {
8
41
  name: string;
@@ -13,11 +46,19 @@ export interface NyxoraConfig {
13
46
  model: string;
14
47
  temperature: number;
15
48
  api_keys?: string[];
16
- credentials?: {
17
- openai_key?: string;
18
- gemini_key?: string;
19
- openrouter_key?: string;
20
- };
49
+ credentials?: any; // Deprecated, kept for parsing during migration
50
+ };
51
+ web_search?: {
52
+ provider: 'tavily' | 'brave' | 'mesh';
53
+ enabled: boolean;
54
+ };
55
+ credentials?: {
56
+ openai_key?: string;
57
+ gemini_key?: string;
58
+ openrouter_key?: string;
59
+ tavily_key?: string;
60
+ brave_key?: string;
61
+ [key: string]: string | undefined;
21
62
  };
22
63
  memory: {
23
64
  type: string;
@@ -52,6 +93,47 @@ export function loadConfig(): NyxoraConfig {
52
93
  const file = fs.readFileSync(configPath, 'utf8');
53
94
  const parsed = yaml.parse(file) as Partial<NyxoraConfig>;
54
95
 
96
+ // Auto-migration logic: move llm.credentials to root credentials
97
+ let needsSave = false;
98
+ if (parsed.llm && (parsed.llm as any).credentials) {
99
+ if (!parsed.credentials) {
100
+ parsed.credentials = {};
101
+ }
102
+ const oldCreds = (parsed.llm as any).credentials;
103
+ Object.keys(oldCreds).forEach(key => {
104
+ if (oldCreds[key] && !parsed.credentials![key]) {
105
+ parsed.credentials![key] = oldCreds[key];
106
+ }
107
+ });
108
+ delete (parsed.llm as any).credentials;
109
+ needsSave = true;
110
+ }
111
+
112
+ if (needsSave) {
113
+ try {
114
+ const yamlStr = yaml.stringify(parsed);
115
+ fs.writeFileSync(configPath, yamlStr, 'utf8');
116
+ console.log('[Config] Auto-migrated llm.credentials to root credentials.');
117
+ } catch (e) {
118
+ console.error('[Config] Failed to auto-migrate config file', e);
119
+ }
120
+ }
121
+
122
+ // Auto-migrate from config.yaml to Keyring/Vault
123
+ if (parsed.credentials && Object.keys(parsed.credentials).length > 0) {
124
+ const credsToMigrate = { ...parsed.credentials };
125
+ saveApiKeys(credsToMigrate).then(() => {
126
+ console.log('[Config] Auto-migrated API keys to secure vault.');
127
+ delete parsed.credentials;
128
+ try {
129
+ const yamlStr = yaml.stringify(parsed);
130
+ fs.writeFileSync(configPath, yamlStr, 'utf8');
131
+ } catch (e) {}
132
+ }).catch(e => {
133
+ console.error('[Config] Failed to migrate API keys to secure vault', e);
134
+ });
135
+ }
136
+
55
137
  // Merge with defaults
56
138
  return {
57
139
  agent: parsed.agent || { name: 'Nyxora-Default', default_chain: 'base' },
@@ -59,9 +141,13 @@ export function loadConfig(): NyxoraConfig {
59
141
  provider: 'openai',
60
142
  model: 'gpt-4o-mini',
61
143
  temperature: 0.2,
62
- api_keys: [],
63
- credentials: {}
144
+ api_keys: []
145
+ },
146
+ web_search: parsed.web_search || {
147
+ provider: 'mesh',
148
+ enabled: true
64
149
  },
150
+ credentials: parsed.credentials || {},
65
151
  memory: parsed.memory || { type: 'file', path: './memory.json' },
66
152
  web3: parsed.web3 || { rpc_urls: {} },
67
153
  integrations: parsed.integrations || {
@@ -72,17 +158,25 @@ export function loadConfig(): NyxoraConfig {
72
158
  system: { allow_shell_execution: false, allow_file_write: false }
73
159
  }
74
160
  } as NyxoraConfig;
75
- } catch (error) {
76
- console.error('Failed to load config.yaml. Using default configuration.', error);
161
+ } catch (error: any) {
162
+ if (error.code === 'ENOENT') {
163
+ console.log('[Config] No config.yaml found. Using default configuration.');
164
+ } else {
165
+ console.error('[Config] Failed to load config.yaml. Using default configuration.', error);
166
+ }
77
167
  return {
78
168
  agent: { name: 'Nyxora-Default', default_chain: 'base' },
79
169
  llm: {
80
170
  provider: 'openai',
81
171
  model: 'gpt-4o-mini',
82
172
  temperature: 0.2,
83
- api_keys: [],
84
- credentials: {}
173
+ api_keys: []
174
+ },
175
+ web_search: {
176
+ provider: 'mesh',
177
+ enabled: true
85
178
  },
179
+ credentials: {},
86
180
  memory: { type: 'file', path: './memory.json' },
87
181
  web3: { rpc_urls: {} },
88
182
  integrations: {
@@ -13,6 +13,7 @@ import { runSetupWizard } from './setup';
13
13
  import { password, isCancel } from '@clack/prompts';
14
14
  import { getSessionToken } from '../utils/state';
15
15
  import pc from 'picocolors';
16
+ import { saveApiKeys } from '../config/parser';
16
17
 
17
18
  async function main() {
18
19
  // 1. Determine configuration directory
@@ -30,6 +31,33 @@ console.log(`================================`);
30
31
  process.exit(0);
31
32
  }
32
33
 
34
+ // Check for set-key shortcut
35
+ if (process.argv.includes('set-key')) {
36
+ const setKeyIndex = process.argv.indexOf('set-key');
37
+ const provider = process.argv[setKeyIndex + 1];
38
+ const key = process.argv[setKeyIndex + 2];
39
+
40
+ if (!provider || !key) {
41
+ console.error(pc.red('Usage: nyxora set-key <provider> <api_key>'));
42
+ console.error(pc.gray('Example: nyxora set-key tavily tvly-xxx'));
43
+ process.exit(1);
44
+ }
45
+
46
+ const keyMap: Record<string, string> = {
47
+ 'openai': 'openai_key',
48
+ 'gemini': 'gemini_key',
49
+ 'openrouter': 'openrouter_key',
50
+ 'tavily': 'tavily_key',
51
+ 'brave': 'brave_key'
52
+ };
53
+
54
+ const mappedKey = keyMap[provider.toLowerCase()] || `${provider.toLowerCase()}_key`;
55
+
56
+ await saveApiKeys({ [mappedKey]: key });
57
+ console.log(pc.green(`✅ API Key for ${provider} saved securely to vault.`));
58
+ process.exit(0);
59
+ }
60
+
33
61
  // 2. Setup boilerplate files if in global mode and they don't exist
34
62
  let isFirstBoot = false;
35
63
  if (isGlobalMode) {
@@ -65,8 +93,6 @@ console.log(`================================`);
65
93
  // 4. Start the Express API Server (which also serves the static dashboard and Telegram bot)
66
94
  startServer();
67
95
  const token = getSessionToken(); // Initialize token file
68
- console.log(`🌐 Nyxora API Server running on port ${process.env.PORT || 3000}`);
69
-
70
96
  setTimeout(() => {
71
97
  console.log(pc.cyan(`\n✨ Dashboard URL: http://localhost:3000/?token=${token}`));
72
98
  console.log(pc.gray(` (Developers: Vite hot-reload available at http://localhost:5173/?token=${token})\n`));
@@ -145,6 +145,25 @@ export async function getAccessToken(): Promise<string | null> {
145
145
  }
146
146
  }
147
147
 
148
+ export async function logoutGoogle(): Promise<boolean> {
149
+ try {
150
+ const { Entry } = require('@napi-rs/keyring');
151
+ const entry = new Entry('nyxora', 'google_refresh_token');
152
+ await entry.deletePassword();
153
+ } catch (e) {}
154
+
155
+ try {
156
+ if (fs.existsSync(FALLBACK_TOKEN_PATH)) {
157
+ fs.unlinkSync(FALLBACK_TOKEN_PATH);
158
+ }
159
+ } catch (e) {}
160
+
161
+ accessToken = null;
162
+ tokenExpiry = 0;
163
+ console.log('[Google Auth] Successfully logged out.');
164
+ return true;
165
+ }
166
+
148
167
  // ---- Secure Storage for Refresh Token ----
149
168
 
150
169
  async function saveRefreshToken(token: string) {
@@ -44,7 +44,7 @@ import { readGmailInboxToolDefinition, listCalendarEventsToolDefinition, appendR
44
44
 
45
45
  import { startTelegramBot } from './telegram';
46
46
  import { formatTransactionSuccess, formatTransactionError } from '../utils/formatter';
47
- import { initGoogleAuth, getAuthUrl, processCallback, isAuthenticated } from './googleAuthModule';
47
+ import { initGoogleAuth, getAuthUrl, processCallback, isAuthenticated, logoutGoogle } from './googleAuthModule';
48
48
 
49
49
  // Initialize Google Auth
50
50
  initGoogleAuth();
@@ -291,7 +291,13 @@ app.get('/api/auth/google/callback', async (req, res) => {
291
291
  });
292
292
 
293
293
  app.get('/api/auth/google/status', async (req, res) => {
294
- res.json({ connected: await isAuthenticated() });
294
+ const connected = await isAuthenticated();
295
+ res.json({ connected });
296
+ });
297
+
298
+ app.delete('/api/auth/google', async (req, res) => {
299
+ const success = await logoutGoogle();
300
+ res.json({ success });
295
301
  });
296
302
 
297
303
  app.get('/api/transactions', (req, res) => {
@@ -405,7 +411,7 @@ export function startServer() {
405
411
  limitOrderManager.startMonitor();
406
412
 
407
413
  const PORT = Number(process.env.PORT || 3000);
408
- app.listen(PORT, '127.0.0.1', () => {
414
+ app.listen(PORT, '0.0.0.0', () => {
409
415
  console.log(`🤖 Nyxora API Server running on port ${PORT}`);
410
416
 
411
417
  // Start the Telegram bot listener
@@ -3,7 +3,7 @@ import pc from 'picocolors';
3
3
  import fs from 'fs';
4
4
  import path from 'path';
5
5
  import { getAppDir } from '../config/paths';
6
- import { loadConfig, saveConfig } from '../config/parser';
6
+ import { loadConfig, saveConfig, saveApiKeys } from '../config/parser';
7
7
  import crypto from 'crypto';
8
8
 
9
9
  function encryptKey(privateKey: string, password: string) {
@@ -161,6 +161,25 @@ Provider: ${config.llm.provider}`;
161
161
  if (isCancel(apiKey)) return process.exit(0);
162
162
  }
163
163
 
164
+ // 3.5. Smart Web Search Setup
165
+ const searchProvider = await select({
166
+ message: 'Enable Smart Web Search for Nyxora AI?',
167
+ options: [
168
+ { value: 'skip', label: 'Skip (Use basic decentralized mesh)' },
169
+ { value: 'tavily', label: 'Tavily Search (Built for AI - 1000 free/mo)' },
170
+ { value: 'brave', label: 'Brave Search (Privacy focused - 2000 free/mo)' },
171
+ ],
172
+ });
173
+ if (isCancel(searchProvider)) return process.exit(0);
174
+
175
+ let searchApiKey = '';
176
+ if (searchProvider !== 'skip') {
177
+ searchApiKey = (await password({
178
+ message: `Enter API Key for ${searchProvider} (Get it free at ${searchProvider === 'tavily' ? 'tavily.com' : 'search.brave.com'}):`,
179
+ })) as string;
180
+ if (isCancel(searchApiKey)) return process.exit(0);
181
+ }
182
+
164
183
  // 4. Default Chain
165
184
  const defaultChain = await select({
166
185
  message: 'Select Default Chain:',
@@ -169,8 +188,9 @@ Provider: ${config.llm.provider}`;
169
188
  { value: 'ethereum', label: 'Ethereum Mainnet' },
170
189
  { value: 'bsc', label: 'BSC' },
171
190
  { value: 'base', label: 'Base' },
172
- { value: 'optimism', label: 'Optimism' },
173
- { value: 'arbitrum', label: 'Arbitrum' },
191
+ { value: 'arbitrum', label: 'Arbitrum One' },
192
+ { value: 'optimism', label: 'OP Mainnet' },
193
+ { value: 'polygon', label: 'Polygon (Matic)' },
174
194
  { value: 'sepolia', label: 'Sepolia (Testnet)' },
175
195
  ],
176
196
  });
@@ -233,8 +253,8 @@ Provider: ${config.llm.provider}`;
233
253
  message: 'Web3 Wallet Setup:',
234
254
  options: [
235
255
  { value: 'skip', label: 'Skip for now (No Web3 execution)' },
236
- { value: 'generate', label: 'Auto-Generate New Wallet (Recommended for testing)' },
237
- { value: 'manual', label: 'Input Manual Private Key' },
256
+ { value: 'generate', label: 'Auto-Generate New Wallet' },
257
+ { value: 'manual', label: 'Manual Input (Existing Private Key)' },
238
258
  ],
239
259
  });
240
260
  if (isCancel(walletSetupType)) return process.exit(0);
@@ -260,11 +280,26 @@ Provider: ${config.llm.provider}`;
260
280
  config.llm.model = model as string;
261
281
  config.agent.default_chain = defaultChain as string;
262
282
 
263
- if (!config.llm.credentials) config.llm.credentials = {};
283
+ const newApiKeys: Record<string, string> = {};
264
284
  if (apiKey) {
265
- if (provider === 'openai') config.llm.credentials.openai_key = apiKey;
266
- if (provider === 'gemini') config.llm.credentials.gemini_key = apiKey;
267
- if (provider === 'openrouter') config.llm.credentials.openrouter_key = apiKey;
285
+ if (provider === 'openai') newApiKeys.openai_key = apiKey;
286
+ if (provider === 'gemini') newApiKeys.gemini_key = apiKey;
287
+ if (provider === 'openrouter') newApiKeys.openrouter_key = apiKey;
288
+ }
289
+
290
+ if (!config.web_search) config.web_search = { provider: 'mesh', enabled: true };
291
+ if (searchProvider !== 'skip') {
292
+ config.web_search.provider = searchProvider as any;
293
+ if (searchApiKey) {
294
+ if (searchProvider === 'tavily') newApiKeys.tavily_key = searchApiKey;
295
+ if (searchProvider === 'brave') newApiKeys.brave_key = searchApiKey;
296
+ }
297
+ } else {
298
+ config.web_search.provider = 'mesh';
299
+ }
300
+
301
+ if (Object.keys(newApiKeys).length > 0) {
302
+ await saveApiKeys(newApiKeys);
268
303
  }
269
304
 
270
305
  if (!config.integrations) config.integrations = {};
@@ -238,7 +238,9 @@ export function startTelegramBot() {
238
238
  console.error('[Telegram] Telegraf error:', err);
239
239
  });
240
240
 
241
- bot.launch();
241
+ bot.launch().catch(err => {
242
+ console.error('[Telegram] Connection failed (likely blocked by ISP or timeout):', err.message);
243
+ });
242
244
 
243
245
  if (isPaired) {
244
246
  console.log('🤖 Telegram Bot is running and securely listening for your messages...');