uplink-cli 0.1.26 → 0.1.28
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/cli/src/index.ts +2 -4
- package/cli/src/subcommands/menu/tunnels.ts +15 -2
- package/cli/src/subcommands/menu.ts +359 -96
- package/docs/AGENTS.md +139 -0
- package/docs/MENU_STRUCTURE.md +292 -0
- package/docs/OPEN_SOURCE_CLI.md +71 -0
- package/docs/PRODUCT.md +314 -0
- package/docs/guides/MANUAL.md +513 -0
- package/docs/guides/QUICKSTART.md +280 -0
- package/docs/guides/README.md +22 -0
- package/docs/guides/TESTING.md +408 -0
- package/docs/guides/TUNNEL_SETUP.md +134 -0
- package/docs/guides/USAGE.md +247 -0
- package/package.json +3 -2
- package/scripts/tunnel/client-improved.js +14 -2
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Tunnel Setup Guide - Step by Step
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
|
|
5
|
+
1. **Backend API running** (recommended: behind Caddy at `api.uplink.spot`)
|
|
6
|
+
2. **Tunnel relay running** (on server: ports 7070/7071)
|
|
7
|
+
3. **Caddy running** (on server: handles routing)
|
|
8
|
+
4. **Your app running locally** (e.g., `npm run dev` on port 3000)
|
|
9
|
+
|
|
10
|
+
## Step-by-Step Instructions
|
|
11
|
+
|
|
12
|
+
### Step 1: Start Your App
|
|
13
|
+
|
|
14
|
+
In **Cursor window #1** (or terminal):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd /path/to/marketmaker # or your app directory
|
|
18
|
+
npm run dev # or whatever starts your app
|
|
19
|
+
# Make sure it's running on localhost:3000 (or note the port)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Verify it's running:**
|
|
23
|
+
```bash
|
|
24
|
+
curl http://localhost:3000
|
|
25
|
+
# Should see your app's HTML/response
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Step 2: Create Tunnel
|
|
29
|
+
|
|
30
|
+
In **Terminal 2** (uplink directory):
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cd /path/to/uplink # or wherever you cloned the repo
|
|
34
|
+
|
|
35
|
+
# Recommended production env (API via Caddy; tunnel ctrl is raw TCP)
|
|
36
|
+
export AGENTCLOUD_API_BASE=https://api.uplink.spot
|
|
37
|
+
export TUNNEL_CTRL=tunnel.uplink.spot:7071
|
|
38
|
+
export TUNNEL_DOMAIN=t.uplink.spot
|
|
39
|
+
|
|
40
|
+
# Create tunnel (replace 3000 with your app's port)
|
|
41
|
+
npm run dev:cli -- dev --tunnel --port 3000
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**You'll see:**
|
|
45
|
+
```
|
|
46
|
+
Tunnel URL: https://abc123.t.uplink.spot
|
|
47
|
+
Starting tunnel client...
|
|
48
|
+
2025-12-11T18:30:39.424Z connected to relay ctrl
|
|
49
|
+
2025-12-11T18:30:39.438Z registered with relay
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Keep this terminal running!** (The tunnel client must stay active)
|
|
53
|
+
|
|
54
|
+
### Step 3: Access Your App
|
|
55
|
+
|
|
56
|
+
**Via HTTPS (wildcard TLS via Cloudflare DNS-01):**
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
curl https://abc123.t.uplink.spot
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Direct HTTP (bypasses Caddy):**
|
|
63
|
+
```bash
|
|
64
|
+
curl -H "Host: abc123.t.uplink.spot" http://<SERVER_IP>:7070/
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Step 4: Stop Tunnel
|
|
68
|
+
|
|
69
|
+
When done, press `Ctrl+C` in the tunnel client terminal to stop it.
|
|
70
|
+
|
|
71
|
+
## Troubleshooting
|
|
72
|
+
|
|
73
|
+
### Check What's Running
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Check local ports
|
|
77
|
+
lsof -i -P | grep LISTEN | grep -E ":(3000|4000|7070|7071)"
|
|
78
|
+
|
|
79
|
+
# Check backend API
|
|
80
|
+
curl http://localhost:4000/health
|
|
81
|
+
# (production via Caddy)
|
|
82
|
+
curl https://api.uplink.spot/health
|
|
83
|
+
|
|
84
|
+
# Check server services
|
|
85
|
+
ssh root@<SERVER_IP> "systemctl status backend-api tunnel-relay caddy"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Common Issues
|
|
89
|
+
|
|
90
|
+
1. **"Connection refused"** → Tunnel client not running or wrong port
|
|
91
|
+
2. **"Tunnel not connected"** → Check tunnel client is running and connected
|
|
92
|
+
3. **HTTPS errors** → Use HTTP instead (http:// not https://)
|
|
93
|
+
4. **App not accessible** → Make sure your app is running on the correct port
|
|
94
|
+
|
|
95
|
+
### Manual Method (if CLI doesn't work)
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# 1. Create tunnel
|
|
99
|
+
curl -X POST https://api.uplink.spot/v1/tunnels \
|
|
100
|
+
-H "Authorization: Bearer dev-token" \
|
|
101
|
+
-H "Content-Type: application/json" \
|
|
102
|
+
-d '{"port":3000}'
|
|
103
|
+
|
|
104
|
+
# Response: {"token":"abc123...", "url":"https://abc123.t.uplink.spot"}
|
|
105
|
+
|
|
106
|
+
# 2. Start tunnel client
|
|
107
|
+
node scripts/tunnel/client.js \
|
|
108
|
+
--token abc123... \
|
|
109
|
+
--port 3000 \
|
|
110
|
+
--ctrl tunnel.uplink.spot:7071
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Current Status
|
|
114
|
+
|
|
115
|
+
- ✅ **HTTPS tunnel (recommended)**: Use Cloudflare DNS hosting + wildcard TLS
|
|
116
|
+
- ✅ **Tunnel client**: Connects and routes traffic
|
|
117
|
+
- ✅ **Database persistence**: Tunnels saved to database
|
|
118
|
+
|
|
119
|
+
## Quick Reference
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Create tunnel for port 3000
|
|
123
|
+
npm run dev:cli -- dev --tunnel --port 3000
|
|
124
|
+
|
|
125
|
+
# Create tunnel for port 3001
|
|
126
|
+
npm run dev:cli -- dev --tunnel --port 3001
|
|
127
|
+
|
|
128
|
+
# Access via HTTP
|
|
129
|
+
https://<token>.t.uplink.spot
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# AgentCloud Service Usage Guide
|
|
2
|
+
|
|
3
|
+
## What You Can Do Now
|
|
4
|
+
|
|
5
|
+
Your AgentCloud service is fully deployed and running! Here's what you can do:
|
|
6
|
+
|
|
7
|
+
### 🌐 Tunnel Service (ngrok-like)
|
|
8
|
+
|
|
9
|
+
Expose your local development servers to the internet with a public URL.
|
|
10
|
+
|
|
11
|
+
#### Using the CLI:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Start a tunnel for a local server on port 3000
|
|
15
|
+
npm run dev:cli -- dev --tunnel --port 3000
|
|
16
|
+
|
|
17
|
+
# Or with the tunnel client directly
|
|
18
|
+
node scripts/tunnel/client.js --token <token> --port 3000 --ctrl tunnel.uplink.spot:7071
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
#### Using the API:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Create a tunnel
|
|
25
|
+
curl -X POST https://api.uplink.spot/v1/tunnels \
|
|
26
|
+
-H "Authorization: Bearer dev-token" \
|
|
27
|
+
-H "Content-Type: application/json" \
|
|
28
|
+
-d '{"port": 3000}'
|
|
29
|
+
|
|
30
|
+
# Response includes:
|
|
31
|
+
# {
|
|
32
|
+
# "id": "tun_...",
|
|
33
|
+
# "token": "abc123...",
|
|
34
|
+
# "url": "https://abc123.t.uplink.spot",
|
|
35
|
+
# "targetPort": 3000,
|
|
36
|
+
# "status": "active"
|
|
37
|
+
# }
|
|
38
|
+
|
|
39
|
+
# List your tunnels
|
|
40
|
+
curl https://api.uplink.spot/v1/tunnels \
|
|
41
|
+
-H "Authorization: Bearer dev-token"
|
|
42
|
+
|
|
43
|
+
# Delete a tunnel
|
|
44
|
+
curl -X DELETE https://api.uplink.spot/v1/tunnels/<tunnel-id> \
|
|
45
|
+
-H "Authorization: Bearer dev-token"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
#### How It Works:
|
|
49
|
+
|
|
50
|
+
1. **Create tunnel**: Request a tunnel from the control plane API
|
|
51
|
+
2. **Get token**: Receive a unique token (e.g., `abc123`)
|
|
52
|
+
3. **Start client**: Run the tunnel client locally, connecting to the relay
|
|
53
|
+
4. **Access**: Your local server is now accessible at `https://abc123.dev.uplink.spot`
|
|
54
|
+
5. **HTTPS**: Recommended: Cloudflare DNS hosting + wildcard TLS (`*.t.uplink.spot`)
|
|
55
|
+
|
|
56
|
+
### 🗄️ Database Service
|
|
57
|
+
|
|
58
|
+
Create and manage PostgreSQL databases via Neon.
|
|
59
|
+
|
|
60
|
+
#### Using the CLI:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Create a database
|
|
64
|
+
npm run dev:cli -- db create myapp-db --project myproject
|
|
65
|
+
|
|
66
|
+
# List databases
|
|
67
|
+
npm run dev:cli -- db list
|
|
68
|
+
|
|
69
|
+
# Get database info
|
|
70
|
+
npm run dev:cli -- db info <db-id>
|
|
71
|
+
|
|
72
|
+
# Delete database
|
|
73
|
+
npm run dev:cli -- db delete <db-id>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### Using the API:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Create a database
|
|
80
|
+
curl -X POST https://api.uplink.spot/v1/dbs \
|
|
81
|
+
-H "Authorization: Bearer dev-token" \
|
|
82
|
+
-H "Content-Type: application/json" \
|
|
83
|
+
-d '{
|
|
84
|
+
"name": "myapp-db",
|
|
85
|
+
"project": "myproject",
|
|
86
|
+
"provider": "neon",
|
|
87
|
+
"region": "us-east-1"
|
|
88
|
+
}'
|
|
89
|
+
|
|
90
|
+
# List databases
|
|
91
|
+
curl https://api.uplink.spot/v1/dbs \
|
|
92
|
+
-H "Authorization: Bearer dev-token"
|
|
93
|
+
|
|
94
|
+
# Get database details
|
|
95
|
+
curl https://api.uplink.spot/v1/dbs/<db-id> \
|
|
96
|
+
-H "Authorization: Bearer dev-token"
|
|
97
|
+
|
|
98
|
+
# Delete database
|
|
99
|
+
curl -X DELETE https://api.uplink.spot/v1/dbs/<db-id> \
|
|
100
|
+
-H "Authorization: Bearer dev-token"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 🔧 Configuration
|
|
104
|
+
|
|
105
|
+
#### Environment Variables (on server):
|
|
106
|
+
|
|
107
|
+
- `CONTROL_PLANE_DATABASE_URL`: Postgres connection for control plane
|
|
108
|
+
- `CONTROL_PLANE_TOKEN_PEPPER`: Optional HMAC pepper for token hashing (set in prod)
|
|
109
|
+
- `ADMIN_TOKENS`: Comma-separated break-glass admin tokens (env-only; bypass DB)
|
|
110
|
+
- `NEON_API_KEY`: Neon API key for database provisioning
|
|
111
|
+
- `NEON_PROJECT_ID`: Neon project ID
|
|
112
|
+
- `PORT`: Backend API port (default: 4000)
|
|
113
|
+
- `TUNNEL_DOMAIN`: Domain for tunnels (recommended: `t.uplink.spot`)
|
|
114
|
+
|
|
115
|
+
#### CLI Configuration:
|
|
116
|
+
|
|
117
|
+
Set in your local `.env`:
|
|
118
|
+
- `AGENTCLOUD_API_BASE`: API base URL (default: http://localhost:4000)
|
|
119
|
+
- `AGENTCLOUD_TOKEN`: Auth token (use a minted token; dev-token only for local sqlite)
|
|
120
|
+
- `TUNNEL_CTRL`: Tunnel control channel (default: 127.0.0.1:7071)
|
|
121
|
+
|
|
122
|
+
### 📊 Service Status
|
|
123
|
+
|
|
124
|
+
Check service health:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Backend API health
|
|
128
|
+
curl https://api.uplink.spot/health
|
|
129
|
+
|
|
130
|
+
# Check services on server
|
|
131
|
+
ssh root@<SERVER_IP> "systemctl status backend-api tunnel-relay caddy"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 🚀 Example Workflow
|
|
135
|
+
|
|
136
|
+
1. **Start a local dev server**:
|
|
137
|
+
```bash
|
|
138
|
+
npm start # Runs on localhost:3000
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
2. **Create a tunnel**:
|
|
142
|
+
```bash
|
|
143
|
+
npm run dev:cli -- dev --tunnel --port 3000
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
3. **Get public URL**: The CLI will output something like:
|
|
147
|
+
```
|
|
148
|
+
Tunnel URL: https://abc123.dev.uplink.spot
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
4. **Access your app**: Open `https://abc123.dev.uplink.spot` in a browser
|
|
152
|
+
|
|
153
|
+
5. **Create a database** (if needed):
|
|
154
|
+
```bash
|
|
155
|
+
npm run dev:cli -- db create myapp-db --project myproject
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
6. **Use connection string**: The CLI will output connection details
|
|
159
|
+
|
|
160
|
+
### 🔐 Authentication & Tokens (DB-backed)
|
|
161
|
+
|
|
162
|
+
- Tokens are stored hashed in the database and can be revoked or set to expire.
|
|
163
|
+
- Admin vs user roles are enforced by the backend.
|
|
164
|
+
- Break-glass `ADMIN_TOKENS` (env-only) remain as emergency access during transition.
|
|
165
|
+
- Local dev with SQLite can still use `AGENTCLOUD_TOKEN_DEV=dev-token`; production should use minted tokens.
|
|
166
|
+
|
|
167
|
+
**Mint an admin token (one-time raw value shown):**
|
|
168
|
+
```bash
|
|
169
|
+
uplink admin tokens create --role admin --label "my-admin-laptop"
|
|
170
|
+
```
|
|
171
|
+
Save the printed token securely, then set locally:
|
|
172
|
+
```bash
|
|
173
|
+
export AGENTCLOUD_TOKEN=<minted-admin-token>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Mint a user token:**
|
|
177
|
+
```bash
|
|
178
|
+
uplink admin tokens create --role user --label "teammate"
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**List tokens (metadata only, no raw tokens):**
|
|
182
|
+
```bash
|
|
183
|
+
uplink admin tokens list
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Revoke a token:**
|
|
187
|
+
```bash
|
|
188
|
+
uplink admin tokens revoke --id <token-id>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Cleanup legacy tunnels (dev-user):**
|
|
192
|
+
```bash
|
|
193
|
+
uplink admin cleanup --dev-user-tunnels
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 👨💼 Admin Commands
|
|
197
|
+
|
|
198
|
+
View system status and manage resources:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
# Show system statistics
|
|
202
|
+
npm run dev:cli -- admin status
|
|
203
|
+
|
|
204
|
+
# List all tunnels
|
|
205
|
+
npm run dev:cli -- admin tunnels
|
|
206
|
+
npm run dev:cli -- admin tunnels --status active --limit 50
|
|
207
|
+
|
|
208
|
+
# List all databases
|
|
209
|
+
npm run dev:cli -- admin databases
|
|
210
|
+
npm run dev:cli -- admin databases --status ready
|
|
211
|
+
|
|
212
|
+
# Token management (DB-backed)
|
|
213
|
+
npm run dev:cli -- admin tokens create --role admin --label "ops"
|
|
214
|
+
npm run dev:cli -- admin tokens list
|
|
215
|
+
npm run dev:cli -- admin tokens revoke --id <token-id>
|
|
216
|
+
|
|
217
|
+
# Cleanup legacy dev-user tunnels
|
|
218
|
+
npm run dev:cli -- admin cleanup --dev-user-tunnels
|
|
219
|
+
|
|
220
|
+
# JSON output for scripting
|
|
221
|
+
npm run dev:cli -- admin status --json
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Admin API Endpoints:**
|
|
225
|
+
- `GET /v1/admin/stats` - System statistics
|
|
226
|
+
- `GET /v1/admin/tunnels` - List all tunnels (with filters)
|
|
227
|
+
- `GET /v1/admin/databases` - List all databases (with filters)
|
|
228
|
+
- `GET /v1/admin/tokens` - List tokens (metadata only)
|
|
229
|
+
- `POST /v1/admin/tokens` - Mint token (returns raw token once)
|
|
230
|
+
- `POST /v1/admin/tokens/revoke` - Revoke by id or raw token
|
|
231
|
+
- `POST /v1/admin/cleanup/dev-user-tunnels` - Soft-delete legacy dev-user tunnels
|
|
232
|
+
|
|
233
|
+
### 📝 Notes
|
|
234
|
+
|
|
235
|
+
- Tunnels are stored in the database and persist across server restarts
|
|
236
|
+
- Databases are provisioned via Neon API (PostgreSQL)
|
|
237
|
+
- HTTPS is handled automatically by Caddy
|
|
238
|
+
- All operations are idempotent and agent-friendly
|
|
239
|
+
- Admin endpoints require authentication (Bearer token)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uplink-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"description": "Expose localhost to the internet in seconds. Interactive terminal UI, permanent custom domains, zero config. A modern ngrok alternative.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tunnel",
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"cli/",
|
|
40
40
|
"scripts/tunnel/client-improved.js",
|
|
41
41
|
"README.md",
|
|
42
|
-
"assets/"
|
|
42
|
+
"assets/",
|
|
43
|
+
"docs/"
|
|
43
44
|
],
|
|
44
45
|
"scripts": {
|
|
45
46
|
"dev:relay": "node scripts/tunnel/relay.js",
|
|
@@ -60,6 +60,7 @@ let reconnectTimer = null;
|
|
|
60
60
|
let isConnected = false;
|
|
61
61
|
let isRegistered = false;
|
|
62
62
|
let healthCheckTimer = null;
|
|
63
|
+
let lastHealthTimeoutLog = 0;
|
|
63
64
|
let stats = {
|
|
64
65
|
requests: 0,
|
|
65
66
|
errors: 0,
|
|
@@ -93,12 +94,18 @@ function logError(err, context) {
|
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
function checkLocalService() {
|
|
97
|
+
// Allow disabling the health check if it is noisy or not needed
|
|
98
|
+
if ((process.env.TUNNEL_HEALTH_CHECK_DISABLE || "").toLowerCase() === "true") {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const timeoutMs = Number(process.env.TUNNEL_HEALTH_CHECK_TIMEOUT_MS || 2000);
|
|
96
103
|
const options = {
|
|
97
104
|
hostname: "127.0.0.1",
|
|
98
105
|
port,
|
|
99
106
|
path: "/",
|
|
100
107
|
method: "HEAD",
|
|
101
|
-
timeout:
|
|
108
|
+
timeout: timeoutMs,
|
|
102
109
|
};
|
|
103
110
|
|
|
104
111
|
const req = http.request(options, (res) => {
|
|
@@ -119,7 +126,12 @@ function checkLocalService() {
|
|
|
119
126
|
|
|
120
127
|
req.on("timeout", () => {
|
|
121
128
|
req.destroy();
|
|
122
|
-
|
|
129
|
+
// Rate-limit timeout warnings to avoid log spam
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
if (now - lastHealthTimeoutLog > 60_000) {
|
|
132
|
+
lastHealthTimeoutLog = now;
|
|
133
|
+
log("warn", `Health check timeout. Local service may be slow or unresponsive.`);
|
|
134
|
+
}
|
|
123
135
|
});
|
|
124
136
|
|
|
125
137
|
req.end();
|