pinomad 0.4.0 → 0.6.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 +4 -2
- package/dist/{chunk-837gh8nr.js → chunk-0xh6cy9v.js} +69 -17
- package/dist/{chunk-n73t5xzw.js → chunk-367n9je8.js} +2 -2
- package/dist/{chunk-xhwkx9bd.js → chunk-3dnnaz4j.js} +14 -15
- package/dist/{chunk-z5pmcscx.js → chunk-4btj08we.js} +676 -377
- package/dist/{chunk-7gmkgyaz.js → chunk-7qb9tq54.js} +324 -4
- package/dist/{chunk-d4y99pfy.js → chunk-7s1f4z4g.js} +4 -3
- package/dist/chunk-b1h374jr.js +159 -0
- package/dist/{chunk-fgx2sh7d.js → chunk-c8nxym2z.js} +1 -1
- package/dist/{chunk-akgxwg8f.js → chunk-d5ypmmr7.js} +29 -12
- package/dist/{chunk-374zqx5h.js → chunk-d93kvxvc.js} +18 -2
- package/dist/chunk-ggb1taqy.js +16 -0
- package/dist/chunk-jn7y815x.js +79 -0
- package/dist/{chunk-b3f0ced1.js → chunk-k2s1hfq5.js} +39 -16
- package/dist/chunk-kzbzacz9.js +12 -0
- package/dist/{chunk-myjp910s.js → chunk-n4tjhcb7.js} +25 -21
- package/dist/{chunk-cjfmfyjb.js → chunk-ngf1zatm.js} +7 -6
- package/dist/{chunk-vw5yb5pd.js → chunk-wqvp1fwh.js} +304 -38
- package/dist/{chunk-5785kwej.js → chunk-ywmm63ks.js} +9 -7
- package/dist/chunk-znhn8af7.js +33 -0
- package/dist/pinomad.js +40 -16
- package/package.json +1 -1
- package/web/apple-touch-icon.png +0 -0
- package/web/assets/index-C_aU_ZiV.js +383 -0
- package/web/assets/index-DaPcXZPM.css +1 -0
- package/web/favicon.svg +1 -0
- package/web/index.html +4 -2
- package/dist/chunk-17p6bfvy.js +0 -63
- package/dist/chunk-302y6by9.js +0 -50
- package/dist/chunk-knaw1a9g.js +0 -182
- package/dist/chunk-prhvksh3.js +0 -14
- package/dist/chunk-xrvmnymc.js +0 -33
- package/web/assets/index-8mNNPXPT.js +0 -383
- package/web/assets/index-BFJJgGGo.css +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# PiNomad
|
|
2
2
|
|
|
3
|
-
English · [中文](README.zh-CN.md)
|
|
3
|
+
English · [中文](README.zh-CN.md) · [pinomad.si](https://pinomad.si)
|
|
4
4
|
|
|
5
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.
|
|
6
6
|
|
|
@@ -87,8 +87,10 @@ These docs are written in Chinese.
|
|
|
87
87
|
| [Usage](docs/usage.md) | Projects, worktrees, tools, MCP configuration |
|
|
88
88
|
| [Remote access](docs/remote-access.md) | Pairing, tunnels, self-hosted relay (VPS or container platforms) |
|
|
89
89
|
| [Running as a service](docs/service.md) | `service` and `upgrade` commands, platform notes |
|
|
90
|
+
| [Website](docs/website.md) | The pinomad.si landing page and the hosted app.pinomad.si instance |
|
|
91
|
+
| [Architecture overview](docs/architecture.md) | How the parts and languages fit together, with send/receive diagrams |
|
|
90
92
|
| [Architecture decisions](docs/adr/README.md) | Why things are the way they are |
|
|
91
|
-
| [Roadmap](docs/roadmap.md) |
|
|
93
|
+
| [Roadmap](docs/roadmap.md) | Direction and order; items live in GitHub Issues |
|
|
92
94
|
| [Glossary](GLOSSARY.md) | Domain terms |
|
|
93
95
|
|
|
94
96
|
## Contributing
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
generateKeyPair,
|
|
4
4
|
keyPairFromPrivate,
|
|
5
5
|
toBase64Url
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-7qb9tq54.js";
|
|
7
7
|
|
|
8
8
|
// apps/host/src/devices.ts
|
|
9
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
9
|
+
import { link, readFile, unlink, writeFile } from "node:fs/promises";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
12
12
|
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
@@ -62,14 +62,8 @@ async function loadHostKey(dataDir) {
|
|
|
62
62
|
if (error.code !== "ENOENT")
|
|
63
63
|
throw error;
|
|
64
64
|
const pair = generateKeyPair();
|
|
65
|
-
|
|
66
|
-
await writeFile(path, `${toBase64Url(pair.privateKey)}
|
|
67
|
-
`, { flag: "wx", mode: 384 });
|
|
68
|
-
} catch (race) {
|
|
69
|
-
if (race.code !== "EEXIST")
|
|
70
|
-
throw race;
|
|
65
|
+
if (!await createKeyFile(path, toBase64Url(pair.privateKey)))
|
|
71
66
|
return loadHostKey(dataDir);
|
|
72
|
-
}
|
|
73
67
|
return pair;
|
|
74
68
|
}
|
|
75
69
|
}
|
|
@@ -84,17 +78,26 @@ async function loadRelayKey(dataDir) {
|
|
|
84
78
|
if (error.code !== "ENOENT")
|
|
85
79
|
throw error;
|
|
86
80
|
const { secretKey, publicKey } = ed25519.keygen();
|
|
87
|
-
|
|
88
|
-
await writeFile(path, `${toBase64Url(secretKey)}
|
|
89
|
-
`, { flag: "wx", mode: 384 });
|
|
90
|
-
} catch (race) {
|
|
91
|
-
if (race.code !== "EEXIST")
|
|
92
|
-
throw race;
|
|
81
|
+
if (!await createKeyFile(path, toBase64Url(secretKey)))
|
|
93
82
|
return loadRelayKey(dataDir);
|
|
94
|
-
}
|
|
95
83
|
return { secretKey, publicKey };
|
|
96
84
|
}
|
|
97
85
|
}
|
|
86
|
+
async function createKeyFile(path, key) {
|
|
87
|
+
const tmp = `${path}.${randomBytes(8).toString("hex")}.tmp`;
|
|
88
|
+
try {
|
|
89
|
+
await writeFile(tmp, `${key}
|
|
90
|
+
`, { flag: "wx", mode: 384 });
|
|
91
|
+
await link(tmp, path);
|
|
92
|
+
return true;
|
|
93
|
+
} catch (race) {
|
|
94
|
+
if (race.code !== "EEXIST")
|
|
95
|
+
throw race;
|
|
96
|
+
return false;
|
|
97
|
+
} finally {
|
|
98
|
+
await unlink(tmp).catch(() => {});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
98
101
|
function pairingOffers(ttlMs = 5 * 60 * 1000) {
|
|
99
102
|
const live = new Map;
|
|
100
103
|
const prune = () => {
|
|
@@ -135,4 +138,53 @@ function pairingOffers(ttlMs = 5 * 60 * 1000) {
|
|
|
135
138
|
};
|
|
136
139
|
}
|
|
137
140
|
|
|
138
|
-
|
|
141
|
+
// apps/host/src/server-config.ts
|
|
142
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
143
|
+
import { readFile as readFile2, rename, rm, writeFile as writeFile2 } from "node:fs/promises";
|
|
144
|
+
import { join as join2 } from "node:path";
|
|
145
|
+
var SERVER_CONFIG_FILE = "server.json";
|
|
146
|
+
var normalizeOrigin = (origin) => origin.replace(/\/+$/, "");
|
|
147
|
+
async function readServerConfig(dataDir) {
|
|
148
|
+
let text;
|
|
149
|
+
try {
|
|
150
|
+
text = await readFile2(join2(dataDir, SERVER_CONFIG_FILE), "utf8");
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (error.code === "ENOENT")
|
|
153
|
+
return;
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
let value;
|
|
157
|
+
try {
|
|
158
|
+
value = JSON.parse(text);
|
|
159
|
+
} catch {
|
|
160
|
+
throw new Error(`${join2(dataDir, SERVER_CONFIG_FILE)} is not valid JSON — delete it or run \`pinomad leave\``);
|
|
161
|
+
}
|
|
162
|
+
const origin = value?.origin;
|
|
163
|
+
if (typeof origin !== "string" || origin.trim() === "") {
|
|
164
|
+
throw new Error(`${join2(dataDir, SERVER_CONFIG_FILE)} is missing a string "origin"`);
|
|
165
|
+
}
|
|
166
|
+
const binding = value.binding;
|
|
167
|
+
return {
|
|
168
|
+
origin,
|
|
169
|
+
...typeof binding === "string" && binding !== "" ? { binding } : {}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
async function writeServerConfig(dataDir, config) {
|
|
173
|
+
const path = join2(dataDir, SERVER_CONFIG_FILE);
|
|
174
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
175
|
+
await writeFile2(tmp, `${JSON.stringify({ origin: normalizeOrigin(config.origin), binding: randomBytes2(16).toString("base64url") })}
|
|
176
|
+
`, { mode: 384 });
|
|
177
|
+
await rename(tmp, path);
|
|
178
|
+
}
|
|
179
|
+
var removeServerConfig = (dataDir) => rm(join2(dataDir, SERVER_CONFIG_FILE), { force: true });
|
|
180
|
+
async function readServerBinding(dataDir) {
|
|
181
|
+
return (await readServerConfig(dataDir))?.binding;
|
|
182
|
+
}
|
|
183
|
+
function resolveRelayOrigin(flag, config) {
|
|
184
|
+
if (flag !== undefined && config !== undefined && normalizeOrigin(flag) !== normalizeOrigin(config.origin)) {
|
|
185
|
+
throw new Error(`--relay ${flag} disagrees with ${SERVER_CONFIG_FILE} (${config.origin}) — remove one`);
|
|
186
|
+
}
|
|
187
|
+
return flag ?? config?.origin;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export { DevicesDoc, ensureDevices, RevocationsDoc, admitDevice, loadHostKey, loadRelayKey, pairingOffers, normalizeOrigin, readServerConfig, writeServerConfig, removeServerConfig, readServerBinding, resolveRelayOrigin };
|
|
@@ -3,31 +3,30 @@ import {
|
|
|
3
3
|
assertSecureServerOrigin,
|
|
4
4
|
probeServerAccounts,
|
|
5
5
|
runServerOperation
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-ywmm63ks.js";
|
|
7
7
|
import {
|
|
8
|
+
loadHostKey,
|
|
9
|
+
loadRelayKey,
|
|
8
10
|
readServerConfig,
|
|
9
11
|
removeServerConfig
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import
|
|
12
|
+
} from "./chunk-0xh6cy9v.js";
|
|
13
|
+
import {
|
|
14
|
+
relayHostId
|
|
15
|
+
} from "./chunk-7qb9tq54.js";
|
|
12
16
|
import {
|
|
13
17
|
argValue,
|
|
14
18
|
restartService,
|
|
15
19
|
serviceManager,
|
|
16
20
|
withoutRelayArgs
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import"./chunk-
|
|
19
|
-
import"./chunk-
|
|
20
|
-
import"./chunk-
|
|
21
|
-
import
|
|
22
|
-
|
|
23
|
-
loadRelayKey
|
|
24
|
-
} from "./chunk-837gh8nr.js";
|
|
25
|
-
import {
|
|
26
|
-
relayHostId
|
|
27
|
-
} from "./chunk-7gmkgyaz.js";
|
|
21
|
+
} from "./chunk-d5ypmmr7.js";
|
|
22
|
+
import"./chunk-kzbzacz9.js";
|
|
23
|
+
import"./chunk-c8nxym2z.js";
|
|
24
|
+
import"./chunk-d93kvxvc.js";
|
|
25
|
+
import"./chunk-n4tjhcb7.js";
|
|
26
|
+
import"./chunk-znhn8af7.js";
|
|
28
27
|
import {
|
|
29
28
|
DEFAULT_DATA_DIR
|
|
30
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-ngf1zatm.js";
|
|
31
30
|
import"./chunk-df32avw6.js";
|
|
32
31
|
|
|
33
32
|
// apps/host/src/cli/leave.ts
|