pinomad 0.5.0 → 0.7.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 (31) hide show
  1. package/README.md +2 -2
  2. package/dist/{chunk-f0v3sht0.js → chunk-07769pqx.js} +10 -8
  3. package/dist/{chunk-ebfaf3k2.js → chunk-0xh6cy9v.js} +1 -1
  4. package/dist/{chunk-aqj4wg33.js → chunk-12kaxggg.js} +29 -12
  5. package/dist/{chunk-d4wdt5qs.js → chunk-1nyzr0mg.js} +2282 -1027
  6. package/dist/chunk-1t58fh0k.js +16 -0
  7. package/dist/{chunk-4by4x5c4.js → chunk-6ysezfy5.js} +44 -14
  8. package/dist/{chunk-kmbh44dt.js → chunk-7qb9tq54.js} +42 -6
  9. package/dist/{chunk-gqr7ahv4.js → chunk-8yme2wep.js} +2 -2
  10. package/dist/chunk-b1h374jr.js +159 -0
  11. package/dist/{chunk-hsp12cjt.js → chunk-e4ahjbmz.js} +212 -50
  12. package/dist/{chunk-9d59jjmt.js → chunk-fbkd73zr.js} +36 -10
  13. package/dist/chunk-kzbzacz9.js +12 -0
  14. package/dist/{chunk-zttf1hvh.js → chunk-m4c5gvce.js} +4 -3
  15. package/dist/{chunk-cjfmfyjb.js → chunk-ngf1zatm.js} +7 -6
  16. package/dist/{chunk-1d9sppj0.js → chunk-rtwxjb90.js} +1 -1
  17. package/dist/{chunk-m4q9wafw.js → chunk-sj7rnvh4.js} +118 -6
  18. package/dist/chunk-sz3rzk55.js +79 -0
  19. package/dist/{chunk-26d04z02.js → chunk-ywmm63ks.js} +7 -3
  20. package/dist/chunk-znhn8af7.js +33 -0
  21. package/dist/pinomad.js +36 -12
  22. package/package.json +1 -1
  23. package/web/assets/index-C0rwAo0H.css +1 -0
  24. package/web/assets/index-DGee2kNU.js +394 -0
  25. package/web/index.html +2 -2
  26. package/dist/chunk-5ng86fmy.js +0 -14
  27. package/dist/chunk-crv3dqzp.js +0 -63
  28. package/web/assets/index-BkF0-im5.css +0 -1
  29. package/web/assets/index-D4lgm3Y4.js +0 -383
  30. package/web/assets/montserrat-latin-ext-wght-normal-BsZE-iaG.woff2 +0 -0
  31. package/web/assets/montserrat-latin-wght-normal-l_AIctKy.woff2 +0 -0
@@ -1,16 +1,8 @@
1
- // packages/protocol/src/settings.ts
2
- var HOST_SETTING_KEY_SET = {
3
- defaultModel: true,
4
- defaultThinkingLevel: true,
5
- steeringMode: true,
6
- followUpMode: true,
7
- acceptAccountDevices: true
8
- };
9
- var HOST_SETTING_KEYS = Object.keys(HOST_SETTING_KEY_SET);
10
- var THINKING_LEVEL_KEYS = { off: true, minimal: true, low: true, medium: true, high: true, xhigh: true, max: true };
11
- var THINKING_LEVELS = Object.keys(THINKING_LEVEL_KEYS);
12
- var isThinkingLevel = (value) => typeof value === "string" && Object.prototype.hasOwnProperty.call(THINKING_LEVEL_KEYS, value);
13
- var isQueueMode = (value) => value === "all" || value === "one-at-a-time";
1
+ import {
2
+ HOST_SETTING_KEYS,
3
+ isQueueMode,
4
+ isThinkingLevel
5
+ } from "./chunk-znhn8af7.js";
14
6
 
15
7
  // packages/protocol/src/frames.ts
16
8
  var HOST_STREAMS = { conversations: 0, index: 0, devices: 0, mcp: 0, models: 1, auth: 1, settings: 2, agents: 6 };
@@ -22,22 +14,55 @@ var conversationStream = (id) => `conversation:${id}`;
22
14
  var docStream = (kind, id) => `doc:${kind}:${id}`;
23
15
  var UNAUTHORIZED_CLOSE_CODE = 4401;
24
16
  var INVALID_FRAME_CLOSE_CODE = 4400;
25
- var PROTOCOL = { major: 5, minor: 10 };
17
+ var PROTOCOL = { major: 5, minor: 19 };
26
18
  var CALL_SINCE = {
27
- logout: 1,
28
- updateSettings: 2,
19
+ addProject: 0,
20
+ removeProject: 0,
29
21
  listFolders: 3,
22
+ listFiles: 10,
23
+ listSkills: 13,
24
+ listCommands: 17,
25
+ createConversation: 0,
26
+ archive: 0,
30
27
  renameConversation: 4,
28
+ submit: 0,
29
+ withdrawQueued: 14,
30
+ steerQueued: 14,
31
+ abort: 0,
32
+ compact: 0,
33
+ runCommand: 17,
34
+ setModel: 0,
35
+ setThinkingLevel: 0,
36
+ setAgent: 18,
37
+ fork: 0,
38
+ answer: 0,
39
+ changes: 0,
40
+ createPairing: 0,
41
+ revokeDevice: 0,
42
+ logout: 1,
43
+ updateSettings: 2,
31
44
  createAgent: 6,
32
45
  updateAgent: 6,
33
46
  deleteAgent: 6,
34
47
  setPushToken: 7,
35
- listFiles: 10
48
+ image: 15,
49
+ beginAttachment: 19,
50
+ appendAttachment: 19
36
51
  };
37
52
  function hostSupportsCall(hostMinor, method) {
38
- return hostMinor >= (CALL_SINCE[method] ?? 0);
53
+ return hostMinor >= CALL_SINCE[method];
39
54
  }
40
- var FEATURE_SINCE = { images: 5, acceptAccountDevices: 8, apnsPush: 9, agentConversations: 10 };
55
+ var FEATURE_SINCE = {
56
+ images: 5,
57
+ acceptAccountDevices: 8,
58
+ apnsPush: 9,
59
+ agentConversations: 10,
60
+ liveBranch: 11,
61
+ hostName: 12,
62
+ imageRefs: 15,
63
+ boundSkills: 16,
64
+ attachments: 19
65
+ };
41
66
  function hostSupportsFeature(hostMinor, feature) {
42
67
  return hostMinor >= FEATURE_SINCE[feature];
43
68
  }
@@ -49,7 +74,8 @@ function parseClientFrame(value) {
49
74
  if (typeof stream !== "string")
50
75
  return { kind: "malformed" };
51
76
  if (isStreamName(stream)) {
52
- return { kind: "frame", frame: { type: value.type, stream } };
77
+ const refs = value.type === "subscribe" && value.images === "ref";
78
+ return { kind: "frame", frame: refs ? { type: "subscribe", stream, images: "ref" } : { type: value.type, stream } };
53
79
  }
54
80
  const prefix = stream.slice(0, stream.indexOf(":") === -1 ? undefined : stream.indexOf(":"));
55
81
  if (STREAM_KINDS.has(prefix))
@@ -71,51 +97,40 @@ function isStreamName(stream) {
71
97
  return stream === "tasks" || HOST_STREAM_NAMES.includes(stream) || /^(?:conversation:|doc:.+:)[1-9]\d*$/.test(stream);
72
98
  }
73
99
  var isCallMethod = (method) => CALL_METHODS.includes(method);
74
- var CALL_METHODS = [
75
- "addProject",
76
- "removeProject",
77
- "listFolders",
78
- "createConversation",
79
- "archive",
80
- "renameConversation",
81
- "submit",
82
- "abort",
83
- "compact",
84
- "setModel",
85
- "setThinkingLevel",
86
- "fork",
87
- "answer",
88
- "changes",
89
- "createPairing",
90
- "revokeDevice",
91
- "logout",
92
- "updateSettings",
93
- "createAgent",
94
- "updateAgent",
95
- "deleteAgent",
96
- "setPushToken",
97
- "listFiles"
98
- ];
100
+ var CALL_METHODS = Object.keys(CALL_SINCE);
99
101
  var SETTING_VALIDATORS = {
100
102
  defaultModel: (value) => value === null || record(value) && nonempty(value.provider) && nonempty(value.modelId),
101
103
  defaultThinkingLevel: (value) => value === null || isThinkingLevel(value),
102
104
  steeringMode: (value) => value === null || isQueueMode(value),
103
105
  followUpMode: (value) => value === null || isQueueMode(value),
104
- acceptAccountDevices: (value) => value === null || typeof value === "boolean"
106
+ acceptAccountDevices: (value) => value === null || typeof value === "boolean",
107
+ hostName: (value) => value === null || typeof value === "string"
105
108
  };
106
109
  function validCallArgs(method, args) {
107
110
  const conversationId = () => typeof args.conversationId === "number" && Number.isSafeInteger(args.conversationId) && args.conversationId >= 1;
108
- const images = () => args.images === undefined || Array.isArray(args.images) && args.images.every((image) => record(image) && typeof image.data === "string" && typeof image.mimeType === "string");
111
+ const submissionId = () => typeof args.submissionId === "number" && Number.isSafeInteger(args.submissionId) && args.submissionId >= 1;
112
+ const images = () => args.images === undefined || Array.isArray(args.images) && args.images.every((image) => record(image) && ("data" in image ? typeof image.data === "string" && typeof image.mimeType === "string" : nonempty(image.ref)));
113
+ const skills = () => args.skills === undefined || Array.isArray(args.skills) && args.skills.every(nonempty);
114
+ const attachments = () => args.attachments === undefined || Array.isArray(args.attachments) && args.attachments.every(nonempty);
109
115
  const role = () => record(args.role) && typeof args.role.name === "string" && typeof args.role.description === "string" && typeof args.role.prompt === "string" && (args.role.model === undefined || typeof args.role.model === "string") && (args.role.thinkingLevel === undefined || typeof args.role.thinkingLevel === "string");
110
116
  switch (method) {
111
117
  case "submit":
112
- return conversationId() && typeof args.text === "string" && (args.whenBusy === "steer" || args.whenBusy === "followUp") && nonempty(args.requestId) && images();
118
+ return conversationId() && typeof args.text === "string" && (args.whenBusy === "steer" || args.whenBusy === "followUp") && nonempty(args.requestId) && images() && skills() && attachments();
119
+ case "withdrawQueued":
120
+ case "steerQueued":
121
+ return conversationId() && submissionId();
113
122
  case "abort":
114
123
  return conversationId();
115
124
  case "setThinkingLevel":
116
125
  return conversationId() && nonempty(args.level);
126
+ case "setAgent":
127
+ return conversationId() && (args.agent === null || nonempty(args.agent));
117
128
  case "compact":
118
129
  return conversationId() && (args.instructions === undefined || typeof args.instructions === "string");
130
+ case "listCommands":
131
+ return conversationId();
132
+ case "runCommand":
133
+ return conversationId() && nonempty(args.name) && (args.args === undefined || typeof args.args === "string");
119
134
  case "setModel":
120
135
  return conversationId() && record(args.model) && nonempty(args.model.provider) && nonempty(args.model.modelId);
121
136
  case "fork":
@@ -124,15 +139,23 @@ function validCallArgs(method, args) {
124
139
  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"));
125
140
  case "changes":
126
141
  return conversationId();
142
+ case "image":
143
+ return nonempty(args.ref);
144
+ case "beginAttachment":
145
+ return nonempty(args.name) && typeof args.size === "number" && Number.isSafeInteger(args.size) && args.size >= 0;
146
+ case "appendAttachment":
147
+ return nonempty(args.attachmentId) && typeof args.offset === "number" && Number.isSafeInteger(args.offset) && args.offset >= 0 && typeof args.data === "string";
127
148
  case "addProject":
128
149
  case "removeProject":
129
150
  return nonempty(args.path);
130
151
  case "listFolders":
131
152
  return args.path === undefined || nonempty(args.path);
132
153
  case "createConversation":
133
- return record(args.home) && (args.home.kind === "chat" || args.home.kind === "project" && nonempty(args.home.path)) && typeof args.text === "string" && (nonempty(args.text) || Array.isArray(args.images) && args.images.length > 0) && nonempty(args.requestId) && images() && (args.checkout === undefined || args.checkout === "worktree" || args.checkout === "project") && (args.model === undefined || record(args.model) && nonempty(args.model.provider) && nonempty(args.model.modelId)) && (args.thinkingLevel === undefined || nonempty(args.thinkingLevel)) && (args.agent === undefined || nonempty(args.agent));
154
+ return record(args.home) && (args.home.kind === "chat" || args.home.kind === "project" && nonempty(args.home.path)) && typeof args.text === "string" && (nonempty(args.text) || Array.isArray(args.images) && args.images.length > 0 || Array.isArray(args.attachments) && args.attachments.length > 0) && nonempty(args.requestId) && images() && skills() && attachments() && (args.checkout === undefined || args.checkout === "worktree" || args.checkout === "project") && (args.model === undefined || record(args.model) && nonempty(args.model.provider) && nonempty(args.model.modelId)) && (args.thinkingLevel === undefined || nonempty(args.thinkingLevel)) && (args.agent === undefined || nonempty(args.agent));
134
155
  case "listFiles":
135
156
  return typeof args.query === "string" && record(args.scope) && (args.scope.kind === "conversation" && typeof args.scope.conversationId === "number" && Number.isSafeInteger(args.scope.conversationId) && args.scope.conversationId >= 1 || args.scope.kind === "project" && nonempty(args.scope.path) && (args.scope.checkout === "worktree" || args.scope.checkout === "project"));
157
+ case "listSkills":
158
+ return record(args.scope) && (args.scope.kind === "conversation" && typeof args.scope.conversationId === "number" && Number.isSafeInteger(args.scope.conversationId) && args.scope.conversationId >= 1 || args.scope.kind === "project" && nonempty(args.scope.path) && (args.scope.checkout === "worktree" || args.scope.checkout === "project") || args.scope.kind === "chat");
136
159
  case "archive":
137
160
  return conversationId() && typeof args.archived === "boolean";
138
161
  case "renameConversation":
@@ -161,6 +184,145 @@ function nonempty(value) {
161
184
  return typeof value === "string" && value.length > 0;
162
185
  }
163
186
 
187
+ // packages/protocol/src/attachments.ts
188
+ var MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;
189
+ var MAX_ATTACHMENTS_PER_MESSAGE = 10;
190
+ var ATTACHMENT_CHUNK_BYTES = 512 * 1024;
191
+ var MAX_PENDING_ATTACHMENTS = 20;
192
+ var MAX_PENDING_ATTACHMENT_BYTES = 200 * 1024 * 1024;
193
+ var ATTACHMENT_TTL_MS = 24 * 60 * 60 * 1000;
194
+ var MAX_ATTACHMENT_NAME_LENGTH = 255;
195
+ var ATTACHMENT_ID_PATTERN = /^[A-Za-z0-9_-]{22}$/;
196
+ function isAttachmentId(value) {
197
+ return ATTACHMENT_ID_PATTERN.test(value);
198
+ }
199
+ var ATTACHMENTS_OPEN = '<attachments v="1">';
200
+ var ATTACHMENTS_INSTRUCTION = "The user attached these files. They are read-only originals on this machine: read them with your tools when relevant, and copy one into the working directory before changing it.";
201
+ var ATTACHMENTS_CLOSE = "</attachments>";
202
+ var BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
203
+ function base64DecodedLength(data) {
204
+ if (data.length % 4 !== 0 || !BASE64.test(data))
205
+ return;
206
+ const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
207
+ return data.length * 3 / 4 - padding;
208
+ }
209
+ function attachmentBeginProblem(name, size) {
210
+ if (name.trim() === "")
211
+ return "Attachment name cannot be empty";
212
+ if (!Number.isSafeInteger(size) || size < 0)
213
+ return "Attachment size must be a non-negative integer";
214
+ if (size > MAX_ATTACHMENT_BYTES)
215
+ return `Attachment is larger than ${MAX_ATTACHMENT_BYTES / (1024 * 1024)} MB`;
216
+ return;
217
+ }
218
+ function attachmentChunkProblem(data) {
219
+ const size = base64DecodedLength(data);
220
+ if (size === undefined)
221
+ return "Attachment chunk is not valid base64";
222
+ if (size > ATTACHMENT_CHUNK_BYTES)
223
+ return `Attachment chunk is larger than ${ATTACHMENT_CHUNK_BYTES / 1024} KiB`;
224
+ return;
225
+ }
226
+ function xmlAttribute(value) {
227
+ let out = "";
228
+ for (let i = 0;i < value.length; i++) {
229
+ const unit = value.charCodeAt(i);
230
+ if (unit === 38)
231
+ out += "&amp;";
232
+ else if (unit === 60)
233
+ out += "&lt;";
234
+ else if (unit === 62)
235
+ out += "&gt;";
236
+ else if (unit === 34)
237
+ out += "&quot;";
238
+ else if (unit < 32 || unit === 127)
239
+ out += `&#x${unit.toString(16)};`;
240
+ else
241
+ out += value[i];
242
+ }
243
+ return out;
244
+ }
245
+ function attachmentBlock(files) {
246
+ const lines = [
247
+ ATTACHMENTS_OPEN,
248
+ ATTACHMENTS_INSTRUCTION,
249
+ ...files.map((file) => `<file id="${xmlAttribute(file.id)}" name="${xmlAttribute(file.name)}" path="${xmlAttribute(file.path)}" size="${file.size}"/>`),
250
+ ATTACHMENTS_CLOSE
251
+ ];
252
+ return lines.join(`
253
+ `);
254
+ }
255
+ var FILE_OPEN = '<file id="';
256
+ var FILE_NAME = '" name="';
257
+ var FILE_PATH = '" path="';
258
+ var FILE_SIZE = '" size="';
259
+ var FILE_END = '"/>';
260
+ var SIZE = /^(?:0|[1-9][0-9]*)$/;
261
+ function decodeXmlAttribute(value) {
262
+ return value.replace(/&(amp|lt|gt|quot|apos|#x[0-9A-Fa-f]+|#[0-9]+);/g, (entity, body) => {
263
+ if (body === "amp")
264
+ return "&";
265
+ if (body === "lt")
266
+ return "<";
267
+ if (body === "gt")
268
+ return ">";
269
+ if (body === "quot")
270
+ return '"';
271
+ if (body === "apos")
272
+ return "'";
273
+ const code = body.startsWith("#x") ? parseInt(body.slice(2), 16) : parseInt(body.slice(1), 10);
274
+ return code >= 0 && code <= 1114111 ? String.fromCodePoint(code) : entity;
275
+ });
276
+ }
277
+ function quoted(text, at, marker) {
278
+ if (!text.startsWith(marker, at))
279
+ return;
280
+ const start = at + marker.length;
281
+ const end = text.indexOf('"', start);
282
+ if (end === -1)
283
+ return;
284
+ return { value: text.slice(start, end), next: end };
285
+ }
286
+ function parseFileLine(line) {
287
+ const id = quoted(line, 0, FILE_OPEN);
288
+ if (id === undefined)
289
+ return;
290
+ const name = quoted(line, id.next, FILE_NAME);
291
+ if (name === undefined)
292
+ return;
293
+ const path = quoted(line, name.next, FILE_PATH);
294
+ if (path === undefined)
295
+ return;
296
+ const size = quoted(line, path.next, FILE_SIZE);
297
+ if (size === undefined || !line.startsWith(FILE_END, size.next) || size.next + FILE_END.length !== line.length) {
298
+ return;
299
+ }
300
+ if (!isAttachmentId(id.value) || !SIZE.test(size.value))
301
+ return;
302
+ const bytes = Number(size.value);
303
+ if (!Number.isSafeInteger(bytes))
304
+ return;
305
+ return { id: id.value, name: decodeXmlAttribute(name.value), size: bytes };
306
+ }
307
+ function parseAttachmentBlock(text) {
308
+ if (!text.startsWith(ATTACHMENTS_OPEN + `
309
+ `) || !text.endsWith(`
310
+ ` + ATTACHMENTS_CLOSE))
311
+ return;
312
+ const lines = text.slice(ATTACHMENTS_OPEN.length + 1, -(ATTACHMENTS_CLOSE.length + 1)).split(`
313
+ `);
314
+ if (lines[0] !== ATTACHMENTS_INSTRUCTION || lines.length < 2)
315
+ return;
316
+ const files = [];
317
+ for (const line of lines.slice(1)) {
318
+ const file = parseFileLine(line);
319
+ if (file === undefined)
320
+ return;
321
+ files.push(file);
322
+ }
323
+ return files;
324
+ }
325
+
164
326
  // packages/protocol/src/organization.ts
165
327
  function organize(index, summaries) {
166
328
  const summaryById = new Map(summaries.map((summary) => [summary.id, summary]));
@@ -254,4 +416,4 @@ function homeOf(index, summaries, id) {
254
416
  return;
255
417
  }
256
418
 
257
- export { HOST_SETTING_KEYS, THINKING_LEVELS, isThinkingLevel, isQueueMode, HOST_STREAM_NAMES, hostStreams, conversationStream, docStream, UNAUTHORIZED_CLOSE_CODE, INVALID_FRAME_CLOSE_CODE, PROTOCOL, hostSupportsCall, hostSupportsFeature, parseClientFrame, organize, checkoutOf, homeOf };
419
+ export { 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 };
@@ -3,43 +3,55 @@ import {
3
3
  assertSecureServerOrigin,
4
4
  probeServerAccounts,
5
5
  runServerOperation
6
- } from "./chunk-26d04z02.js";
6
+ } from "./chunk-ywmm63ks.js";
7
+ import {
8
+ constantValue,
9
+ openHostSettings
10
+ } from "./chunk-b1h374jr.js";
7
11
  import {
8
12
  loadHostKey,
9
13
  loadRelayKey,
10
14
  writeServerConfig
11
- } from "./chunk-ebfaf3k2.js";
15
+ } from "./chunk-0xh6cy9v.js";
12
16
  import {
13
17
  relayHostId
14
- } from "./chunk-kmbh44dt.js";
18
+ } from "./chunk-7qb9tq54.js";
15
19
  import {
16
20
  argValue,
17
21
  installSpec,
18
22
  restartService,
19
23
  serviceManager,
20
24
  withoutRelayArgs
21
- } from "./chunk-aqj4wg33.js";
25
+ } from "./chunk-12kaxggg.js";
26
+ import"./chunk-kzbzacz9.js";
22
27
  import {
23
28
  commandHint,
24
29
  hostEntry
25
- } from "./chunk-1d9sppj0.js";
26
- import"./chunk-m4q9wafw.js";
27
- import"./chunk-hsp12cjt.js";
30
+ } from "./chunk-rtwxjb90.js";
31
+ import"./chunk-sj7rnvh4.js";
32
+ import"./chunk-e4ahjbmz.js";
33
+ import {
34
+ cleanHostName,
35
+ normalizeHostName
36
+ } from "./chunk-znhn8af7.js";
28
37
  import {
29
38
  DEFAULT_DATA_DIR
30
- } from "./chunk-cjfmfyjb.js";
39
+ } from "./chunk-ngf1zatm.js";
31
40
  import"./chunk-df32avw6.js";
32
41
 
33
42
  // apps/host/src/cli/join.ts
34
43
  import { mkdir } from "node:fs/promises";
35
44
  import { homedir, hostname } from "node:os";
36
- import { resolve } from "node:path";
45
+ import { join, resolve } from "node:path";
37
46
  import { parseArgs } from "node:util";
38
47
  var message = (error) => error instanceof Error ? error.message : String(error);
39
48
  async function joinServer(args, deps) {
40
49
  const origin = args.origin.replace(/\/+$/, "");
41
50
  const dataDir = resolve(args.dataDir);
42
51
  assertSecureServerOrigin(origin);
52
+ if (args.name !== undefined && cleanHostName(args.name) === "") {
53
+ throw new Error(`Invalid host name: ${JSON.stringify(args.name)}`);
54
+ }
43
55
  await mkdir(dataDir, { recursive: true });
44
56
  const signingKey = await loadRelayKey(dataDir);
45
57
  const hostKey = await loadHostKey(dataDir);
@@ -56,13 +68,25 @@ async function joinServer(args, deps) {
56
68
  if (probe === "disabled") {
57
69
  throw new Error(`${origin} does not run server accounts — the server is too old or was started without a data dir`);
58
70
  }
71
+ const settings = await openHostSettings({
72
+ path: join(dataDir, "settings.json"),
73
+ fallback: {
74
+ defaultModel: () => null,
75
+ defaultThinkingLevel: () => null,
76
+ steeringMode: () => "one-at-a-time",
77
+ followUpMode: () => "one-at-a-time"
78
+ },
79
+ models: constantValue([])
80
+ });
81
+ const current = settings.value;
82
+ const storedName = current.custom.includes("hostName") ? current.hostName : undefined;
59
83
  const reply = await runServerOperation({
60
84
  origin,
61
85
  signingKey,
62
86
  noisePublicKey: hostKey.publicKey,
63
87
  op: "join",
64
88
  joinToken: args.token,
65
- name: args.name ?? deps?.hostname ?? hostname()
89
+ name: normalizeHostName(args.name ?? storedName ?? deps?.hostname ?? hostname())
66
90
  });
67
91
  if (reply.t === "refused") {
68
92
  throw new Error(reply.reason === "expired_token" ? "the join token expired — generate a fresh one" : reply.reason === "invalid_token" ? "the join token is invalid or already spent — generate a fresh one" : `the server refused the join: ${reply.reason}`);
@@ -73,6 +97,8 @@ async function joinServer(args, deps) {
73
97
  let service;
74
98
  try {
75
99
  await writeServerConfig(dataDir, { origin });
100
+ if (args.name !== undefined)
101
+ await settings.update({ hostName: args.name });
76
102
  const freshSpec = installSpec(["--data-dir", dataDir], {
77
103
  execPath: process.execPath,
78
104
  env: process.env,
@@ -0,0 +1,12 @@
1
+ // apps/host/src/cli/args.ts
2
+ function isParseArgsError(error) {
3
+ const code = error.code;
4
+ return typeof code === "string" && code.startsWith("ERR_PARSE_ARGS_");
5
+ }
6
+ function reportParseArgsError(command, error) {
7
+ console.error(`${command}: ${error instanceof Error ? error.message : String(error)}`);
8
+ console.error(`run "${command} --help" for usage`);
9
+ process.exitCode = 2;
10
+ }
11
+
12
+ export { isParseArgsError, reportParseArgsError };
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  connectRemoteDurable
3
- } from "./chunk-m4q9wafw.js";
4
- import"./chunk-hsp12cjt.js";
3
+ } from "./chunk-sj7rnvh4.js";
4
+ import"./chunk-e4ahjbmz.js";
5
+ import"./chunk-znhn8af7.js";
5
6
  import {
6
7
  hostAddress
7
- } from "./chunk-cjfmfyjb.js";
8
+ } from "./chunk-ngf1zatm.js";
8
9
  import"./chunk-df32avw6.js";
9
10
 
10
11
  // apps/host/src/cli/pair.ts
@@ -4,18 +4,19 @@ import { homedir } from "node:os";
4
4
  import { join, resolve } from "node:path";
5
5
  import { parseArgs } from "node:util";
6
6
  var DEFAULT_DATA_DIR = process.env.PINOMAD_DATA_DIR ?? join(homedir(), ".pinomad");
7
+ var hostAddressOptions = {
8
+ "data-dir": { type: "string" },
9
+ url: { type: "string", default: "ws://127.0.0.1:7420" },
10
+ token: { type: "string" }
11
+ };
7
12
  async function hostAddress(argv) {
8
13
  const { values } = parseArgs({
9
14
  args: [...argv],
10
- options: {
11
- "data-dir": { type: "string" },
12
- url: { type: "string", default: "ws://127.0.0.1:7420" },
13
- token: { type: "string" }
14
- }
15
+ options: hostAddressOptions
15
16
  });
16
17
  const dataDir = resolve(values["data-dir"] ?? DEFAULT_DATA_DIR);
17
18
  const token = values.token ?? (await readFile(join(dataDir, "token"), "utf8")).trim();
18
19
  return { url: values.url, token, dataDir };
19
20
  }
20
21
 
21
- export { DEFAULT_DATA_DIR, hostAddress };
22
+ export { DEFAULT_DATA_DIR, hostAddressOptions, hostAddress };
@@ -1,6 +1,6 @@
1
1
  // apps/host/src/distribution.ts
2
2
  import { fileURLToPath } from "node:url";
3
- var packagedVersion = "0.5.0";
3
+ var packagedVersion = "0.7.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}`;