pinomad 0.2.0 → 0.4.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/README.md +60 -182
- package/dist/{chunk-n4bvmcy8.js → chunk-17p6bfvy.js} +4 -4
- package/dist/chunk-302y6by9.js +50 -0
- package/dist/{chunk-5h97zxag.js → chunk-374zqx5h.js} +150 -121
- package/dist/chunk-5785kwej.js +128 -0
- package/dist/{chunk-z4x88s0q.js → chunk-7gmkgyaz.js} +76 -3
- package/dist/chunk-837gh8nr.js +138 -0
- package/dist/{chunk-d9q0dz8s.js → chunk-akgxwg8f.js} +98 -6
- package/dist/chunk-b3f0ced1.js +147 -0
- package/dist/{chunk-q26n39mf.js → chunk-d4y99pfy.js} +2 -2
- package/dist/{chunk-rkfdsacq.js → chunk-fgx2sh7d.js} +1 -1
- package/dist/chunk-knaw1a9g.js +182 -0
- package/dist/chunk-myjp910s.js +253 -0
- package/dist/{chunk-haycqqp1.js → chunk-n73t5xzw.js} +1 -1
- package/dist/{chunk-5712fvyp.js → chunk-prhvksh3.js} +4 -4
- package/dist/chunk-vw5yb5pd.js +1123 -0
- package/dist/chunk-xhwkx9bd.js +139 -0
- package/dist/{chunk-7fqn69ys.js → chunk-xrvmnymc.js} +2 -2
- package/dist/{chunk-fhtkkcgt.js → chunk-z5pmcscx.js} +1491 -794
- package/dist/pinomad.js +14 -9
- package/package.json +7 -7
- package/web/assets/index-8mNNPXPT.js +383 -0
- package/web/assets/{index-KwICnYux.css → index-BFJJgGGo.css} +1 -1
- package/web/index.html +2 -2
- package/dist/chunk-fxaewke2.js +0 -113
- package/dist/chunk-h35fermn.js +0 -92
- package/dist/chunk-qdxhqva8.js +0 -284
- package/web/assets/index-4p91UVoc.js +0 -383
package/README.md
CHANGED
|
@@ -1,222 +1,100 @@
|
|
|
1
1
|
# PiNomad
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
English · [中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
- 多端客户端:桌面、Web、移动端连接同一个宿主,界面由 PiNomad 统一定义。
|
|
5
|
+
A self-hosted coding agent built on [Pi Durable](https://www.npmjs.com/package/@earendil-works/pi-durable). The agent runs on your own machine; the Mac app, the web client, and the iPhone app all attach to the same runs.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+

|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
<sub>One run, three clients: Mac app (left), iPhone via iPhone Mirroring (center), web (right). The host is a Linux box on the same tailnet.</sub>
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
- **Your machine does the work.** The host runs on a Mac mini, a Linux box, or a VPS, and the agent uses that machine's shell and repositories.
|
|
12
|
+
- **Runs survive crashes and restarts.** Pi Durable checkpoints every model call and tool call, so a restarted host picks up where it stopped.
|
|
13
|
+
- **Clients are just windows.** Start a task at your desk, then check on it or steer it from your phone. Devices pair with a one-time QR code and talk to the host over an end-to-end encrypted channel: directly on your LAN or tailnet, or through a relay that only sees ciphertext.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
npm i -g pinomad
|
|
16
|
-
|
|
17
|
-
# 起宿主:真实模型需要先在 pi 里配好认证(读 ~/.pi/agent);
|
|
18
|
-
# 没有认证可以用脚本化的假模型冒烟:
|
|
19
|
-
pinomad host --faux "hi"
|
|
20
|
-
|
|
21
|
-
# 打印带 token 的浏览器链接,打开即用(Web 客户端由宿主自己提供):
|
|
22
|
-
pinomad link
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
### 开发 / 从源码运行
|
|
26
|
-
|
|
27
|
-
检出仓库,需要 Bun(装依赖、跑脚本)+ Node 25+:
|
|
28
|
-
|
|
29
|
-
```sh
|
|
30
|
-
bun install
|
|
31
|
-
|
|
32
|
-
bun run host -- --faux "hi"
|
|
33
|
-
|
|
34
|
-
# 另一个终端起 Web 客户端开发服务器(http://127.0.0.1:5199):
|
|
35
|
-
bun run web
|
|
36
|
-
bun run link
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
默认数据目录是 `~/.pinomad`(session.sqlite、token、目录锁),可用 `--data-dir` 或 `PINOMAD_DATA_DIR` 覆盖;同一数据目录同时只能有一个宿主进程。
|
|
40
|
-
|
|
41
|
-
对话按项目组织(ADR-0007):在客户端侧栏点 Add project 登记一个宿主上的目录,也可以启动时用可重复的 `--project DIR` 登记;不选项目的对话归入 Chat。宿主启动时不再自动建对话,第一条消息发出时才新建。
|
|
42
|
-
|
|
43
|
-
Git 项目的对话默认在独立 worktree 里工作(ADR-0010):宿主在 `<数据目录>/worktrees/<对话 id>` 检出项目仓库,分支是 `pinomad/<对话 id>-<随机>`,起点是创建时的 HEAD;项目的原目录不会被改动,改动留在分支上,由你自己 merge 或发 PR 合回。新建对话时勾选 "Work directly in project directory" 可以直接在项目目录里工作;非 Git 目录和还没有 commit 的仓库也直接使用项目目录。fork 会得到自己的 worktree,内容复制自父对话在 fork 那一刻的文件状态(已提交、未提交、未跟踪的都在,被 gitignore 的文件不复制);子代理和所属对话共用一个 worktree。归档对话时干净的 worktree 目录会被删除(分支保留),有未提交改动的保留并提示。注意 worktree 是全新检出,`node_modules`、`.env` 这类被忽略文件不在里面,agent 需要时按 AGENTS.md 自行安装。如果移动或删除了数据目录,在仓库里跑一次 `git worktree prune` 清掉失效的登记。
|
|
15
|
+
> **Status:** early and moving fast. Pi Durable is itself experimental, and the host and its clients must speak the same protocol major version.
|
|
44
16
|
|
|
45
|
-
|
|
17
|
+
## Quick start
|
|
46
18
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- AGENTS.md:`~/.agents/AGENTS.md`,以及从根目录到 cwd 沿途的 AGENTS.md / CLAUDE.md;
|
|
50
|
-
- Skills:`<cwd>/.agents/skills` 和 `~/.agents/skills`,同名时项目的生效。
|
|
51
|
-
|
|
52
|
-
MCP server 在 `~/.agents/mcp.json` 里配置(ADR-0012),格式是各家客户端通用的 `mcpServers`:
|
|
53
|
-
|
|
54
|
-
```json
|
|
55
|
-
{
|
|
56
|
-
"mcpServers": {
|
|
57
|
-
"docs": { "command": "npx", "args": ["-y", "@example/docs-mcp"] },
|
|
58
|
-
"github": { "url": "https://api.githubcopilot.com/mcp/", "headers": { "Authorization": "Bearer ${GITHUB_TOKEN}" } },
|
|
59
|
-
"browser": { "command": "npx", "args": ["-y", "@playwright/mcp"], "exposure": "direct", "description": "Drive a browser" },
|
|
60
|
-
"local": { "command": "my-mcp-server", "env": { "KEY": "${MY_KEY}" }, "enabled": false }
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
stdio server 写 `command`(可选 `args`、`env`、`cwd`——相对的 cwd 按主目录解析);HTTP server 写 `url`(可选 `headers`)。`env` 和 `headers` 的值可以用 `${NAME}` 引用宿主进程的环境变量。`timeout`(秒,默认 60)是单次工具调用的超时;`enabled: false` 保留配置但不连接。每个 server 在宿主里只连一份、所有对话共用;工具名是 `mcp__<server>__<tool>`。`exposure` 决定模型怎么够到它们(ADR-0013):默认 `codemode`,工具不声明给模型,只能在 codemode 脚本里调用,模型用 `searchTools` / `describeNamespace` 找到它们,server 连上前后工具声明不变;写 `"direct"` 则同时直接声明给模型,结果里的图片显示在工具卡片里。`description`(可选)是给模型看的一句话说明,列在系统提示词里;不写时只列 server 名和工具数,server 自带的 instructions 由 `describeNamespace` 返回。右侧状态面板的 MCP 区列出每个 server 的状态、工具数和错误,以及配置错误(比如引用了一个不存在的环境变量)。配置只在启动时读一次,改动后重启宿主生效(常驻模式用 `bun run service -- restart`)。暂不支持:项目级 `mcp.json`、OAuth 登录(server 回 401 会在状态里提示)、resources / prompts、`deferred` / `tool_search`。
|
|
66
|
-
|
|
67
|
-
## 远程访问(ADR-0008)
|
|
68
|
-
|
|
69
|
-
一条命令起全部(构建 Web、起宿主、起 vite),并在终端打印电脑浏览器链接和手机配对二维码:
|
|
19
|
+
The host needs Node 25+ on macOS or Linux.
|
|
70
20
|
|
|
71
21
|
```sh
|
|
72
|
-
|
|
73
|
-
#
|
|
22
|
+
npm i -g pinomad
|
|
23
|
+
pinomad host # uses the models and credentials you set up in pi (~/.pi/agent)
|
|
24
|
+
pinomad link # in another terminal: prints a browser link to the web client
|
|
74
25
|
```
|
|
75
26
|
|
|
76
|
-
|
|
27
|
+
Haven't set up pi yet? `pinomad host --faux "hi"` runs a scripted fake model so you can click around.
|
|
77
28
|
|
|
78
|
-
|
|
29
|
+
## Connect your other devices
|
|
79
30
|
|
|
80
|
-
|
|
31
|
+
Open a port for paired devices, then pair each device with its own QR code:
|
|
81
32
|
|
|
82
33
|
```sh
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
bun run web # 电脑上的开发服务器 http://127.0.0.1:5199
|
|
86
|
-
bun run link # 打印带 token 的浏览器链接
|
|
87
|
-
bun run pair # 打印配对二维码(一次性,5 分钟过期)
|
|
88
|
-
# 走隧道/自建入口,或 LAN 地址探测选错网卡时用 --public-url 覆盖:
|
|
89
|
-
bun run host -- --remote-port 7422 --public-url https://pinomad.example.com
|
|
34
|
+
pinomad host --remote-port 7422
|
|
35
|
+
pinomad pair # one-time QR code and link, expires after 5 minutes
|
|
90
36
|
```
|
|
91
37
|
|
|
92
|
-
|
|
38
|
+
Scan the code with the iPhone app, or paste the link into the Mac app. The remote port accepts paired devices only; revoke them under Devices in the sidebar.
|
|
93
39
|
|
|
94
|
-
|
|
40
|
+
The pairing link uses the LAN address the host detects. On a tailnet or behind a tunnel, add `--public-url http://<tailnet-ip>:7422` so the link points there. To reach a host at home from anywhere, run the relay on a VPS: see [Remote access](docs/remote-access.md).
|
|
95
41
|
|
|
96
|
-
|
|
42
|
+
## Keep it running
|
|
97
43
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
宿主这边:先打印中继身份(Ed25519 公钥,存在 `<dataDir>/relay-key`):
|
|
44
|
+
Install the host as a user service (LaunchAgent on macOS, systemd user unit on Linux) so it starts at boot and restarts on crash:
|
|
101
45
|
|
|
102
46
|
```sh
|
|
103
|
-
pinomad
|
|
104
|
-
pinomad
|
|
105
|
-
#
|
|
106
|
-
pinomad pair # 打出的二维码指向中继
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
VPS 这边:同一个包(只要 Node 25+)——中继和 Web 客户端来自同一个 `pinomad` 安装,版本天然一致:
|
|
110
|
-
|
|
111
|
-
```sh
|
|
112
|
-
npm i -g pinomad
|
|
113
|
-
pinomad relay --public-origin https://relay.example.com --allow-host <hostId>
|
|
114
|
-
# 默认只监听 127.0.0.1:7430,--port / --listen 可改;多台宿主就写多个 --allow-host
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
Caddy 终止 TLS(自动签证书,WebSocket 无需额外配置):
|
|
118
|
-
|
|
119
|
-
```
|
|
120
|
-
relay.example.com {
|
|
121
|
-
reverse_proxy 127.0.0.1:7430
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
systemd 单元示例(VPS 上的系统级单元;node 和包路径都用绝对路径,`command -v node` 和 `npm root -g` 查):
|
|
126
|
-
|
|
127
|
-
```ini
|
|
128
|
-
[Unit]
|
|
129
|
-
Description=PiNomad relay
|
|
130
|
-
After=network-online.target
|
|
131
|
-
|
|
132
|
-
[Service]
|
|
133
|
-
User=pinomad
|
|
134
|
-
ExecStart=/usr/bin/node /usr/lib/node_modules/pinomad/dist/pinomad.js relay --public-origin https://relay.example.com --allow-host <hostId>
|
|
135
|
-
Restart=always
|
|
136
|
-
|
|
137
|
-
[Install]
|
|
138
|
-
WantedBy=multi-user.target
|
|
47
|
+
pinomad service install --remote-port 7422
|
|
48
|
+
pinomad service status
|
|
49
|
+
pinomad upgrade # install pinomad@latest, wait for running tasks, restart
|
|
139
50
|
```
|
|
140
51
|
|
|
141
|
-
|
|
52
|
+
Service flags, logs, and platform caveats: [Running as a service](docs/service.md).
|
|
142
53
|
|
|
143
|
-
|
|
54
|
+
## Clients
|
|
144
55
|
|
|
145
|
-
|
|
|
56
|
+
| Client | How to get it |
|
|
146
57
|
|---|---|
|
|
147
|
-
|
|
|
148
|
-
|
|
|
149
|
-
|
|
|
150
|
-
|
|
151
|
-
Zeabur 上的步骤(在 Dedicated Server 上部署,用 CLI 上传目录,不接 GitHub):
|
|
152
|
-
|
|
153
|
-
```sh
|
|
154
|
-
cd deploy/relay
|
|
155
|
-
zeabur deploy --project-id <项目 id> --create --name pinomad-relay # 首次:在项目里建出服务
|
|
156
|
-
# 之后重新部署同一个服务:
|
|
157
|
-
zeabur deploy --service-id <服务 id>
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
然后在服务上配好上面的环境变量(`PORT` 由平台注入,不用自己设),在 Domains 里绑定自定义域名,到 DNS 加一条记录:Dedicated Server 用 **A 记录指向服务器 IP**;Cloudflare 上建议**关掉代理(灰云)**,橙云会让国内访问多绕一圈,证书也由 Zeabur 自己签。域名状态变成 `PROVISIONED` 后,验证:
|
|
161
|
-
|
|
162
|
-
```sh
|
|
163
|
-
curl -sI https://nomad.example.com/ # 200,中继托管的 Web 客户端
|
|
164
|
-
pinomad service install --relay https://nomad.example.com … # 宿主接上中继
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
宿主登记成功会在日志里写一行 `{"event":"relay","status":"registered",…}`,掉线重连时写 `"status":"connecting"`(`pinomad service logs` 查看);也可以从外面连设备入口 `wss://<origin>/c/<hostId>` 确认:宿主在线时连接保持打开(等握手),没登记会立刻以 `4604`(hostOffline)关闭。
|
|
168
|
-
|
|
169
|
-
也可以从 GitHub 仓库部署:Root Directory 设 `deploy/relay`、Watch Paths 限 `deploy/relay/**`,其余同上。
|
|
170
|
-
|
|
171
|
-
#### 升级
|
|
58
|
+
| Web | Served by the host itself (`pinomad link`) and by the relay; any modern browser |
|
|
59
|
+
| macOS (Apple silicon) | Signed DMG from [GitHub Releases](https://github.com/bubbleptr/pinomad/releases); updates itself |
|
|
60
|
+
| iOS | Internal TestFlight only for now |
|
|
61
|
+
| Linux, Windows | Use the web client |
|
|
172
62
|
|
|
173
|
-
|
|
63
|
+
## What the agent can do
|
|
174
64
|
|
|
175
|
-
|
|
65
|
+
The agent has pi's four tools (read, bash, edit, write) plus:
|
|
176
66
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
- `install` 把宿主参数原样写进服务定义;`--data-dir`(或 `PINOMAD_DATA_DIR`,默认 `~/.pinomad`)展开成显式的绝对路径。改参数就是带新参数再跑一次 `install`。装好后用 `pinomad link` 拿本机浏览器链接、`pinomad pair` 发手机配对码。
|
|
191
|
-
- **PATH 在安装时固定**:服务里 agent 的 bash 继承的是安装那一刻终端的 PATH(已经剥掉 `node_modules/.bin` 和 Bun 注入的临时目录)。后来装了新工具、PATH 变了,重新跑一次 `install` 刷新。
|
|
192
|
-
- `restart` 先连上宿主读任务图,有未结束的任务就等它们跑完再重启(Ctrl-C 取消);`--force` 立即重启,交给 Durable 恢复。打包安装时 `upgrade` 把 `pinomad@latest` 装进运行包所在的 npm 全局前缀(非全局安装会拒绝执行)+ 同样的 restart;源码检出里(`bun run upgrade`)依次 `git pull --ff-only`、`bun install`、`bun run build`,工作区有未提交改动时拒绝执行。前面任何一步失败都直接停下,不重启,服务继续跑旧代码。
|
|
193
|
-
- `uninstall` 只卸载服务,不动数据目录。
|
|
194
|
-
- macOS:宿主以登录会话运行,机器重启后要能自动起来需在系统设置里打开自动登录,并为这台机器关掉睡眠。日志写到 `<数据目录>/logs/host.log`。
|
|
195
|
-
- Linux:要在未登录时也运行需自己执行 `loginctl enable-linger`(install 检测到没开会提示);日志走 `journalctl --user`。开着 ufw 时,局域网设备访问 `--remote-port` 要先放行,比如 `sudo ufw allow from 192.168.0.0/24 to any port 7422 proto tcp`(只走中继就不用开)。
|
|
196
|
-
- **服务绑定安装时的 node 路径**:单元里写的是 node 和包的绝对路径。用 mise / nvm 管理 Node 时,路径里带着具体版本号(`…/node/26.8.2/…`);换了 Node 版本、旧目录被删后服务就起不来,要用新版本重新 `npm i -g pinomad`,再跑一次 `pinomad service install …`。`pinomad upgrade` 只升 pinomad,装回原来的前缀,不受影响。
|
|
197
|
-
- 服务模式下**回环端口也用 HTTP 提供构建好的 Web 客户端**。npm 安装时 `pinomad link` 总是指向宿主自己提供的 Web;源码检出里按顺序探测:正在跑的 vite 开发服务器(5199)优先,否则宿主提供 Web 的端口,都没有则仍是 5199。开发时 `bun run start` + vite 照旧。
|
|
198
|
-
- 服务在跑时 `bun run start` 会因为目录锁拿不到而失败(同一数据目录只有一个宿主);开发时用另一个 `--data-dir`,或先 `uninstall`。
|
|
67
|
+
| Feature | What it does |
|
|
68
|
+
|---|---|
|
|
69
|
+
| Projects and worktrees | Register a directory as a project. In a Git repo each conversation works in its own worktree and branch, so your checkout stays untouched |
|
|
70
|
+
| Forks | Branch off from a message and keep both lines going |
|
|
71
|
+
| Subagents | Hand self-contained tasks to subagents that run in parallel |
|
|
72
|
+
| Named agents | Reusable roles in `<data dir>/agents/<name>.md` — a `subagent` call or an `@name` mention delegates to one, optionally pinning its model and thinking level |
|
|
73
|
+
| Questions, not approvals | Tools run without approval prompts; when the direction is unclear, the agent asks 1–4 multiple-choice questions |
|
|
74
|
+
| codemode | The model writes JavaScript that calls other tools in a QuickJS sandbox; only the script's output enters the context |
|
|
75
|
+
| MCP | Servers configured in `~/.agents/mcp.json`, in the usual `mcpServers` format |
|
|
76
|
+
| Images | Paste, drop, or pick up to 4 images per message in the Web/desktop composer — resized to JPEG before sending |
|
|
77
|
+
| AGENTS.md and skills | Reads `AGENTS.md` / `CLAUDE.md` and skills from `~/.agents` and the project, on every request |
|
|
199
78
|
|
|
200
|
-
|
|
79
|
+
Details and the MCP config reference: [Usage](docs/usage.md).
|
|
201
80
|
|
|
202
|
-
##
|
|
81
|
+
## Documentation
|
|
203
82
|
|
|
204
|
-
|
|
205
|
-
bun run typecheck
|
|
206
|
-
bun run test # vitest + Playwright,首次跑前先 bunx playwright install chromium
|
|
207
|
-
bun run build
|
|
208
|
-
```
|
|
83
|
+
These docs are written in Chinese.
|
|
209
84
|
|
|
210
|
-
|
|
85
|
+
| Doc | Covers |
|
|
86
|
+
|---|---|
|
|
87
|
+
| [Usage](docs/usage.md) | Projects, worktrees, tools, MCP configuration |
|
|
88
|
+
| [Remote access](docs/remote-access.md) | Pairing, tunnels, self-hosted relay (VPS or container platforms) |
|
|
89
|
+
| [Running as a service](docs/service.md) | `service` and `upgrade` commands, platform notes |
|
|
90
|
+
| [Architecture decisions](docs/adr/README.md) | Why things are the way they are |
|
|
91
|
+
| [Roadmap](docs/roadmap.md) | What's done and what's next |
|
|
92
|
+
| [Glossary](GLOSSARY.md) | Domain terms |
|
|
211
93
|
|
|
212
|
-
|
|
94
|
+
## Contributing
|
|
213
95
|
|
|
214
|
-
|
|
96
|
+
Running from source, verification, and the release process are in [CONTRIBUTING.md](CONTRIBUTING.md) (Chinese).
|
|
215
97
|
|
|
216
|
-
|
|
217
|
-
git tag v0.1.1
|
|
218
|
-
git push origin v0.1.1
|
|
219
|
-
```
|
|
98
|
+
## License
|
|
220
99
|
|
|
221
|
-
-
|
|
222
|
-
- 发布后几分钟内 registry 可能还没处理完(新包还会先出现一个 `0.0.0-stage` 占位版本);这期间查过的机器缓存了旧元数据,安装报 `ETARGET` / "No matching version" 时加 `--prefer-online`。
|
|
100
|
+
[Apache-2.0](LICENSE). PiNomad includes code adapted from [Pi](https://github.com/earendil-works/pi) (MIT) and other projects; their notices are in [`docs/licenses/`](docs/licenses/).
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import {
|
|
3
3
|
restartService,
|
|
4
4
|
serviceManager
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-akgxwg8f.js";
|
|
6
6
|
import {
|
|
7
7
|
globalPrefixOf,
|
|
8
8
|
packageDir,
|
|
9
9
|
packagedVersion
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import"./chunk-
|
|
12
|
-
import"./chunk-
|
|
10
|
+
} from "./chunk-fgx2sh7d.js";
|
|
11
|
+
import"./chunk-374zqx5h.js";
|
|
12
|
+
import"./chunk-myjp910s.js";
|
|
13
13
|
import"./chunk-cjfmfyjb.js";
|
|
14
14
|
import"./chunk-df32avw6.js";
|
|
15
15
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// apps/host/src/server-config.ts
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
var SERVER_CONFIG_FILE = "server.json";
|
|
6
|
+
var normalizeOrigin = (origin) => origin.replace(/\/+$/, "");
|
|
7
|
+
async function readServerConfig(dataDir) {
|
|
8
|
+
let text;
|
|
9
|
+
try {
|
|
10
|
+
text = await readFile(join(dataDir, SERVER_CONFIG_FILE), "utf8");
|
|
11
|
+
} catch (error) {
|
|
12
|
+
if (error.code === "ENOENT")
|
|
13
|
+
return;
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
let value;
|
|
17
|
+
try {
|
|
18
|
+
value = JSON.parse(text);
|
|
19
|
+
} catch {
|
|
20
|
+
throw new Error(`${join(dataDir, SERVER_CONFIG_FILE)} is not valid JSON — delete it or run \`pinomad leave\``);
|
|
21
|
+
}
|
|
22
|
+
const origin = value?.origin;
|
|
23
|
+
if (typeof origin !== "string" || origin.trim() === "") {
|
|
24
|
+
throw new Error(`${join(dataDir, SERVER_CONFIG_FILE)} is missing a string "origin"`);
|
|
25
|
+
}
|
|
26
|
+
const binding = value.binding;
|
|
27
|
+
return {
|
|
28
|
+
origin,
|
|
29
|
+
...typeof binding === "string" && binding !== "" ? { binding } : {}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
async function writeServerConfig(dataDir, config) {
|
|
33
|
+
const path = join(dataDir, SERVER_CONFIG_FILE);
|
|
34
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
35
|
+
await writeFile(tmp, `${JSON.stringify({ origin: normalizeOrigin(config.origin), binding: randomBytes(16).toString("base64url") })}
|
|
36
|
+
`, { mode: 384 });
|
|
37
|
+
await rename(tmp, path);
|
|
38
|
+
}
|
|
39
|
+
var removeServerConfig = (dataDir) => rm(join(dataDir, SERVER_CONFIG_FILE), { force: true });
|
|
40
|
+
async function readServerBinding(dataDir) {
|
|
41
|
+
return (await readServerConfig(dataDir))?.binding;
|
|
42
|
+
}
|
|
43
|
+
function resolveRelayOrigin(flag, config) {
|
|
44
|
+
if (flag !== undefined && config !== undefined && normalizeOrigin(flag) !== normalizeOrigin(config.origin)) {
|
|
45
|
+
throw new Error(`--relay ${flag} disagrees with ${SERVER_CONFIG_FILE} (${config.origin}) — remove one`);
|
|
46
|
+
}
|
|
47
|
+
return flag ?? config?.origin;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { normalizeOrigin, readServerConfig, writeServerConfig, removeServerConfig, readServerBinding, resolveRelayOrigin };
|