upfynai-code 2.9.8 → 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.
- package/client/dist/assets/{AppContent-BXZDeSIC.js → AppContent-CapWOZnI.js} +3 -3
- package/client/dist/assets/{CanvasFullScreen-mnpCnLZ9.js → CanvasFullScreen-WOseUyrD.js} +1 -1
- package/client/dist/assets/{CanvasWorkspace-4CqmjAVQ.js → CanvasWorkspace-DksIgrfl.js} +1 -1
- package/client/dist/assets/{DashboardPanel-zFIFlw56.js → DashboardPanel-DmHbSyTP.js} +1 -1
- package/client/dist/assets/{FileTree-B0c_GaB3.js → FileTree-CbJF7m5P.js} +1 -1
- package/client/dist/assets/{GitPanel-DUP4zVU4.js → GitPanel-avzqm9Zj.js} +1 -1
- package/client/dist/assets/{LoginModal-BRycfsyD.js → LoginModal-Bk9mxZsi.js} +1 -1
- package/client/dist/assets/{Onboarding-BcnaZZ0o.js → Onboarding-CaiBrbYB.js} +1 -1
- package/client/dist/assets/{SetupForm-S0g6u5yT.js → SetupForm-DuIDpVGw.js} +1 -1
- package/client/dist/assets/{WorkflowsPanel-CouH9JDO.js → WorkflowsPanel-BOaVEpdu.js} +1 -1
- package/client/dist/assets/{index-CNDcVl2g.js → index-DtIQCXd6.js} +3 -3
- package/client/dist/index.html +1 -1
- package/commands/upfynai-connect.md +1 -1
- package/package.json +2 -4
- package/server/cli.js +8 -2
- package/server/database/db.js +5 -2
- package/server/index.js +10 -2
- package/server/middleware/auth.js +7 -2
- package/server/relay-client.js +19 -8
- package/server/routes/agent.js +11 -3
- package/server/services/workflowScheduler.js +6 -1
package/server/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Upfyn-Code CLI
|
|
4
4
|
* by Thinqmesh Technologies
|
|
@@ -46,6 +46,12 @@ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
|
46
46
|
const CONFIG_DIR = path.join(os.homedir(), '.upfynai');
|
|
47
47
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
48
48
|
|
|
49
|
+
/** Mask internal Railway URL for display */
|
|
50
|
+
function displayServerUrl(url) {
|
|
51
|
+
if (!url || url === 'https://upfynai-code-production.up.railway.app') return 'Upfyn Cloud';
|
|
52
|
+
try { return new URL(url).host; } catch { return url; }
|
|
53
|
+
}
|
|
54
|
+
|
|
49
55
|
// Load environment variables from .env file if it exists
|
|
50
56
|
function loadEnvFile() {
|
|
51
57
|
try {
|
|
@@ -351,7 +357,7 @@ async function launchInteractive() {
|
|
|
351
357
|
// 5. Show launch screen
|
|
352
358
|
showLaunchScreen(packageJson.version, claudeBin, {
|
|
353
359
|
relayStatus: config.relayKey && config.server
|
|
354
|
-
? `Auto-connecting to ${config.server}`
|
|
360
|
+
? `Auto-connecting to ${displayServerUrl(config.server)}`
|
|
355
361
|
: null,
|
|
356
362
|
apiKey: config.anthropicApiKey || null,
|
|
357
363
|
});
|
package/server/database/db.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
let createClient;
|
|
2
2
|
try {
|
|
3
3
|
createClient = (await import('@libsql/client')).createClient;
|
|
4
|
-
} catch {
|
|
5
|
-
|
|
4
|
+
} catch (e) {
|
|
5
|
+
console.warn('[DB] @libsql/client not available — cloud database features disabled.', e?.message || '');
|
|
6
6
|
}
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import fs from 'fs';
|
|
@@ -49,6 +49,9 @@ function resolveDbConfig() {
|
|
|
49
49
|
|
|
50
50
|
function getDb() {
|
|
51
51
|
if (!_db) {
|
|
52
|
+
if (!createClient) {
|
|
53
|
+
throw new Error('[DB] @libsql/client is not installed. Run: npm install @libsql/client');
|
|
54
|
+
}
|
|
52
55
|
resolveDbConfig();
|
|
53
56
|
_db = createClient({ url: _dbUrl, ...(_dbAuthToken ? { authToken: _dbAuthToken } : {}) });
|
|
54
57
|
}
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/server/relay-client.js
CHANGED
|
@@ -36,6 +36,13 @@ try {
|
|
|
36
36
|
|
|
37
37
|
const CONFIG_DIR = path.join(os.homedir(), '.upfynai');
|
|
38
38
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
39
|
+
const CLOUD_SERVER = 'https://upfynai-code-production.up.railway.app';
|
|
40
|
+
|
|
41
|
+
/** Mask internal server URL for display */
|
|
42
|
+
function displayUrl(url) {
|
|
43
|
+
if (!url || url === CLOUD_SERVER) return 'Upfyn Cloud (cli.upfyn.com)';
|
|
44
|
+
try { return new URL(url).host; } catch { return url; }
|
|
45
|
+
}
|
|
39
46
|
|
|
40
47
|
function loadConfig() {
|
|
41
48
|
try {
|
|
@@ -163,7 +170,7 @@ function createRelayConnection(wsUrl, config = {}) {
|
|
|
163
170
|
*/
|
|
164
171
|
export async function connectToServer(options = {}) {
|
|
165
172
|
const config = loadConfig();
|
|
166
|
-
const serverUrl = options.server || config.server ||
|
|
173
|
+
const serverUrl = options.server || config.server || CLOUD_SERVER;
|
|
167
174
|
const relayKey = options.key || config.relayKey;
|
|
168
175
|
|
|
169
176
|
if (!relayKey) {
|
|
@@ -182,7 +189,7 @@ export async function connectToServer(options = {}) {
|
|
|
182
189
|
|
|
183
190
|
saveConfig({ ...config, server: serverUrl, relayKey });
|
|
184
191
|
|
|
185
|
-
await showConnectStartup(serverUrl, os.hostname(), os.userInfo().username, VERSION);
|
|
192
|
+
await showConnectStartup(displayUrl(serverUrl), os.hostname(), os.userInfo().username, VERSION);
|
|
186
193
|
|
|
187
194
|
const wsUrl = serverUrl.replace(/^http/, 'ws') + '/relay?token=' + encodeURIComponent(relayKey);
|
|
188
195
|
|
|
@@ -210,7 +217,7 @@ export async function connectToServer(options = {}) {
|
|
|
210
217
|
spinner.stop('Connected!');
|
|
211
218
|
const nameMatch = data.message?.match(/Connected as (.+?)\./);
|
|
212
219
|
const username = nameMatch ? nameMatch[1] : 'Unknown';
|
|
213
|
-
showConnectionBanner(username, serverUrl);
|
|
220
|
+
showConnectionBanner(username, displayUrl(serverUrl));
|
|
214
221
|
|
|
215
222
|
const agents = detectInstalledAgents();
|
|
216
223
|
const installed = Object.entries(agents)
|
|
@@ -271,13 +278,17 @@ export async function connectToServer(options = {}) {
|
|
|
271
278
|
|
|
272
279
|
ws.on('error', (err) => {
|
|
273
280
|
if (err.code === 'ECONNREFUSED') {
|
|
274
|
-
spinner.fail(`Cannot reach ${serverUrl}. Is the server running?`);
|
|
275
|
-
} else if (err.message?.includes('401')) {
|
|
281
|
+
spinner.fail(`Cannot reach ${displayUrl(serverUrl)}. Is the server running?`);
|
|
282
|
+
} else if (err.message?.includes('401') || err.message?.includes('Unauthorized')) {
|
|
276
283
|
spinner.fail('Authentication failed (401). Your relay token may be invalid or expired.');
|
|
277
|
-
logRelayEvent('!', 'Get a new token from
|
|
278
|
-
logRelayEvent('!', 'Then run: uc connect --
|
|
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');
|
|
279
286
|
} else if (err.message?.includes('403') || err.message?.includes('Forbidden')) {
|
|
280
|
-
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.');
|
|
281
292
|
} else {
|
|
282
293
|
logRelayEvent('!', `WebSocket error: ${err.message || err.code || 'unknown'}`, 'red');
|
|
283
294
|
}
|
package/server/routes/agent.js
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|