pinomad 0.12.0 → 0.13.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.
Files changed (38) hide show
  1. package/dist/{chunk-np4jd070.js → chunk-4fferbxw.js} +97 -29
  2. package/dist/{chunk-ksfxqd8s.js → chunk-53xv3fny.js} +241 -48
  3. package/dist/{chunk-nppgxh6c.js → chunk-563fnad9.js} +1 -1
  4. package/dist/{chunk-41q0saz0.js → chunk-6th9xv39.js} +4 -4
  5. package/dist/{chunk-aqj9wfmb.js → chunk-90g1cg9n.js} +1 -1
  6. package/dist/{chunk-mnmqyq7d.js → chunk-aapsc9g0.js} +42 -48
  7. package/dist/{chunk-5qf1grkk.js → chunk-d4tq62yr.js} +46 -11
  8. package/dist/{chunk-gg2dcqbg.js → chunk-d9mq5d46.js} +22 -7
  9. package/dist/{chunk-47ff1d1t.js → chunk-kmzzjv53.js} +51 -4
  10. package/dist/{chunk-agq4tn9r.js → chunk-m51mq8vh.js} +4 -4
  11. package/dist/{chunk-vqkb70d7.js → chunk-maqv34cs.js} +2 -2
  12. package/dist/{chunk-vx7dy07y.js → chunk-ra86wjp5.js} +4 -4
  13. package/dist/{chunk-7wj0jh3t.js → chunk-s0h7c7da.js} +1 -1
  14. package/dist/{chunk-42nqtx5r.js → chunk-sd640dt7.js} +6 -6
  15. package/dist/{chunk-jeqsdcj1.js → chunk-t6fw0wa9.js} +4 -4
  16. package/dist/{chunk-t5abqsva.js → chunk-wd7aracm.js} +17 -5
  17. package/dist/pinomad.js +12 -12
  18. package/package.json +12 -11
  19. package/web/assets/{core-DnpzcQzs.js → core-BHm2gdHL.js} +1 -1
  20. package/web/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  21. package/web/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  22. package/web/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  23. package/web/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  24. package/web/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  25. package/web/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  26. package/web/assets/{highlighted-diff-Dn5ifsvK.js → highlighted-diff-DrHvSLK9.js} +2 -2
  27. package/web/assets/{highlights-DnHv6NBM.js → highlights-1aLwssz-.js} +1 -1
  28. package/web/assets/{highlights-themes-CUdrlZk3.js → highlights-themes-C1p4pFYS.js} +1 -1
  29. package/web/assets/index-A_AUJoO6.css +1 -0
  30. package/web/assets/index-BafYMKiN.js +396 -0
  31. package/web/assets/{shiki-BuPP5HCg.js → shiki-BwyCm3kZ.js} +2 -2
  32. package/web/assets/{shiki-langs-FJk0v0Ga.js → shiki-langs-CMlALLSg.js} +1 -1
  33. package/web/assets/{stream-tokenizer-Bl6bisIa.js → stream-tokenizer-BlgIhw0y.js} +1 -1
  34. package/web/assets/{theming-themes-BL_CC-Mm.js → theming-themes-DDvoqEqT.js} +1 -1
  35. package/web/assets/{xterm-view-ZsuzJb2C.js → xterm-view-BnKOnF3C.js} +1 -1
  36. package/web/index.html +2 -2
  37. package/web/assets/index-BV0ecolo.css +0 -1
  38. package/web/assets/index-DNsTTgPE.js +0 -396
@@ -16,7 +16,7 @@ import {
16
16
  import {
17
17
  commandHint,
18
18
  webRoot
19
- } from "./chunk-7wj0jh3t.js";
19
+ } from "./chunk-s0h7c7da.js";
20
20
  import"./chunk-df32avw6.js";
21
21
 
22
22
  // apps/server/src/main.ts
@@ -1609,6 +1609,15 @@ async function readJson(request, optional = false) {
1609
1609
  return { ok: false, tooLarge: false };
1610
1610
  }
1611
1611
  }
1612
+ async function authedJson(accounts, request, token, optional = false) {
1613
+ const body = await readJson(request, optional);
1614
+ if (!body.ok)
1615
+ return { ok: false, status: body.tooLarge ? 413 : 400, error: body.tooLarge ? "payload_too_large" : "invalid_json" };
1616
+ const session = accounts.authenticate(token);
1617
+ if (session === undefined)
1618
+ return { ok: false, status: 401, error: "unauthenticated" };
1619
+ return { ok: true, value: body.value, session };
1620
+ }
1612
1621
  function sessionToken(request) {
1613
1622
  const authorization = request.headers.authorization;
1614
1623
  if (authorization !== undefined && authorization.startsWith("Bearer "))
@@ -1829,9 +1838,9 @@ async function handleApiRequest(request, response, ctx) {
1829
1838
  deny(401, "unauthenticated");
1830
1839
  return;
1831
1840
  }
1832
- const body = await readJson(request);
1841
+ const body = await authedJson(accounts, request, token);
1833
1842
  if (!body.ok) {
1834
- deny(body.tooLarge ? 413 : 400, body.tooLarge ? "payload_too_large" : "invalid_json");
1843
+ deny(body.status, body.error);
1835
1844
  return;
1836
1845
  }
1837
1846
  const named = body.value;
@@ -1840,12 +1849,7 @@ async function handleApiRequest(request, response, ctx) {
1840
1849
  deny(400, "invalid_request");
1841
1850
  return;
1842
1851
  }
1843
- const session = accounts.authenticate(token);
1844
- if (session === undefined) {
1845
- deny(401, "unauthenticated");
1846
- return;
1847
- }
1848
- const args = { userId: session.userId, ip: clientIp(request, ctx.trustProxy), ...ref };
1852
+ const args = { userId: body.session.userId, ip: clientIp(request, ctx.trustProxy), ...ref };
1849
1853
  const result = pathname === "/api/host-link/lookup" ? accounts.lookupHostLink(args) : pathname === "/api/host-link/confirm" ? accounts.confirmHostLink(args) : accounts.denyHostLink(args);
1850
1854
  if (result.status === "limited") {
1851
1855
  deny(429, "rate_limited");
@@ -1886,9 +1890,13 @@ async function handleApiRequest(request, response, ctx) {
1886
1890
  send(200, accounts.createInvite(session.userId));
1887
1891
  return;
1888
1892
  }
1889
- const body = await readJson(request);
1893
+ const body = await authedJson(accounts, request, token);
1890
1894
  if (!body.ok) {
1891
- deny(body.tooLarge ? 413 : 400, body.tooLarge ? "payload_too_large" : "invalid_json");
1895
+ deny(body.status, body.error);
1896
+ return;
1897
+ }
1898
+ if (!body.session.admin) {
1899
+ deny(401, "unauthenticated");
1892
1900
  return;
1893
1901
  }
1894
1902
  const id = body.value?.id;
@@ -1896,10 +1904,6 @@ async function handleApiRequest(request, response, ctx) {
1896
1904
  deny(400, "invalid_request");
1897
1905
  return;
1898
1906
  }
1899
- if (accounts.authenticate(token)?.admin !== true) {
1900
- deny(401, "unauthenticated");
1901
- return;
1902
- }
1903
1907
  if (accounts.revokeInvite(id))
1904
1908
  send(204);
1905
1909
  else
@@ -1917,9 +1921,9 @@ async function handleApiRequest(request, response, ctx) {
1917
1921
  deny(401, "unauthenticated");
1918
1922
  return;
1919
1923
  }
1920
- const body = await readJson(request);
1924
+ const body = await authedJson(accounts, request, token);
1921
1925
  if (!body.ok) {
1922
- deny(body.tooLarge ? 413 : 400, body.tooLarge ? "payload_too_large" : "invalid_json");
1926
+ deny(body.status, body.error);
1923
1927
  return;
1924
1928
  }
1925
1929
  const value = body.value;
@@ -1927,11 +1931,7 @@ async function handleApiRequest(request, response, ctx) {
1927
1931
  deny(400, "invalid_request");
1928
1932
  return;
1929
1933
  }
1930
- const current = accounts.authenticate(token);
1931
- if (current === undefined) {
1932
- deny(401, "unauthenticated");
1933
- return;
1934
- }
1934
+ const current = body.session;
1935
1935
  const result = await accounts.removeHost({
1936
1936
  userId: current.userId,
1937
1937
  username: current.username,
@@ -2044,9 +2044,9 @@ async function handleApiRequest(request, response, ctx) {
2044
2044
  send(200, accounts.listDevices(session.userId).map((device) => ({ ...device, current: device.id === session.deviceId })));
2045
2045
  return;
2046
2046
  }
2047
- const body = await readJson(request);
2047
+ const body = await authedJson(accounts, request, token);
2048
2048
  if (!body.ok) {
2049
- deny(body.tooLarge ? 413 : 400, body.tooLarge ? "payload_too_large" : "invalid_json");
2049
+ deny(body.status, body.error);
2050
2050
  return;
2051
2051
  }
2052
2052
  const value = body.value;
@@ -2054,11 +2054,7 @@ async function handleApiRequest(request, response, ctx) {
2054
2054
  deny(400, "invalid_request");
2055
2055
  return;
2056
2056
  }
2057
- const current = accounts.authenticate(token);
2058
- if (current === undefined) {
2059
- deny(401, "unauthenticated");
2060
- return;
2061
- }
2057
+ const current = body.session;
2062
2058
  const result = await accounts.kickDevice({
2063
2059
  userId: current.userId,
2064
2060
  username: current.username,
@@ -2096,9 +2092,9 @@ async function handleApiRequest(request, response, ctx) {
2096
2092
  return;
2097
2093
  }
2098
2094
  if (pathname === "/api/push-tokens" || pathname === "/api/push-tokens/remove") {
2099
- const body = await readJson(request);
2095
+ const body = await authedJson(accounts, request, token);
2100
2096
  if (!body.ok) {
2101
- deny(body.tooLarge ? 413 : 400, body.tooLarge ? "payload_too_large" : "invalid_json");
2097
+ deny(body.status, body.error);
2102
2098
  return;
2103
2099
  }
2104
2100
  const value = body.value;
@@ -2108,30 +2104,27 @@ async function handleApiRequest(request, response, ctx) {
2108
2104
  deny(400, "invalid_request");
2109
2105
  return;
2110
2106
  }
2111
- const current = accounts.authenticate(token);
2112
- if (current === undefined) {
2113
- deny(401, "unauthenticated");
2114
- return;
2115
- }
2107
+ const current = body.session;
2116
2108
  if (pathname === "/api/push-tokens")
2117
- accounts.registerPushToken(session.userId, apns, environment, current.deviceId);
2109
+ accounts.registerPushToken(current.userId, apns, environment, current.deviceId);
2118
2110
  else
2119
- accounts.removePushToken(session.userId, apns, environment);
2111
+ accounts.removePushToken(current.userId, apns, environment);
2120
2112
  send(204);
2121
2113
  return;
2122
2114
  }
2123
2115
  if (pathname === "/api/me/delete") {
2124
- const body = await readJson(request);
2116
+ const body = await authedJson(accounts, request, token);
2125
2117
  if (!body.ok) {
2126
- deny(body.tooLarge ? 413 : 400, body.tooLarge ? "payload_too_large" : "invalid_json");
2118
+ deny(body.status, body.error);
2127
2119
  return;
2128
2120
  }
2121
+ const current = body.session;
2129
2122
  const value = body.value;
2130
2123
  if (typeof value?.password !== "string" || [...value.password].length > MAX_PASSWORD_CODEPOINTS) {
2131
2124
  deny(400, "invalid_request");
2132
2125
  return;
2133
2126
  }
2134
- const result = await accounts.deleteAccount({ userId: session.userId, username: session.username, password: value.password });
2127
+ const result = await accounts.deleteAccount({ userId: current.userId, username: current.username, password: value.password });
2135
2128
  if (result.status === "limited") {
2136
2129
  deny(429, "rate_limited");
2137
2130
  return;
@@ -2142,24 +2135,25 @@ async function handleApiRequest(request, response, ctx) {
2142
2135
  }
2143
2136
  for (const hostId of result.hostIds)
2144
2137
  ctx.disconnectHost(hostId);
2145
- ctx.forgetAccount(session.userId);
2138
+ ctx.forgetAccount(current.userId);
2146
2139
  send(204, undefined, { "set-cookie": clearSessionCookie });
2147
2140
  return;
2148
2141
  }
2149
2142
  if (pathname === "/api/password") {
2150
- const body = await readJson(request);
2143
+ const body = await authedJson(accounts, request, token);
2151
2144
  if (!body.ok) {
2152
- deny(body.tooLarge ? 413 : 400, body.tooLarge ? "payload_too_large" : "invalid_json");
2145
+ deny(body.status, body.error);
2153
2146
  return;
2154
2147
  }
2148
+ const current = body.session;
2155
2149
  const value = body.value;
2156
2150
  if (typeof value?.currentPassword !== "string" || typeof value.newPassword !== "string" || [...value.currentPassword].length > MAX_PASSWORD_CODEPOINTS) {
2157
2151
  deny(400, "invalid_request");
2158
2152
  return;
2159
2153
  }
2160
2154
  const result = await accounts.changePassword({
2161
- userId: session.userId,
2162
- username: session.username,
2155
+ userId: current.userId,
2156
+ username: current.username,
2163
2157
  keepToken: token,
2164
2158
  currentPassword: value.currentPassword,
2165
2159
  newPassword: value.newPassword
@@ -2185,9 +2179,9 @@ async function handleApiRequest(request, response, ctx) {
2185
2179
  return;
2186
2180
  }
2187
2181
  if (pathname === "/api/logout") {
2188
- const body = await readJson(request, true);
2182
+ const body = await authedJson(accounts, request, token, true);
2189
2183
  if (!body.ok) {
2190
- deny(body.tooLarge ? 413 : 400, body.tooLarge ? "payload_too_large" : "invalid_json");
2184
+ deny(body.status, body.error);
2191
2185
  return;
2192
2186
  }
2193
2187
  const value = body.value;
@@ -3,9 +3,9 @@ import {
3
3
  joinServer,
4
4
  openServiceSlot,
5
5
  writeService
6
- } from "./chunk-gg2dcqbg.js";
6
+ } from "./chunk-d9mq5d46.js";
7
7
  import"./chunk-7mn38n4f.js";
8
- import"./chunk-nppgxh6c.js";
8
+ import"./chunk-563fnad9.js";
9
9
  import"./chunk-t91zt5f0.js";
10
10
  import {
11
11
  normalizeOrigin,
@@ -14,25 +14,28 @@ import {
14
14
  } from "./chunk-nq57fdnq.js";
15
15
  import"./chunk-g3ahp7b9.js";
16
16
  import {
17
+ LAUNCHER_EXECUTABLE,
17
18
  argValue,
19
+ isLauncher,
18
20
  serviceManager,
19
21
  withoutArg,
20
22
  withoutRelayArgs
21
- } from "./chunk-t5abqsva.js";
22
- import"./chunk-np4jd070.js";
23
+ } from "./chunk-wd7aracm.js";
24
+ import"./chunk-4fferbxw.js";
23
25
  import"./chunk-kzbzacz9.js";
24
- import"./chunk-47ff1d1t.js";
26
+ import"./chunk-kmzzjv53.js";
25
27
  import"./chunk-pqe7q1s7.js";
26
28
  import {
27
29
  DEFAULT_DATA_DIR
28
30
  } from "./chunk-ngf1zatm.js";
29
31
  import {
30
32
  commandHint
31
- } from "./chunk-7wj0jh3t.js";
33
+ } from "./chunk-s0h7c7da.js";
32
34
  import"./chunk-df32avw6.js";
33
35
 
34
36
  // apps/host/src/cli/up.ts
35
- import { resolve } from "node:path";
37
+ import { existsSync } from "node:fs";
38
+ import { isAbsolute, resolve } from "node:path";
36
39
  import { createInterface } from "node:readline/promises";
37
40
  import { parseArgs } from "node:util";
38
41
  var OFFICIAL_SERVER_ORIGIN = "https://app.pinomad.si";
@@ -85,7 +88,14 @@ async function upHost(args, deps) {
85
88
  const lan = (hostArgs) => withLan(hostArgs, lanPort);
86
89
  const notes = [];
87
90
  if (reach.server && config === undefined) {
88
- const joined = await joinServer({ origin, dataDir, code: args.code, editHostArgs: lan }, { ...deps, manager });
91
+ const joined = await joinServer({
92
+ origin,
93
+ dataDir,
94
+ code: args.code,
95
+ ...args.token === undefined ? {} : { token: args.token },
96
+ ...args.launcher === undefined ? {} : { launcher: args.launcher },
97
+ editHostArgs: lan
98
+ }, { ...deps, manager });
89
99
  return {
90
100
  joined: { origin: joined.origin, username: joined.username, fresh: true },
91
101
  ...lanPort === undefined ? {} : { lanPort },
@@ -94,7 +104,7 @@ async function upHost(args, deps) {
94
104
  };
95
105
  }
96
106
  const edit = reach.server ? (hostArgs) => lan(withoutRelayArgs(hostArgs)) : lan;
97
- const service = await writeService(slot, edit, { changed: reach.server });
107
+ const service = await writeService(slot, edit, { changed: reach.server, launcher: args.launcher });
98
108
  if (!reach.server && config !== undefined) {
99
109
  notes.push(`still joined to ${config.origin} — run \`${commandHint("leave")}\` to leave it`);
100
110
  }
@@ -105,6 +115,12 @@ async function upHost(args, deps) {
105
115
  notes
106
116
  };
107
117
  }
118
+ async function readAllStdin() {
119
+ let text = "";
120
+ for await (const chunk of process.stdin)
121
+ text += String(chunk);
122
+ return text;
123
+ }
108
124
  async function askOnTerminal(question) {
109
125
  const terminal = createInterface({ input: process.stdin, output: process.stdout });
110
126
  try {
@@ -139,7 +155,8 @@ var FLAGS = `choose with flags:
139
155
  --server [--origin URL] through a PiNomad server (default ${OFFICIAL_SERVER_ORIGIN})
140
156
  --lan [--remote-port N] only on the local network and tailnet (default port ${DEFAULT_REMOTE_PORT})
141
157
  --server --lan both
142
- --yes keep what this host has, or the server on a new host`;
158
+ --yes keep what this host has, or the server on a new host
159
+ --token-stdin join with a one-time token read from stdin, without a code`;
143
160
  async function main(argv, deps) {
144
161
  const { values } = parseArgs({
145
162
  args: [...argv],
@@ -150,6 +167,8 @@ async function main(argv, deps) {
150
167
  yes: { type: "boolean", short: "y" },
151
168
  origin: { type: "string" },
152
169
  code: { type: "boolean" },
170
+ "token-stdin": { type: "boolean" },
171
+ launcher: { type: "string" },
153
172
  "remote-port": { type: "string" },
154
173
  "data-dir": { type: "string" }
155
174
  }
@@ -157,6 +176,20 @@ async function main(argv, deps) {
157
176
  const log = deps?.log ?? ((line) => console.log(line));
158
177
  try {
159
178
  const dataDir = resolve(values["data-dir"] ?? DEFAULT_DATA_DIR);
179
+ const launcher = values.launcher;
180
+ if (launcher !== undefined && process.platform !== "darwin")
181
+ throw new Error("--launcher is for the Mac App's service only");
182
+ if (launcher !== undefined && !(isAbsolute(launcher) && isLauncher(launcher))) {
183
+ throw new Error(`--launcher ${launcher}: not a path to the launcher inside an app (….app/Contents/MacOS/${LAUNCHER_EXECUTABLE})`);
184
+ }
185
+ if (launcher !== undefined && !existsSync(launcher))
186
+ throw new Error(`--launcher ${launcher}: no such file`);
187
+ let token;
188
+ if (values["token-stdin"] === true) {
189
+ token = (await (deps?.readStdin ?? readAllStdin)()).trim();
190
+ if (token === "")
191
+ throw new Error("--token-stdin: no join token on stdin");
192
+ }
160
193
  const origin = normalizeOrigin(values.origin ?? (await readServerConfig(dataDir))?.origin ?? await lastRemovedOrigin(dataDir) ?? OFFICIAL_SERVER_ORIGIN);
161
194
  const current = await currentReach(dataDir, deps);
162
195
  let reach = resolveReach({ server: values.server, lan: values.lan }, current);
@@ -182,7 +215,9 @@ async function main(argv, deps) {
182
215
  origin,
183
216
  reach,
184
217
  ...values["remote-port"] === undefined ? {} : { remotePort: values["remote-port"] },
185
- ...values.code === true ? { code: true } : {}
218
+ ...values.code === true ? { code: true } : {},
219
+ ...token === undefined ? {} : { token },
220
+ ...launcher === undefined ? {} : { launcher }
186
221
  }, deps);
187
222
  if (outcome.joined !== undefined) {
188
223
  const who = outcome.joined.username ?? "an account";
@@ -7,7 +7,7 @@ import {
7
7
  import {
8
8
  constantValue,
9
9
  openHostSettings
10
- } from "./chunk-nppgxh6c.js";
10
+ } from "./chunk-563fnad9.js";
11
11
  import {
12
12
  loadHostKey,
13
13
  loadRelayKey
@@ -24,7 +24,7 @@ import {
24
24
  restartService,
25
25
  serviceManager,
26
26
  withoutRelayArgs
27
- } from "./chunk-t5abqsva.js";
27
+ } from "./chunk-wd7aracm.js";
28
28
  import {
29
29
  cleanHostName,
30
30
  normalizeHostName
@@ -35,7 +35,7 @@ import {
35
35
  import {
36
36
  commandHint,
37
37
  hostEntry
38
- } from "./chunk-7wj0jh3t.js";
38
+ } from "./chunk-s0h7c7da.js";
39
39
 
40
40
  // apps/host/src/cli/join.ts
41
41
  import { mkdir } from "node:fs/promises";
@@ -315,6 +315,7 @@ async function requestJoinToken(args, deps) {
315
315
  }
316
316
 
317
317
  // apps/host/src/cli/service-slot.ts
318
+ import { existsSync } from "node:fs";
318
319
  import { homedir } from "node:os";
319
320
  import { resolve } from "node:path";
320
321
  async function openServiceSlot(dataDir, manager) {
@@ -325,8 +326,19 @@ async function openServiceSlot(dataDir, manager) {
325
326
  }
326
327
  return { dataDir, manager, state, previous };
327
328
  }
329
+ function launcherFor(given, previous) {
330
+ if (given !== undefined)
331
+ return given;
332
+ const installed = previous?.launcher;
333
+ return installed !== undefined && existsSync(installed) ? installed : undefined;
334
+ }
328
335
  async function writeService(slot, edit, options) {
329
336
  const { dataDir, manager, previous } = slot;
337
+ const launcher = launcherFor(options.launcher, previous);
338
+ const withLauncher = (spec2) => {
339
+ const { launcher: _, ...rest } = spec2;
340
+ return launcher === undefined ? rest : { ...rest, launcher };
341
+ };
330
342
  const fresh = installSpec(["--data-dir", dataDir, ...edit([])], {
331
343
  execPath: process.execPath,
332
344
  env: process.env,
@@ -336,11 +348,11 @@ async function writeService(slot, edit, options) {
336
348
  entryArgs: hostEntry.args
337
349
  });
338
350
  if (!slot.state.installed) {
339
- await manager.install(fresh);
351
+ await manager.install(withLauncher(fresh));
340
352
  return "installed";
341
353
  }
342
- const spec = previous === undefined ? fresh : { ...previous, hostArgs: edit(previous.hostArgs) };
343
- const same = previous !== undefined && spec.hostArgs.join("\x00") === previous.hostArgs.join("\x00");
354
+ const spec = withLauncher(previous === undefined ? fresh : { ...previous, hostArgs: edit(previous.hostArgs) });
355
+ const same = previous !== undefined && spec.hostArgs.join("\x00") === previous.hostArgs.join("\x00") && spec.launcher === previous.launcher;
344
356
  if (same && !options.changed && slot.state.running)
345
357
  return "unchanged";
346
358
  const port = argValue(spec.hostArgs, "--port");
@@ -409,7 +421,10 @@ async function joinServer(args, deps) {
409
421
  if (args.name !== undefined)
410
422
  await settings.update({ hostName: args.name });
411
423
  const edit = args.editHostArgs ?? ((hostArgs) => hostArgs);
412
- service = await writeService(slot, (hostArgs) => edit(withoutRelayArgs(hostArgs)), { changed: true });
424
+ service = await writeService(slot, (hostArgs) => edit(withoutRelayArgs(hostArgs)), {
425
+ changed: true,
426
+ launcher: args.launcher
427
+ });
413
428
  } catch (error) {
414
429
  throw new Error(`joined ${username} at ${origin}, but local setup failed: ${message(error)}
415
430
  ` + (args.token === undefined ? `run the command again and confirm it again (re-joining the same host is safe)` : `generate a fresh join token and run the command again (re-joining the same host is safe)`));
@@ -187,7 +187,7 @@ var conversationStream = (id) => `conversation:${id}`;
187
187
  var docStream = (kind, id) => `doc:${kind}:${id}`;
188
188
  var UNAUTHORIZED_CLOSE_CODE = 4401;
189
189
  var INVALID_FRAME_CLOSE_CODE = 4400;
190
- var PROTOCOL = { major: 5, minor: 27 };
190
+ var PROTOCOL = { major: 5, minor: 29 };
191
191
  var CALL_SINCE = {
192
192
  addProject: 0,
193
193
  removeProject: 0,
@@ -202,6 +202,7 @@ var CALL_SINCE = {
202
202
  submission: 25,
203
203
  withdrawQueued: 14,
204
204
  steerQueued: 14,
205
+ reorderQueued: 29,
205
206
  abort: 0,
206
207
  compact: 0,
207
208
  runCommand: 17,
@@ -226,6 +227,7 @@ var CALL_SINCE = {
226
227
  deleteAgent: 6,
227
228
  setPushToken: 7,
228
229
  image: 15,
230
+ earlierEntries: 28,
229
231
  beginAttachment: 19,
230
232
  appendAttachment: 19,
231
233
  rtcOffer: 23,
@@ -253,7 +255,8 @@ var FEATURE_SINCE = {
253
255
  imageVariants: 20,
254
256
  rtc: 23,
255
257
  submitEntry: 25,
256
- terminals: 27
258
+ terminals: 27,
259
+ transcriptPaging: 28
257
260
  };
258
261
  function hostSupportsFeature(hostMinor, feature) {
259
262
  return hostMinor >= FEATURE_SINCE[feature];
@@ -275,7 +278,18 @@ function parseClientFrame(value) {
275
278
  return { kind: "frame", frame: { type: "subscribe", stream, since } };
276
279
  }
277
280
  const refs = value.images === "ref";
278
- return { kind: "frame", frame: refs ? { type: "subscribe", stream, images: "ref" } : { type: "subscribe", stream } };
281
+ let window;
282
+ if (stream.startsWith("conversation:") && value.window !== undefined) {
283
+ if (!record(value.window) || !positive(value.window.limit) || !(value.window.from === undefined || positive(value.window.from))) {
284
+ return { kind: "malformed" };
285
+ }
286
+ const limit = value.window.limit;
287
+ window = value.window.from === undefined ? { limit } : { limit, from: value.window.from };
288
+ }
289
+ return {
290
+ kind: "frame",
291
+ frame: { type: "subscribe", stream, ...refs ? { images: "ref" } : {}, ...window === undefined ? {} : { window } }
292
+ };
279
293
  }
280
294
  const prefix = stream.slice(0, stream.indexOf(":") === -1 ? undefined : stream.indexOf(":"));
281
295
  if (STREAM_KINDS.has(prefix))
@@ -335,6 +349,8 @@ function validCallArgs(method, args) {
335
349
  case "withdrawQueued":
336
350
  case "steerQueued":
337
351
  return conversationId() && submissionId();
352
+ case "reorderQueued":
353
+ return conversationId() && submissionId() && (args.before === undefined || typeof args.before === "number" && Number.isSafeInteger(args.before) && args.before >= 1);
338
354
  case "abort":
339
355
  return conversationId();
340
356
  case "setThinkingLevel":
@@ -355,6 +371,8 @@ function validCallArgs(method, args) {
355
371
  return conversationId() && nonempty(args.kind) && nonempty(args.requestId) && Array.isArray(args.answers) && args.answers.every((answer) => record(answer) && Array.isArray(answer.selected) && answer.selected.every((l) => typeof l === "string") && (answer.other === undefined || typeof answer.other === "string"));
356
372
  case "changes":
357
373
  return conversationId();
374
+ case "earlierEntries":
375
+ return conversationId() && positive(args.before) && positive(args.limit);
358
376
  case "image":
359
377
  return nonempty(args.ref) && (args.variant === undefined || args.variant === "thumbnail" || args.variant === "preview");
360
378
  case "beginAttachment":
@@ -411,6 +429,9 @@ function validCallArgs(method, args) {
411
429
  function record(value) {
412
430
  return value !== null && typeof value === "object" && !Array.isArray(value);
413
431
  }
432
+ function positive(value) {
433
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
434
+ }
414
435
  function nonempty(value) {
415
436
  return typeof value === "string" && value.length > 0;
416
437
  }
@@ -647,4 +668,30 @@ function homeOf(index, summaries, id) {
647
668
  return;
648
669
  }
649
670
 
650
- export { rtcSelectedPair, RTC_DEFAULT_STUN, RTC_DATA_CHANNEL_LABEL, RTC_MAX_RECORD_BYTES, isRtcHello, rtcCandidateType, sdpCandidates, withRtcCandidates, parseRtcMaxMessageSize, isCustomProviderApi, HOST_STREAM_NAMES, hostStreams, conversationStream, docStream, UNAUTHORIZED_CLOSE_CODE, INVALID_FRAME_CLOSE_CODE, PROTOCOL, hostSupportsCall, hostSupportsFeature, parseClientFrame, MAX_ATTACHMENTS_PER_MESSAGE, MAX_PENDING_ATTACHMENTS, MAX_PENDING_ATTACHMENT_BYTES, ATTACHMENT_TTL_MS, MAX_ATTACHMENT_NAME_LENGTH, isAttachmentId, base64DecodedLength, attachmentBeginProblem, attachmentChunkProblem, attachmentBlock, parseAttachmentBlock, organize, checkoutOf, homeOf };
671
+ // packages/protocol/src/transcript.ts
672
+ var TRANSCRIPT_PAGE = 50;
673
+ var MAX_TRANSCRIPT_PAGE = 500;
674
+ function withEarlier(view, earlier) {
675
+ if (earlier === undefined)
676
+ return view;
677
+ const { before: _window, ...rest } = view;
678
+ return {
679
+ ...rest,
680
+ entries: [...earlier.entries, ...view.entries],
681
+ ...earlier.before === undefined ? {} : { before: earlier.before }
682
+ };
683
+ }
684
+ function addEarlier(shown, earlier, requested, page) {
685
+ if (shown.before !== requested)
686
+ return earlier;
687
+ return {
688
+ entries: [...page.entries, ...earlier?.entries ?? []],
689
+ ...page.before === undefined ? {} : { before: page.before }
690
+ };
691
+ }
692
+ function isBusy(view) {
693
+ const live = view.docs?.["pi.live"];
694
+ return live?.run !== undefined;
695
+ }
696
+
697
+ export { rtcSelectedPair, RTC_DEFAULT_STUN, RTC_DATA_CHANNEL_LABEL, RTC_MAX_RECORD_BYTES, isRtcHello, rtcCandidateType, sdpCandidates, withRtcCandidates, parseRtcMaxMessageSize, isCustomProviderApi, HOST_STREAM_NAMES, hostStreams, conversationStream, docStream, UNAUTHORIZED_CLOSE_CODE, INVALID_FRAME_CLOSE_CODE, PROTOCOL, hostSupportsCall, hostSupportsFeature, parseClientFrame, MAX_ATTACHMENTS_PER_MESSAGE, MAX_PENDING_ATTACHMENTS, MAX_PENDING_ATTACHMENT_BYTES, ATTACHMENT_TTL_MS, MAX_ATTACHMENT_NAME_LENGTH, isAttachmentId, base64DecodedLength, attachmentBeginProblem, attachmentChunkProblem, attachmentBlock, parseAttachmentBlock, organize, checkoutOf, homeOf, TRANSCRIPT_PAGE, MAX_TRANSCRIPT_PAGE, withEarlier, addEarlier, isBusy };
@@ -21,17 +21,17 @@ import {
21
21
  restartService,
22
22
  serviceManager,
23
23
  withoutRelayArgs
24
- } from "./chunk-t5abqsva.js";
25
- import"./chunk-np4jd070.js";
24
+ } from "./chunk-wd7aracm.js";
25
+ import"./chunk-4fferbxw.js";
26
26
  import"./chunk-kzbzacz9.js";
27
- import"./chunk-47ff1d1t.js";
27
+ import"./chunk-kmzzjv53.js";
28
28
  import"./chunk-pqe7q1s7.js";
29
29
  import {
30
30
  DEFAULT_DATA_DIR
31
31
  } from "./chunk-ngf1zatm.js";
32
32
  import {
33
33
  commandHint
34
- } from "./chunk-7wj0jh3t.js";
34
+ } from "./chunk-s0h7c7da.js";
35
35
  import"./chunk-df32avw6.js";
36
36
 
37
37
  // apps/host/src/cli/leave.ts
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  connectRemoteDurable
3
- } from "./chunk-np4jd070.js";
4
- import"./chunk-47ff1d1t.js";
3
+ } from "./chunk-4fferbxw.js";
4
+ import"./chunk-kmzzjv53.js";
5
5
  import {
6
6
  isLoopbackUrl
7
7
  } from "./chunk-pqe7q1s7.js";
@@ -7,13 +7,13 @@ import {
7
7
  startService,
8
8
  stopService,
9
9
  versionLine
10
- } from "./chunk-t5abqsva.js";
11
- import"./chunk-np4jd070.js";
10
+ } from "./chunk-wd7aracm.js";
11
+ import"./chunk-4fferbxw.js";
12
12
  import"./chunk-kzbzacz9.js";
13
- import"./chunk-47ff1d1t.js";
13
+ import"./chunk-kmzzjv53.js";
14
14
  import"./chunk-pqe7q1s7.js";
15
15
  import"./chunk-ngf1zatm.js";
16
- import"./chunk-7wj0jh3t.js";
16
+ import"./chunk-s0h7c7da.js";
17
17
  import"./chunk-df32avw6.js";
18
18
  export {
19
19
  versionLine,
@@ -1,6 +1,6 @@
1
1
  // apps/host/src/distribution.ts
2
2
  import { fileURLToPath } from "node:url";
3
- var packagedVersion = "0.12.0";
3
+ var packagedVersion = "0.13.0";
4
4
  var webRoot = fileURLToPath(new URL(packagedVersion === undefined ? "../../web/dist" : "../web/", import.meta.url));
5
5
  var hostEntry = packagedVersion === undefined ? { script: fileURLToPath(new URL("./main.ts", import.meta.url)), args: [] } : { script: fileURLToPath(new URL("./pinomad.js", import.meta.url)), args: ["host"] };
6
6
  var commandHint = (name) => packagedVersion === undefined ? `bun run ${name}` : `pinomad ${name}`;
@@ -2,19 +2,19 @@
2
2
  import {
3
3
  joinServer,
4
4
  main
5
- } from "./chunk-gg2dcqbg.js";
5
+ } from "./chunk-d9mq5d46.js";
6
6
  import"./chunk-7mn38n4f.js";
7
- import"./chunk-nppgxh6c.js";
7
+ import"./chunk-563fnad9.js";
8
8
  import"./chunk-t91zt5f0.js";
9
9
  import"./chunk-nq57fdnq.js";
10
10
  import"./chunk-g3ahp7b9.js";
11
- import"./chunk-t5abqsva.js";
12
- import"./chunk-np4jd070.js";
11
+ import"./chunk-wd7aracm.js";
12
+ import"./chunk-4fferbxw.js";
13
13
  import"./chunk-kzbzacz9.js";
14
- import"./chunk-47ff1d1t.js";
14
+ import"./chunk-kmzzjv53.js";
15
15
  import"./chunk-pqe7q1s7.js";
16
16
  import"./chunk-ngf1zatm.js";
17
- import"./chunk-7wj0jh3t.js";
17
+ import"./chunk-s0h7c7da.js";
18
18
  import"./chunk-df32avw6.js";
19
19
  export {
20
20
  main,
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  restartService,
4
4
  serviceManager
5
- } from "./chunk-t5abqsva.js";
6
- import"./chunk-np4jd070.js";
5
+ } from "./chunk-wd7aracm.js";
6
+ import"./chunk-4fferbxw.js";
7
7
  import"./chunk-kzbzacz9.js";
8
- import"./chunk-47ff1d1t.js";
8
+ import"./chunk-kmzzjv53.js";
9
9
  import"./chunk-pqe7q1s7.js";
10
10
  import {
11
11
  hostAddressOptions
@@ -15,7 +15,7 @@ import {
15
15
  globalPrefixOf,
16
16
  packageDir,
17
17
  packagedVersion
18
- } from "./chunk-7wj0jh3t.js";
18
+ } from "./chunk-s0h7c7da.js";
19
19
  import"./chunk-df32avw6.js";
20
20
 
21
21
  // apps/host/src/cli/upgrade.ts