upfynai-code 2.9.9 → 3.0.0

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.
@@ -31,7 +31,7 @@ You are the Upfyn-Code assistant. The user wants to connect their current Claude
31
31
  Local server is not running and no hosted config found.
32
32
 
33
33
  Option A: Connect to hosted server:
34
- uc connect --server https://upfynai-code-production.up.railway.app --key upfyn_your_token
34
+ uc connect --key upfyn_your_token
35
35
 
36
36
  Option B: Start local server first:
37
37
  uc start
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "upfynai-code",
3
- "version": "2.9.9",
3
+ "version": "3.0.0",
4
4
  "description": "Visual AI coding interface for Claude Code, Cursor & Codex. Canvas whiteboard, multi-agent chat, terminal, git, voice assistant. Self-host locally or connect to cli.upfyn.com for remote access.",
5
5
  "type": "module",
6
6
  "main": "server/index.js",
@@ -58,27 +58,20 @@
58
58
  "@iarna/toml": "^2.2.5",
59
59
  "@libsql/client": "^0.14.0",
60
60
  "@modelcontextprotocol/sdk": "^1.26.0",
61
- "@neondatabase/serverless": "^0.10.4",
62
- "@octokit/rest": "^22.0.0",
63
61
  "@openai/codex-sdk": "^0.101.0",
64
62
  "bcryptjs": "^3.0.3",
65
63
  "chokidar": "^4.0.3",
66
- "composio-core": "^0.5.39",
67
64
  "cookie-parser": "^1.4.7",
68
65
  "cors": "^2.8.5",
69
66
  "cross-spawn": "^7.0.3",
70
67
  "edge-tts-universal": "^1.3.3",
71
68
  "express": "^4.18.2",
72
69
  "form-data": "^4.0.5",
73
- "google-auth-library": "^9.15.1",
74
70
  "gray-matter": "^4.0.3",
75
71
  "js-yaml": "^4.1.0",
76
72
  "jsonwebtoken": "^9.0.2",
77
73
  "mime-types": "^3.0.1",
78
74
  "multer": "^2.0.2",
79
- "node-cron": "^4.2.1",
80
- "razorpay": "^2.9.6",
81
- "resend": "^4.5.1",
82
75
  "ws": "^8.14.2",
83
76
  "zod": "^3.25.76"
84
77
  },
package/server/index.js CHANGED
@@ -552,16 +552,24 @@ if (server) {
552
552
  server,
553
553
  verifyClient: (info, done) => {
554
554
  const reqUrl = info.req.url || '';
555
- const origin = info.req.headers?.origin || 'no-origin';
555
+ const clientIp = info.req.headers['x-forwarded-for']?.split(',')[0]?.trim() || info.req.socket?.remoteAddress || 'unknown';
556
+ // Timeout the entire verifyClient to prevent hung upgrades
557
+ const authTimeout = setTimeout(() => {
558
+ console.warn(`[WS] Auth TIMEOUT for ${reqUrl} from ${clientIp}`);
559
+ done(false, 504, 'Auth timeout');
560
+ }, 15000);
556
561
  authenticateWebSocket(info.req).then(user => {
562
+ clearTimeout(authTimeout);
557
563
  if (!user) {
564
+ console.warn(`[WS] Auth REJECTED for ${reqUrl} from ${clientIp} — no valid token`);
558
565
  done(false, 401, 'Unauthorized');
559
566
  return;
560
567
  }
561
568
  info.req.user = user;
562
569
  done(true);
563
570
  }).catch(err => {
564
- console.error(`[WS] Auth ERROR for ${reqUrl}:`, err.message);
571
+ clearTimeout(authTimeout);
572
+ console.error(`[WS] Auth ERROR for ${reqUrl} from ${clientIp}:`, err.message);
565
573
  done(false, 500, 'Auth error');
566
574
  });
567
575
  }
@@ -147,11 +147,16 @@ const authenticateWebSocket = async (request) => {
147
147
  // Relay token (upfyn_ prefix) — validate against DB, not JWT
148
148
  if (token.startsWith('upfyn_') || token.startsWith('rt_')) {
149
149
  try {
150
- const tokenData = await relayTokensDb.validateToken(token);
150
+ // Timeout relay token validation to prevent hung WebSocket upgrades when Turso is slow
151
+ const tokenData = await Promise.race([
152
+ relayTokensDb.validateToken(token),
153
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Token validation timeout')), 10000))
154
+ ]);
151
155
  if (tokenData) {
152
156
  return { userId: Number(tokenData.user_id), username: tokenData.username };
153
157
  }
154
- } catch {
158
+ } catch (err) {
159
+ console.warn('[WS] Relay token validation failed:', err.message);
155
160
  }
156
161
  return null;
157
162
  }
@@ -279,12 +279,16 @@ export async function connectToServer(options = {}) {
279
279
  ws.on('error', (err) => {
280
280
  if (err.code === 'ECONNREFUSED') {
281
281
  spinner.fail(`Cannot reach ${displayUrl(serverUrl)}. Is the server running?`);
282
- } else if (err.message?.includes('401')) {
282
+ } else if (err.message?.includes('401') || err.message?.includes('Unauthorized')) {
283
283
  spinner.fail('Authentication failed (401). Your relay token may be invalid or expired.');
284
- logRelayEvent('!', 'Get a new token from the dashboard: https://cli.upfyn.com/dashboard', 'yellow');
285
- logRelayEvent('!', 'Then run: uc connect --server <url> --key <new_token>', 'yellow');
284
+ logRelayEvent('!', 'Get a new token from Settings > Keys & Connect at: https://cli.upfyn.com', 'yellow');
285
+ logRelayEvent('!', 'Then run: uc connect --key <new_token>', 'yellow');
286
286
  } else if (err.message?.includes('403') || err.message?.includes('Forbidden')) {
287
- spinner.fail('Access forbidden (403). Your account may be inactive.');
287
+ spinner.fail('Access forbidden (403). Your account may be inactive or subscription expired.');
288
+ } else if (err.message?.includes('504') || err.message?.includes('timeout')) {
289
+ spinner.fail('Server auth timed out. The server may be overloaded — retrying...');
290
+ } else if (err.code === 'ENOTFOUND' || err.code === 'EAI_AGAIN') {
291
+ spinner.fail('DNS resolution failed. Check your internet connection.');
288
292
  } else {
289
293
  logRelayEvent('!', `WebSocket error: ${err.message || err.code || 'unknown'}`, 'red');
290
294
  }
@@ -9,7 +9,14 @@ import { addProjectManually } from '../projects.js';
9
9
  import { queryClaudeSDK } from '../claude-sdk.js';
10
10
  import { spawnCursor } from '../cursor-cli.js';
11
11
  import { queryCodex } from '../openai-codex.js';
12
- import { Octokit } from '@octokit/rest';
12
+ // @octokit/rest is cloud-only lazy-loaded so local installs don't crash
13
+ let _Octokit = null;
14
+ async function getOctokit(auth) {
15
+ if (!_Octokit) {
16
+ try { _Octokit = (await import('@octokit/rest')).Octokit; } catch { return null; }
17
+ }
18
+ return new _Octokit({ auth });
19
+ }
13
20
  import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants.js';
14
21
  import { queryOpenRouter, OPENROUTER_MODELS } from '../openrouter.js';
15
22
  import { IS_PLATFORM } from '../constants/config.js';
@@ -1000,8 +1007,9 @@ router.post('/', validateExternalApiKey, async (req, res) => {
1000
1007
  throw new Error('GitHub token required for branch/PR creation. Please configure a GitHub token in settings.');
1001
1008
  }
1002
1009
 
1003
- // Initialize Octokit
1004
- const octokit = new Octokit({ auth: tokenToUse });
1010
+ // Initialize Octokit (cloud-only)
1011
+ const octokit = await getOctokit(tokenToUse);
1012
+ if (!octokit) throw new Error('GitHub integration not available. @octokit/rest is not installed.');
1005
1013
 
1006
1014
  // Get GitHub URL - either from parameter or from git remote
1007
1015
  let repoUrl = githubUrl;
@@ -1,4 +1,7 @@
1
- import cron from 'node-cron';
1
+ // node-cron is cloud-only — dynamically imported so local installs don't crash
2
+ let cron = null;
3
+ try { cron = (await import('node-cron')).default; } catch { /* not installed locally */ }
4
+
2
5
  import { workflowDb, webhookDb } from '../database/db.js';
3
6
  import * as composioService from './composio.js';
4
7
 
@@ -131,6 +134,8 @@ async function executeWorkflow(workflow) {
131
134
  * Schedule a single workflow's cron job.
132
135
  */
133
136
  function scheduleWorkflow(workflow) {
137
+ if (!cron) return; // node-cron not available (local install)
138
+
134
139
  const id = workflow.id;
135
140
 
136
141
  // Stop existing job if any