alink-cli 0.7.1 → 0.7.3
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 +3 -6
- package/bin/agentlink-account.js +133 -0
- package/bin/agentlink.js +95 -12
- package/dist/bin.mjs +9 -17
- package/dist/bin.mjs.map +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -6,20 +6,17 @@
|
|
|
6
6
|
npx alink-cli
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
首次运行输入 AgentLink 账号和密码,这台电脑会自动加入账号并上线。之后直接重复 `npx alink-cli` 即可上线;密码不会保存到本机。
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npx alink-cli --hub wss://your-hub.example.com
|
|
13
13
|
npx alink-cli --dir /path/to/project
|
|
14
|
-
npx alink-cli
|
|
15
|
-
npx alink-cli qr
|
|
16
|
-
npx alink-cli credential status
|
|
14
|
+
npx alink-cli logout
|
|
17
15
|
```
|
|
18
16
|
|
|
19
17
|
- `--hub`:Hub 地址,默认 `wss://link.harmopath.com`。
|
|
20
18
|
- `--dir`:默认工作目录,缺省为当前目录。
|
|
21
|
-
-
|
|
22
|
-
- `--token`:导入完整机器凭证。
|
|
19
|
+
- `logout`:退出当前账号并清除本机登录记录,下次运行重新输入账号密码。
|
|
23
20
|
- `--legacy`:临时运行旧 daemon;默认始终是 daemon-t3。
|
|
24
21
|
|
|
25
22
|
Node.js 22.16+。连接为出站 WebSocket,无需端口转发;Hub 只转发 E2EE 密文。
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
const MACHINE_BUCKET = "agentlink-machines";
|
|
4
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
5
|
+
|
|
6
|
+
function httpUrl(hub, pathname) {
|
|
7
|
+
const url = new URL(pathname, hub);
|
|
8
|
+
if (url.protocol === "ws:") url.protocol = "http:";
|
|
9
|
+
if (url.protocol === "wss:") url.protocol = "https:";
|
|
10
|
+
return url;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function json(response) {
|
|
14
|
+
return response.json().catch(() => ({}));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function loadAgentLinkAccountConfig(hub, fetchFn = fetch) {
|
|
18
|
+
const response = await fetchFn(httpUrl(hub, "/api/config"), {
|
|
19
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
20
|
+
});
|
|
21
|
+
if (!response.ok) throw new Error(`Hub 配置读取失败 (${response.status})。`);
|
|
22
|
+
return json(response);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function machineIdFromToken(authToken) {
|
|
26
|
+
try {
|
|
27
|
+
const payload = JSON.parse(
|
|
28
|
+
Buffer.from(authToken.split(".")[1] || "", "base64url").toString("utf8"),
|
|
29
|
+
);
|
|
30
|
+
return typeof payload.mid === "string" ? payload.mid : "";
|
|
31
|
+
} catch {
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function readMachines(bucket, path) {
|
|
37
|
+
const { data, error } = await bucket.download(path);
|
|
38
|
+
if (error) {
|
|
39
|
+
if (/not found|no such|404/i.test(error.message || "")) return [];
|
|
40
|
+
throw new Error(`读取账号机器列表失败:${error.message}`);
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(await data.text());
|
|
44
|
+
return Array.isArray(parsed.machines) ? parsed.machines : [];
|
|
45
|
+
} catch {
|
|
46
|
+
throw new Error("账号机器列表格式损坏,请在 Web 端移除异常记录后重试。");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function registerAgentLinkAccountMachine({
|
|
51
|
+
hub,
|
|
52
|
+
config,
|
|
53
|
+
identifier,
|
|
54
|
+
password,
|
|
55
|
+
machineName,
|
|
56
|
+
fetchFn = fetch,
|
|
57
|
+
cloudbaseSdk,
|
|
58
|
+
randomBytesFn = randomBytes,
|
|
59
|
+
}) {
|
|
60
|
+
if (!config?.cloudbaseEnvId) throw new Error("当前 Hub 尚未配置账号机器目录。");
|
|
61
|
+
|
|
62
|
+
const loginResponse = await fetchFn(httpUrl(hub, "/auth/password/login"), {
|
|
63
|
+
method: "POST",
|
|
64
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
65
|
+
headers: { "Content-Type": "application/json", "X-Requested-With": "agentlink" },
|
|
66
|
+
body: JSON.stringify({ identifier, password, client: "cli" }),
|
|
67
|
+
});
|
|
68
|
+
const login = await json(loginResponse);
|
|
69
|
+
if (!loginResponse.ok || typeof login.jwt !== "string" || !login.jwt) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
login.error === "invalid_account_credentials"
|
|
72
|
+
? "账号或密码错误。"
|
|
73
|
+
: login.error || "账号登录失败。",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const mintResponse = await fetchFn(httpUrl(hub, "/api/machines/mint"), {
|
|
78
|
+
method: "POST",
|
|
79
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
80
|
+
headers: {
|
|
81
|
+
Authorization: `Bearer ${login.jwt}`,
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
"X-Requested-With": "agentlink",
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify({ name: machineName }),
|
|
86
|
+
});
|
|
87
|
+
const minted = await json(mintResponse);
|
|
88
|
+
const authToken = typeof minted.authToken === "string" ? minted.authToken : "";
|
|
89
|
+
const machineId =
|
|
90
|
+
typeof minted.machineId === "string" ? minted.machineId : machineIdFromToken(authToken);
|
|
91
|
+
if (!mintResponse.ok || !authToken.startsWith("al1.") || !machineId) {
|
|
92
|
+
throw new Error(minted.error || "机器注册失败。");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const sdk = cloudbaseSdk ?? (await import("@cloudbase/js-sdk")).default;
|
|
96
|
+
const app = sdk.init({ env: config.cloudbaseEnvId, region: "ap-shanghai" });
|
|
97
|
+
const auth = app.auth();
|
|
98
|
+
const signedIn = await auth.signInWithPassword(
|
|
99
|
+
identifier.includes("@") ? { email: identifier, password } : { username: identifier, password },
|
|
100
|
+
);
|
|
101
|
+
if (signedIn?.error) throw new Error(signedIn.error.message || "CloudBase 账号登录失败。");
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const user = await auth.getCurrentUser();
|
|
105
|
+
if (!user?.uid) throw new Error("CloudBase 账号登录状态无效。");
|
|
106
|
+
const path = `${user.uid}/machines.json`;
|
|
107
|
+
const bucket = app.storage.from(MACHINE_BUCKET);
|
|
108
|
+
const machines = await readMachines(bucket, path);
|
|
109
|
+
const current = machines.find((machine) => machine?.machineId === machineId);
|
|
110
|
+
const enckey = randomBytesFn(32).toString("base64url");
|
|
111
|
+
const machine = {
|
|
112
|
+
machineId,
|
|
113
|
+
name: machineName,
|
|
114
|
+
hostname: machineName,
|
|
115
|
+
createdAt: current?.createdAt ?? Date.now(),
|
|
116
|
+
enckey,
|
|
117
|
+
};
|
|
118
|
+
// ponytail: read-modify-write is enough for normal first-time setup; add
|
|
119
|
+
// versioned retries if concurrent registrations for one account become common.
|
|
120
|
+
const { error } = await bucket.update(
|
|
121
|
+
path,
|
|
122
|
+
JSON.stringify({
|
|
123
|
+
version: 2,
|
|
124
|
+
machines: [machine, ...machines.filter((entry) => entry?.machineId !== machineId)],
|
|
125
|
+
}),
|
|
126
|
+
{ contentType: "application/json" },
|
|
127
|
+
);
|
|
128
|
+
if (error) throw new Error(`保存账号机器列表失败:${error.message}`);
|
|
129
|
+
return { credential: `${authToken}.${enckey}`, machineId };
|
|
130
|
+
} finally {
|
|
131
|
+
await auth.signOut().catch(() => undefined);
|
|
132
|
+
}
|
|
133
|
+
}
|
package/bin/agentlink.js
CHANGED
|
@@ -4,9 +4,15 @@ import { randomBytes } from "node:crypto";
|
|
|
4
4
|
import { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { hostname, homedir } from "node:os";
|
|
6
6
|
import { join } from "node:path";
|
|
7
|
+
import { createInterface } from "node:readline/promises";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
9
|
import WebSocket from "ws";
|
|
9
10
|
|
|
11
|
+
import {
|
|
12
|
+
loadAgentLinkAccountConfig,
|
|
13
|
+
registerAgentLinkAccountMachine,
|
|
14
|
+
} from "./agentlink-account.js";
|
|
15
|
+
|
|
10
16
|
const OFFICIAL_HUB = process.env.AGENTLINK_DEFAULT_HUB || "wss://link.harmopath.com";
|
|
11
17
|
const STATE_DIR = process.env.AGENTLINK_STATE_DIR || join(homedir(), ".agentlink");
|
|
12
18
|
const CREDENTIAL_FILE = join(STATE_DIR, "credential");
|
|
@@ -65,15 +71,6 @@ function parseCredential(credential) {
|
|
|
65
71
|
return { machineToken: parts.slice(0, 3).join("."), enckey: parts[3] };
|
|
66
72
|
}
|
|
67
73
|
|
|
68
|
-
async function hubMode(hub) {
|
|
69
|
-
const url = new URL("/api/config", hub);
|
|
70
|
-
if (url.protocol === "ws:") url.protocol = "http:";
|
|
71
|
-
if (url.protocol === "wss:") url.protocol = "https:";
|
|
72
|
-
const response = await fetch(url);
|
|
73
|
-
if (!response.ok) throw new Error(`Hub 配置读取失败 (${response.status})。`);
|
|
74
|
-
return (await response.json()).mode;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
74
|
async function createSingleCredential(hub) {
|
|
78
75
|
const token = randomBytes(24).toString("base64url");
|
|
79
76
|
const enckey = randomBytes(32).toString("base64url");
|
|
@@ -90,6 +87,75 @@ async function createSingleCredential(hub) {
|
|
|
90
87
|
return credential;
|
|
91
88
|
}
|
|
92
89
|
|
|
90
|
+
async function readPassword(label) {
|
|
91
|
+
if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== "function") {
|
|
92
|
+
throw new Error(
|
|
93
|
+
"当前终端无法安全读取密码,请在交互式终端运行,或设置 AGENTLINK_ACCOUNT 和 AGENTLINK_PASSWORD。",
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
process.stdout.write(label);
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
let password = "";
|
|
99
|
+
const wasRaw = process.stdin.isRaw;
|
|
100
|
+
const finish = () => {
|
|
101
|
+
process.stdin.off("data", onData);
|
|
102
|
+
process.stdin.setRawMode(wasRaw);
|
|
103
|
+
process.stdin.pause();
|
|
104
|
+
process.stdout.write("\n");
|
|
105
|
+
};
|
|
106
|
+
const onData = (chunk) => {
|
|
107
|
+
for (const char of String(chunk)) {
|
|
108
|
+
if (char === "\u0003") {
|
|
109
|
+
finish();
|
|
110
|
+
reject(new Error("已取消登录。"));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (char === "\r" || char === "\n") {
|
|
114
|
+
finish();
|
|
115
|
+
resolve(password);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (char === "\u007f" || char === "\b") password = password.slice(0, -1);
|
|
119
|
+
else if (char >= " ") password += char;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
process.stdin.setRawMode(true);
|
|
123
|
+
process.stdin.setEncoding("utf8");
|
|
124
|
+
process.stdin.resume();
|
|
125
|
+
process.stdin.on("data", onData);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function accountCredentials() {
|
|
130
|
+
const fromEnv = process.env.AGENTLINK_ACCOUNT?.trim();
|
|
131
|
+
const passwordFromEnv = process.env.AGENTLINK_PASSWORD;
|
|
132
|
+
if (fromEnv && passwordFromEnv) return { identifier: fromEnv, password: passwordFromEnv };
|
|
133
|
+
if (!process.stdin.isTTY) {
|
|
134
|
+
throw new Error("首次运行需要登录账号,请在交互式终端运行。");
|
|
135
|
+
}
|
|
136
|
+
const readline = createInterface({ input: process.stdin, output: process.stdout });
|
|
137
|
+
const identifier = (await readline.question("AgentLink 账号:")).trim();
|
|
138
|
+
readline.close();
|
|
139
|
+
const password = await readPassword("AgentLink 密码:");
|
|
140
|
+
if (!identifier || !password) throw new Error("账号和密码不能为空。");
|
|
141
|
+
return { identifier, password };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function loginAndCreateCredential(hub, config) {
|
|
145
|
+
console.log("[agentlink] 首次使用,请登录 AgentLink 账号。");
|
|
146
|
+
const { identifier, password } = await accountCredentials();
|
|
147
|
+
const result = await registerAgentLinkAccountMachine({
|
|
148
|
+
hub,
|
|
149
|
+
config,
|
|
150
|
+
identifier,
|
|
151
|
+
password,
|
|
152
|
+
machineName: hostname(),
|
|
153
|
+
});
|
|
154
|
+
saveCredential(result.credential, hub);
|
|
155
|
+
console.log("[agentlink] 登录成功,这台电脑已加入账号。");
|
|
156
|
+
return result.credential;
|
|
157
|
+
}
|
|
158
|
+
|
|
93
159
|
async function printQr(text) {
|
|
94
160
|
const { renderQr } = await import(new URL("../dist/qr.js", import.meta.url));
|
|
95
161
|
renderQr(text);
|
|
@@ -130,7 +196,9 @@ async function pair(hub) {
|
|
|
130
196
|
socket.close();
|
|
131
197
|
const credential = `${message.token}.${enckey}`;
|
|
132
198
|
saveCredential(credential, hub);
|
|
133
|
-
console.log(
|
|
199
|
+
console.log(
|
|
200
|
+
`[agentlink] 配对成功。alp1 配对串只能使用这一次;已转换为本机凭证并保存到 ${CREDENTIAL_FILE}(0600)。`,
|
|
201
|
+
);
|
|
134
202
|
resolve(credential);
|
|
135
203
|
}
|
|
136
204
|
});
|
|
@@ -140,11 +208,17 @@ async function pair(hub) {
|
|
|
140
208
|
|
|
141
209
|
if (args.includes("--help") || args.includes("-h")) {
|
|
142
210
|
console.log(
|
|
143
|
-
`用法: npx alink-cli [--hub <wss://…>] [--dir <目录>]
|
|
211
|
+
`用法: npx alink-cli [--hub <wss://…>] [--dir <目录>]\n npx alink-cli logout\n\n首次运行输入 AgentLink 账号密码,这台电脑会自动加入账号;以后直接运行即可上线。\nlogout 退出当前账号;--legacy 临时运行旧 daemon。`,
|
|
144
212
|
);
|
|
145
213
|
process.exit(0);
|
|
146
214
|
}
|
|
147
215
|
|
|
216
|
+
if (args[0] === "logout") {
|
|
217
|
+
rmSync(CREDENTIAL_FILE, { force: true });
|
|
218
|
+
console.log("[agentlink] 已退出账号;下次启动时需要重新登录。");
|
|
219
|
+
process.exit(0);
|
|
220
|
+
}
|
|
221
|
+
|
|
148
222
|
if (args[0] === "credential") {
|
|
149
223
|
const hub = value("hub") || OFFICIAL_HUB;
|
|
150
224
|
const command = args[1] || "status";
|
|
@@ -171,11 +245,20 @@ if (args.includes("--legacy")) {
|
|
|
171
245
|
} else {
|
|
172
246
|
const hub = value("hub") || OFFICIAL_HUB;
|
|
173
247
|
const explicit = value("token") || process.env.AGENTLINK_TOKEN;
|
|
248
|
+
const config =
|
|
249
|
+
explicit || savedCredential(hub) || args.includes("--pair")
|
|
250
|
+
? null
|
|
251
|
+
: await loadAgentLinkAccountConfig(hub);
|
|
174
252
|
const credential =
|
|
175
253
|
explicit ||
|
|
176
254
|
(!args.includes("--pair") && savedCredential(hub)) ||
|
|
177
|
-
(
|
|
255
|
+
(config?.mode === "multi"
|
|
256
|
+
? await loginAndCreateCredential(hub, config)
|
|
257
|
+
: await createSingleCredential(hub));
|
|
178
258
|
if (explicit && explicit !== savedCredential(hub)) saveCredential(explicit, hub);
|
|
259
|
+
if (!explicit && savedCredential(hub) && !args.includes("--pair")) {
|
|
260
|
+
console.log("[agentlink] 已登录,正在让这台电脑上线。");
|
|
261
|
+
}
|
|
179
262
|
const { machineToken, enckey } = parseCredential(credential);
|
|
180
263
|
const daemon = fileURLToPath(new URL("../dist/bin.mjs", import.meta.url));
|
|
181
264
|
const cwd = value("dir") || process.cwd();
|
package/dist/bin.mjs
CHANGED
|
@@ -2871,7 +2871,7 @@ const RelayServerGroup = HttpApiGroup.make("server").add(HttpApiEndpoint.post("p
|
|
|
2871
2871
|
success: RelayPublishResponse,
|
|
2872
2872
|
error: RelayAgentActivityPublishErrors
|
|
2873
2873
|
}).annotate(OpenApi.Summary, "Publish agent activity")).annotate(OpenApi.Description, "Environment-authenticated activity publication.").middleware(RelayEnvironmentAuth);
|
|
2874
|
-
HttpApi.make("RelayApi").add(RelayHealthGroup, RelayMetadataGroup, RelayMobileGroup, RelayClientGroup, RelayTokenGroup, RelayDpopClientGroup, RelayServerGroup).annotate(OpenApi.Title, "
|
|
2874
|
+
HttpApi.make("RelayApi").add(RelayHealthGroup, RelayMetadataGroup, RelayMobileGroup, RelayClientGroup, RelayTokenGroup, RelayDpopClientGroup, RelayServerGroup).annotate(OpenApi.Title, "AgentLink Relay API").annotate(OpenApi.Version, "1.0.0").annotate(OpenApi.Description, "Control-plane API for linking T3 environments, connecting authorized clients, and publishing agent activity.");
|
|
2875
2875
|
//#endregion
|
|
2876
2876
|
//#region ../t3-contracts/src/environmentHttp.ts
|
|
2877
2877
|
const OptionalBearerHeaders = Schema$1.Struct({
|
|
@@ -7445,20 +7445,20 @@ const trimmedNonEmpty = (annotations, maxLength) => {
|
|
|
7445
7445
|
return encoded.pipe(Schema$1.decodeTo(encoded, SchemaTransformation.trim()));
|
|
7446
7446
|
};
|
|
7447
7447
|
const T3ProjectFileScript = Schema$1.Struct({
|
|
7448
|
-
name: trimmedNonEmpty({ description: "Display name for the script, shown in the
|
|
7449
|
-
command: trimmedNonEmpty({ description: "Shell command executed in
|
|
7448
|
+
name: trimmedNonEmpty({ description: "Display name for the script, shown in the AgentLink scripts menu." }),
|
|
7449
|
+
command: trimmedNonEmpty({ description: "Shell command executed in an AgentLink terminal at the project root." }),
|
|
7450
7450
|
icon: Schema$1.optionalKey(ProjectScriptIcon.annotate({ description: "Icon shown next to the script in the scripts menu. Defaults to \"play\"." })),
|
|
7451
7451
|
runOnWorktreeCreate: Schema$1.optionalKey(Schema$1.Boolean.annotate({ description: "When true, the script runs automatically after a worktree is created for a new thread." })),
|
|
7452
7452
|
previewUrl: Schema$1.optionalKey(trimmedNonEmpty({ description: "URL opened in the in-app browser preview when this script runs. Only honored on the desktop build." })),
|
|
7453
7453
|
autoOpenPreview: Schema$1.optionalKey(Schema$1.Boolean.annotate({ description: "When true, automatically open the preview panel at `previewUrl` the moment the script starts." }))
|
|
7454
|
-
}).annotate({ description: "A project script that team members can import into
|
|
7454
|
+
}).annotate({ description: "A project script that team members can import into AgentLink." });
|
|
7455
7455
|
const T3ProjectFile = Schema$1.Struct({
|
|
7456
7456
|
$schema: Schema$1.optionalKey(Schema$1.String.annotate({ description: `URL of the JSON Schema for this file, typically "${T3_PROJECT_FILE_SCHEMA_URL}".` })),
|
|
7457
|
-
iconPath: Schema$1.optionalKey(trimmedNonEmpty({ description: "Workspace-relative path to the project icon (e.g. \"assets/logo.svg\"). Checked before
|
|
7458
|
-
scripts: Schema$1.optionalKey(Schema$1.Array(T3ProjectFileScript).annotate({ description: "Project scripts shared with everyone who opens this repository in
|
|
7457
|
+
iconPath: Schema$1.optionalKey(trimmedNonEmpty({ description: "Workspace-relative path to the project icon (e.g. \"assets/logo.svg\"). Checked before AgentLink's built-in icon locations." }, T3_PROJECT_FILE_PATH_MAX_LENGTH)),
|
|
7458
|
+
scripts: Schema$1.optionalKey(Schema$1.Array(T3ProjectFileScript).annotate({ description: "Project scripts shared with everyone who opens this repository in AgentLink." }).check(Schema$1.isMaxLength(T3_PROJECT_FILE_MAX_SCRIPTS)))
|
|
7459
7459
|
}).annotate({
|
|
7460
7460
|
title: "T3 project file",
|
|
7461
|
-
description: "Checked-in project configuration for
|
|
7461
|
+
description: "Checked-in project configuration for AgentLink (t3.json at the repository root)."
|
|
7462
7462
|
});
|
|
7463
7463
|
//#endregion
|
|
7464
7464
|
//#region ../t3-contracts/src/project.ts
|
|
@@ -17921,15 +17921,7 @@ const renderTerminalQrCode = (value, margin = 2) => {
|
|
|
17921
17921
|
}
|
|
17922
17922
|
return rows.join("\n");
|
|
17923
17923
|
};
|
|
17924
|
-
const formatHeadlessServeOutput = (
|
|
17925
|
-
"T3 Code server is ready.",
|
|
17926
|
-
`Connection string: ${accessInfo.connectionString}`,
|
|
17927
|
-
`Token: ${accessInfo.token}`,
|
|
17928
|
-
`Pairing URL: ${accessInfo.pairingUrl}`,
|
|
17929
|
-
"",
|
|
17930
|
-
renderTerminalQrCode(accessInfo.pairingUrl),
|
|
17931
|
-
""
|
|
17932
|
-
].join("\n");
|
|
17924
|
+
const formatHeadlessServeOutput = (_accessInfo) => "AgentLink server is ready.\n";
|
|
17933
17925
|
const issueHeadlessServeAccessInfo = Effect.fn("issueHeadlessServeAccessInfo")(function* () {
|
|
17934
17926
|
const serverConfig = yield* ServerConfig;
|
|
17935
17927
|
const httpServer = yield* HttpServer.HttpServer;
|
|
@@ -68642,7 +68634,7 @@ const runServerCommand = (flags, options) => Effect.gen(function* () {
|
|
|
68642
68634
|
return yield* runServer.pipe(Effect.provideService(ServerConfig, config));
|
|
68643
68635
|
});
|
|
68644
68636
|
const startCommand = Command.make("start", { ...sharedServerCommandFlags }).pipe(Command.withDescription("Run the T3 Code server."), Command.withHandler((flags) => runServerCommand(flags)));
|
|
68645
|
-
const serveCommand = Command.make("serve", { ...sharedServerCommandFlags }).pipe(Command.withDescription("Run the T3 Code server without opening a browser
|
|
68637
|
+
const serveCommand = Command.make("serve", { ...sharedServerCommandFlags }).pipe(Command.withDescription("Run the T3 Code server without opening a browser."), Command.withHandler((flags) => runServerCommand(flags, {
|
|
68646
68638
|
startupPresentation: "headless",
|
|
68647
68639
|
forceAutoBootstrapProjectFromCwd: false
|
|
68648
68640
|
})));
|