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.
Files changed (34) hide show
  1. package/README.md +4 -2
  2. package/dist/{chunk-837gh8nr.js → chunk-0xh6cy9v.js} +69 -17
  3. package/dist/{chunk-n73t5xzw.js → chunk-367n9je8.js} +2 -2
  4. package/dist/{chunk-xhwkx9bd.js → chunk-3dnnaz4j.js} +14 -15
  5. package/dist/{chunk-z5pmcscx.js → chunk-4btj08we.js} +676 -377
  6. package/dist/{chunk-7gmkgyaz.js → chunk-7qb9tq54.js} +324 -4
  7. package/dist/{chunk-d4y99pfy.js → chunk-7s1f4z4g.js} +4 -3
  8. package/dist/chunk-b1h374jr.js +159 -0
  9. package/dist/{chunk-fgx2sh7d.js → chunk-c8nxym2z.js} +1 -1
  10. package/dist/{chunk-akgxwg8f.js → chunk-d5ypmmr7.js} +29 -12
  11. package/dist/{chunk-374zqx5h.js → chunk-d93kvxvc.js} +18 -2
  12. package/dist/chunk-ggb1taqy.js +16 -0
  13. package/dist/chunk-jn7y815x.js +79 -0
  14. package/dist/{chunk-b3f0ced1.js → chunk-k2s1hfq5.js} +39 -16
  15. package/dist/chunk-kzbzacz9.js +12 -0
  16. package/dist/{chunk-myjp910s.js → chunk-n4tjhcb7.js} +25 -21
  17. package/dist/{chunk-cjfmfyjb.js → chunk-ngf1zatm.js} +7 -6
  18. package/dist/{chunk-vw5yb5pd.js → chunk-wqvp1fwh.js} +304 -38
  19. package/dist/{chunk-5785kwej.js → chunk-ywmm63ks.js} +9 -7
  20. package/dist/chunk-znhn8af7.js +33 -0
  21. package/dist/pinomad.js +40 -16
  22. package/package.json +1 -1
  23. package/web/apple-touch-icon.png +0 -0
  24. package/web/assets/index-C_aU_ZiV.js +383 -0
  25. package/web/assets/index-DaPcXZPM.css +1 -0
  26. package/web/favicon.svg +1 -0
  27. package/web/index.html +4 -2
  28. package/dist/chunk-17p6bfvy.js +0 -63
  29. package/dist/chunk-302y6by9.js +0 -50
  30. package/dist/chunk-knaw1a9g.js +0 -182
  31. package/dist/chunk-prhvksh3.js +0 -14
  32. package/dist/chunk-xrvmnymc.js +0 -33
  33. package/web/assets/index-8mNNPXPT.js +0 -383
  34. package/web/assets/index-BFJJgGGo.css +0 -1
@@ -11,7 +11,7 @@ import {
11
11
  hostSupportsCall,
12
12
  hostSupportsFeature,
13
13
  organize
14
- } from "./chunk-myjp910s.js";
14
+ } from "./chunk-n4tjhcb7.js";
15
15
 
16
16
  // packages/protocol/src/remote-durable.ts
17
17
  import { applyImmutable } from "@earendil-works/chord/delta";
@@ -440,6 +440,9 @@ class RemoteClient {
440
440
  if (images.length > 0 && !hostSupportsFeature(this.#hostMinor, "images")) {
441
441
  throw new Error("The host is too old for images (needs protocol 5.5); run `pinomad upgrade` on the host");
442
442
  }
443
+ if (options?.agent !== undefined && !hostSupportsFeature(this.#hostMinor, "agentConversations")) {
444
+ throw new Error("The host is too old for named agents (needs protocol 5.10); run `pinomad upgrade` on the host");
445
+ }
443
446
  const { conversationId: conversationId2 } = await this.#call("createConversation", {
444
447
  home,
445
448
  text,
@@ -447,6 +450,7 @@ class RemoteClient {
447
450
  ...options?.checkout === undefined ? {} : { checkout: options.checkout },
448
451
  ...options?.model === undefined ? {} : { model: options.model },
449
452
  ...options?.thinkingLevel === undefined ? {} : { thinkingLevel: options.thinkingLevel },
453
+ ...options?.agent === undefined ? {} : { agent: options.agent },
450
454
  ...images.length === 0 ? {} : { images }
451
455
  });
452
456
  await this.#switch(conversationId2);
@@ -510,6 +514,12 @@ class RemoteClient {
510
514
  }),
511
515
  answer: (kind, requestId, answers, target) => this.#command(() => this.#call("answer", { conversationId: target ?? conversationId(), kind, requestId, answers })),
512
516
  changes: () => this.#request(() => this.#call("changes", { conversationId: conversationId() })),
517
+ listFiles: (scope, query) => {
518
+ if (!hostSupportsCall(this.#hostMinor, "listFiles")) {
519
+ return Promise.reject(new Error("The host is too old for this (needs protocol 5.10); run `pinomad upgrade` on the host"));
520
+ }
521
+ return this.#call("listFiles", { scope, query });
522
+ },
513
523
  createPairing: () => this.#request(() => this.#call("createPairing", {})),
514
524
  revokeDevice: (publicKey) => this.#command(() => this.#call("revokeDevice", { publicKey })),
515
525
  logout: (providerId) => this.#command(() => {
@@ -525,6 +535,9 @@ class RemoteClient {
525
535
  if (patch.acceptAccountDevices !== undefined && !hostSupportsFeature(this.#hostMinor, "acceptAccountDevices")) {
526
536
  throw new Error("The host is too old for this (needs protocol 5.8); run `pinomad upgrade` on the host");
527
537
  }
538
+ if (patch.hostName !== undefined && !hostSupportsFeature(this.#hostMinor, "hostName")) {
539
+ throw new Error("The host is too old for this (needs protocol 5.12); run `pinomad upgrade` on the host");
540
+ }
528
541
  return this.#call("updateSettings", { patch });
529
542
  }),
530
543
  createAgent: (role) => this.#request(async () => {
@@ -549,6 +562,9 @@ class RemoteClient {
549
562
  if (!hostSupportsCall(this.#hostMinor, "setPushToken")) {
550
563
  throw new Error("The host is too old for this (needs protocol 5.7); run `pinomad upgrade` on the host");
551
564
  }
565
+ if (token !== null && typeof token !== "string" && !hostSupportsFeature(this.#hostMinor, "apnsPush")) {
566
+ throw new Error("The host is too old for this (needs protocol 5.9); run `pinomad upgrade` on the host");
567
+ }
552
568
  await this.#call("setPushToken", { token });
553
569
  })
554
570
  };
@@ -591,4 +607,4 @@ class RemoteClient {
591
607
  }
592
608
  }
593
609
 
594
- export { connectRemoteDurable };
610
+ export { connectRemoteDurable, ProtocolMismatchError };
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ main,
4
+ restartService
5
+ } from "./chunk-d5ypmmr7.js";
6
+ import"./chunk-kzbzacz9.js";
7
+ import"./chunk-c8nxym2z.js";
8
+ import"./chunk-d93kvxvc.js";
9
+ import"./chunk-n4tjhcb7.js";
10
+ import"./chunk-znhn8af7.js";
11
+ import"./chunk-ngf1zatm.js";
12
+ import"./chunk-df32avw6.js";
13
+ export {
14
+ restartService,
15
+ main
16
+ };
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ restartService,
4
+ serviceManager
5
+ } from "./chunk-d5ypmmr7.js";
6
+ import"./chunk-kzbzacz9.js";
7
+ import {
8
+ globalPrefixOf,
9
+ packageDir,
10
+ packagedVersion
11
+ } from "./chunk-c8nxym2z.js";
12
+ import"./chunk-d93kvxvc.js";
13
+ import"./chunk-n4tjhcb7.js";
14
+ import"./chunk-znhn8af7.js";
15
+ import {
16
+ hostAddressOptions
17
+ } from "./chunk-ngf1zatm.js";
18
+ import"./chunk-df32avw6.js";
19
+
20
+ // apps/host/src/cli/upgrade.ts
21
+ import { execFile, spawn } from "node:child_process";
22
+ import { fileURLToPath } from "node:url";
23
+ import { parseArgs, promisify } from "node:util";
24
+ var exec = promisify(execFile);
25
+ var repoRoot = fileURLToPath(new URL("../../../../", import.meta.url));
26
+ var step = (command, args) => new Promise((resolve) => {
27
+ spawn(command, [...args], { cwd: repoRoot, stdio: "inherit" }).once("exit", (code) => resolve(code ?? 1));
28
+ });
29
+ var serviceInstalled = () => Promise.resolve().then(() => serviceManager().state()).then((state) => state.installed, () => false);
30
+ async function main(argv, deps = {}) {
31
+ const { values } = parseArgs({
32
+ args: [...argv],
33
+ options: { force: { type: "boolean", default: false }, ...hostAddressOptions }
34
+ });
35
+ const run = deps.run ?? ((command, args) => exec(command, args, { cwd: repoRoot }));
36
+ const runStep = deps.step ?? step;
37
+ const restart = deps.restart ?? restartService;
38
+ if (packagedVersion === undefined) {
39
+ const { stdout } = await run("git", ["status", "--porcelain"]);
40
+ if (stdout.trim() !== "") {
41
+ console.error("upgrade: the checkout has uncommitted changes; commit or stash them first");
42
+ process.exitCode = 1;
43
+ return;
44
+ }
45
+ for (const [command, args] of [
46
+ ["git", ["pull", "--ff-only"]],
47
+ ["bun", ["install"]],
48
+ ["bun", ["run", "build"]]
49
+ ]) {
50
+ const code = await runStep(command, args);
51
+ if (code !== 0) {
52
+ process.exitCode = code;
53
+ return;
54
+ }
55
+ }
56
+ } else {
57
+ const prefix = globalPrefixOf(packageDir);
58
+ if (prefix === undefined) {
59
+ console.error(`upgrade: pinomad isn't a global npm install (${packageDir}); upgrade it the way it was installed`);
60
+ process.exitCode = 1;
61
+ return;
62
+ }
63
+ const code = await runStep("npm", ["install", "-g", "--prefix", prefix, "pinomad@latest"]);
64
+ if (code !== 0) {
65
+ process.exitCode = code;
66
+ return;
67
+ }
68
+ }
69
+ if (!await (deps.serviceInstalled ?? serviceInstalled)()) {
70
+ console.log("service not installed; upgrade done, nothing restarted");
71
+ process.exitCode = 0;
72
+ return;
73
+ }
74
+ (deps.exit ?? process.exit)(await restart({ force: values.force, argv: argv.filter((arg) => arg !== "--force") }));
75
+ }
76
+ if (false) {}
77
+ export {
78
+ main
79
+ };
@@ -3,46 +3,55 @@ import {
3
3
  assertSecureServerOrigin,
4
4
  probeServerAccounts,
5
5
  runServerOperation
6
- } from "./chunk-5785kwej.js";
6
+ } from "./chunk-ywmm63ks.js";
7
7
  import {
8
+ constantValue,
9
+ openHostSettings
10
+ } from "./chunk-b1h374jr.js";
11
+ import {
12
+ loadHostKey,
13
+ loadRelayKey,
8
14
  writeServerConfig
9
- } from "./chunk-302y6by9.js";
10
- import"./chunk-knaw1a9g.js";
15
+ } from "./chunk-0xh6cy9v.js";
16
+ import {
17
+ relayHostId
18
+ } from "./chunk-7qb9tq54.js";
11
19
  import {
12
20
  argValue,
13
21
  installSpec,
14
22
  restartService,
15
23
  serviceManager,
16
24
  withoutRelayArgs
17
- } from "./chunk-akgxwg8f.js";
25
+ } from "./chunk-d5ypmmr7.js";
26
+ import"./chunk-kzbzacz9.js";
18
27
  import {
19
28
  commandHint,
20
29
  hostEntry
21
- } from "./chunk-fgx2sh7d.js";
22
- import"./chunk-374zqx5h.js";
23
- import"./chunk-myjp910s.js";
30
+ } from "./chunk-c8nxym2z.js";
31
+ import"./chunk-d93kvxvc.js";
32
+ import"./chunk-n4tjhcb7.js";
24
33
  import {
25
- loadHostKey,
26
- loadRelayKey
27
- } from "./chunk-837gh8nr.js";
28
- import {
29
- relayHostId
30
- } from "./chunk-7gmkgyaz.js";
34
+ cleanHostName,
35
+ normalizeHostName
36
+ } from "./chunk-znhn8af7.js";
31
37
  import {
32
38
  DEFAULT_DATA_DIR
33
- } from "./chunk-cjfmfyjb.js";
39
+ } from "./chunk-ngf1zatm.js";
34
40
  import"./chunk-df32avw6.js";
35
41
 
36
42
  // apps/host/src/cli/join.ts
37
43
  import { mkdir } from "node:fs/promises";
38
44
  import { homedir, hostname } from "node:os";
39
- import { resolve } from "node:path";
45
+ import { join, resolve } from "node:path";
40
46
  import { parseArgs } from "node:util";
41
47
  var message = (error) => error instanceof Error ? error.message : String(error);
42
48
  async function joinServer(args, deps) {
43
49
  const origin = args.origin.replace(/\/+$/, "");
44
50
  const dataDir = resolve(args.dataDir);
45
51
  assertSecureServerOrigin(origin);
52
+ if (args.name !== undefined && cleanHostName(args.name) === "") {
53
+ throw new Error(`Invalid host name: ${JSON.stringify(args.name)}`);
54
+ }
46
55
  await mkdir(dataDir, { recursive: true });
47
56
  const signingKey = await loadRelayKey(dataDir);
48
57
  const hostKey = await loadHostKey(dataDir);
@@ -59,13 +68,25 @@ async function joinServer(args, deps) {
59
68
  if (probe === "disabled") {
60
69
  throw new Error(`${origin} does not run server accounts — the server is too old or was started without a data dir`);
61
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;
62
83
  const reply = await runServerOperation({
63
84
  origin,
64
85
  signingKey,
65
86
  noisePublicKey: hostKey.publicKey,
66
87
  op: "join",
67
88
  joinToken: args.token,
68
- name: args.name ?? deps?.hostname ?? hostname()
89
+ name: normalizeHostName(args.name ?? storedName ?? deps?.hostname ?? hostname())
69
90
  });
70
91
  if (reply.t === "refused") {
71
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}`);
@@ -76,6 +97,8 @@ async function joinServer(args, deps) {
76
97
  let service;
77
98
  try {
78
99
  await writeServerConfig(dataDir, { origin });
100
+ if (args.name !== undefined)
101
+ await settings.update({ hostName: args.name });
79
102
  const freshSpec = installSpec(["--data-dir", dataDir], {
80
103
  execPath: process.execPath,
81
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,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,7 +14,7 @@ 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: 8 };
17
+ var PROTOCOL = { major: 5, minor: 12 };
26
18
  var CALL_SINCE = {
27
19
  logout: 1,
28
20
  updateSettings: 2,
@@ -31,12 +23,20 @@ var CALL_SINCE = {
31
23
  createAgent: 6,
32
24
  updateAgent: 6,
33
25
  deleteAgent: 6,
34
- setPushToken: 7
26
+ setPushToken: 7,
27
+ listFiles: 10
35
28
  };
36
29
  function hostSupportsCall(hostMinor, method) {
37
30
  return hostMinor >= (CALL_SINCE[method] ?? 0);
38
31
  }
39
- var FEATURE_SINCE = { images: 5, acceptAccountDevices: 8 };
32
+ var FEATURE_SINCE = {
33
+ images: 5,
34
+ acceptAccountDevices: 8,
35
+ apnsPush: 9,
36
+ agentConversations: 10,
37
+ liveBranch: 11,
38
+ hostName: 12
39
+ };
40
40
  function hostSupportsFeature(hostMinor, feature) {
41
41
  return hostMinor >= FEATURE_SINCE[feature];
42
42
  }
@@ -92,14 +92,16 @@ var CALL_METHODS = [
92
92
  "createAgent",
93
93
  "updateAgent",
94
94
  "deleteAgent",
95
- "setPushToken"
95
+ "setPushToken",
96
+ "listFiles"
96
97
  ];
97
98
  var SETTING_VALIDATORS = {
98
99
  defaultModel: (value) => value === null || record(value) && nonempty(value.provider) && nonempty(value.modelId),
99
100
  defaultThinkingLevel: (value) => value === null || isThinkingLevel(value),
100
101
  steeringMode: (value) => value === null || isQueueMode(value),
101
102
  followUpMode: (value) => value === null || isQueueMode(value),
102
- acceptAccountDevices: (value) => value === null || typeof value === "boolean"
103
+ acceptAccountDevices: (value) => value === null || typeof value === "boolean",
104
+ hostName: (value) => value === null || typeof value === "string"
103
105
  };
104
106
  function validCallArgs(method, args) {
105
107
  const conversationId = () => typeof args.conversationId === "number" && Number.isSafeInteger(args.conversationId) && args.conversationId >= 1;
@@ -128,7 +130,9 @@ function validCallArgs(method, args) {
128
130
  case "listFolders":
129
131
  return args.path === undefined || nonempty(args.path);
130
132
  case "createConversation":
131
- 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));
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));
134
+ case "listFiles":
135
+ 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"));
132
136
  case "archive":
133
137
  return conversationId() && typeof args.archived === "boolean";
134
138
  case "renameConversation":
@@ -145,7 +149,7 @@ function validCallArgs(method, args) {
145
149
  case "deleteAgent":
146
150
  return typeof args.name === "string";
147
151
  case "setPushToken":
148
- return args.token === null || typeof args.token === "string";
152
+ return args.token === null || typeof args.token === "string" || record(args.token) && typeof args.token.apns === "string" && (args.token.environment === "production" || args.token.environment === "development");
149
153
  case "updateSettings":
150
154
  return record(args.patch) && Object.keys(args.patch).every((key) => HOST_SETTING_KEYS.includes(key)) && Object.entries(args.patch).every(([key, value]) => SETTING_VALIDATORS[key]?.(value) === true);
151
155
  }
@@ -250,4 +254,4 @@ function homeOf(index, summaries, id) {
250
254
  return;
251
255
  }
252
256
 
253
- 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 };
257
+ export { HOST_STREAM_NAMES, hostStreams, conversationStream, docStream, UNAUTHORIZED_CLOSE_CODE, INVALID_FRAME_CLOSE_CODE, PROTOCOL, hostSupportsCall, hostSupportsFeature, parseClientFrame, organize, checkoutOf, homeOf };
@@ -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 };