alink-cli 0.11.12 → 0.11.14
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 +42 -4
- package/bin/agentlink.js +2 -0
- package/dist/bin.mjs +44 -6
- package/dist/bin.mjs.map +1 -1
- package/dist/ui.js +5 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# AgentLink
|
|
2
2
|
|
|
3
|
+
**中文(默认)** · [English](#english)
|
|
4
|
+
|
|
3
5
|
AgentLink 让你随时从手机或浏览器继续使用运行在自己电脑上的 Claude Code、Codex、Gemini、Qwen、Kimi 等编码 agent。
|
|
4
6
|
|
|
5
7
|
**官网:[https://link.harmopath.com/marketing](https://link.harmopath.com/marketing)**
|
|
@@ -8,7 +10,7 @@ AgentLink 让你随时从手机或浏览器继续使用运行在自己电脑上
|
|
|
8
10
|
|
|
9
11
|
- **Local-first:** agent、项目文件和任务记录都保存在你的电脑上。
|
|
10
12
|
- **安全连接:** 设备之间端到端加密,Hub 不保存你的对话。
|
|
11
|
-
- **跨设备继续:**
|
|
13
|
+
- **跨设备继续:** 离开电脑后,仍可查看进度、回复消息、处理授权、操作终端、访问本机服务和加密分享文件。
|
|
12
14
|
- **一个账号:** 登录后自动发现自己的电脑,无需手动配置连接。
|
|
13
15
|
|
|
14
16
|
## 开始使用
|
|
@@ -20,10 +22,46 @@ npm install -g alink-cli
|
|
|
20
22
|
alink
|
|
21
23
|
```
|
|
22
24
|
|
|
25
|
+
`alink` 是推荐的短命令别名;`agentlink` 和 `alink-cli` 也都支持。
|
|
26
|
+
|
|
23
27
|
按提示登录,然后在手机或浏览器打开 [AgentLink 控制台](https://link.harmopath.com/login),即可开始任务或继续已有工作。
|
|
24
28
|
|
|
25
|
-
`alink login` 会打开浏览器,支持使用 GitHub 或已有 AgentLink
|
|
29
|
+
`alink login` 会打开浏览器,支持使用 GitHub 或已有 AgentLink 账号登录并添加当前电脑。脚本或排障时可用 `alink status --json` 和 `alink machines --json` 获取结构化状态。
|
|
30
|
+
|
|
31
|
+
## 联系
|
|
32
|
+
|
|
33
|
+
问题、建议或 TestFlight 申请:[imbistone@gmail.com](mailto:imbistone@gmail.com)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## English
|
|
38
|
+
|
|
39
|
+
AgentLink lets you continue using Claude Code, Codex, Gemini, Qwen, Kimi, and other coding agents running on your own computer from any phone or browser.
|
|
40
|
+
|
|
41
|
+
### Runs locally, ready remotely
|
|
42
|
+
|
|
43
|
+
- **Local-first:** Your agents, project files, and task history stay on your computer.
|
|
44
|
+
- **Secure connection:** Devices use end-to-end encryption, and the Hub does not store your conversations.
|
|
45
|
+
- **Continue anywhere:** Follow progress, reply, approve actions, operate terminals, open local services, and share encrypted files while away from your computer.
|
|
46
|
+
- **One account:** Sign in to find your computers automatically, without manually configuring connections.
|
|
47
|
+
|
|
48
|
+
### Get started
|
|
49
|
+
|
|
50
|
+
Node.js 22.16 or later is required.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install -g alink-cli
|
|
54
|
+
alink
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`alink` is the recommended short alias. `agentlink` and `alink-cli` are also supported.
|
|
58
|
+
|
|
59
|
+
Follow the sign-in prompt, then open the [AgentLink console](https://link.harmopath.com/login) on your phone or browser to start a task or continue existing work.
|
|
60
|
+
|
|
61
|
+
Use `alink status --json` and `alink machines --json` for structured local connection and machine status in scripts or troubleshooting.
|
|
62
|
+
|
|
63
|
+
### Contact
|
|
26
64
|
|
|
27
|
-
|
|
65
|
+
Questions, feedback, or TestFlight access: [imbistone@gmail.com](mailto:imbistone@gmail.com)
|
|
28
66
|
|
|
29
|
-
[GitHub](https://github.com/baichen99/agentlink) · [
|
|
67
|
+
[Website](https://link.harmopath.com/marketing) · [GitHub](https://github.com/baichen99/agentlink) · [Issues](https://github.com/baichen99/agentlink/issues) · MIT
|
package/bin/agentlink.js
CHANGED
|
@@ -62,6 +62,7 @@ const STATE_DIR = process.env.AGENTLINK_STATE_DIR || join(homedir(), ".agentlink
|
|
|
62
62
|
const CREDENTIAL_FILE = join(STATE_DIR, "credential");
|
|
63
63
|
const SESSION_FILE = join(STATE_DIR, "session");
|
|
64
64
|
const STATUS_FILE = join(STATE_DIR, "status.json");
|
|
65
|
+
const CLI_VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
65
66
|
const args = process.argv.slice(2);
|
|
66
67
|
const legacyCredentialCommands = process.env.AGENTLINK_ENABLE_LEGACY_CREDENTIALS === "1";
|
|
67
68
|
const forceTui = process.env.AGENTLINK_TUI === "1";
|
|
@@ -446,6 +447,7 @@ async function doNoTuiStart() {
|
|
|
446
447
|
T3CODE_HUB_URL: hub,
|
|
447
448
|
T3CODE_HUB_MACHINE_TOKEN: machineToken,
|
|
448
449
|
T3CODE_HUB_ENCKEY: enckey,
|
|
450
|
+
AGENTLINK_CLI_VERSION: CLI_VERSION,
|
|
449
451
|
},
|
|
450
452
|
});
|
|
451
453
|
for (const signal of ["SIGINT", "SIGTERM"]) process.on(signal, () => child.kill(signal));
|
package/dist/bin.mjs
CHANGED
|
@@ -52047,6 +52047,9 @@ var PtyAdapter = class extends Service$2()("t3/terminal/PtyAdapter") {};
|
|
|
52047
52047
|
//#region src/terminal/Manager.ts
|
|
52048
52048
|
const DEFAULT_COLS = 120;
|
|
52049
52049
|
const DEFAULT_ROWS = 30;
|
|
52050
|
+
function terminalLocale(environment, platform) {
|
|
52051
|
+
return environment.LC_ALL ?? environment.LC_CTYPE ?? environment.LANG ?? (platform === "darwin" ? "en_US.UTF-8" : "C.UTF-8");
|
|
52052
|
+
}
|
|
52050
52053
|
var TerminalManager = class extends Service$2()("agentlink/terminal/TerminalManager") {};
|
|
52051
52054
|
function sessionKey(threadId, terminalId) {
|
|
52052
52055
|
return `${threadId}\0${terminalId}`;
|
|
@@ -52155,7 +52158,11 @@ const layer$7 = effect(TerminalManager, gen(function* () {
|
|
|
52155
52158
|
env: {
|
|
52156
52159
|
...process.env,
|
|
52157
52160
|
...session.env,
|
|
52158
|
-
TERM: "xterm-256color"
|
|
52161
|
+
TERM: "xterm-256color",
|
|
52162
|
+
LANG: terminalLocale({
|
|
52163
|
+
...process.env,
|
|
52164
|
+
...session.env
|
|
52165
|
+
}, process.platform)
|
|
52159
52166
|
}
|
|
52160
52167
|
}));
|
|
52161
52168
|
if (result$1._tag === "Success") {
|
|
@@ -53292,6 +53299,12 @@ function hubHttpUrl(hub) {
|
|
|
53292
53299
|
url.hash = "";
|
|
53293
53300
|
return url.toString().replace(/\/$/, "");
|
|
53294
53301
|
}
|
|
53302
|
+
function hubRegistration(cliVersion) {
|
|
53303
|
+
return cliVersion ? {
|
|
53304
|
+
type: "register",
|
|
53305
|
+
daemonVersion: cliVersion
|
|
53306
|
+
} : null;
|
|
53307
|
+
}
|
|
53295
53308
|
function writeHubConnectionStatus(config, connected, stateDir = process.env.AGENTLINK_STATE_DIR || join(homedir(), ".agentlink")) {
|
|
53296
53309
|
try {
|
|
53297
53310
|
mkdirSync(stateDir, { recursive: true });
|
|
@@ -53306,6 +53319,7 @@ function writeHubConnectionStatus(config, connected, stateDir = process.env.AGEN
|
|
|
53306
53319
|
state: connected ? "connected" : "reconnecting",
|
|
53307
53320
|
hub: config.hubUrl,
|
|
53308
53321
|
hubConnected: connected,
|
|
53322
|
+
...process.env.AGENTLINK_CLI_VERSION ? { daemonVersion: process.env.AGENTLINK_CLI_VERSION } : {},
|
|
53309
53323
|
machineId: machineIdFromToken(config.machineToken) ?? void 0,
|
|
53310
53324
|
startedAt: typeof current.startedAt === "number" ? current.startedAt : Date.now(),
|
|
53311
53325
|
ts: Date.now()
|
|
@@ -53714,7 +53728,10 @@ const runHubTunnel = (config) => gen(function* () {
|
|
|
53714
53728
|
else dropTunnel(framed.tunnelId);
|
|
53715
53729
|
return;
|
|
53716
53730
|
}
|
|
53717
|
-
}, { onOpen: logInfo("hub tunnel: connected to hub").pipe(tap(() => sync(() =>
|
|
53731
|
+
}, { onOpen: logInfo("hub tunnel: connected to hub").pipe(tap(() => sync(() => {
|
|
53732
|
+
const registration = hubRegistration(process.env.AGENTLINK_CLI_VERSION);
|
|
53733
|
+
if (registration) writeHub(JSON.stringify(registration));
|
|
53734
|
+
})), tap(() => sync(() => writeHubConnectionStatus(config, true))), tap(() => sync(() => void runFork(upload)))) }).pipe(ensuring(sync(() => writeHubConnectionStatus(config, false))), tapDefect((cause) => logWarning$1("hub tunnel: hub socket defect", { cause: String(cause) })));
|
|
53718
53735
|
yield* logWarning$1("hub tunnel: hub connection closed");
|
|
53719
53736
|
return yield* fail(/* @__PURE__ */ new Error("hub tunnel closed"));
|
|
53720
53737
|
}).pipe(scoped, tapError((error) => logWarning$1("hub tunnel: connection error", { error }))), {
|
|
@@ -88871,6 +88888,9 @@ effect(RuntimeReceiptBus, makeRuntimeReceiptBusTest);
|
|
|
88871
88888
|
//#region src/orchestration/Layers/ProviderRuntimeIngestion.ts
|
|
88872
88889
|
const providerTurnKey = (threadId, turnId) => `${threadId}:${turnId}`;
|
|
88873
88890
|
const providerTaskKey = (threadId, taskId) => `${threadId}:${taskId}`;
|
|
88891
|
+
function providerReconciliationFailureDetail(cause) {
|
|
88892
|
+
return cause instanceof Error && cause.message.toLowerCase().includes("active writer") ? "对话正在另一端运行,请等待另一端结束后再继续。" : cause instanceof Error ? cause.message : "Provider snapshot sync failed.";
|
|
88893
|
+
}
|
|
88874
88894
|
function findTaskTitleInActivities(activities, taskId) {
|
|
88875
88895
|
if (!activities) return;
|
|
88876
88896
|
for (let index = activities.length - 1; index >= 0; index -= 1) {
|
|
@@ -89880,7 +89900,9 @@ const ProviderRuntimeIngestionLive = effect(ProviderRuntimeIngestionService, gen
|
|
|
89880
89900
|
});
|
|
89881
89901
|
const reconcileThreadUnlocked = fn("reconcileThreadUnlocked")(function* (threadId, options) {
|
|
89882
89902
|
const thread = yield* resolveThreadShell(threadId);
|
|
89883
|
-
if (!thread
|
|
89903
|
+
if (!thread) return;
|
|
89904
|
+
const session = thread.session;
|
|
89905
|
+
if (session?.providerName !== ProviderDriverKind.make("codex")) return;
|
|
89884
89906
|
const snapshot = yield* providerService.readThread(threadId);
|
|
89885
89907
|
for (const turn of snapshot.turns) {
|
|
89886
89908
|
const existing = getOrUndefined(yield* projectionTurnRepository.getByTurnId({
|
|
@@ -89892,7 +89914,7 @@ const ProviderRuntimeIngestionLive = effect(ProviderRuntimeIngestionService, gen
|
|
|
89892
89914
|
if (isTerminal) {
|
|
89893
89915
|
const pendingTurnStart = yield* projectionTurnRepository.getPendingTurnStartByThreadId({ threadId });
|
|
89894
89916
|
if (!(existing?.pendingMessageId !== null && existing?.pendingMessageId !== void 0 ? true : isSome(pendingTurnStart))) {
|
|
89895
|
-
const createdAt = turn.startedAt ?? turn.runtimeEvents?.[0]?.createdAt ??
|
|
89917
|
+
const createdAt = turn.startedAt ?? turn.runtimeEvents?.[0]?.createdAt ?? session.updatedAt;
|
|
89896
89918
|
const providerThreadId = snapshot.providerThreadId ?? String(threadId);
|
|
89897
89919
|
for (const userMessage of turn.userMessages ?? []) yield* orchestrationEngine.dispatch({
|
|
89898
89920
|
type: "thread.message.user.import",
|
|
@@ -89912,11 +89934,27 @@ const ProviderRuntimeIngestionLive = effect(ProviderRuntimeIngestionService, gen
|
|
|
89912
89934
|
});
|
|
89913
89935
|
}
|
|
89914
89936
|
if (options?.requireWritable !== true) return;
|
|
89915
|
-
yield* providerService.ensureSession(threadId)
|
|
89937
|
+
yield* providerService.ensureSession(threadId).pipe(tapError((cause) => gen(function* () {
|
|
89938
|
+
const updatedAt = formatIso(yield* now$1);
|
|
89939
|
+
const detail = providerReconciliationFailureDetail(cause);
|
|
89940
|
+
yield* orchestrationEngine.dispatch({
|
|
89941
|
+
type: "thread.session.set",
|
|
89942
|
+
commandId: CommandId.make(`provider-writer-acquisition:${threadId}:${yield* crypto.randomUUIDv4}`),
|
|
89943
|
+
threadId,
|
|
89944
|
+
session: {
|
|
89945
|
+
...session,
|
|
89946
|
+
status: "error",
|
|
89947
|
+
activeTurnId: null,
|
|
89948
|
+
lastError: detail,
|
|
89949
|
+
updatedAt
|
|
89950
|
+
},
|
|
89951
|
+
createdAt: updatedAt
|
|
89952
|
+
});
|
|
89953
|
+
})));
|
|
89916
89954
|
});
|
|
89917
89955
|
const reconcileThread = (threadId, options) => withThreadLock(threadId, reconcileThreadUnlocked(threadId, options)).pipe(mapError((cause) => cause instanceof ProviderRuntimeReconciliationError ? cause : new ProviderRuntimeReconciliationError({
|
|
89918
89956
|
threadId,
|
|
89919
|
-
detail: cause
|
|
89957
|
+
detail: providerReconciliationFailureDetail(cause),
|
|
89920
89958
|
cause
|
|
89921
89959
|
})));
|
|
89922
89960
|
const processDomainEvent = (_event) => void_$1;
|