uplink-cli 0.1.37 → 0.1.39
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/AGENTS.md +161 -0
- package/LICENSE +21 -0
- package/README.md +45 -44
- package/cli/src/index.ts +5 -3
- package/cli/src/registrars/cloudflare.ts +148 -0
- package/cli/src/registrars/godaddy.ts +99 -0
- package/cli/src/registrars/hostinger.ts +106 -0
- package/cli/src/registrars/http.ts +18 -0
- package/cli/src/registrars/index.ts +30 -0
- package/cli/src/registrars/namecheap.ts +163 -0
- package/cli/src/registrars/secret.ts +66 -0
- package/cli/src/registrars/store.ts +55 -0
- package/cli/src/registrars/types.ts +40 -0
- package/cli/src/subcommands/admin.ts +17 -30
- package/cli/src/subcommands/db.ts +63 -57
- package/cli/src/subcommands/dev.ts +23 -25
- package/cli/src/subcommands/domains.ts +268 -0
- package/cli/src/subcommands/host-domains.ts +148 -0
- package/cli/src/subcommands/host.ts +106 -32
- package/cli/src/subcommands/menu/colors.ts +1 -1
- package/cli/src/subcommands/menu/effects/tunnel-clients.ts +87 -14
- package/cli/src/subcommands/menu/inline-tree-select.ts +6 -5
- package/cli/src/subcommands/menu/io.ts +27 -5
- package/cli/src/subcommands/menu/menus/domains.ts +199 -0
- package/cli/src/subcommands/menu/menus/hosting.ts +14 -46
- package/cli/src/subcommands/menu/menus/index.ts +1 -0
- package/cli/src/subcommands/menu/menus/tunnels.ts +25 -67
- package/cli/src/subcommands/menu/render.ts +2 -2
- package/cli/src/subcommands/menu/tests.ts +1 -1
- package/cli/src/subcommands/menu/tunnels.ts +10 -99
- package/cli/src/subcommands/menu/types.ts +8 -0
- package/cli/src/subcommands/menu.ts +32 -524
- package/cli/src/subcommands/system.ts +58 -36
- package/cli/src/subcommands/tunnel.ts +124 -33
- package/cli/src/templates/index.ts +122 -0
- package/cli/src/tui/App.tsx +197 -0
- package/cli/src/tui/AppInspector.tsx +114 -0
- package/cli/src/tui/HomeStatus.tsx +59 -0
- package/cli/src/tui/brand.tsx +20 -0
- package/cli/src/tui/format.ts +22 -0
- package/cli/src/tui/index.mts +6 -0
- package/cli/src/tui/liveTree.ts +40 -0
- package/cli/src/tui/package.json +3 -0
- package/cli/src/tui/runMenu.tsx +57 -0
- package/cli/src/tui/session.mts +382 -0
- package/cli/src/tui/snapshot.ts +146 -0
- package/cli/src/utils/analyze.ts +27 -43
- package/cli/src/utils/framework-output.ts +171 -0
- package/cli/src/utils/launchDomainking.ts +64 -0
- package/docs/AGENTS.md +113 -146
- package/docs/MENU_STRUCTURE.md +56 -288
- package/docs/README.md +6 -6
- package/package.json +18 -35
- package/scripts/tunnel/client-improved.js +127 -38
- package/scripts/tunnel/client.js +118 -0
- package/assets/cli-screenshot.png +0 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import type { FrameworkInfo } from "./analyze";
|
|
4
|
+
|
|
5
|
+
export type FrameworkOutputMode = "standalone" | "export" | "static" | "unknown";
|
|
6
|
+
|
|
7
|
+
export type FrameworkOutputInfo = {
|
|
8
|
+
framework: "nextjs" | "vite" | "cra";
|
|
9
|
+
mode: FrameworkOutputMode;
|
|
10
|
+
distDir?: string;
|
|
11
|
+
configPath?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type FrameworkOutputCheck = {
|
|
15
|
+
framework: "nextjs" | "vite" | "cra";
|
|
16
|
+
mode: FrameworkOutputMode;
|
|
17
|
+
expectedPaths: string[];
|
|
18
|
+
summary: string;
|
|
19
|
+
guidance: string[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function findNextConfigPath(dir: string): string | null {
|
|
23
|
+
const nextConfigPath = join(dir, "next.config.ts");
|
|
24
|
+
const nextConfigJsPath = join(dir, "next.config.js");
|
|
25
|
+
const nextConfigMjsPath = join(dir, "next.config.mjs");
|
|
26
|
+
if (existsSync(nextConfigPath)) return nextConfigPath;
|
|
27
|
+
if (existsSync(nextConfigJsPath)) return nextConfigJsPath;
|
|
28
|
+
if (existsSync(nextConfigMjsPath)) return nextConfigMjsPath;
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseNextConfigContent(content: string): { mode: FrameworkOutputMode; distDir?: string } {
|
|
33
|
+
const outputMatches = [...content.matchAll(/output\s*:\s*["'](standalone|export)["']/g)];
|
|
34
|
+
const mode = outputMatches.length > 0 ? (outputMatches[outputMatches.length - 1][1] as FrameworkOutputMode) : "unknown";
|
|
35
|
+
const distMatches = [...content.matchAll(/distDir\s*:\s*["']([^"']+)["']/g)];
|
|
36
|
+
const distDir = distMatches.length > 0 ? distMatches[distMatches.length - 1][1] : undefined;
|
|
37
|
+
return { mode, distDir };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function detectNextOutput(dir: string): FrameworkOutputInfo {
|
|
41
|
+
const configPath = findNextConfigPath(dir);
|
|
42
|
+
if (!configPath) {
|
|
43
|
+
return { framework: "nextjs", mode: "unknown" };
|
|
44
|
+
}
|
|
45
|
+
const content = readFileSync(configPath, "utf8");
|
|
46
|
+
const parsed = parseNextConfigContent(content);
|
|
47
|
+
return {
|
|
48
|
+
framework: "nextjs",
|
|
49
|
+
mode: parsed.mode,
|
|
50
|
+
distDir: parsed.distDir,
|
|
51
|
+
configPath,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function findViteConfigPath(dir: string): string | null {
|
|
56
|
+
const viteTs = join(dir, "vite.config.ts");
|
|
57
|
+
const viteJs = join(dir, "vite.config.js");
|
|
58
|
+
const viteMjs = join(dir, "vite.config.mjs");
|
|
59
|
+
if (existsSync(viteTs)) return viteTs;
|
|
60
|
+
if (existsSync(viteJs)) return viteJs;
|
|
61
|
+
if (existsSync(viteMjs)) return viteMjs;
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseViteConfigContent(content: string): { distDir?: string } {
|
|
66
|
+
const outDirMatches = [...content.matchAll(/outDir\s*:\s*["']([^"']+)["']/g)];
|
|
67
|
+
const distDir = outDirMatches.length > 0 ? outDirMatches[outDirMatches.length - 1][1] : undefined;
|
|
68
|
+
return { distDir };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function detectViteOutput(dir: string): FrameworkOutputInfo {
|
|
72
|
+
const configPath = findViteConfigPath(dir);
|
|
73
|
+
if (!configPath) {
|
|
74
|
+
return { framework: "vite", mode: "static" };
|
|
75
|
+
}
|
|
76
|
+
const content = readFileSync(configPath, "utf8");
|
|
77
|
+
const parsed = parseViteConfigContent(content);
|
|
78
|
+
return {
|
|
79
|
+
framework: "vite",
|
|
80
|
+
mode: "static",
|
|
81
|
+
distDir: parsed.distDir,
|
|
82
|
+
configPath,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function detectCraOutput(): FrameworkOutputInfo {
|
|
87
|
+
return { framework: "cra", mode: "static", distDir: "build" };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function detectFrameworkOutput(
|
|
91
|
+
dir: string,
|
|
92
|
+
framework: FrameworkInfo | null
|
|
93
|
+
): FrameworkOutputInfo | null {
|
|
94
|
+
if (!framework) return null;
|
|
95
|
+
if (framework.name === "nextjs") {
|
|
96
|
+
return detectNextOutput(dir);
|
|
97
|
+
}
|
|
98
|
+
if (framework.name === "vite") {
|
|
99
|
+
return detectViteOutput(dir);
|
|
100
|
+
}
|
|
101
|
+
if (framework.name === "cra") {
|
|
102
|
+
return detectCraOutput();
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function getFrameworkOutputCheck(info: FrameworkOutputInfo | null): FrameworkOutputCheck | null {
|
|
108
|
+
if (!info) return null;
|
|
109
|
+
if (info.framework === "nextjs") {
|
|
110
|
+
const distDir = info.distDir || ".next";
|
|
111
|
+
if (info.mode === "standalone") {
|
|
112
|
+
return {
|
|
113
|
+
framework: "nextjs",
|
|
114
|
+
mode: info.mode,
|
|
115
|
+
expectedPaths: [`${distDir}/standalone`, `${distDir}/static`],
|
|
116
|
+
summary: "Next.js standalone output detected.",
|
|
117
|
+
guidance: [],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (info.mode === "export") {
|
|
121
|
+
return {
|
|
122
|
+
framework: "nextjs",
|
|
123
|
+
mode: info.mode,
|
|
124
|
+
expectedPaths: ["out"],
|
|
125
|
+
summary: "Next.js output is set to export.",
|
|
126
|
+
guidance: [
|
|
127
|
+
"Uplink hosting expects a server build by default.",
|
|
128
|
+
"Either set output: \"standalone\" in next.config.*",
|
|
129
|
+
"Or provide a Dockerfile that serves the static out/ directory.",
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
framework: "nextjs",
|
|
135
|
+
mode: "unknown",
|
|
136
|
+
expectedPaths: [`${distDir}/standalone`, `${distDir}/static`],
|
|
137
|
+
summary: "Next.js output mode not detected.",
|
|
138
|
+
guidance: [
|
|
139
|
+
"Uplink hosting expects output: \"standalone\" by default.",
|
|
140
|
+
"Add output: \"standalone\" to next.config.* for server hosting.",
|
|
141
|
+
],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (info.framework === "vite") {
|
|
145
|
+
const distDir = info.distDir || "dist";
|
|
146
|
+
return {
|
|
147
|
+
framework: "vite",
|
|
148
|
+
mode: "static",
|
|
149
|
+
expectedPaths: [distDir],
|
|
150
|
+
summary: "Vite static build detected.",
|
|
151
|
+
guidance: [
|
|
152
|
+
`Build output is expected in ${distDir}/.`,
|
|
153
|
+
"Provide a Dockerfile that serves the static files (e.g., nginx or a static file server).",
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (info.framework === "cra") {
|
|
158
|
+
const distDir = info.distDir || "build";
|
|
159
|
+
return {
|
|
160
|
+
framework: "cra",
|
|
161
|
+
mode: "static",
|
|
162
|
+
expectedPaths: [distDir],
|
|
163
|
+
summary: "Create React App static build detected.",
|
|
164
|
+
guidance: [
|
|
165
|
+
`Build output is expected in ${distDir}/.`,
|
|
166
|
+
"Provide a Dockerfile that serves the static files (e.g., nginx or a static file server).",
|
|
167
|
+
],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { spawnSync } from "child_process";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
|
|
6
|
+
function projectRoot(): string {
|
|
7
|
+
return join(__dirname, "../..");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function resolveTsx(): string {
|
|
11
|
+
const root = projectRoot();
|
|
12
|
+
try {
|
|
13
|
+
return require.resolve("tsx/dist/cli.cjs", { paths: [root] });
|
|
14
|
+
} catch {
|
|
15
|
+
try {
|
|
16
|
+
return require.resolve("tsx/cli", { paths: [root] });
|
|
17
|
+
} catch {
|
|
18
|
+
return "tsx";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Domainking stays its own package; we spawn it, we do not import it. */
|
|
24
|
+
export function resolveDomainkingEntry(): string | null {
|
|
25
|
+
if (process.env.DOMAINKING_ENTRY && existsSync(process.env.DOMAINKING_ENTRY)) {
|
|
26
|
+
return process.env.DOMAINKING_ENTRY;
|
|
27
|
+
}
|
|
28
|
+
const candidates = [
|
|
29
|
+
join(homedir(), "domainking", "src", "index.tsx"),
|
|
30
|
+
join(projectRoot(), "..", "domainking", "src", "index.tsx"),
|
|
31
|
+
join(projectRoot(), "..", "..", "domainking", "src", "index.tsx"),
|
|
32
|
+
];
|
|
33
|
+
return candidates.find((path) => existsSync(path)) ?? null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function launchDomainking(): string {
|
|
37
|
+
const entry = resolveDomainkingEntry();
|
|
38
|
+
if (!entry) {
|
|
39
|
+
return [
|
|
40
|
+
"Domain search TUI is not bundled with uplink-cli.",
|
|
41
|
+
"",
|
|
42
|
+
"Agent-friendly commands (no TUI needed):",
|
|
43
|
+
" uplink domains list --json",
|
|
44
|
+
" uplink domains check example.com --json",
|
|
45
|
+
" uplink host domains add --id app_xxx --hostname example.com --json",
|
|
46
|
+
" uplink host domains verify --id app_xxx --hostname example.com --json",
|
|
47
|
+
"",
|
|
48
|
+
"Optional: set DOMAINKING_ENTRY to a Domainking src/index.tsx for the search UI.",
|
|
49
|
+
].join("\n");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const result = spawnSync(resolveTsx(), [entry], {
|
|
53
|
+
stdio: "inherit",
|
|
54
|
+
cwd: join(entry, "..", ".."),
|
|
55
|
+
env: process.env,
|
|
56
|
+
});
|
|
57
|
+
if (result.error) {
|
|
58
|
+
throw result.error;
|
|
59
|
+
}
|
|
60
|
+
if (result.status && result.status !== 0) {
|
|
61
|
+
return "Domain search exited.";
|
|
62
|
+
}
|
|
63
|
+
return "Back from domain search. Attach a hostname with Domains › Attach to app.";
|
|
64
|
+
}
|
package/docs/AGENTS.md
CHANGED
|
@@ -1,194 +1,161 @@
|
|
|
1
1
|
# Agent Integration Guide
|
|
2
2
|
|
|
3
|
-
For agents (Cursor
|
|
3
|
+
For agents (Cursor, Claude Code, Codex, Windsurf, and similar) to use Uplink **non-interactively**.
|
|
4
|
+
|
|
5
|
+
Install: `npm install -g uplink-cli` or `npx uplink-cli …`
|
|
6
|
+
Package name: `uplink-cli` · Binary: `uplink`
|
|
4
7
|
|
|
5
8
|
## Auth
|
|
6
|
-
|
|
9
|
+
|
|
10
|
+
- Use `AGENTCLOUD_TOKEN` (bearer). Prefer stdin over argv:
|
|
7
11
|
```bash
|
|
8
|
-
echo "$TOKEN" | uplink --token-stdin
|
|
12
|
+
echo "$TOKEN" | uplink --token-stdin …
|
|
9
13
|
```
|
|
10
|
-
- API base
|
|
14
|
+
- API base: `--api-base https://api.uplink.spot` or `AGENTCLOUD_API_BASE`.
|
|
11
15
|
|
|
12
16
|
## Signup (no auth required)
|
|
17
|
+
|
|
13
18
|
```bash
|
|
14
19
|
uplink signup --json
|
|
15
20
|
uplink signup --label "cursor-agent" --expires-days 30 --json
|
|
16
21
|
```
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"tokenPrefix": "abc123",
|
|
23
|
-
"role": "user",
|
|
24
|
-
"userId": "user_xxx",
|
|
25
|
-
"label": "cursor-agent",
|
|
26
|
-
"createdAt": "2025-01-01T00:00:00.000Z",
|
|
27
|
-
"expiresAt": "2025-01-31T00:00:00.000Z",
|
|
28
|
-
"message": "Token created successfully. Save this token securely..."
|
|
29
|
-
}
|
|
22
|
+
|
|
23
|
+
Save `token` from the JSON — it is shown only once. Then:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
export AGENTCLOUD_TOKEN='…'
|
|
30
27
|
```
|
|
31
|
-
Save `token`—shown only once.
|
|
32
28
|
|
|
33
29
|
## Machine-mode contract
|
|
34
|
-
- `--json` → stdout = JSON only; stderr = logs/errors.
|
|
35
|
-
- Exit codes: 0 ok; 2 usage; 10 auth missing/invalid; 20 network; 30 server/unknown.
|
|
36
|
-
- Premium alias gating: alias commands may return `ALIAS_NOT_ENABLED` / `ALIAS_LIMIT_REACHED`.
|
|
37
30
|
|
|
38
|
-
|
|
31
|
+
| Rule | Detail |
|
|
32
|
+
|------|--------|
|
|
33
|
+
| `--json` | stdout = JSON only; logs/errors go to stderr |
|
|
34
|
+
| Exit `0` | success |
|
|
35
|
+
| Exit `2` | usage / bad args |
|
|
36
|
+
| Exit `10` | auth missing/invalid |
|
|
37
|
+
| Exit `20` | network |
|
|
38
|
+
| Exit `30` | server / unknown |
|
|
39
|
+
|
|
40
|
+
Premium aliases may return `ALIAS_NOT_ENABLED` / `ALIAS_LIMIT_REACHED`.
|
|
41
|
+
|
|
42
|
+
## Tunnels (share localhost)
|
|
43
|
+
|
|
44
|
+
`tunnel create` **creates the API record and starts the local client** so the public URL works. Use `--api-only` only if you will start the client yourself.
|
|
45
|
+
|
|
39
46
|
```bash
|
|
40
|
-
# Create
|
|
41
|
-
echo "$TOKEN" | uplink --token-stdin
|
|
47
|
+
# Create + start client (optional alias if enabled)
|
|
48
|
+
echo "$TOKEN" | uplink --token-stdin \
|
|
42
49
|
tunnel create --port 3000 --alias myapp --json
|
|
43
50
|
|
|
44
|
-
# List
|
|
51
|
+
# List (includes connected status)
|
|
45
52
|
echo "$TOKEN" | uplink --token-stdin tunnel list --json
|
|
46
53
|
|
|
47
|
-
#
|
|
54
|
+
# Alias on an existing tunnel
|
|
48
55
|
echo "$TOKEN" | uplink --token-stdin tunnel alias-set --id tun_xxx --alias myapp --json
|
|
49
|
-
|
|
50
|
-
# Delete alias from tunnel
|
|
51
56
|
echo "$TOKEN" | uplink --token-stdin tunnel alias-delete --id tun_xxx --json
|
|
52
57
|
|
|
53
|
-
# Stats
|
|
58
|
+
# Stats / stop
|
|
54
59
|
echo "$TOKEN" | uplink --token-stdin tunnel stats --id tun_xxx --json
|
|
55
|
-
|
|
56
|
-
# Stop/delete tunnel
|
|
57
60
|
echo "$TOKEN" | uplink --token-stdin tunnel stop --id tun_xxx --json
|
|
61
|
+
echo "$TOKEN" | uplink --token-stdin tunnel stop --all --json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
JSON create shape (representative):
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"tunnel": { "id": "tun_…", "url": "https://abc.x.uplink.spot", "token": "…", "status": "…" },
|
|
69
|
+
"alias": "myapp",
|
|
70
|
+
"aliasError": null,
|
|
71
|
+
"url": "https://myapp.uplink.spot",
|
|
72
|
+
"client": { "pid": 12345, "started": true }
|
|
73
|
+
}
|
|
58
74
|
```
|
|
59
75
|
|
|
60
|
-
|
|
61
|
-
|
|
76
|
+
`connected` on `tunnel list` means the local client is attached to the relay.
|
|
77
|
+
|
|
78
|
+
## Hosting
|
|
79
|
+
|
|
80
|
+
Use `--json`. For prompts, pass `--yes`.
|
|
62
81
|
|
|
63
82
|
```bash
|
|
64
|
-
# Full setup (analyze + init + create + deploy)
|
|
65
83
|
echo "$TOKEN" | uplink --token-stdin host setup \
|
|
66
|
-
--path /path/to/app \
|
|
67
|
-
--
|
|
68
|
-
|
|
69
|
-
--wait-timeout 900 \
|
|
70
|
-
--wait-interval 5 \
|
|
71
|
-
--yes \
|
|
72
|
-
--json
|
|
73
|
-
|
|
74
|
-
# Deploy only (Dockerfile-based app)
|
|
84
|
+
--path /path/to/app --name myapp --env-file /path/to/.env \
|
|
85
|
+
--wait-timeout 900 --wait-interval 5 --yes --json
|
|
86
|
+
|
|
75
87
|
echo "$TOKEN" | uplink --token-stdin host deploy \
|
|
76
|
-
--path /path/to/app
|
|
77
|
-
--name myapp \
|
|
78
|
-
--env-file /path/to/.env \
|
|
79
|
-
--wait \
|
|
80
|
-
--wait-timeout 900 \
|
|
81
|
-
--wait-interval 5 \
|
|
82
|
-
--json
|
|
83
|
-
|
|
84
|
-
# Analyze project
|
|
85
|
-
echo "$TOKEN" | uplink --token-stdin host analyze --path /path/to/app --json
|
|
88
|
+
--path /path/to/app --name myapp --wait --json
|
|
86
89
|
|
|
87
|
-
|
|
90
|
+
echo "$TOKEN" | uplink --token-stdin host analyze --path /path/to/app --json
|
|
91
|
+
echo "$TOKEN" | uplink --token-stdin host preflight --path /path/to/app --json
|
|
88
92
|
echo "$TOKEN" | uplink --token-stdin host list --json
|
|
89
|
-
|
|
90
|
-
# Status + logs
|
|
91
93
|
echo "$TOKEN" | uplink --token-stdin host status --id app_xxx --json
|
|
92
94
|
echo "$TOKEN" | uplink --token-stdin host logs --id app_xxx --json
|
|
93
|
-
|
|
94
|
-
# Delete app
|
|
95
95
|
echo "$TOKEN" | uplink --token-stdin host delete --id app_xxx --yes --json
|
|
96
96
|
```
|
|
97
97
|
|
|
98
98
|
Notes:
|
|
99
|
-
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
- `host list` (non-JSON) prints two lines per app: `- name (app_id)` and the URL on the next line.
|
|
104
|
-
- `host delete` requires typing `DELETE` unless `--yes` is provided.
|
|
105
|
-
|
|
106
|
-
## Interactive menu notes
|
|
107
|
-
- All selection lists use arrow keys + Enter (no numeric entry required).
|
|
108
|
-
- Hosted app lists display the app URL alongside name and ID.
|
|
109
|
-
|
|
110
|
-
### JSON shapes (representative)
|
|
111
|
-
- Create: `{ "tunnel": { id, url?, token?, alias?, aliasUrl?, targetPort, status, connected?, createdAt }, "alias": "myapp"|null, "aliasError": "..."|null }`
|
|
112
|
-
- List: `{ "tunnels": [ { id, url?, token?, alias?, aliasUrl?, targetPort, status, connected, createdAt } ], "count": n }`
|
|
113
|
-
- Stats: alias tunnels include persisted totals + relay overlay; token-only tunnels show in-memory relay stats.
|
|
114
|
-
|
|
115
|
-
**Note:** The `connected` field indicates whether the tunnel is actually connected to the relay server (verified via socket health check).
|
|
116
|
-
|
|
117
|
-
## HTTP API Reference
|
|
118
|
-
|
|
119
|
-
Auth: `Authorization: Bearer <AGENTCLOUD_TOKEN>`
|
|
120
|
-
|
|
121
|
-
### Tunnels
|
|
122
|
-
| Method | Endpoint | Description |
|
|
123
|
-
|--------|----------|-------------|
|
|
124
|
-
| `POST` | `/v1/tunnels` | Create tunnel (body: `{ port, alias? }`) |
|
|
125
|
-
| `GET` | `/v1/tunnels` | List user's tunnels (includes `connected` status) |
|
|
126
|
-
| `GET` | `/v1/tunnels/{id}` | Get tunnel details |
|
|
127
|
-
| `GET` | `/v1/tunnels/{id}/stats` | Get tunnel statistics |
|
|
128
|
-
| `DELETE` | `/v1/tunnels/{id}` | Delete tunnel |
|
|
129
|
-
| `POST` | `/v1/tunnels/{id}/alias` | Set alias on tunnel (body: `{ alias }`) |
|
|
130
|
-
| `DELETE` | `/v1/tunnels/{id}/alias` | Remove alias from tunnel |
|
|
131
|
-
|
|
132
|
-
### Port-Based Aliases (Premium)
|
|
133
|
-
Aliases are now **port-based**: they persist across tunnel restarts and always point to the same port.
|
|
134
|
-
|
|
135
|
-
| Method | Endpoint | Description |
|
|
136
|
-
|--------|----------|-------------|
|
|
137
|
-
| `GET` | `/v1/tunnels/aliases` | List all aliases for user |
|
|
138
|
-
| `POST` | `/v1/tunnels/aliases` | Create alias for port (body: `{ alias, port }`) |
|
|
139
|
-
| `PUT` | `/v1/tunnels/aliases/{alias}` | Reassign alias to different port (body: `{ port }`) |
|
|
140
|
-
| `DELETE` | `/v1/tunnels/aliases/{alias}` | Delete alias |
|
|
141
|
-
|
|
142
|
-
### Example: Create port-based alias
|
|
143
|
-
```bash
|
|
144
|
-
curl -X POST https://api.uplink.spot/v1/tunnels/aliases \
|
|
145
|
-
-H "Authorization: Bearer $TOKEN" \
|
|
146
|
-
-H "Content-Type: application/json" \
|
|
147
|
-
-d '{"alias": "myapp", "port": 3000}'
|
|
148
|
-
```
|
|
99
|
+
- Next.js server hosting expects `output: "standalone"`. Vite/CRA → static `dist`/`build`.
|
|
100
|
+
- Prefer a `.uplinkignore` (`node_modules`, `.next`, `dist`, `*.log`, local `.db`, …).
|
|
101
|
+
- `host logs` may return `NOT_READY` until a deployment is running.
|
|
102
|
+
- `host delete` requires `--yes` (or typing `DELETE` interactively).
|
|
149
103
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
104
|
+
## Custom domains
|
|
105
|
+
|
|
106
|
+
Registrar inventory is CLI. Attach/verify is under `host domains`.
|
|
107
|
+
The bare `uplink domains` search TUI is **optional** and not bundled with npm — use the JSON commands below.
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
uplink domains providers connect godaddy --token-env GODADDY_PAT --json
|
|
111
|
+
uplink domains providers connect cloudflare --token-env CF_API_TOKEN --json
|
|
112
|
+
uplink domains providers connect hostinger --token-env HOSTINGER_API_TOKEN --json
|
|
113
|
+
uplink domains providers connect namecheap --token-env NAMECHEAP_API_KEY --user-env NAMECHEAP_API_USER --json
|
|
114
|
+
uplink domains providers list --json
|
|
115
|
+
uplink domains providers disconnect godaddy --json
|
|
116
|
+
|
|
117
|
+
uplink domains list --json
|
|
118
|
+
uplink domains check example.com --json
|
|
119
|
+
|
|
120
|
+
echo "$TOKEN" | uplink --token-stdin host domains add --id app_xxx --hostname example.com --json
|
|
121
|
+
echo "$TOKEN" | uplink --token-stdin host domains verify --id app_xxx --hostname example.com --json
|
|
122
|
+
echo "$TOKEN" | uplink --token-stdin host domains list --id app_xxx --json
|
|
123
|
+
echo "$TOKEN" | uplink --token-stdin host domains remove --id app_xxx --hostname example.com --json
|
|
159
124
|
```
|
|
160
125
|
|
|
161
|
-
|
|
162
|
-
- Public tunnels: `https://<token>.x.uplink.spot`
|
|
163
|
-
- Permanent URLs (aliases): `https://<alias>.uplink.spot`
|
|
126
|
+
Do not treat RDAP “available” as buyable unless `domains check` says `buyable: true`. Purchase is not wired yet.
|
|
164
127
|
|
|
165
|
-
##
|
|
166
|
-
Control plane + hosting runtime live in the private repo `uplink-hosting-runtime`.
|
|
128
|
+
## Databases (optional)
|
|
167
129
|
|
|
168
|
-
### Deploy code changes
|
|
169
130
|
```bash
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
systemctl restart tunnel-relay
|
|
175
|
-
systemctl restart uplink-builder uplink-runner uplink-router # if hosting runtime changed
|
|
131
|
+
echo "$TOKEN" | uplink --token-stdin db create --name mydb --project myproj --json
|
|
132
|
+
echo "$TOKEN" | uplink --token-stdin db list --json
|
|
133
|
+
echo "$TOKEN" | uplink --token-stdin db info --id db_xxx --json
|
|
134
|
+
echo "$TOKEN" | uplink --token-stdin db delete --id db_xxx --yes --json
|
|
176
135
|
```
|
|
177
136
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
|
183
|
-
|
|
|
184
|
-
| `
|
|
185
|
-
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
##
|
|
194
|
-
|
|
137
|
+
## Failure modes agents should expect
|
|
138
|
+
|
|
139
|
+
| Symptom | Likely cause |
|
|
140
|
+
|---------|----------------|
|
|
141
|
+
| URL 502 / not connected | Local process on `--port` not running, or client died — re-run `tunnel create` or check `tunnel list` |
|
|
142
|
+
| Auth errors | Missing/invalid `AGENTCLOUD_TOKEN`; use `--token-stdin` |
|
|
143
|
+
| `ALIAS_NOT_ENABLED` | Account does not have permanent aliases |
|
|
144
|
+
| Domain search TUI missing | Expected on npm — use `domains list` / `check` / `host domains *` |
|
|
145
|
+
| Hosting stuck `queued` | Edge builder/runner issue — check `host status` / `host logs` |
|
|
146
|
+
|
|
147
|
+
## Interactive menu
|
|
148
|
+
|
|
149
|
+
Humans: `uplink` or `uplink menu` (Share · Hosting · Domains).
|
|
150
|
+
Agents should prefer the non-interactive commands above.
|
|
151
|
+
|
|
152
|
+
## URLs
|
|
153
|
+
|
|
154
|
+
- Ephemeral tunnels: `https://<token>.x.uplink.spot`
|
|
155
|
+
- Aliases: `https://<alias>.uplink.spot`
|
|
156
|
+
|
|
157
|
+
## More
|
|
158
|
+
|
|
159
|
+
- Menu map: `docs/MENU_STRUCTURE.md`
|
|
160
|
+
- Website: https://uplink.spot
|
|
161
|
+
- npm: https://www.npmjs.com/package/uplink-cli
|