upfynai-code 3.0.0 → 3.0.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.
@@ -62,7 +62,7 @@
62
62
  40% { transform: translateY(-8px); }
63
63
  }
64
64
  </style>
65
- <script type="module" crossorigin src="/assets/index-DtIQCXd6.js"></script>
65
+ <script type="module" crossorigin src="/assets/index-C5ptjuTl.js"></script>
66
66
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-96lCPsRK.js">
67
67
  <link rel="modulepreload" crossorigin href="/assets/vendor-markdown-CimbIo6Y.js">
68
68
  <link rel="modulepreload" crossorigin href="/assets/vendor-syntax-DuHI9Ok6.js">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "upfynai-code",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
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",
@@ -56,7 +56,6 @@
56
56
  "@anthropic-ai/claude-agent-sdk": "^0.1.71",
57
57
  "@anthropic-ai/sdk": "^0.78.0",
58
58
  "@iarna/toml": "^2.2.5",
59
- "@libsql/client": "^0.14.0",
60
59
  "@modelcontextprotocol/sdk": "^1.26.0",
61
60
  "@openai/codex-sdk": "^0.101.0",
62
61
  "bcryptjs": "^3.0.3",
@@ -76,6 +75,7 @@
76
75
  "zod": "^3.25.76"
77
76
  },
78
77
  "optionalDependencies": {
78
+ "libsql": "^0.5.22",
79
79
  "node-pty": "^1.1.0-beta34"
80
80
  },
81
81
  "devDependencies": {
package/server/cli-ui.js CHANGED
@@ -520,13 +520,16 @@ function showStyledHelp(version) {
520
520
  lines.push(` ${c.bBright('Commands:')}`);
521
521
  const cmds = [
522
522
  ['(default)', 'Launch Claude Code with Upfyn branding'],
523
- ['start', 'Start the local server only'],
523
+ ['start', 'Start the local server (web UI)'],
524
+ ['stop', 'Stop a running server'],
524
525
  ['connect', 'Connect local machine to hosted server'],
525
526
  ['config', 'View/set configuration (API key, etc.)'],
526
527
  ['status', 'Show configuration and data locations'],
527
528
  ['install-commands', 'Install /upfynai-* slash commands'],
528
529
  ['uninstall-commands', 'Remove /upfynai-* slash commands'],
529
530
  ['update', 'Update to the latest version'],
531
+ ['reset', 'Clear local database (fresh start)'],
532
+ ['uninstall', 'Remove all data, config, and slash commands'],
530
533
  ['help', 'Show this help information'],
531
534
  ['version', 'Show version information'],
532
535
  ];
@@ -607,6 +610,14 @@ function showStyledStatus(info) {
607
610
  }
608
611
  lines.push('');
609
612
 
613
+ // Server status
614
+ if (info.serverRunning) {
615
+ lines.push(` ${c.bright('Server')} ${c.green('Running')} ${c.dim(`(PID ${info.serverRunning}, port ${info.port})`)}`);
616
+ } else {
617
+ lines.push(` ${c.bright('Server')} ${c.dim('Stopped')}`);
618
+ }
619
+ lines.push('');
620
+
610
621
  // Configuration
611
622
  lines.push(` ${c.bright('Config')}`);
612
623
  lines.push(` ${c.gray('PORT')} ${c.cyan(info.port)} ${info.portDefault ? c.dim('(default)') : ''}`);
@@ -621,9 +632,11 @@ function showStyledStatus(info) {
621
632
  lines.push(` ${c.dark('='.repeat(50))}`);
622
633
  lines.push('');
623
634
  lines.push(` ${c.bCyan('Tips:')}`);
624
- lines.push(` ${c.dim('>')} Use ${c.bright('uc')} to launch Claude Code with Upfyn branding`);
635
+ lines.push(` ${c.dim('>')} Use ${c.bright('uc start')} to start the local web UI`);
636
+ lines.push(` ${c.dim('>')} Use ${c.bright('uc stop')} to stop the server`);
625
637
  lines.push(` ${c.dim('>')} Use ${c.bright('uc connect --key upfyn_xxx')} to bridge to web UI`);
626
638
  lines.push(` ${c.dim('>')} Use ${c.bright('uc config --api-key sk-ant-xxx')} to save your API key`);
639
+ lines.push(` ${c.dim('>')} Use ${c.bright('uc help')} for all commands`);
627
640
  lines.push('');
628
641
 
629
642
  process.stdout.write(lines.join('\n') + '\n');
package/server/cli.js CHANGED
@@ -7,10 +7,14 @@
7
7
  *
8
8
  * Commands:
9
9
  * (no args) - Launch Claude Code with Upfyn branding (default)
10
- * start - Start the server
10
+ * start - Start the local server (web UI)
11
+ * stop - Stop a running server
11
12
  * connect - Connect to hosted server (relay bridge)
12
13
  * config - View/set configuration (API key, server, etc.)
13
14
  * status - Show configuration and data locations
15
+ * update - Update to the latest version
16
+ * reset - Clear local database
17
+ * uninstall - Remove all data, config, and slash commands
14
18
  * help - Show help information
15
19
  * version - Show version information
16
20
  */
@@ -45,6 +49,7 @@ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
45
49
 
46
50
  const CONFIG_DIR = path.join(os.homedir(), '.upfynai');
47
51
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
52
+ const PID_FILE = path.join(CONFIG_DIR, 'server.pid');
48
53
 
49
54
  /** Mask internal Railway URL for display */
50
55
  function displayServerUrl(url) {
@@ -122,6 +127,20 @@ function findClaudeBinary() {
122
127
  return null;
123
128
  }
124
129
 
130
+ // Check if a server is running via PID file
131
+ function isServerRunning() {
132
+ if (!fs.existsSync(PID_FILE)) return false;
133
+ try {
134
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
135
+ process.kill(pid, 0); // signal 0 = check if alive
136
+ return pid;
137
+ } catch {
138
+ // Process not running, clean up stale PID file
139
+ try { fs.unlinkSync(PID_FILE); } catch { /* ignore */ }
140
+ return false;
141
+ }
142
+ }
143
+
125
144
  // --- Show status command ---
126
145
  function showStatus() {
127
146
  const config = loadConfig();
@@ -134,6 +153,8 @@ function showStatus() {
134
153
  dbSize = (stats.size / 1024).toFixed(2) + ' KB';
135
154
  }
136
155
 
156
+ const serverPid = isServerRunning();
157
+
137
158
  showStyledStatus({
138
159
  version: packageJson.version,
139
160
  installDir,
@@ -144,6 +165,7 @@ function showStatus() {
144
165
  portDefault: !process.env.PORT,
145
166
  claudeCli: findClaudeBinary() || null,
146
167
  apiKey: config.anthropicApiKey || null,
168
+ serverRunning: serverPid,
147
169
  });
148
170
  }
149
171
 
@@ -444,6 +466,118 @@ function startBackgroundRelay(config) {
444
466
  });
445
467
  }
446
468
 
469
+ // Write PID file so `uc stop` can find and kill the server
470
+ function writePidFile() {
471
+ if (!fs.existsSync(CONFIG_DIR)) {
472
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
473
+ }
474
+ fs.writeFileSync(PID_FILE, String(process.pid));
475
+ }
476
+
477
+ // Remove PID file on exit
478
+ function cleanupPidFile() {
479
+ try { fs.unlinkSync(PID_FILE); } catch { /* ignore */ }
480
+ }
481
+
482
+ // Stop a running server
483
+ function stopServer() {
484
+ if (!fs.existsSync(PID_FILE)) {
485
+ console.log(`\n ${c.yellow('!')} No running server found.`);
486
+ console.log(` ${c.dim('Start one with:')} ${c.bright('uc start')}\n`);
487
+ return;
488
+ }
489
+
490
+ try {
491
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
492
+ process.kill(pid, 'SIGTERM');
493
+ fs.unlinkSync(PID_FILE);
494
+ console.log(`\n ${c.green('OK')} Server stopped (PID ${pid})\n`);
495
+ } catch (e) {
496
+ // Process might already be dead
497
+ try { fs.unlinkSync(PID_FILE); } catch { /* ignore */ }
498
+ if (e.code === 'ESRCH') {
499
+ console.log(`\n ${c.yellow('!')} Server was not running (stale PID file removed)\n`);
500
+ } else {
501
+ console.log(`\n ${c.red('FAIL')} Could not stop server: ${e.message}\n`);
502
+ }
503
+ }
504
+ }
505
+
506
+ // Uninstall: remove config, database, slash commands
507
+ function uninstallApp() {
508
+ console.log(`\n ${c.bBright('Upfyn-Code — Uninstall')}`);
509
+ console.log(` ${c.dark('='.repeat(40))}\n`);
510
+
511
+ // 1. Stop any running server
512
+ if (fs.existsSync(PID_FILE)) {
513
+ try {
514
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
515
+ process.kill(pid, 'SIGTERM');
516
+ console.log(` ${c.green('OK')} Stopped running server (PID ${pid})`);
517
+ } catch { /* ignore */ }
518
+ }
519
+
520
+ // 2. Remove slash commands
521
+ const commandsDest = path.join(os.homedir(), '.claude', 'commands');
522
+ const cmdsRemoved = [];
523
+ if (fs.existsSync(commandsDest)) {
524
+ const files = fs.readdirSync(commandsDest).filter(f => f.startsWith('upfynai'));
525
+ for (const file of files) {
526
+ fs.unlinkSync(path.join(commandsDest, file));
527
+ cmdsRemoved.push(file);
528
+ }
529
+ }
530
+ if (cmdsRemoved.length > 0) {
531
+ console.log(` ${c.green('OK')} Removed ${cmdsRemoved.length} slash commands`);
532
+ }
533
+
534
+ // 3. Remove config directory (~/.upfynai/)
535
+ if (fs.existsSync(CONFIG_DIR)) {
536
+ fs.rmSync(CONFIG_DIR, { recursive: true, force: true });
537
+ console.log(` ${c.green('OK')} Removed config directory: ${c.dim(CONFIG_DIR)}`);
538
+ }
539
+
540
+ // 4. Remove local database (in install dir)
541
+ const dbPath = path.join(__dirname, 'database', 'auth.db');
542
+ const dbFiles = [dbPath, dbPath + '-wal', dbPath + '-shm'];
543
+ for (const f of dbFiles) {
544
+ if (fs.existsSync(f)) {
545
+ fs.unlinkSync(f);
546
+ }
547
+ }
548
+ if (fs.existsSync(dbPath + '-wal') === false) {
549
+ console.log(` ${c.green('OK')} Removed local database`);
550
+ }
551
+
552
+ console.log('');
553
+ console.log(` ${c.bBright('Almost done!')} Run this to finish:`);
554
+ console.log(` ${c.bright('npm uninstall -g upfynai-code')}`);
555
+ console.log('');
556
+ }
557
+
558
+ // Reset: clear database only
559
+ function resetApp() {
560
+ console.log(`\n ${c.bBright('Upfyn-Code — Reset')}`);
561
+ console.log(` ${c.dark('='.repeat(40))}\n`);
562
+
563
+ const dbPath = getDatabasePath();
564
+ const dbFiles = [dbPath, dbPath + '-wal', dbPath + '-shm'];
565
+ let removed = false;
566
+ for (const f of dbFiles) {
567
+ if (fs.existsSync(f)) {
568
+ fs.unlinkSync(f);
569
+ removed = true;
570
+ }
571
+ }
572
+
573
+ if (removed) {
574
+ console.log(` ${c.green('OK')} Database cleared: ${c.dim(dbPath)}`);
575
+ console.log(` ${c.dim('A fresh database will be created on next server start.')}\n`);
576
+ } else {
577
+ console.log(` ${c.yellow('!')} No database found at ${c.dim(dbPath)}\n`);
578
+ }
579
+ }
580
+
447
581
  // Start the server (self-hosted local mode)
448
582
  async function startServer() {
449
583
  // Check for updates silently on startup
@@ -456,6 +590,12 @@ async function startServer() {
456
590
  process.env.VITE_IS_PLATFORM = 'true';
457
591
  }
458
592
 
593
+ // Write PID file so `uc stop` can kill this process
594
+ writePidFile();
595
+ process.on('exit', cleanupPidFile);
596
+ process.on('SIGINT', () => { cleanupPidFile(); process.exit(0); });
597
+ process.on('SIGTERM', () => { cleanupPidFile(); process.exit(0); });
598
+
459
599
  // Show server banner
460
600
  showServerBanner(port, packageJson.version);
461
601
 
@@ -580,6 +720,15 @@ async function main() {
580
720
  case 'uninstall-commands':
581
721
  await uninstallCommands();
582
722
  break;
723
+ case 'stop':
724
+ stopServer();
725
+ break;
726
+ case 'uninstall':
727
+ uninstallApp();
728
+ break;
729
+ case 'reset':
730
+ resetApp();
731
+ break;
583
732
  case 'connect': {
584
733
  const { connectToServer } = await import('./relay-client.js');
585
734
  await connectToServer({
@@ -1,9 +1,59 @@
1
+ // ─── Database Client Resolution ──────────────────────────────────────────────
2
+ // Cloud (Railway): uses @libsql/client for Turso remote databases
3
+ // Local (npm install): uses libsql (native SQLite binding) with async adapter
4
+ // This avoids shipping deprecated transitive deps (node-domexception) to npm users
5
+
1
6
  let createClient;
7
+
8
+ // 1. Try @libsql/client (cloud — installed on Railway via nixpacks)
2
9
  try {
3
10
  createClient = (await import('@libsql/client')).createClient;
4
- } catch (e) {
5
- console.warn('[DB] @libsql/client not available cloud database features disabled.', e?.message || '');
11
+ } catch {
12
+ // 2. Fallback: use libsql native binding with async adapter (local installs)
13
+ try {
14
+ const DatabaseMod = await import('libsql');
15
+ const Database = DatabaseMod.default || DatabaseMod;
16
+ createClient = function createLocalClient({ url }) {
17
+ const dbPath = url.replace(/^file:/, '');
18
+ const sqliteDb = new Database(dbPath);
19
+ sqliteDb.pragma('journal_mode = WAL');
20
+ return {
21
+ execute: async (query) => {
22
+ const sql = typeof query === 'string' ? query : query.sql;
23
+ const args = typeof query === 'string' ? [] : (query.args || []);
24
+ const stmt = sqliteDb.prepare(sql);
25
+ if (sql.trim().match(/^(SELECT|PRAGMA|EXPLAIN)/i)) {
26
+ const rows = args.length ? stmt.all(...args) : stmt.all();
27
+ return { rows, columns: rows.length > 0 ? Object.keys(rows[0]) : [] };
28
+ }
29
+ const info = args.length ? stmt.run(...args) : stmt.run();
30
+ return { rows: [], columns: [], rowsAffected: info.changes, lastInsertRowid: info.lastInsertRowid };
31
+ },
32
+ batch: async (queries) => {
33
+ const results = [];
34
+ for (const q of queries) {
35
+ // Re-use the adapter's own execute method
36
+ const sql = typeof q === 'string' ? q : q.sql;
37
+ const args = typeof q === 'string' ? [] : (q.args || []);
38
+ const stmt = sqliteDb.prepare(sql);
39
+ if (sql.trim().match(/^(SELECT|PRAGMA|EXPLAIN)/i)) {
40
+ const rows = args.length ? stmt.all(...args) : stmt.all();
41
+ results.push({ rows, columns: rows.length > 0 ? Object.keys(rows[0]) : [] });
42
+ } else {
43
+ const info = args.length ? stmt.run(...args) : stmt.run();
44
+ results.push({ rows: [], columns: [], rowsAffected: info.changes, lastInsertRowid: info.lastInsertRowid });
45
+ }
46
+ }
47
+ return results;
48
+ },
49
+ close: () => sqliteDb.close(),
50
+ };
51
+ };
52
+ } catch {
53
+ // Neither available
54
+ }
6
55
  }
56
+
7
57
  import path from 'path';
8
58
  import fs from 'fs';
9
59
  import crypto from 'crypto';
@@ -50,7 +100,7 @@ function resolveDbConfig() {
50
100
  function getDb() {
51
101
  if (!_db) {
52
102
  if (!createClient) {
53
- throw new Error('[DB] @libsql/client is not installed. Run: npm install @libsql/client');
103
+ throw new Error('[DB] No database driver available. Install libsql or @libsql/client.');
54
104
  }
55
105
  resolveDbConfig();
56
106
  _db = createClient({ url: _dbUrl, ...(_dbAuthToken ? { authToken: _dbAuthToken } : {}) });
package/server/index.js CHANGED
@@ -3646,7 +3646,14 @@ app.get('*', (req, res) => {
3646
3646
  res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
3647
3647
  res.setHeader('Pragma', 'no-cache');
3648
3648
  res.setHeader('Expires', '0');
3649
- res.sendFile(indexPath);
3649
+
3650
+ // Inject runtime config so the frontend knows the server mode
3651
+ // (VITE_IS_PLATFORM is a build-time var that may not match the runtime)
3652
+ let html = fs.readFileSync(indexPath, 'utf8');
3653
+ const runtimeConfig = JSON.stringify({ isPlatform: IS_PLATFORM, isLocal: IS_LOCAL });
3654
+ html = html.replace('</head>', `<script>window.__UPFYN_CONFIG__=${runtimeConfig}</script>\n</head>`);
3655
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
3656
+ res.send(html);
3650
3657
  } else {
3651
3658
  // In development, redirect to Vite dev server only if dist doesn't exist
3652
3659
  res.redirect(`http://localhost:${process.env.VITE_PORT || 5173}`);
@@ -1 +0,0 @@
1
- import{r as a,j as e}from"./vendor-react-96lCPsRK.js";import{d as S,k as v}from"./index-DtIQCXd6.js";import{u as I,a as R}from"./AppContent-CapWOZnI.js";import{ab as M,R as T,M as A,Z as w,bi as L,t as $,at as D,aB as E,v as B,bj as O}from"./vendor-icons-BaD0x9SL.js";import"./vendor-syntax-DuHI9Ok6.js";import"./vendor-markdown-CimbIo6Y.js";import"./vendor-i18n-DCFGyhQR.js";import"./LoginModal-Bk9mxZsi.js";import"./vendor-xterm-CZq1hqo1.js";import"./vendor-canvas-D39yWul6.js";import"./vendor-mermaid-CH7SGc99.js";import"./vendor-codemirror-CbtmxxaB.js";function U({onRefresh:s,threshold:t=80,maxPull:l=120,enabled:r=!0}){const i=a.useRef(0),[n,d]=a.useState(0),[c,h]=a.useState(!1),u=a.useRef(!1),f=a.useCallback(o=>{!r||c||(i.current=o.touches[0].clientY,u.current=!1)},[r,c]),x=a.useCallback(o=>{if(!r||c||o.currentTarget.scrollTop>0)return;const g=o.touches[0].clientY-i.current;g>0&&(u.current=!0,d(Math.min(g*.5,l)))},[r,c,l]),m=a.useCallback(async()=>{if(u.current){if(n>=t){h(!0);try{await s()}finally{h(!1)}}d(0),u.current=!1}},[n,t,s]);return{pullDistance:n,isRefreshing:c,handlers:{onTouchStart:f,onTouchMove:x,onTouchEnd:m}}}function y({className:s}){return e.jsx("div",{className:`animate-pulse rounded-md bg-muted/60 ${s||""}`})}function K({selectedProject:s}){const[t,l]=a.useState(null),[r,i]=a.useState(null),[n,d]=a.useState(!0),[c,h]=a.useState(null),{canPrompt:u,promptInstall:f}=I(),{isMobile:x}=R({trackPWA:!1}),m=a.useCallback(async()=>{d(!0),h(null);try{const j=await S("/api/dashboard/stats");if(j.ok){const C=await j.json();l(C)}}catch{}d(!1)},[]),{pullDistance:o,isRefreshing:b,handlers:g}=U({onRefresh:m,enabled:x});return a.useEffect(()=>{m()},[m]),e.jsxs("div",{className:"h-full overflow-y-auto p-4 sm:p-6 space-y-6","data-pull-refresh":!0,...x?g:{},style:o>0?{transform:`translateY(${o*.3}px)`}:void 0,children:[x&&(o>0||b)&&e.jsx("div",{className:"flex items-center justify-center py-2 -mt-2",children:b?e.jsx("div",{className:"w-5 h-5 border-2 border-primary/30 border-t-primary rounded-full animate-spin"}):e.jsx("svg",{className:"w-5 h-5 text-muted-foreground transition-transform",style:{transform:`rotate(${Math.min(o/80*180,180)}deg)`},fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 14l-7 7m0 0l-7-7m7 7V3"})})}),e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{children:[e.jsxs("h2",{className:"text-lg font-semibold text-foreground flex items-center gap-2",children:[e.jsx(M,{className:"w-5 h-5 text-blue-500"}),"Dashboard"]}),e.jsx("p",{className:"text-sm text-muted-foreground mt-0.5",children:s?`Project: ${s.displayName||s.name}`:"Overview"})]}),e.jsx("button",{onClick:m,disabled:n,className:"p-2 rounded-lg hover:bg-muted/60 text-muted-foreground hover:text-foreground transition-colors",title:"Refresh stats",children:e.jsx(T,{className:`w-4 h-4 ${n?"animate-spin":""}`})})]}),e.jsxs("div",{className:"grid grid-cols-2 lg:grid-cols-4 gap-3",children:[e.jsx(k,{icon:A,label:"Sessions",value:(t==null?void 0:t.total)??0,subtext:t!=null&&t.today?`${t.today} today`:void 0,color:"blue",loading:n}),e.jsx(k,{icon:w,label:"AI Providers",value:t!=null&&t.providers?Object.keys(t.providers).length:0,subtext:t!=null&&t.providers?Object.keys(t.providers).join(", "):void 0,color:"purple",loading:n}),v]}),e.jsxs("div",{children:[e.jsx("h3",{className:"text-sm font-medium text-muted-foreground mb-3 uppercase tracking-wider",children:"Quick Actions"}),e.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2",children:[e.jsx(N,{label:"Install CLI",description:"npm install -g upfynai-code",onClick:()=>navigator.clipboard.writeText("npm install -g upfynai-code")}),e.jsx(N,{label:"Connect Machine",description:"uc connect",onClick:()=>navigator.clipboard.writeText("uc connect")}),v,u&&e.jsx(N,{label:"Install App",description:"Add Upfyn to your home screen",onClick:f})]})]}),v,s&&e.jsxs("div",{children:[e.jsx("h3",{className:"text-sm font-medium text-muted-foreground mb-3 uppercase tracking-wider",children:"Project Info"}),e.jsxs("div",{className:"rounded-lg bg-muted/30 border border-border/50 p-4 space-y-2",children:[e.jsx(p,{label:"Name",value:s.displayName||s.name}),e.jsx(p,{label:"Path",value:s.fullPath||s.path||"—"}),s.sessions&&e.jsx(p,{label:"Claude Sessions",value:String(s.sessions.length)}),s.cursorSessions&&e.jsx(p,{label:"Cursor Sessions",value:String(s.cursorSessions.length)}),s.codexSessions&&e.jsx(p,{label:"Codex Sessions",value:String(s.codexSessions.length)})]})]}),!s&&e.jsxs("div",{children:[e.jsxs("h3",{className:"text-sm font-medium text-muted-foreground mb-3 uppercase tracking-wider flex items-center gap-2",children:[e.jsx(L,{className:"w-3.5 h-3.5"}),"Getting Started"]}),e.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-2 gap-3",children:[e.jsxs("div",{className:"rounded-lg border border-border/50 bg-card/50 p-4",children:[e.jsxs("div",{className:"flex items-center gap-2 mb-2",children:[e.jsx($,{className:"w-4 h-4 text-blue-500"}),e.jsx("span",{className:"text-sm font-medium text-foreground",children:"1. Install the CLI"})]}),e.jsxs("p",{className:"text-xs text-muted-foreground mb-2",children:["Install globally, then run ",e.jsx("code",{className:"bg-muted px-1 rounded",children:"uc"})," to launch."]}),e.jsx("code",{className:"text-xs bg-muted/50 px-2 py-1 rounded block text-foreground/80",children:"npm install -g upfynai-code"})]}),e.jsxs("div",{className:"rounded-lg border border-border/50 bg-card/50 p-4",children:[e.jsxs("div",{className:"flex items-center gap-2 mb-2",children:[e.jsx(w,{className:"w-4 h-4 text-yellow-500"}),e.jsx("span",{className:"text-sm font-medium text-foreground",children:"2. Connect Your Machine"})]}),e.jsx("p",{className:"text-xs text-muted-foreground mb-2",children:"Bridge your local dev environment to this web UI."}),e.jsx("code",{className:"text-xs bg-muted/50 px-2 py-1 rounded block text-foreground/80",children:"uc connect --key your_relay_token"})]}),e.jsxs("div",{className:"rounded-lg border border-border/50 bg-card/50 p-4",children:[e.jsxs("div",{className:"flex items-center gap-2 mb-2",children:[e.jsx(D,{className:"w-4 h-4 text-indigo-500"}),e.jsx("span",{className:"text-sm font-medium text-foreground",children:"3. Use the Canvas"})]}),e.jsx("p",{className:"text-xs text-muted-foreground",children:"Switch to the Canvas tab to create visual workspaces with code blocks, diagrams, and notes."})]}),e.jsxs("div",{className:"rounded-lg border border-border/50 bg-card/50 p-4",children:[e.jsxs("div",{className:"flex items-center gap-2 mb-2",children:[e.jsx(E,{className:"w-4 h-4 text-emerald-500"}),e.jsx("span",{className:"text-sm font-medium text-foreground",children:"4. Chat with AI"})]}),e.jsx("p",{className:"text-xs text-muted-foreground",children:"Use the Chat tab to talk to Claude, Cursor, or Codex. Bring your own API keys in Settings."})]})]})]})]})}function k({icon:s,label:t,value:l,subtext:r,color:i,loading:n}){const d={blue:"text-blue-500 bg-blue-500/10",purple:"text-purple-500 bg-purple-500/10",amber:"text-amber-500 bg-amber-500/10",emerald:"text-emerald-500 bg-emerald-500/10"};return e.jsxs("div",{className:"rounded-lg border border-border/50 bg-card/50 p-3 sm:p-4",children:[e.jsxs("div",{className:"flex items-center gap-2 mb-2",children:[e.jsx("div",{className:`w-7 h-7 rounded-md flex items-center justify-center ${d[i]}`,children:e.jsx(s,{className:"w-3.5 h-3.5"})}),e.jsx("span",{className:"text-xs text-muted-foreground",children:t})]}),n?e.jsxs(e.Fragment,{children:[e.jsx(y,{className:"h-6 w-16 mb-1"}),e.jsx(y,{className:"h-3 w-20"})]}):e.jsxs(e.Fragment,{children:[e.jsx("p",{className:"text-xl font-semibold text-foreground",children:l}),r&&e.jsx("p",{className:"text-xs text-muted-foreground mt-0.5",children:r})]})]})}function N({label:s,description:t,onClick:l,href:r}){const i=r?"a":"button",n=r?{href:r,target:"_blank",rel:"noopener noreferrer"}:{onClick:l};return e.jsxs(i,{...n,className:"flex items-center justify-between p-3 rounded-lg border border-border/50 bg-card/50 hover:bg-muted/60 transition-colors text-left group",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-sm font-medium text-foreground",children:s}),e.jsx("p",{className:"text-xs text-muted-foreground",children:t})]}),r?e.jsx(B,{className:"w-3.5 h-3.5 text-muted-foreground group-hover:text-foreground"}):e.jsx(O,{className:"w-3.5 h-3.5 text-muted-foreground group-hover:text-foreground"})]})}function p({label:s,value:t}){return e.jsxs("div",{className:"flex items-center justify-between text-sm",children:[e.jsx("span",{className:"text-muted-foreground",children:s}),e.jsx("span",{className:"text-foreground font-mono text-xs truncate max-w-[60%] text-right",children:t})]})}export{K as default};
@@ -1 +0,0 @@
1
- import{r,j as e}from"./vendor-react-96lCPsRK.js";import{L as X,b as ee,C as te,a as se}from"./LoginModal-Bk9mxZsi.js";import{h as ae,d as c}from"./index-DtIQCXd6.js";import{b as re,t as ie,at as ne,Z as oe,G as le,au as de,av as ce,j as ue,r as me,L as R,a4 as q,q as xe}from"./vendor-icons-BaD0x9SL.js";import"./vendor-xterm-CZq1hqo1.js";import"./vendor-canvas-D39yWul6.js";import"./vendor-mermaid-CH7SGc99.js";import"./vendor-syntax-DuHI9Ok6.js";import"./vendor-markdown-CimbIo6Y.js";import"./vendor-i18n-DCFGyhQR.js";const Ce=({onComplete:y})=>{const[a,G]=r.useState(0),[m,N]=r.useState(""),[d,v]=r.useState(""),[o,x]=r.useState(!1),[w,l]=r.useState(""),[O,C]=r.useState(!1),[M,T]=r.useState("right"),[i,g]=r.useState(null),[B]=r.useState({name:"default",fullPath:""}),[k,p]=r.useState({authenticated:!1,email:null,loading:!0,error:null}),[S,b]=r.useState({authenticated:!1,email:null,loading:!0,error:null}),[A,j]=r.useState({authenticated:!1,email:null,loading:!0,error:null}),{user:h}=ae(),L=r.useRef(void 0);r.useEffect(()=>{D()},[]);const D=async()=>{try{const t=await c("/api/user/git-config");if(t.ok){const s=await t.json();s.gitName&&N(s.gitName),s.gitEmail&&v(s.gitEmail)}}catch{}};r.useEffect(()=>{const t=L.current;L.current=i,(t===void 0||t!==null&&i===null)&&(F(),E(),I())},[i]);const F=async()=>{try{const t=await c("/api/cli/claude/status");if(t.ok){const s=await t.json();p({authenticated:s.authenticated,email:s.email,loading:!1,error:s.error||null})}else p({authenticated:!1,email:null,loading:!1,error:"Failed to check status"})}catch(t){p({authenticated:!1,email:null,loading:!1,error:t.message})}},E=async()=>{try{const t=await c("/api/cli/cursor/status");if(t.ok){const s=await t.json();b({authenticated:s.authenticated,email:s.email,loading:!1,error:s.error||null})}else b({authenticated:!1,email:null,loading:!1,error:"Failed to check status"})}catch(t){b({authenticated:!1,email:null,loading:!1,error:t.message})}},I=async()=>{try{const t=await c("/api/cli/codex/status");if(t.ok){const s=await t.json();j({authenticated:s.authenticated,email:s.email,loading:!1,error:s.error||null})}else j({authenticated:!1,email:null,loading:!1,error:"Failed to check status"})}catch(t){j({authenticated:!1,email:null,loading:!1,error:t.message})}},U=()=>g("claude"),Y=()=>g("cursor"),z=()=>g("codex"),J=t=>{t===0&&(i==="claude"?F():i==="cursor"?E():i==="codex"&&I())},u=(t,s="right")=>{T(s),C(!0),setTimeout(()=>{G(t),C(!1)},200)},V=async()=>{if(l(""),a===0){u(1,"right");return}if(a===1){if(!m.trim()||!d.trim()){l("Both git name and email are required");return}if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(d)){l("Please enter a valid email address");return}x(!0);try{const s=await c("/api/user/git-config",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({gitName:m,gitEmail:d})});if(!s.ok){const n=await s.json();throw new Error(n.error||"Failed to save git configuration")}u(2,"right")}catch(s){l(s.message)}finally{x(!1)}return}u(a+1,"right")},W=()=>{l(""),u(a-1,"left")},Z=async()=>{x(!0),l("");try{const t=await c("/api/user/complete-onboarding",{method:"POST"});if(!t.ok){const s=await t.json();throw new Error(s.error||"Failed to complete onboarding")}y&&y()}catch(t){l(t.message)}finally{x(!1)}},f=[{title:"Welcome",required:!1},{title:"Git Identity",required:!0},{title:"Agents",required:!1}],_=()=>a===0?!0:a===1?m.trim()&&d.trim()&&/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(d):!0,P=[k,S,A].filter(t=>t.authenticated).length,Q=[{icon:ie,title:"AI Agents",desc:"Claude, Cursor & Codex — unified in one interface",gradient:"from-blue-500/10 to-blue-600/5",iconColor:"text-blue-500"},{icon:ne,title:"Canvas",desc:"Visual workspace with code blocks, diagrams & notes",gradient:"from-violet-500/10 to-violet-600/5",iconColor:"text-violet-500"},{icon:oe,title:"Relay",desc:"Bridge your local machine to the web UI seamlessly",gradient:"from-amber-500/10 to-amber-600/5",iconColor:"text-amber-500"}],H=[{name:"Claude Code",status:k,onLogin:U,logo:e.jsx(ee,{size:22}),accent:"blue"},{name:"Cursor",status:S,onLogin:Y,logo:e.jsx(te,{size:22}),accent:"violet"},{name:"OpenAI Codex",status:A,onLogin:z,logo:e.jsx(se,{className:"w-5 h-5"}),accent:"neutral"}],K=O?`opacity-0 ${M==="right"?"translate-x-4":"-translate-x-4"}`:"opacity-100 translate-x-0";return e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"min-h-screen bg-background flex items-center justify-center p-4 sm:p-8",children:e.jsxs("div",{className:"w-full max-w-xl",children:[e.jsxs("div",{className:"mb-10",children:[e.jsx("div",{className:"flex items-center justify-between mb-3 px-1",children:f.map((t,s)=>e.jsxs("button",{onClick:()=>{s<a&&u(s,"left")},disabled:s>a,className:`text-xs font-medium tracking-wide uppercase transition-colors duration-300 ${s===a?"text-foreground":s<a?"text-primary cursor-pointer hover:text-primary/80":"text-muted-foreground/50"}`,children:[t.title,t.required&&s===a&&e.jsx("span",{className:"text-destructive ml-1",children:"*"})]},s))}),e.jsx("div",{className:"h-1 bg-muted/60 rounded-full overflow-hidden",children:e.jsx("div",{className:"h-full bg-primary rounded-full transition-all duration-500 ease-out",style:{width:`${a/(f.length-1)*100}%`}})})]}),e.jsxs("div",{className:"bg-card rounded-2xl border border-border/60 shadow-xl shadow-black/[0.04] dark:shadow-black/[0.2] overflow-hidden",children:[e.jsxs("div",{className:`p-8 sm:p-10 transition-all duration-200 ease-out ${K}`,children:[a===0&&e.jsxs("div",{className:"space-y-8",children:[e.jsxs("div",{className:"text-center",children:[e.jsxs("div",{className:"inline-flex items-center gap-2 px-3 py-1 rounded-full bg-primary/10 text-primary text-xs font-medium mb-6",children:[e.jsx(re,{className:"w-3.5 h-3.5"}),"Quick setup — under a minute"]}),e.jsxs("h1",{className:"text-3xl sm:text-4xl font-bold text-foreground tracking-tight leading-tight",children:["Welcome to Upfyn",h!=null&&h.first_name?e.jsxs("span",{className:"text-primary",children:[", ",h.first_name]}):null]}),e.jsx("p",{className:"text-muted-foreground mt-3 text-base max-w-md mx-auto leading-relaxed",children:"Your visual AI coding interface. Connect your favorite agents, manage projects, and build faster."})]}),e.jsx("div",{className:"grid gap-3",children:Q.map((t,s)=>e.jsxs("div",{className:`group flex items-start gap-4 p-4 rounded-xl bg-gradient-to-r ${t.gradient} border border-border/40 hover:border-border/80 transition-all duration-200`,children:[e.jsx("div",{className:`mt-0.5 p-2 rounded-lg bg-background/80 ${t.iconColor} flex-shrink-0`,children:e.jsx(t.icon,{className:"w-5 h-5"})}),e.jsxs("div",{children:[e.jsx("p",{className:"font-semibold text-foreground text-sm",children:t.title}),e.jsx("p",{className:"text-muted-foreground text-sm mt-0.5",children:t.desc})]})]},s))})]}),a===1&&e.jsxs("div",{className:"space-y-8",children:[e.jsxs("div",{className:"text-center",children:[e.jsx("div",{className:"w-14 h-14 rounded-2xl bg-primary/10 flex items-center justify-center mx-auto mb-5",children:e.jsx(le,{className:"w-7 h-7 text-primary"})}),e.jsx("h2",{className:"text-2xl sm:text-3xl font-bold text-foreground tracking-tight",children:"Git Identity"}),e.jsx("p",{className:"text-muted-foreground mt-2 text-sm max-w-sm mx-auto",children:"Set your name and email for commit attribution"})]}),e.jsxs("div",{className:"space-y-5",children:[e.jsxs("div",{children:[e.jsxs("label",{htmlFor:"gitName",className:"flex items-center gap-2 text-sm font-medium text-foreground mb-2",children:[e.jsx(de,{className:"w-3.5 h-3.5 text-muted-foreground"}),"Name"]}),e.jsx("input",{type:"text",id:"gitName",value:m,onChange:t=>N(t.target.value),className:"w-full px-4 py-3 border border-border/60 rounded-xl bg-background text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/40 transition-all duration-200",placeholder:"John Doe",required:!0,disabled:o})]}),e.jsxs("div",{children:[e.jsxs("label",{htmlFor:"gitEmail",className:"flex items-center gap-2 text-sm font-medium text-foreground mb-2",children:[e.jsx(ce,{className:"w-3.5 h-3.5 text-muted-foreground"}),"Email"]}),e.jsx("input",{type:"email",id:"gitEmail",value:d,onChange:t=>v(t.target.value),className:"w-full px-4 py-3 border border-border/60 rounded-xl bg-background text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/40 transition-all duration-200",placeholder:"john@example.com",required:!0,disabled:o})]}),e.jsxs("p",{className:"text-xs text-muted-foreground/70 text-center",children:["Applied as ",e.jsx("code",{className:"px-1 py-0.5 rounded bg-muted/60 text-xs",children:"git config --global"})," on your machine"]})]})]}),a===2&&e.jsxs("div",{className:"space-y-8",children:[e.jsxs("div",{className:"text-center",children:[e.jsx("h2",{className:"text-2xl sm:text-3xl font-bold text-foreground tracking-tight",children:"Connect Your Agents"}),e.jsxs("p",{className:"text-muted-foreground mt-2 text-sm max-w-sm mx-auto",children:["Optional — login to one or more AI assistants.",P>0&&e.jsxs("span",{className:"text-primary font-medium",children:[" ",P," connected"]})]})]}),e.jsx("div",{className:"space-y-3",children:H.map((t,s)=>{const n=t.status.authenticated,$=t.status.loading;return e.jsxs("div",{className:`group flex items-center justify-between p-4 rounded-xl border transition-all duration-200 ${n?"bg-primary/5 border-primary/20":"bg-card border-border/50 hover:border-border"}`,children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("div",{className:`w-10 h-10 rounded-xl flex items-center justify-center flex-shrink-0 ${n?"bg-primary/10":"bg-muted/60"}`,children:t.logo}),e.jsxs("div",{children:[e.jsxs("div",{className:"font-medium text-sm text-foreground flex items-center gap-2",children:[t.name,n&&e.jsxs("span",{className:"inline-flex items-center gap-1 px-1.5 py-0.5 rounded-md bg-green-500/10 text-green-600 dark:text-green-400 text-[10px] font-semibold uppercase tracking-wider",children:[e.jsx(ue,{className:"w-3 h-3"}),"Connected"]})]}),e.jsx("p",{className:"text-xs text-muted-foreground mt-0.5",children:$?"Checking...":n?t.status.email||"Ready to use":"Not connected"})]})]}),!n&&!$&&e.jsx("button",{onClick:t.onLogin,className:"px-4 py-2 text-sm font-medium rounded-lg bg-foreground text-background hover:opacity-90 transition-opacity flex-shrink-0",children:"Connect"})]},s)})}),e.jsx("p",{className:"text-xs text-muted-foreground/60 text-center",children:"You can always add or change these in Settings later."})]})]}),w&&e.jsx("div",{className:"mx-8 sm:mx-10 mb-2 p-3 rounded-xl bg-destructive/10 border border-destructive/20",children:e.jsx("p",{className:"text-sm text-destructive",children:w})}),e.jsxs("div",{className:"flex items-center justify-between px-8 sm:px-10 py-5 border-t border-border/40 bg-muted/20",children:[e.jsxs("button",{onClick:W,disabled:a===0||o,className:"flex items-center gap-1.5 text-sm font-medium text-muted-foreground hover:text-foreground disabled:opacity-0 disabled:pointer-events-none transition-all duration-200",children:[e.jsx(me,{className:"w-4 h-4"}),"Back"]}),a<f.length-1?e.jsx("button",{onClick:V,disabled:!_()||o,className:"flex items-center gap-2 px-6 py-2.5 bg-primary text-primary-foreground font-medium text-sm rounded-xl hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 shadow-sm",children:o?e.jsxs(e.Fragment,{children:[e.jsx(R,{className:"w-4 h-4 animate-spin"}),"Saving..."]}):a===0?e.jsxs(e.Fragment,{children:["Get Started",e.jsx(q,{className:"w-4 h-4"})]}):e.jsxs(e.Fragment,{children:["Continue",e.jsx(xe,{className:"w-4 h-4"})]})}):e.jsx("button",{onClick:Z,disabled:o,className:"flex items-center gap-2 px-6 py-2.5 bg-primary text-primary-foreground font-medium text-sm rounded-xl hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 shadow-sm",children:o?e.jsxs(e.Fragment,{children:[e.jsx(R,{className:"w-4 h-4 animate-spin"}),"Finishing..."]}):e.jsxs(e.Fragment,{children:["Start Building",e.jsx(q,{className:"w-4 h-4"})]})})]})]}),e.jsxs("p",{className:"text-center text-xs text-muted-foreground/50 mt-6",children:["Step ",a+1," of ",f.length]})]})}),i&&e.jsx(X,{isOpen:!!i,onClose:()=>g(null),provider:i,project:B,onComplete:J,isOnboarding:!0})]})};export{Ce as default};