alink-cli 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 agentlink contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # agentlink
2
+
3
+ 在工作机上**一条命令**把本机接入 [AgentLink](https://github.com/baichen99/agentlink),随时随地从手机 / 网页遥控本机安装的编码 agent(Claude Code / Codex CLI / JoyCode)。连接是**出站**的——走 NAT / 防火墙无需端口转发、无需 VPN,断线自动重连。
4
+
5
+ One command on your work machine links it to AgentLink so you can drive your local coding agents (Claude Code / Codex CLI / JoyCode) from anywhere. The connection dials **out**, so it just works behind NAT/firewalls; it reconnects automatically.
6
+
7
+ ## Quick start
8
+
9
+ 在控制台“添加机器”拿到机器凭证(`al1.…` 整串),然后在工作机上:
10
+
11
+ ```bash
12
+ npx alink-cli --token al1.xxxxxxxx
13
+ ```
14
+
15
+ 首次成功连上后,凭证会存到 `~/.agentlink/credential`(权限 `0600`)。之后在同一台机器上**直接裸跑**即可,命令行 token 只需出现这一次:
16
+
17
+ ```bash
18
+ npx alink-cli
19
+ ```
20
+
21
+ ## 参数 Options
22
+
23
+ | 参数 | 说明 | 默认值 |
24
+ | --- | --- | --- |
25
+ | `--token <token>` | 机器凭证(控制台生成的 `al1.…` 整串)。也可用环境变量 `AGENTLINK_TOKEN` 提供(`--token` 优先)。 | 依次取 `AGENTLINK_TOKEN` → `~/.agentlink/credential` |
26
+ | `--hub <wss://…>` | 要连接的 hub 地址。 | `wss://link.harmopath.com`(官方 hub) |
27
+ | `--dir <path>` | 工作目录根,可重复给多次;运行时可用其下任意子目录,网页里以目录树浏览。 | 当前目录 `process.cwd()` |
28
+ | `--help`, `-h` | 显示用法。 | — |
29
+
30
+ ## 凭证与端到端加密 Credential & E2EE
31
+
32
+ 凭证是这台机器的**远程执行凭据**,请妥善保管:公网请务必用 `wss://`(TLS)。多租户凭证(`al1.` 前缀)里的第四段是端到端加密(E2EE)主密钥——它**只留在本机、从不上网**,daemon 把它单独归档到 `~/.agentlink/enckeys.json`(`0600`),hub 永远只看到密文。撤销某台机器:在控制台里重新添加即可让旧凭证失效。
33
+
34
+ The credential grants remote code execution on this machine — keep it safe and use `wss://` (TLS) on any public hub. The trailing segment of an `al1.` credential is the end-to-end encryption key; it never leaves this machine, so the hub only ever sees ciphertext.
35
+
36
+ 完整文档 Full docs: [github.com/baichen99/agentlink](https://github.com/baichen99/agentlink) · MIT
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env node
2
+ // bin/agentlink.js — `npx alink-cli` 的薄入口。
3
+ //
4
+ // 它自己几乎不做事:把命令行参数补齐默认值后,直接 import 打好的 daemon
5
+ // bundle(dist/daemon.js),由 daemon 完成探测 agent、连 hub、跑任务这套完整
6
+ // 流程。daemon 在被 import 时会读 `process.argv` / `AGENTLINK_TOKEN`,所以这里
7
+ // 必须在 import 之前把 argv 和 env 准备好。
8
+ //
9
+ // 相对 daemon 收敛的默认值(让工作机上一条命令即接入官方 hub):
10
+ // --hub 缺省 wss://link.harmopath.com(daemon 裸跑缺省是 localhost,只适合开发)
11
+ // --dir 缺省 process.cwd()(daemon 本就如此,无需干预)
12
+ // --token 缺省依次取 AGENTLINK_TOKEN 环境变量 → ~/.agentlink/credential 落盘凭证
13
+ //
14
+ // 凭证落盘:首次带 `--token` 成功连上 hub 后,把整串 token 写到
15
+ // ~/.agentlink/credential(0600)。之后裸跑 `npx alink-cli` 直接复用——命令行
16
+ // 上的 token 只需出现一次。al1. 多租户凭证的第四段(E2EE 主密钥)也一并存进这
17
+ // 个文件,从不上网;daemon 另会把它单独归档进 enckeys.json 供 E2EE 层使用。
18
+
19
+ import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
20
+ import { homedir } from "node:os";
21
+ import { dirname, join } from "node:path";
22
+ import { fileURLToPath } from "node:url";
23
+
24
+ const OFFICIAL_HUB = "wss://link.harmopath.com";
25
+
26
+ // 凭证文件与 daemon 的状态目录同根(默认 ~/.agentlink,可用 AGENTLINK_STATE_DIR
27
+ // 覆盖,测试用),文件名固定为 credential。
28
+ const STATE_DIR = process.env.AGENTLINK_STATE_DIR || join(homedir(), ".agentlink");
29
+ const CREDENTIAL_FILE = join(STATE_DIR, "credential");
30
+
31
+ const rawArgs = process.argv.slice(2);
32
+
33
+ // --help / -h:打印简洁中文用法后退出,不启动 daemon。
34
+ if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
35
+ printHelp();
36
+ process.exit(0);
37
+ }
38
+
39
+ // 判断某个具名参数是否已由用户给出(同时认 `--name value` 与 `--name=value`)。
40
+ function hasFlag(name) {
41
+ return rawArgs.some((a) => a === `--${name}` || a.startsWith(`--${name}=`));
42
+ }
43
+
44
+ // 读取具名参数的值(用于取出用户显式给的 --token 整串)。
45
+ function flagValue(name) {
46
+ for (let i = 0; i < rawArgs.length; i++) {
47
+ const a = rawArgs[i];
48
+ if (a === `--${name}`) return rawArgs[i + 1];
49
+ if (a.startsWith(`--${name}=`)) return a.slice(name.length + 3);
50
+ }
51
+ return undefined;
52
+ }
53
+
54
+ function readCredentialFile() {
55
+ try {
56
+ const t = readFileSync(CREDENTIAL_FILE, "utf-8").trim();
57
+ return t || undefined;
58
+ } catch {
59
+ return undefined; // 文件不存在或不可读——当作没有凭证
60
+ }
61
+ }
62
+
63
+ function persistCredential(token) {
64
+ mkdirSync(STATE_DIR, { recursive: true });
65
+ // 先写再 chmod,确保即便文件是新建也是 0600(mode 选项只在创建时生效)。
66
+ writeFileSync(CREDENTIAL_FILE, token, { mode: 0o600 });
67
+ chmodSync(CREDENTIAL_FILE, 0o600);
68
+ }
69
+
70
+ // --tunnel 模式下 daemon 会自起本地 hub,且与 --hub 互斥,所以此时绝不注入 --hub。
71
+ const tunnelMode = hasFlag("tunnel");
72
+
73
+ // 1) 补 --hub 默认值:官方 hub。
74
+ if (!hasFlag("hub") && !tunnelMode) {
75
+ rawArgs.push("--hub", OFFICIAL_HUB);
76
+ }
77
+
78
+ // 2) 解析 token 来源,并决定是否需要“成功连接后落盘”。
79
+ // 优先级:命令行 --token > AGENTLINK_TOKEN 环境变量 > 凭证文件。
80
+ let tokenToPersist; // 仅当用户这次显式给了新 --token 时,连上后写盘
81
+ const explicitToken = hasFlag("token") ? flagValue("token") : undefined;
82
+ if (explicitToken) {
83
+ // 用户显式给了 token——daemon 会直接用 argv 里的 --token。若它和已存凭证不同,
84
+ // 安排在成功连上 hub 后落盘(避免把连不上的坏 token 也存下来)。
85
+ if (explicitToken !== readCredentialFile()) tokenToPersist = explicitToken;
86
+ } else if (process.env.AGENTLINK_TOKEN) {
87
+ // 环境变量已给——daemon 自己会读 AGENTLINK_TOKEN,这里什么都不用做。
88
+ } else {
89
+ // 命令行和环境变量都没有——回落到落盘凭证(若有),塞进 AGENTLINK_TOKEN 供
90
+ // daemon 读取。都没有时不注入:daemon 会生成随机 token 走单机模式并打印配对链接。
91
+ const saved = readCredentialFile();
92
+ if (saved) process.env.AGENTLINK_TOKEN = saved;
93
+ }
94
+
95
+ // 3) 把补好默认值的 argv 交还给 daemon(daemon 读 process.argv.slice(2))。
96
+ process.argv = [process.argv[0], process.argv[1], ...rawArgs];
97
+
98
+ // 4) 若需要落盘:daemon 用 console.log 打印进度,成功连上时会输出
99
+ // "[daemon] registered. …"。在 import daemon 前挂一个一次性的 stdout 嗅探器,
100
+ // 看到该标记即把 token 写盘,然后还原 stdout。这样只有真正连上的凭证才落盘。
101
+ if (tokenToPersist) {
102
+ const original = process.stdout.write.bind(process.stdout);
103
+ let filed = false;
104
+ process.stdout.write = (chunk, ...rest) => {
105
+ if (!filed && typeof chunk === "string" && chunk.includes("[daemon] registered.")) {
106
+ filed = true;
107
+ try {
108
+ persistCredential(tokenToPersist);
109
+ original(`[agentlink] 凭证已保存到 ${CREDENTIAL_FILE}(0600)——之后可直接裸跑 \`npx alink-cli\`\n`);
110
+ } catch {
111
+ original(`[agentlink] 警告:凭证写入 ${CREDENTIAL_FILE} 失败——下次仍需 --token\n`);
112
+ }
113
+ process.stdout.write = original; // 还原,之后正常输出
114
+ }
115
+ return original(chunk, ...rest);
116
+ };
117
+ }
118
+
119
+ // 5) 启动 daemon。import 求值时它就会解析 argv 并开始连 hub。
120
+ const daemon = new URL("../dist/daemon.js", import.meta.url);
121
+ await import(daemon.href);
122
+
123
+ function printHelp() {
124
+ const self = "agentlink";
125
+ process.stdout.write(
126
+ [
127
+ `用法: npx ${self} [--token <al1.…>] [--hub <wss://…>] [--dir <目录>]…`,
128
+ ``,
129
+ `把当前这台工作机接入 AgentLink,随时随地遥控本机的编码 agent。`,
130
+ ``,
131
+ `参数:`,
132
+ ` --token <token> 机器凭证(控制台里“添加机器”生成的 al1.… 整串)。`,
133
+ ` 首次成功连上后会存到 ~/.agentlink/credential(0600),`,
134
+ ` 之后裸跑 npx ${self} 自动复用。也可用环境变量`,
135
+ ` AGENTLINK_TOKEN 提供(--token 优先)。`,
136
+ ` --hub <url> hub 地址,缺省官方 hub ${OFFICIAL_HUB}。`,
137
+ ` --dir <path> 工作目录根,可重复;缺省当前目录。运行时可用其下任意子目录。`,
138
+ ` --help, -h 显示本帮助。`,
139
+ ``,
140
+ `示例:`,
141
+ ` npx ${self} --token al1.xxxxx # 首次接入,token 只需给这一次`,
142
+ ` npx ${self} # 之后裸跑,复用已存凭证`,
143
+ ``,
144
+ `连接是出站的(走 NAT/防火墙无需端口转发),断线自动重连。al1. 凭证的加密`,
145
+ `密钥段只留在本机、从不上网(端到端加密)。`,
146
+ ``,
147
+ ].join("\n"),
148
+ );
149
+ }