upfynai-code 2.8.5 → 2.8.6
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/README.md +26 -86
- package/bin/cli.js +1 -1
- package/package.json +4 -1
- package/src/connect.js +86 -36
package/README.md
CHANGED
|
@@ -2,35 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
> Unified AI coding interface — access AI chat, terminal, file explorer, git, and visual canvas from any browser.
|
|
4
4
|
|
|
5
|
-
**Upfyn-Code** connects your local development machine to a browser-based UI through a secure
|
|
5
|
+
**Upfyn-Code** connects your local development machine to a browser-based UI through a secure, encrypted connection. Write code with AI assistance, run shell commands, manage files, handle git operations, and plan on a visual canvas — all from any device, anywhere.
|
|
6
6
|
|
|
7
|
-
Your source code **never leaves your machine**. All file operations, terminal commands, and git actions execute locally
|
|
7
|
+
Your source code **never leaves your machine**. All file operations, terminal commands, and git actions execute locally.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npm install -g
|
|
12
|
+
npm install -g upfynai-code
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Or run directly with npx:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npx
|
|
18
|
+
npx upfynai-code
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
**Requirements:** Node.js
|
|
21
|
+
**Requirements:** Node.js 22 or later. Works on Windows, macOS, and Linux.
|
|
22
22
|
|
|
23
23
|
## Quick Start
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
# 1. Authenticate with your Upfyn account
|
|
27
|
-
|
|
27
|
+
uc login
|
|
28
28
|
|
|
29
29
|
# 2. Connect your machine to the web interface
|
|
30
|
-
|
|
30
|
+
uc connect
|
|
31
31
|
|
|
32
32
|
# 3. Open the web UI (opens automatically)
|
|
33
|
-
|
|
33
|
+
uc start
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
Once connected, open [cli.upfyn.com](https://cli.upfyn.com) on any device to access your full development environment.
|
|
@@ -39,45 +39,22 @@ Once connected, open [cli.upfyn.com](https://cli.upfyn.com) on any device to acc
|
|
|
39
39
|
|
|
40
40
|
| Command | Description |
|
|
41
41
|
|---------|-------------|
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
42
|
+
| `uc` | Launch Claude Code with Upfyn web UI in background |
|
|
43
|
+
| `uc start` | Start the web UI server |
|
|
44
|
+
| `uc connect` | Connect your local machine to the cloud interface |
|
|
45
|
+
| `uc login` | Authenticate with your Upfyn account |
|
|
46
|
+
| `uc logout` | Clear saved credentials |
|
|
47
|
+
| `uc status` | Show current configuration |
|
|
48
|
+
| `uc config` | View or set configuration |
|
|
49
|
+
| `uc help` | Show all available commands |
|
|
49
50
|
|
|
50
51
|
### Connect Options
|
|
51
52
|
|
|
52
53
|
```bash
|
|
53
|
-
|
|
54
|
+
uc connect --key <token>
|
|
54
55
|
```
|
|
55
56
|
|
|
56
|
-
- `--
|
|
57
|
-
- `--key <token>` — Relay token (get from the web UI after signing in)
|
|
58
|
-
|
|
59
|
-
### MCP Integration
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
upfyn-code mcp --server <url> --key <token>
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Outputs the MCP (Model Context Protocol) configuration JSON for use with Claude Desktop, Cursor, or other MCP-compatible tools.
|
|
66
|
-
|
|
67
|
-
**Example output:**
|
|
68
|
-
|
|
69
|
-
```json
|
|
70
|
-
{
|
|
71
|
-
"mcpServers": {
|
|
72
|
-
"upfyn-code": {
|
|
73
|
-
"url": "https://your-server.com/mcp",
|
|
74
|
-
"headers": {
|
|
75
|
-
"Authorization": "Bearer rt_xxxxx"
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
```
|
|
57
|
+
- `--key <token>` — Your connection token (get from the web UI after signing in)
|
|
81
58
|
|
|
82
59
|
## Features
|
|
83
60
|
|
|
@@ -102,55 +79,18 @@ Connect external tools and services via the Model Context Protocol. Use Upfyn-Co
|
|
|
102
79
|
## How It Works
|
|
103
80
|
|
|
104
81
|
1. **Install** the CLI on your development machine
|
|
105
|
-
2. **Authenticate** with `
|
|
106
|
-
3. **Connect** with `
|
|
82
|
+
2. **Authenticate** with `uc login`
|
|
83
|
+
3. **Connect** with `uc connect` — creates a secure, encrypted connection
|
|
107
84
|
4. **Open** [cli.upfyn.com](https://cli.upfyn.com) on any browser (desktop, tablet, or mobile)
|
|
108
85
|
|
|
109
|
-
The CLI
|
|
110
|
-
|
|
111
|
-
```
|
|
112
|
-
┌─────────────┐ WebSocket ┌──────────────┐ Browser ┌──────────────┐
|
|
113
|
-
│ Your Local │ ──────────────▶ │ Upfyn Relay │ ◀────────────── │ Web UI at │
|
|
114
|
-
│ Machine │ (encrypted) │ Server │ │ cli.upfyn.com│
|
|
115
|
-
└─────────────┘ └──────────────┘ └──────────────┘
|
|
116
|
-
```
|
|
86
|
+
The CLI connects your local filesystem, terminal, and git to the web interface. No ports are exposed and no firewall configuration is needed.
|
|
117
87
|
|
|
118
88
|
## Security
|
|
119
89
|
|
|
120
|
-
- All communication is encrypted
|
|
121
|
-
- Source code is
|
|
122
|
-
-
|
|
123
|
-
-
|
|
124
|
-
- Self-hosting is supported for full data sovereignty
|
|
125
|
-
|
|
126
|
-
## Self-Hosting
|
|
127
|
-
|
|
128
|
-
Upfyn-Code is open source under the GPL-3.0 license. You can deploy the entire stack on your own infrastructure:
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
git clone https://github.com/AnitChaudhry/UpfynAI-Code.git
|
|
132
|
-
cd UpfynAI-Code
|
|
133
|
-
|
|
134
|
-
# Install dependencies
|
|
135
|
-
npm install
|
|
136
|
-
|
|
137
|
-
# Start the backend
|
|
138
|
-
cd backend && npm start
|
|
139
|
-
|
|
140
|
-
# Start the frontend
|
|
141
|
-
cd frontend && npm run dev
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
See the [repository](https://github.com/AnitChaudhry/UpfynAI-Code) for full deployment instructions.
|
|
145
|
-
|
|
146
|
-
## Configuration
|
|
147
|
-
|
|
148
|
-
Credentials and config are stored locally at:
|
|
149
|
-
|
|
150
|
-
| Platform | Path |
|
|
151
|
-
|----------|------|
|
|
152
|
-
| macOS / Linux | `~/.config/upfyn-code/` |
|
|
153
|
-
| Windows | `%APPDATA%\upfyn-code\` |
|
|
90
|
+
- All communication is encrypted end-to-end
|
|
91
|
+
- Source code is **never stored** on servers
|
|
92
|
+
- Secure authentication with automatic session management
|
|
93
|
+
- Each user's environment is fully isolated
|
|
154
94
|
|
|
155
95
|
## Links
|
|
156
96
|
|
|
@@ -163,4 +103,4 @@ Credentials and config are stored locally at:
|
|
|
163
103
|
|
|
164
104
|
GPL-3.0 — see [LICENSE](https://github.com/AnitChaudhry/UpfynAI-Code/blob/main/LICENSE) for details.
|
|
165
105
|
|
|
166
|
-
Built by [
|
|
106
|
+
Built by [Upfynai-Code](https://cli.upfyn.com).
|
package/bin/cli.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "upfynai-code",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.6",
|
|
4
4
|
"description": "Unified AI coding interface — access AI chat, terminal, file explorer, git, and visual canvas from any browser. Connect your local machine and code from anywhere.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -67,6 +67,9 @@
|
|
|
67
67
|
"prompts": "^2.4.2",
|
|
68
68
|
"ws": "^8.18.0"
|
|
69
69
|
},
|
|
70
|
+
"optionalDependencies": {
|
|
71
|
+
"node-pty": "^1.1.0-beta34"
|
|
72
|
+
},
|
|
70
73
|
"engines": {
|
|
71
74
|
"node": ">=18.0.0"
|
|
72
75
|
},
|
package/src/connect.js
CHANGED
|
@@ -11,6 +11,10 @@ import { getPersistentShell } from './persistent-shell.js';
|
|
|
11
11
|
import { needsPermission, requestPermission, handlePermissionResponse } from './permissions.js';
|
|
12
12
|
import { playConnectAnimation } from './animation.js';
|
|
13
13
|
|
|
14
|
+
// Optional node-pty for proper terminal emulation (graceful fallback to spawn)
|
|
15
|
+
let pty;
|
|
16
|
+
try { pty = (await import('node-pty')).default; } catch { pty = null; }
|
|
17
|
+
|
|
14
18
|
// Resolve agents: dist/agents/ (npm package) or ../../shared/agents/ (monorepo)
|
|
15
19
|
const __connectDir = dirname(fileURLToPath(import.meta.url));
|
|
16
20
|
const _agentsPath = existsSync(join(__connectDir, '../dist/agents/index.js'))
|
|
@@ -26,15 +30,17 @@ const activeShellSessions = new Map();
|
|
|
26
30
|
|
|
27
31
|
/**
|
|
28
32
|
* Start an interactive shell session on the local machine, relayed to the browser.
|
|
29
|
-
*
|
|
33
|
+
* Uses node-pty for proper terminal emulation when available, falls back to spawn.
|
|
30
34
|
*/
|
|
31
35
|
function handleShellSessionStart(data, ws) {
|
|
32
36
|
const shellSessionId = data.requestId;
|
|
33
37
|
const projectPath = data.projectPath || process.cwd();
|
|
34
38
|
const isWin = process.platform === 'win32';
|
|
35
39
|
const shellType = data.shellType;
|
|
40
|
+
const cols = data.cols || 80;
|
|
41
|
+
const rows = data.rows || 24;
|
|
36
42
|
|
|
37
|
-
console.log(chalk.cyan(` [relay] Starting shell session in ${projectPath}`));
|
|
43
|
+
console.log(chalk.cyan(` [relay] Starting shell session in ${projectPath}${pty ? ' (pty)' : ' (spawn)'}`));
|
|
38
44
|
|
|
39
45
|
let shellCmd, shellArgs;
|
|
40
46
|
const provider = data.provider || 'claude';
|
|
@@ -42,8 +48,8 @@ function handleShellSessionStart(data, ws) {
|
|
|
42
48
|
|
|
43
49
|
function getInteractiveShell(projectDir) {
|
|
44
50
|
if (isWin) {
|
|
45
|
-
if (shellType === 'cmd') return { cmd: 'cmd.exe', args: [
|
|
46
|
-
return { cmd: 'powershell.exe', args: ['-NoExit'
|
|
51
|
+
if (shellType === 'cmd') return { cmd: 'cmd.exe', args: [] };
|
|
52
|
+
return { cmd: 'powershell.exe', args: ['-NoExit'] };
|
|
47
53
|
}
|
|
48
54
|
const sh = shellType || process.env.SHELL || 'bash';
|
|
49
55
|
return { cmd: sh, args: ['--login'] };
|
|
@@ -88,40 +94,68 @@ function handleShellSessionStart(data, ws) {
|
|
|
88
94
|
|
|
89
95
|
ws.send(JSON.stringify({ type: 'relay-shell-output', shellSessionId, data: '' }));
|
|
90
96
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
97
|
+
// Use node-pty for proper terminal emulation (resize, isatty, line discipline)
|
|
98
|
+
if (pty) {
|
|
99
|
+
const proc = pty.spawn(shellCmd, shellArgs, {
|
|
100
|
+
name: 'xterm-256color',
|
|
101
|
+
cols,
|
|
102
|
+
rows,
|
|
103
|
+
cwd: projectPath,
|
|
104
|
+
env: { ...process.env, TERM: 'xterm-256color', COLORTERM: 'truecolor', FORCE_COLOR: '3' },
|
|
105
|
+
});
|
|
96
106
|
|
|
97
|
-
|
|
107
|
+
activeShellSessions.set(shellSessionId, { proc, projectPath, isPty: true });
|
|
98
108
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
109
|
+
proc.onData((chunk) => {
|
|
110
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
111
|
+
ws.send(JSON.stringify({ type: 'relay-shell-output', shellSessionId, data: chunk }));
|
|
112
|
+
}
|
|
113
|
+
});
|
|
104
114
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
ws.
|
|
108
|
-
|
|
109
|
-
|
|
115
|
+
proc.onExit(({ exitCode }) => {
|
|
116
|
+
activeShellSessions.delete(shellSessionId);
|
|
117
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
118
|
+
ws.send(JSON.stringify({ type: 'relay-shell-exited', shellSessionId, exitCode }));
|
|
119
|
+
}
|
|
120
|
+
console.log(chalk.dim(` [relay] Shell session ended (code ${exitCode})`));
|
|
121
|
+
});
|
|
122
|
+
} else {
|
|
123
|
+
// Fallback: spawn without PTY (text wrapping may be incorrect)
|
|
124
|
+
const proc = spawn(shellCmd, shellArgs, {
|
|
125
|
+
cwd: projectPath,
|
|
126
|
+
env: { ...process.env, TERM: 'xterm-256color', COLORTERM: 'truecolor', FORCE_COLOR: '3', COLUMNS: String(cols), LINES: String(rows) },
|
|
127
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
128
|
+
});
|
|
110
129
|
|
|
111
|
-
|
|
112
|
-
activeShellSessions.delete(shellSessionId);
|
|
113
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
114
|
-
ws.send(JSON.stringify({ type: 'relay-shell-exited', shellSessionId, exitCode: code }));
|
|
115
|
-
}
|
|
116
|
-
console.log(chalk.dim(` [relay] Shell session ended (code ${code})`));
|
|
117
|
-
});
|
|
130
|
+
activeShellSessions.set(shellSessionId, { proc, projectPath, isPty: false });
|
|
118
131
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
132
|
+
proc.stdout.on('data', (chunk) => {
|
|
133
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
134
|
+
ws.send(JSON.stringify({ type: 'relay-shell-output', shellSessionId, data: chunk.toString() }));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
proc.stderr.on('data', (chunk) => {
|
|
139
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
140
|
+
ws.send(JSON.stringify({ type: 'relay-shell-output', shellSessionId, data: chunk.toString() }));
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
proc.on('close', (code) => {
|
|
145
|
+
activeShellSessions.delete(shellSessionId);
|
|
146
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
147
|
+
ws.send(JSON.stringify({ type: 'relay-shell-exited', shellSessionId, exitCode: code }));
|
|
148
|
+
}
|
|
149
|
+
console.log(chalk.dim(` [relay] Shell session ended (code ${code})`));
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
proc.on('error', (err) => {
|
|
153
|
+
activeShellSessions.delete(shellSessionId);
|
|
154
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
155
|
+
ws.send(JSON.stringify({ type: 'relay-shell-output', shellSessionId, data: `\r\n\x1b[31mError: ${err.message}\x1b[0m\r\n` }));
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
125
159
|
}
|
|
126
160
|
|
|
127
161
|
/**
|
|
@@ -292,14 +326,30 @@ export async function connect(options = {}) {
|
|
|
292
326
|
}
|
|
293
327
|
if (data.type === 'relay-shell-input') {
|
|
294
328
|
const session = activeShellSessions.get(data.shellSessionId);
|
|
295
|
-
if (session
|
|
329
|
+
if (session) {
|
|
330
|
+
if (session.isPty) {
|
|
331
|
+
session.proc.write(data.data);
|
|
332
|
+
} else if (session.proc?.stdin?.writable) {
|
|
333
|
+
session.proc.stdin.write(data.data);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (data.type === 'relay-shell-resize') {
|
|
339
|
+
const session = activeShellSessions.get(data.shellSessionId);
|
|
340
|
+
if (session?.isPty && session.proc?.resize) {
|
|
341
|
+
try { session.proc.resize(data.cols || 80, data.rows || 24); } catch { /* ignore resize errors */ }
|
|
342
|
+
}
|
|
296
343
|
return;
|
|
297
344
|
}
|
|
298
|
-
if (data.type === 'relay-shell-resize') return;
|
|
299
345
|
if (data.type === 'relay-shell-kill') {
|
|
300
346
|
const session = activeShellSessions.get(data.shellSessionId);
|
|
301
347
|
if (session?.proc) {
|
|
302
|
-
session.
|
|
348
|
+
if (session.isPty) {
|
|
349
|
+
session.proc.kill();
|
|
350
|
+
} else {
|
|
351
|
+
session.proc.kill('SIGTERM');
|
|
352
|
+
}
|
|
303
353
|
activeShellSessions.delete(data.shellSessionId);
|
|
304
354
|
console.log(chalk.dim(' [relay] Shell session killed'));
|
|
305
355
|
}
|