toyon 0.20.0 → 0.21.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 (104) hide show
  1. package/README.md +3 -1
  2. package/cloud/Dockerfile +1 -1
  3. package/dist/CHANGELOG.md +46 -0
  4. package/dist/cli.js +295 -140
  5. package/dist/daemon.js +4462 -2808
  6. package/dist/shell/assets/{Editor--Z8ktewC.js → Editor-D_bxx8PL.js} +4 -4
  7. package/dist/shell/assets/Editor-D_bxx8PL.js.br +0 -0
  8. package/dist/shell/assets/Editor-D_bxx8PL.js.gz +0 -0
  9. package/dist/shell/assets/{XTerm-DsUQ0Xew.js → XTerm-DNFNsoHC.js} +1 -1
  10. package/dist/shell/assets/XTerm-DNFNsoHC.js.br +0 -0
  11. package/dist/shell/assets/XTerm-DNFNsoHC.js.gz +0 -0
  12. package/dist/shell/assets/{cssMode-BjAkCPrp.js → cssMode-BxHmuU8y.js} +1 -1
  13. package/dist/shell/assets/cssMode-BxHmuU8y.js.br +0 -0
  14. package/dist/shell/assets/cssMode-BxHmuU8y.js.gz +0 -0
  15. package/dist/shell/assets/{freemarker2-BZCFxXwq.js → freemarker2-Cx9ZoWdp.js} +1 -1
  16. package/dist/shell/assets/freemarker2-Cx9ZoWdp.js.br +0 -0
  17. package/dist/shell/assets/freemarker2-Cx9ZoWdp.js.gz +0 -0
  18. package/dist/shell/assets/{grammar-CQ1uaD6_.js → grammar-DlhzivAk.js} +1 -1
  19. package/dist/shell/assets/grammar-DlhzivAk.js.br +0 -0
  20. package/dist/shell/assets/{grammar-CQ1uaD6_.js.gz → grammar-DlhzivAk.js.gz} +0 -0
  21. package/dist/shell/assets/{handlebars-G1zJeDLP.js → handlebars-D0MhpM71.js} +1 -1
  22. package/dist/shell/assets/handlebars-D0MhpM71.js.br +0 -0
  23. package/dist/shell/assets/handlebars-D0MhpM71.js.gz +0 -0
  24. package/dist/shell/assets/{html-CUHSCFMy.js → html-Da9gyCH4.js} +1 -1
  25. package/dist/shell/assets/html-Da9gyCH4.js.br +0 -0
  26. package/dist/shell/assets/html-Da9gyCH4.js.gz +0 -0
  27. package/dist/shell/assets/{htmlMode-gpMedtd9.js → htmlMode-CGo3ddCJ.js} +1 -1
  28. package/dist/shell/assets/htmlMode-CGo3ddCJ.js.br +0 -0
  29. package/dist/shell/assets/htmlMode-CGo3ddCJ.js.gz +0 -0
  30. package/dist/shell/assets/index-DJ0qL1Ze.css +1 -0
  31. package/dist/shell/assets/index-DJ0qL1Ze.css.br +0 -0
  32. package/dist/shell/assets/index-DJ0qL1Ze.css.gz +0 -0
  33. package/dist/shell/assets/index-DMDdU-hM.js +288 -0
  34. package/dist/shell/assets/index-DMDdU-hM.js.br +0 -0
  35. package/dist/shell/assets/index-DMDdU-hM.js.gz +0 -0
  36. package/dist/shell/assets/{javascript-BHWH8gQN.js → javascript-BXQxakKJ.js} +1 -1
  37. package/dist/shell/assets/{jsonMode-DyV5nvVT.js → jsonMode-DT4ON4ij.js} +1 -1
  38. package/dist/shell/assets/jsonMode-DT4ON4ij.js.br +0 -0
  39. package/dist/shell/assets/jsonMode-DT4ON4ij.js.gz +0 -0
  40. package/dist/shell/assets/{liquid-Dy3tUgRC.js → liquid-Bm6Hq-FF.js} +1 -1
  41. package/dist/shell/assets/liquid-Bm6Hq-FF.js.br +0 -0
  42. package/dist/shell/assets/liquid-Bm6Hq-FF.js.gz +0 -0
  43. package/dist/shell/assets/{lspLanguageFeatures-CB_Ow-w5.js → lspLanguageFeatures-DWvAzXxS.js} +1 -1
  44. package/dist/shell/assets/lspLanguageFeatures-DWvAzXxS.js.br +0 -0
  45. package/dist/shell/assets/lspLanguageFeatures-DWvAzXxS.js.gz +0 -0
  46. package/dist/shell/assets/{mdx-DD5jgco_.js → mdx-DNcDUEs7.js} +1 -1
  47. package/dist/shell/assets/mdx-DNcDUEs7.js.br +0 -0
  48. package/dist/shell/assets/mdx-DNcDUEs7.js.gz +0 -0
  49. package/dist/shell/assets/{python-BvLKUXll.js → python-BgETNCIe.js} +1 -1
  50. package/dist/shell/assets/python-BgETNCIe.js.br +0 -0
  51. package/dist/shell/assets/python-BgETNCIe.js.gz +0 -0
  52. package/dist/shell/assets/{razor-B2tUlbzR.js → razor-Cf_D69YI.js} +1 -1
  53. package/dist/shell/assets/razor-Cf_D69YI.js.br +0 -0
  54. package/dist/shell/assets/razor-Cf_D69YI.js.gz +0 -0
  55. package/dist/shell/assets/{tsMode-DMuNnqM7.js → tsMode-BQA_zU_Q.js} +1 -1
  56. package/dist/shell/assets/tsMode-BQA_zU_Q.js.br +0 -0
  57. package/dist/shell/assets/tsMode-BQA_zU_Q.js.gz +0 -0
  58. package/dist/shell/assets/{xml-DdYgb5a2.js → xml-DrZtVQVT.js} +1 -1
  59. package/dist/shell/assets/xml-DrZtVQVT.js.br +0 -0
  60. package/dist/shell/assets/xml-DrZtVQVT.js.gz +0 -0
  61. package/dist/shell/assets/{yaml-BDujJdoL.js → yaml-CzR4Kx36.js} +1 -1
  62. package/dist/shell/assets/yaml-CzR4Kx36.js.br +0 -0
  63. package/dist/shell/assets/yaml-CzR4Kx36.js.gz +0 -0
  64. package/dist/shell/index.html +2 -2
  65. package/package.json +1 -1
  66. package/dist/shell/assets/Editor--Z8ktewC.js.br +0 -0
  67. package/dist/shell/assets/Editor--Z8ktewC.js.gz +0 -0
  68. package/dist/shell/assets/XTerm-DsUQ0Xew.js.br +0 -0
  69. package/dist/shell/assets/XTerm-DsUQ0Xew.js.gz +0 -0
  70. package/dist/shell/assets/cssMode-BjAkCPrp.js.br +0 -0
  71. package/dist/shell/assets/cssMode-BjAkCPrp.js.gz +0 -0
  72. package/dist/shell/assets/freemarker2-BZCFxXwq.js.br +0 -0
  73. package/dist/shell/assets/freemarker2-BZCFxXwq.js.gz +0 -0
  74. package/dist/shell/assets/grammar-CQ1uaD6_.js.br +0 -0
  75. package/dist/shell/assets/handlebars-G1zJeDLP.js.br +0 -0
  76. package/dist/shell/assets/handlebars-G1zJeDLP.js.gz +0 -0
  77. package/dist/shell/assets/html-CUHSCFMy.js.br +0 -0
  78. package/dist/shell/assets/html-CUHSCFMy.js.gz +0 -0
  79. package/dist/shell/assets/htmlMode-gpMedtd9.js.br +0 -0
  80. package/dist/shell/assets/htmlMode-gpMedtd9.js.gz +0 -0
  81. package/dist/shell/assets/index-B3Alj0WH.css +0 -1
  82. package/dist/shell/assets/index-B3Alj0WH.css.br +0 -0
  83. package/dist/shell/assets/index-B3Alj0WH.css.gz +0 -0
  84. package/dist/shell/assets/index-VVd_p5tJ.js +0 -278
  85. package/dist/shell/assets/index-VVd_p5tJ.js.br +0 -0
  86. package/dist/shell/assets/index-VVd_p5tJ.js.gz +0 -0
  87. package/dist/shell/assets/jsonMode-DyV5nvVT.js.br +0 -0
  88. package/dist/shell/assets/jsonMode-DyV5nvVT.js.gz +0 -0
  89. package/dist/shell/assets/liquid-Dy3tUgRC.js.br +0 -0
  90. package/dist/shell/assets/liquid-Dy3tUgRC.js.gz +0 -0
  91. package/dist/shell/assets/lspLanguageFeatures-CB_Ow-w5.js.br +0 -0
  92. package/dist/shell/assets/lspLanguageFeatures-CB_Ow-w5.js.gz +0 -0
  93. package/dist/shell/assets/mdx-DD5jgco_.js.br +0 -0
  94. package/dist/shell/assets/mdx-DD5jgco_.js.gz +0 -0
  95. package/dist/shell/assets/python-BvLKUXll.js.br +0 -0
  96. package/dist/shell/assets/python-BvLKUXll.js.gz +0 -0
  97. package/dist/shell/assets/razor-B2tUlbzR.js.br +0 -0
  98. package/dist/shell/assets/razor-B2tUlbzR.js.gz +0 -0
  99. package/dist/shell/assets/tsMode-DMuNnqM7.js.br +0 -0
  100. package/dist/shell/assets/tsMode-DMuNnqM7.js.gz +0 -0
  101. package/dist/shell/assets/xml-DdYgb5a2.js.br +0 -0
  102. package/dist/shell/assets/xml-DdYgb5a2.js.gz +0 -0
  103. package/dist/shell/assets/yaml-BDujJdoL.js.br +0 -0
  104. package/dist/shell/assets/yaml-BDujJdoL.js.gz +0 -0
package/dist/cli.js CHANGED
@@ -2,70 +2,8 @@
2
2
  // @bun
3
3
 
4
4
  // src/cli.ts
5
- import { existsSync as existsSync12, statSync as statSync5 } from "fs";
6
- import { join as join7, resolve } from "path";
7
- // package.json
8
- var package_default = {
9
- name: "toyon",
10
- version: "0.20.0",
11
- description: "Toyon is where coding agents work on your web app while you check each change by using it. Every chat gets its own running copy with a live preview.",
12
- license: "MIT",
13
- type: "module",
14
- homepage: "https://toyon.dev",
15
- repository: {
16
- type: "git",
17
- url: "git+https://github.com/toyon-dev/toyon.git"
18
- },
19
- bugs: "https://github.com/toyon-dev/toyon/issues",
20
- keywords: [
21
- "ai",
22
- "agents",
23
- "claude-code",
24
- "codex",
25
- "opencode",
26
- "git",
27
- "worktree",
28
- "preview",
29
- "dev-server"
30
- ],
31
- bin: {
32
- toyon: "bin/toyon.js"
33
- },
34
- files: [
35
- "bin",
36
- "dist",
37
- "cloud"
38
- ],
39
- engines: {
40
- node: ">=18"
41
- },
42
- os: [
43
- "darwin",
44
- "linux"
45
- ],
46
- scripts: {
47
- prepublishOnly: "bun ../../scripts/pack.ts"
48
- },
49
- dependencies: {
50
- bun: "1.4.4",
51
- "bun-pty": "0.4.10"
52
- },
53
- devDependencies: {
54
- "@toyon/shared": "workspace:*"
55
- }
56
- };
57
-
58
- // src/app.ts
59
- import { spawn as spawn3, spawnSync } from "child_process";
60
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
61
- import { homedir as homedir2 } from "os";
62
- import { join as join3 } from "path";
63
-
64
- // src/daemon.ts
65
- import { spawn } from "child_process";
66
- import { existsSync as existsSync3, mkdirSync, readFileSync } from "fs";
67
- import { homedir } from "os";
68
- import { join as join2 } from "path";
5
+ import { existsSync as existsSync13, statSync as statSync5 } from "fs";
6
+ import { join as join8, resolve } from "path";
69
7
 
70
8
  // ../shared/src/knock.ts
71
9
  var KNOCK_TTL_MS = 5 * 60000;
@@ -104,6 +42,8 @@ function acceptRemote(view, policy) {
104
42
  return why ?? { host, previews: view.previews };
105
43
  }
106
44
  var PREVIEW_PORTS = { from: 10001, to: 10008 };
45
+ var FRAME_PORT = 1e4;
46
+ var FRAME_LABEL = "frames";
107
47
  var DNS_NAME = /^(?=.{1,253}$)[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$/;
108
48
  function isRemoteHost(name) {
109
49
  return DNS_NAME.test(name) && !/\.\d+$/.test(name) && name !== "localhost" && !name.endsWith(".localhost");
@@ -129,6 +69,9 @@ function checkPreviews(previews, host) {
129
69
  if (hostname !== host && !hostname.endsWith(`.${host}`)) {
130
70
  return `previews at ${hostname} are not ${host} or under it, so the preview cookie cannot reach them`;
131
71
  }
72
+ if (previews.includes("{id}") && new URL(previewOrigin(previews, FRAME_LABEL, 1)).hostname === `${FRAME_LABEL}.${host}`) {
73
+ return `${previews} would put a preview at ${FRAME_LABEL}.${host}, which is where rendered pages live`;
74
+ }
132
75
  return null;
133
76
  }
134
77
  function previewOrigin(previews, worktreeId, port) {
@@ -5171,6 +5114,100 @@ function describeManaged(p) {
5171
5114
  // ../shared/src/ports.ts
5172
5115
  var DAEMON_DEFAULT_PORT = 4141;
5173
5116
 
5117
+ // ../shared/src/project.ts
5118
+ function hasControlChar(s) {
5119
+ for (const ch of s) {
5120
+ const code = ch.codePointAt(0) ?? 0;
5121
+ if (code < 32 || code === 127)
5122
+ return true;
5123
+ }
5124
+ return false;
5125
+ }
5126
+ function projectNameError(raw) {
5127
+ const name = raw.trim();
5128
+ if (!name)
5129
+ return "a project name is required";
5130
+ if (name.length > 100)
5131
+ return "that name is too long: keep it under 100 characters";
5132
+ if (name.includes("/") || name.includes("\\"))
5133
+ return "a project name cannot contain a slash";
5134
+ if (/\s/.test(name))
5135
+ return "a project name cannot contain spaces";
5136
+ if (hasControlChar(name))
5137
+ return "a project name cannot contain control characters";
5138
+ if (name === "." || name === "..")
5139
+ return `"${name}" is not a project name`;
5140
+ if (name.startsWith("-") || name.startsWith("."))
5141
+ return "a project name cannot start with a dot or a dash";
5142
+ return null;
5143
+ }
5144
+ var URL_FORM = /^(?:https?|ssh|git|file):\/\/\S+$/i;
5145
+ var SCP_FORM = /^[\w.-]+@[\w.-]+:\S+$/;
5146
+ var BARE_HOST_FORM = /^[\w-]+(?:\.[\w-]+)+\/\S+$/;
5147
+ function gitUrl(raw) {
5148
+ const typed = raw.trim();
5149
+ const url = BARE_HOST_FORM.test(typed) ? `https://${typed}` : typed;
5150
+ if (!URL_FORM.test(url) && !SCP_FORM.test(url))
5151
+ return null;
5152
+ const page = fromForgePage(url);
5153
+ const name = repoNameFrom(page?.url ?? url);
5154
+ if (!name || projectNameError(name))
5155
+ return null;
5156
+ return page ? { url: page.url, name, ...page.branch ? { branch: page.branch } : {} } : { url, name };
5157
+ }
5158
+ var FORGES = {
5159
+ "github.com": (segs) => {
5160
+ if (segs.length < 2)
5161
+ return null;
5162
+ const [kind, ...rest] = segs.slice(2);
5163
+ const branch = kind === "tree" ? rest.join("/") : kind === "blob" || kind === "commits" ? rest[0] : undefined;
5164
+ return { repo: segs.slice(0, 2), branch };
5165
+ },
5166
+ "gitlab.com": (segs) => {
5167
+ const cut = segs.indexOf("-");
5168
+ if (cut < 2)
5169
+ return segs.length >= 2 && cut < 0 ? { repo: segs } : null;
5170
+ const [kind, ...rest] = segs.slice(cut + 1);
5171
+ const branch = kind === "tree" ? rest.join("/") : kind === "blob" || kind === "commits" ? rest[0] : undefined;
5172
+ return { repo: segs.slice(0, cut), branch };
5173
+ },
5174
+ "codeberg.org": (segs) => {
5175
+ if (segs.length < 2)
5176
+ return null;
5177
+ const [kind, what, ...rest] = segs.slice(2);
5178
+ const branch = kind === "src" && what === "branch" ? rest[0] : undefined;
5179
+ return { repo: segs.slice(0, 2), branch };
5180
+ }
5181
+ };
5182
+ function fromForgePage(url) {
5183
+ if (!/^https?:\/\//i.test(url))
5184
+ return null;
5185
+ let u;
5186
+ try {
5187
+ u = new URL(url);
5188
+ } catch {
5189
+ return null;
5190
+ }
5191
+ const read = FORGES[u.hostname.toLowerCase()]?.(u.pathname.split("/").filter(Boolean));
5192
+ if (!read)
5193
+ return null;
5194
+ const branch = read.branch ? decodeSegment(read.branch).replace(/\/+$/, "") : "";
5195
+ return { url: `${u.protocol}//${u.host}/${read.repo.join("/")}`, ...branch ? { branch } : {} };
5196
+ }
5197
+ function decodeSegment(s) {
5198
+ try {
5199
+ return decodeURIComponent(s);
5200
+ } catch {
5201
+ return s;
5202
+ }
5203
+ }
5204
+ function repoNameFrom(url) {
5205
+ const bare = url.split(/[?#]/)[0]?.replace(/\/+$/, "") ?? "";
5206
+ const afterHost = SCP_FORM.test(bare) ? bare.split(":")[1] ?? "" : bare;
5207
+ const last = afterHost.split("/").pop() ?? "";
5208
+ return last.replace(/\.git$/i, "");
5209
+ }
5210
+
5174
5211
  // ../shared/src/sandbox.ts
5175
5212
  var BWRAP_APPARMOR_PROFILE = `abi <abi/4.0>,
5176
5213
  include <tunables/global>
@@ -5250,6 +5287,68 @@ function parseVersion(v) {
5250
5287
  return null;
5251
5288
  return { nums: [Number(m[1]), Number(m[2]), Number(m[3])], pre: m[4] ?? null };
5252
5289
  }
5290
+ // package.json
5291
+ var package_default = {
5292
+ name: "toyon",
5293
+ version: "0.21.0",
5294
+ description: "Toyon is where coding agents work on your web app while you check each change by using it. Every chat gets its own running copy with a live preview.",
5295
+ license: "MIT",
5296
+ type: "module",
5297
+ homepage: "https://toyon.dev",
5298
+ repository: {
5299
+ type: "git",
5300
+ url: "git+https://github.com/toyon-dev/toyon.git"
5301
+ },
5302
+ bugs: "https://github.com/toyon-dev/toyon/issues",
5303
+ keywords: [
5304
+ "ai",
5305
+ "agents",
5306
+ "claude-code",
5307
+ "codex",
5308
+ "opencode",
5309
+ "git",
5310
+ "worktree",
5311
+ "preview",
5312
+ "dev-server"
5313
+ ],
5314
+ bin: {
5315
+ toyon: "bin/toyon.js"
5316
+ },
5317
+ files: [
5318
+ "bin",
5319
+ "dist",
5320
+ "cloud"
5321
+ ],
5322
+ engines: {
5323
+ node: ">=18"
5324
+ },
5325
+ os: [
5326
+ "darwin",
5327
+ "linux"
5328
+ ],
5329
+ scripts: {
5330
+ prepublishOnly: "bun ../../scripts/pack.ts"
5331
+ },
5332
+ dependencies: {
5333
+ bun: "1.4.4",
5334
+ "bun-pty": "0.4.10"
5335
+ },
5336
+ devDependencies: {
5337
+ "@toyon/shared": "workspace:*"
5338
+ }
5339
+ };
5340
+
5341
+ // src/app.ts
5342
+ import { spawn as spawn3, spawnSync } from "child_process";
5343
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
5344
+ import { homedir as homedir2 } from "os";
5345
+ import { join as join4 } from "path";
5346
+
5347
+ // src/daemon.ts
5348
+ import { spawn } from "child_process";
5349
+ import { existsSync as existsSync4, mkdirSync, readFileSync } from "fs";
5350
+ import { homedir } from "os";
5351
+ import { join as join3 } from "path";
5253
5352
 
5254
5353
  // ../shared/src/logfile.ts
5255
5354
  import { chmodSync, existsSync, openSync, renameSync, statSync } from "fs";
@@ -5264,14 +5363,15 @@ function openLog(path, maxBytes = LOG_ROTATE_BYTES) {
5264
5363
  }
5265
5364
 
5266
5365
  // src/layout.ts
5267
- import { existsSync as existsSync2, realpathSync } from "fs";
5268
- import { delimiter, dirname, join } from "path";
5366
+ import { existsSync as existsSync3 } from "fs";
5367
+ import { dirname, join as join2 } from "path";
5269
5368
  import { fileURLToPath } from "url";
5270
- var here = dirname(fileURLToPath(import.meta.url));
5271
- var packaged = existsSync2(join(here, "daemon.js"));
5272
- var method = installMethod(packaged ? join(here, "..", "package.json") : null);
5273
- function installOnPath() {
5274
- for (const dir of (process.env.PATH ?? "").split(delimiter)) {
5369
+
5370
+ // ../shared/src/install-path.ts
5371
+ import { existsSync as existsSync2, realpathSync } from "fs";
5372
+ import { delimiter, join } from "path";
5373
+ function installOnPath(path = process.env.PATH ?? "") {
5374
+ for (const dir of path.split(delimiter)) {
5275
5375
  if (dir === "" || dir.includes("/_npx/"))
5276
5376
  continue;
5277
5377
  const bin = join(dir, "toyon");
@@ -5283,17 +5383,22 @@ function installOnPath() {
5283
5383
  }
5284
5384
  return "none";
5285
5385
  }
5386
+
5387
+ // src/layout.ts
5388
+ var here = dirname(fileURLToPath(import.meta.url));
5389
+ var packaged = existsSync3(join2(here, "daemon.js"));
5390
+ var method = installMethod(packaged ? join2(here, "..", "package.json") : null);
5286
5391
  var restartCmd = restartCommand(method, package_default.version, method === "npx" ? installOnPath() : "none", process.env.npm_config_registry ?? null);
5287
- var daemonEntry = packaged ? join(here, "daemon.js") : join(here, "../../daemon/src/index.ts");
5392
+ var daemonEntry = packaged ? join2(here, "daemon.js") : join2(here, "../../daemon/src/index.ts");
5288
5393
 
5289
5394
  // src/daemon.ts
5290
5395
  var port = Number(process.env.TOYON_PORT ?? DAEMON_DEFAULT_PORT);
5291
5396
  var base = `http://127.0.0.1:${port}`;
5292
- var home = process.env.TOYON_HOME ?? join2(homedir(), ".toyon");
5293
- var tokenFile = join2(home, DAEMON_FILES.token);
5294
- var pidFile = join2(home, DAEMON_FILES.pid);
5295
- var logFile = join2(home, DAEMON_FILES.log);
5296
- var remoteFile = join2(home, DAEMON_FILES.remote);
5397
+ var home = process.env.TOYON_HOME ?? join3(homedir(), ".toyon");
5398
+ var tokenFile = join3(home, DAEMON_FILES.token);
5399
+ var pidFile = join3(home, DAEMON_FILES.pid);
5400
+ var logFile = join3(home, DAEMON_FILES.log);
5401
+ var remoteFile = join3(home, DAEMON_FILES.remote);
5297
5402
  async function health() {
5298
5403
  try {
5299
5404
  const r = await fetch(`${base}/health`, { signal: AbortSignal.timeout(1000) });
@@ -5322,13 +5427,13 @@ async function daemonFetch(path, init = {}) {
5322
5427
  }
5323
5428
  }
5324
5429
  function readToken() {
5325
- if (!existsSync3(tokenFile))
5430
+ if (!existsSync4(tokenFile))
5326
5431
  return null;
5327
5432
  const t = readFileSync(tokenFile, "utf8").trim();
5328
5433
  return t === "" ? null : t;
5329
5434
  }
5330
5435
  function readPid() {
5331
- if (!existsSync3(pidFile))
5436
+ if (!existsSync4(pidFile))
5332
5437
  return null;
5333
5438
  const n = Number(readFileSync(pidFile, "utf8").trim());
5334
5439
  return Number.isInteger(n) && n > 0 ? n : null;
@@ -5411,19 +5516,19 @@ var CHROMIUMS = [
5411
5516
  { name: "Microsoft Edge", dataDir: "Microsoft Edge", bundleId: "com.microsoft.edgemac" },
5412
5517
  { name: "Chromium", dataDir: "Chromium", bundleId: "org.chromium.chromium" }
5413
5518
  ];
5414
- var pwaCacheDir = join3(home, "pwa");
5519
+ var pwaCacheDir = join4(home, "pwa");
5415
5520
  function profilesOf(browser) {
5416
- const root = join3(homedir2(), "Library", "Application Support", browser.dataDir);
5417
- if (!existsSync4(root))
5521
+ const root = join4(homedir2(), "Library", "Application Support", browser.dataDir);
5522
+ if (!existsSync5(root))
5418
5523
  return [];
5419
- return readdirSync(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join3(root, d.name));
5524
+ return readdirSync(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join4(root, d.name));
5420
5525
  }
5421
5526
  function pwaStillInstalled(browser, id) {
5422
- return profilesOf(browser).some((p) => existsSync4(join3(p, "Web Applications", "Manifest Resources", id)));
5527
+ return profilesOf(browser).some((p) => existsSync5(join4(p, "Web Applications", "Manifest Resources", id)));
5423
5528
  }
5424
5529
  function findPwaId(browser) {
5425
- const cache = join3(pwaCacheDir, `${browser.name.replace(/\W+/g, "-")}.id`);
5426
- if (existsSync4(cache)) {
5530
+ const cache = join4(pwaCacheDir, `${browser.name.replace(/\W+/g, "-")}.id`);
5531
+ if (existsSync5(cache)) {
5427
5532
  const id = readFileSync2(cache, "utf8").trim();
5428
5533
  if (/^[a-p]{32}$/.test(id) && pwaStillInstalled(browser, id))
5429
5534
  return id;
@@ -5431,13 +5536,13 @@ function findPwaId(browser) {
5431
5536
  const ids = shellOrigins.map((o) => `${o}/`.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&")).join("|");
5432
5537
  const re = new RegExp(`web_apps-dt-([a-p]{32})[\\s\\S]{0,16}?(?:${ids})`);
5433
5538
  for (const p of profilesOf(browser)) {
5434
- const dir = join3(p, "Sync Data", "LevelDB");
5435
- if (!existsSync4(dir))
5539
+ const dir = join4(p, "Sync Data", "LevelDB");
5540
+ if (!existsSync5(dir))
5436
5541
  continue;
5437
5542
  for (const f of readdirSync(dir)) {
5438
5543
  if (!/\.(log|ldb)$/.test(f))
5439
5544
  continue;
5440
- const m = re.exec(readFileSync2(join3(dir, f), "latin1"));
5545
+ const m = re.exec(readFileSync2(join4(dir, f), "latin1"));
5441
5546
  if (m && pwaStillInstalled(browser, m[1])) {
5442
5547
  mkdirSync2(pwaCacheDir, { recursive: true });
5443
5548
  writeFileSync(cache, m[1]);
@@ -5469,16 +5574,16 @@ function focusShell() {
5469
5574
  child.unref();
5470
5575
  }
5471
5576
  function installedAppBundle() {
5472
- const apps = join3(homedir2(), "Applications");
5473
- const shelves = existsSync4(apps) ? readdirSync(apps, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.endsWith(".localized")).map((d) => join3(apps, d.name)) : [];
5577
+ const apps = join4(homedir2(), "Applications");
5578
+ const shelves = existsSync5(apps) ? readdirSync(apps, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.endsWith(".localized")).map((d) => join4(apps, d.name)) : [];
5474
5579
  for (const app of CHROMIUMS) {
5475
5580
  const id = findPwaId(app);
5476
5581
  if (!id)
5477
5582
  continue;
5478
- const kept = profilesOf(app).map((p) => join3(p, "Web Applications", `_crx_${id}`));
5583
+ const kept = profilesOf(app).map((p) => join4(p, "Web Applications", `_crx_${id}`));
5479
5584
  const hit = [...shelves, ...kept].flatMap(bundlesIn).find((b) => {
5480
5585
  try {
5481
- return readFileSync2(join3(b, "Contents", "Info.plist"), "latin1").includes(id);
5586
+ return readFileSync2(join4(b, "Contents", "Info.plist"), "latin1").includes(id);
5482
5587
  } catch {
5483
5588
  return false;
5484
5589
  }
@@ -5489,9 +5594,9 @@ function installedAppBundle() {
5489
5594
  return null;
5490
5595
  }
5491
5596
  function bundlesIn(dir) {
5492
- if (!existsSync4(dir))
5597
+ if (!existsSync5(dir))
5493
5598
  return [];
5494
- return readdirSync(dir).filter((n) => n.endsWith(".app")).map((n) => join3(dir, n));
5599
+ return readdirSync(dir).filter((n) => n.endsWith(".app")).map((n) => join4(dir, n));
5495
5600
  }
5496
5601
  function launchInstalledApp() {
5497
5602
  let seen = null;
@@ -5508,7 +5613,7 @@ function launchInstalledApp() {
5508
5613
  return seen ? { kind: "uninstalled", browser: seen } : { kind: "none" };
5509
5614
  }
5510
5615
  function readLinkHandlers() {
5511
- const plist = join3(homedir2(), "Library", "Preferences", "com.apple.LaunchServices", "com.apple.launchservices.secure.plist");
5616
+ const plist = join4(homedir2(), "Library", "Preferences", "com.apple.LaunchServices", "com.apple.launchservices.secure.plist");
5512
5617
  const out = spawnSync("plutil", ["-convert", "json", "-o", "-", plist], { encoding: "utf8" });
5513
5618
  if (out.status !== 0)
5514
5619
  return null;
@@ -5679,6 +5784,9 @@ usage
5679
5784
  toyon [path] start the daemon if it is not running, register the repo at path
5680
5785
  (or open the file at path: in its project, or on its own)
5681
5786
  (default: the current directory) and open the shell
5787
+ toyon <git url> open the shell with that repository ready to clone into the current
5788
+ directory; a page on GitHub, GitLab or Codeberg is read back to its
5789
+ repository, and to its branch
5682
5790
  toyon start start the daemon if it is not running, and open nothing
5683
5791
  toyon stop stop the daemon and every dev server and agent it runs
5684
5792
  toyon restart stop the daemon and start it again from what is installed now; every
@@ -5723,9 +5831,9 @@ policy
5723
5831
  // src/deploy/fly.ts
5724
5832
  import { randomBytes } from "crypto";
5725
5833
  import { Resolver } from "dns/promises";
5726
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, mkdtempSync, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "fs";
5834
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, mkdtempSync, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "fs";
5727
5835
  import { tmpdir } from "os";
5728
- import { join as join5 } from "path";
5836
+ import { join as join6 } from "path";
5729
5837
 
5730
5838
  // ../shared/src/managed-load.ts
5731
5839
  import { readFileSync as readFileSync3, statSync as statSync2 } from "fs";
@@ -5783,12 +5891,12 @@ function refusedByPolicy(verb, m) {
5783
5891
  }
5784
5892
 
5785
5893
  // src/deploy/context.ts
5786
- import { cpSync, existsSync as existsSync5, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
5787
- import { join as join4 } from "path";
5894
+ import { cpSync, existsSync as existsSync6, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
5895
+ import { join as join5 } from "path";
5788
5896
  var bunVersion = package_default.dependencies.bun;
5789
5897
  function machineSources() {
5790
- const dist = packaged ? here : join4(here, "..", "dist");
5791
- return existsSync5(join4(dist, "daemon.js")) ? { dist, cloud: join4(dist, "..", "cloud") } : null;
5898
+ const dist = packaged ? here : join5(here, "..", "dist");
5899
+ return existsSync6(join5(dist, "daemon.js")) ? { dist, cloud: join5(dist, "..", "cloud") } : null;
5792
5900
  }
5793
5901
  function machinePackageJson() {
5794
5902
  const manifest = {
@@ -5802,18 +5910,18 @@ function machinePackageJson() {
5802
5910
  }
5803
5911
  function writeMachineContext(src, out) {
5804
5912
  mkdirSync3(out, { recursive: true });
5805
- cpSync(src.dist, join4(out, "dist"), { recursive: true });
5806
- cpSync(join4(src.cloud, "Dockerfile"), join4(out, "Dockerfile"));
5807
- cpSync(join4(src.cloud, "entrypoint.sh"), join4(out, "entrypoint.sh"));
5808
- writeFileSync2(join4(out, "package.json"), machinePackageJson());
5913
+ cpSync(src.dist, join5(out, "dist"), { recursive: true });
5914
+ cpSync(join5(src.cloud, "Dockerfile"), join5(out, "Dockerfile"));
5915
+ cpSync(join5(src.cloud, "entrypoint.sh"), join5(out, "entrypoint.sh"));
5916
+ writeFileSync2(join5(out, "package.json"), machinePackageJson());
5809
5917
  }
5810
5918
 
5811
5919
  // src/deploy/fly.ts
5812
5920
  var DAEMON_PORT = 4141;
5813
5921
  var VOLUME = "toyon_data";
5814
5922
  var VOLUME_GB = 5;
5815
- var cloudDir = join5(home, "cloud");
5816
- var tokenFile2 = (app) => join5(cloudDir, `${app}.token`);
5923
+ var cloudDir = join6(home, "cloud");
5924
+ var tokenFile2 = (app) => join6(cloudDir, `${app}.token`);
5817
5925
 
5818
5926
  class DeployError extends Error {
5819
5927
  }
@@ -5857,7 +5965,10 @@ function flyToml(app, region) {
5857
5965
  ' method = "get"',
5858
5966
  ' path = "/health"'
5859
5967
  ];
5860
- for (let p = PREVIEW_PORTS.from;p <= PREVIEW_PORTS.to; p++) {
5968
+ const ports = [FRAME_PORT];
5969
+ for (let p = PREVIEW_PORTS.from;p <= PREVIEW_PORTS.to; p++)
5970
+ ports.push(p);
5971
+ for (const p of ports) {
5861
5972
  lines.push("", "[[services]]", ` internal_port = ${p}`, ' protocol = "tcp"', ' auto_stop_machines = "stop"', " auto_start_machines = true", " min_machines_running = 0", " [[services.ports]]", ` port = ${p}`, ' handlers = ["tls", "http"]');
5862
5973
  }
5863
5974
  return `${lines.join(`
@@ -5892,8 +6003,8 @@ function secret(env, file) {
5892
6003
  const fromEnv = process.env[env]?.trim();
5893
6004
  if (fromEnv)
5894
6005
  return fromEnv;
5895
- const path = join5(cloudDir, file);
5896
- return existsSync6(path) ? readFileSync4(path, "utf8").trim() || null : null;
6006
+ const path = join6(cloudDir, file);
6007
+ return existsSync7(path) ? readFileSync4(path, "utf8").trim() || null : null;
5897
6008
  }
5898
6009
  async function capture(bin, args, input) {
5899
6010
  const p = Bun.spawn([bin, ...args], {
@@ -5958,7 +6069,7 @@ async function up(cmd, bin) {
5958
6069
  throw new DeployError("could not tell which Fly region is closest; pass --region (`fly platform regions` lists them)");
5959
6070
  console.log(cmd.region ? `region ${region}` : `region ${region}, the closest to you; --region picks another`);
5960
6071
  mkdirSync4(cloudDir, { recursive: true, mode: 448 });
5961
- if (!existsSync6(tokenFile2(cmd.name)))
6072
+ if (!existsSync7(tokenFile2(cmd.name)))
5962
6073
  writeFileSync3(tokenFile2(cmd.name), randomBytes(32).toString("hex"), { mode: 384 });
5963
6074
  const token = readFileSync4(tokenFile2(cmd.name), "utf8").trim();
5964
6075
  const app = ["-a", cmd.name];
@@ -5994,12 +6105,12 @@ async function up(cmd, bin) {
5994
6105
  must(await capture(bin, ["ips", "allocate-v4", "--shared", ...app]), "could not add an IPv4 address");
5995
6106
  if (!/"Type":\s*"v6"/.test(ips))
5996
6107
  must(await capture(bin, ["ips", "allocate-v6", ...app]), "could not add an IPv6 address");
5997
- const ctx = mkdtempSync(join5(tmpdir(), "toyon-machine-"));
6108
+ const ctx = mkdtempSync(join6(tmpdir(), "toyon-machine-"));
5998
6109
  try {
5999
6110
  writeMachineContext(src, ctx);
6000
- writeFileSync3(join5(ctx, "fly.toml"), flyToml(cmd.name, region));
6111
+ writeFileSync3(join6(ctx, "fly.toml"), flyToml(cmd.name, region));
6001
6112
  console.log("building the machine in your Fly builder and deploying it");
6002
- const deployArgs = ["deploy", ctx, "--config", join5(ctx, "fly.toml"), "--remote-only", "--ha=false", "--yes"];
6113
+ const deployArgs = ["deploy", ctx, "--config", join6(ctx, "fly.toml"), "--remote-only", "--ha=false", "--yes"];
6003
6114
  if (!await show(bin, [...deployArgs, "--build-arg", `BUN_VERSION=${bunVersion}`])) {
6004
6115
  throw new DeployError("the deploy failed; flyctl's output above says why");
6005
6116
  }
@@ -6066,7 +6177,7 @@ async function deploy(cmd) {
6066
6177
  await up(cmd, bin);
6067
6178
  else if (cmd.action === "destroy")
6068
6179
  return await destroy(cmd, bin);
6069
- else if (existsSync6(tokenFile2(cmd.name))) {
6180
+ else if (existsSync7(tokenFile2(cmd.name))) {
6070
6181
  await printLinks(cmd.name, readFileSync4(tokenFile2(cmd.name), "utf8").trim());
6071
6182
  } else {
6072
6183
  throw new DeployError(`${cmd.name} was not deployed from this machine; its token is not in ${cloudDir}`);
@@ -6081,7 +6192,7 @@ async function deploy(cmd) {
6081
6192
  }
6082
6193
 
6083
6194
  // src/doctor.ts
6084
- import { existsSync as existsSync7, statSync as statSync3 } from "fs";
6195
+ import { existsSync as existsSync8, statSync as statSync3 } from "fs";
6085
6196
 
6086
6197
  // src/sandboxDeps.ts
6087
6198
  import { readFileSync as readFileSync5 } from "fs";
@@ -6131,6 +6242,34 @@ async function toolVersion(cmd, args) {
6131
6242
  return null;
6132
6243
  }
6133
6244
  }
6245
+ var GH_AUTH_TIMEOUT_MS = 8000;
6246
+ async function ghStatus() {
6247
+ const version = await toolVersion("gh", ["--version"]);
6248
+ if (!version)
6249
+ return { kind: "missing" };
6250
+ try {
6251
+ const p = Bun.spawn(["gh", "auth", "status"], {
6252
+ stdout: "ignore",
6253
+ stderr: "ignore",
6254
+ env: { ...process.env, GH_PROMPT_DISABLED: "1", GH_NO_UPDATE_NOTIFIER: "1" },
6255
+ timeout: GH_AUTH_TIMEOUT_MS
6256
+ });
6257
+ await p.exited;
6258
+ return p.exitCode === 0 ? { kind: "ready", version } : { kind: "signedOut", version };
6259
+ } catch {
6260
+ return { kind: "signedOut", version };
6261
+ }
6262
+ }
6263
+ function githubLine(status, platform = process.platform) {
6264
+ const install = platform === "darwin" ? "brew install gh" : "sudo apt install gh";
6265
+ if (status.kind === "missing") {
6266
+ return line(true, "github", `gh is not installed, so the pull request land route pushes and leaves the pull request to you; \`${install}\`, then \`gh auth login\``);
6267
+ }
6268
+ if (status.kind === "signedOut") {
6269
+ return line(true, "github", `${status.version}, but not signed in or GitHub was out of reach, so the pull request land route pushes and leaves the pull request to you; \`gh auth login\``);
6270
+ }
6271
+ return line(true, "github", `${status.version}, signed in; the pull request land route can open and merge pull requests`);
6272
+ }
6134
6273
  function handshake(token) {
6135
6274
  return new Promise((resolve) => {
6136
6275
  const ws = new WebSocket(`ws://127.0.0.1:${port}/ws?token=${token}`);
@@ -6185,6 +6324,7 @@ async function doctor() {
6185
6324
  const managed = await loadManaged();
6186
6325
  const git = await toolVersion("git", ["--version"]);
6187
6326
  lines.push(git ? line(true, "git", git) : line(false, "git", "not found on PATH; Toyon needs git 2.x"));
6327
+ lines.push(githubLine(await ghStatus()));
6188
6328
  if (process.platform === "linux") {
6189
6329
  const missing = missingSandboxTools();
6190
6330
  const blocked = missing.length === 0 ? bwrapStartError() : null;
@@ -6243,10 +6383,10 @@ async function doctor() {
6243
6383
  lines.push(line(false, "shell", `could not fetch: ${e.message}`));
6244
6384
  }
6245
6385
  }
6246
- if (existsSync7(logFile)) {
6386
+ if (existsSync8(logFile)) {
6247
6387
  const kb = (p) => Math.round(statSync3(p).size / 1024);
6248
6388
  const before = previousLog(logFile);
6249
- const kept = existsSync7(before) ? `, and ${kb(before)} KB kept from before` : "";
6389
+ const kept = existsSync8(before) ? `, and ${kb(before)} KB kept from before` : "";
6250
6390
  lines.push(line(true, "log", `${logFile} (${kb(logFile)} KB${kept})`));
6251
6391
  }
6252
6392
  const width = Math.max(...lines.map((l) => l.label.length));
@@ -6298,9 +6438,9 @@ async function helper(args) {
6298
6438
  }
6299
6439
 
6300
6440
  // src/logs.ts
6301
- import { existsSync as existsSync8, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
6441
+ import { existsSync as existsSync9, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
6302
6442
  async function logs(opts) {
6303
- if (!existsSync8(logFile)) {
6443
+ if (!existsSync9(logFile)) {
6304
6444
  console.log(`no log yet at ${logFile}; it appears once \`toyon\` has started the daemon`);
6305
6445
  return opts.follow ? 0 : 1;
6306
6446
  }
@@ -6319,7 +6459,7 @@ async function logs(opts) {
6319
6459
  let offset = statSync4(logFile).size;
6320
6460
  for (;; ) {
6321
6461
  await Bun.sleep(300);
6322
- if (!existsSync8(logFile))
6462
+ if (!existsSync9(logFile))
6323
6463
  continue;
6324
6464
  const size = statSync4(logFile).size;
6325
6465
  if (size < offset)
@@ -6342,17 +6482,17 @@ function confirm(question) {
6342
6482
  }
6343
6483
 
6344
6484
  // src/remote.ts
6345
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
6485
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
6346
6486
 
6347
6487
  // ../shared/src/tailscale.ts
6348
- import { existsSync as existsSync9, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
6488
+ import { existsSync as existsSync10, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
6349
6489
  var dnsName = (raw) => (raw ?? "").replace(/\.$/, "").toLowerCase();
6350
6490
 
6351
6491
  class TailscaleError extends Error {
6352
6492
  }
6353
6493
  var MAC_APP_CLI = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
6354
6494
  function findTailscale() {
6355
- return Bun.which("tailscale") ?? (existsSync9(MAC_APP_CLI) ? MAC_APP_CLI : null);
6495
+ return Bun.which("tailscale") ?? (existsSync10(MAC_APP_CLI) ? MAC_APP_CLI : null);
6356
6496
  }
6357
6497
  var NOT_INSTALLED = "Tailscale is not installed here (no tailscale on PATH); https://tailscale.com/download";
6358
6498
  function tailscaleCli(bin = findTailscale()) {
@@ -6366,10 +6506,11 @@ function tailscaleCli(bin = findTailscale()) {
6366
6506
  }
6367
6507
  function entries(daemonPort) {
6368
6508
  const list = [{ port: 443, target: `http://127.0.0.1:${daemonPort}` }];
6369
- for (let p = PREVIEW_PORTS.from;p <= PREVIEW_PORTS.to; p++)
6509
+ for (const p of [FRAME_PORT, ...previewPorts()])
6370
6510
  list.push({ port: p, target: `http://127.0.0.1:${p}` });
6371
6511
  return list;
6372
6512
  }
6513
+ var previewPorts = () => Array.from({ length: PREVIEW_PORTS.to - PREVIEW_PORTS.from + 1 }, (_, i) => PREVIEW_PORTS.from + i);
6373
6514
  var serveOn = (e) => ["serve", "--bg", `--https=${e.port}`, e.target];
6374
6515
  var serveOff = (e) => ["serve", `--https=${e.port}`, "off"];
6375
6516
  var lastLine = (r) => (r.err.trim() || r.out.trim()).split(`
@@ -6518,11 +6659,11 @@ async function turnRemoteOff(ts, daemonPort, file, was) {
6518
6659
 
6519
6660
  // src/remote.ts
6520
6661
  function saved() {
6521
- if (!existsSync10(remoteFile))
6662
+ if (!existsSync11(remoteFile))
6522
6663
  return null;
6523
6664
  return parseRemote(readFileSync7(remoteFile, "utf8"));
6524
6665
  }
6525
- var ports = `${PREVIEW_PORTS.from}-${PREVIEW_PORTS.to}`;
6666
+ var ports = `${FRAME_PORT} and ${PREVIEW_PORTS.from}-${PREVIEW_PORTS.to}`;
6526
6667
  var describe2 = (r) => `remote access: https://${r.host}/, ${previewsLine(r)}`;
6527
6668
  async function switchRemote(body) {
6528
6669
  const res = await daemonFetch("/remote", { body });
@@ -6747,11 +6888,11 @@ async function restart() {
6747
6888
  }
6748
6889
 
6749
6890
  // src/uninstall.ts
6750
- import { existsSync as existsSync11, readFileSync as readFileSync8, rmSync as rmSync4 } from "fs";
6751
- import { join as join6 } from "path";
6891
+ import { existsSync as existsSync12, readFileSync as readFileSync8, rmSync as rmSync4 } from "fs";
6892
+ import { join as join7 } from "path";
6752
6893
  function readState() {
6753
- const file = join6(home, DAEMON_FILES.state);
6754
- if (!existsSync11(file))
6894
+ const file = join7(home, DAEMON_FILES.state);
6895
+ if (!existsSync12(file))
6755
6896
  return {};
6756
6897
  try {
6757
6898
  return JSON.parse(readFileSync8(file, "utf8"));
@@ -6772,7 +6913,7 @@ async function uninstall(opts) {
6772
6913
  const state = readState();
6773
6914
  const repos = state.repos ?? [];
6774
6915
  const worktrees = (state.worktrees ?? []).filter((w) => w.kind !== "main");
6775
- const helper = join6(home, DAEMON_FILES.helper);
6916
+ const helper = join7(home, DAEMON_FILES.helper);
6776
6917
  const branches = worktrees.map((w) => w.branch).filter((b) => !!b && b.startsWith("toyon/"));
6777
6918
  console.log("toyon uninstall removes:");
6778
6919
  console.log(` ${home} (state, transcripts, agent adapters, ${worktrees.length} worktree director${worktrees.length === 1 ? "y" : "ies"})`);
@@ -6780,7 +6921,7 @@ async function uninstall(opts) {
6780
6921
  const repo = repos.find((r) => r.id === w.repoId);
6781
6922
  console.log(` ${w.path} (worktree of ${repo?.path ?? "?"})`);
6782
6923
  }
6783
- if (existsSync11(helper))
6924
+ if (existsSync12(helper))
6784
6925
  console.log(` ${helper} (the hidden helper that starts Toyon from the Dock app)`);
6785
6926
  console.log("and stops the daemon if it is running.");
6786
6927
  console.log("it keeps: your repos, every branch Toyon made" + (branches.length ? ` (${branches.length} under toyon/)` : "") + ", and Toyon's settings in each repo (.toyon/ or toyon.json).");
@@ -6792,16 +6933,16 @@ async function uninstall(opts) {
6792
6933
  await stop();
6793
6934
  for (const w of worktrees) {
6794
6935
  const repo = repos.find((r) => r.id === w.repoId);
6795
- if (repo && existsSync11(repo.path)) {
6936
+ if (repo && existsSync12(repo.path)) {
6796
6937
  const ok = await git(repo.path, "worktree", "remove", "--force", w.path);
6797
- if (!ok && existsSync11(w.path))
6938
+ if (!ok && existsSync12(w.path))
6798
6939
  rmSync4(w.path, { recursive: true, force: true });
6799
6940
  await git(repo.path, "worktree", "prune");
6800
- } else if (existsSync11(w.path)) {
6941
+ } else if (existsSync12(w.path)) {
6801
6942
  rmSync4(w.path, { recursive: true, force: true });
6802
6943
  }
6803
6944
  }
6804
- if (existsSync11(helper)) {
6945
+ if (existsSync12(helper)) {
6805
6946
  try {
6806
6947
  await Bun.spawn([LSREGISTER, "-u", helper], { stdout: "ignore", stderr: "ignore" }).exited;
6807
6948
  } catch {}
@@ -6898,8 +7039,9 @@ async function restartOnto(version) {
6898
7039
  // src/cli.ts
6899
7040
  async function open2(cmd) {
6900
7041
  const explicit = cmd.path !== null;
7042
+ const clone = cmd.path !== null ? gitUrl(cmd.path) : null;
6901
7043
  const target = resolve(cmd.path ?? process.cwd());
6902
- const register = explicit || existsSync12(join7(target, ".git"));
7044
+ const register = !clone && (explicit || existsSync13(join8(target, ".git")));
6903
7045
  if (process.platform === "linux") {
6904
7046
  const missing = missingSandboxTools();
6905
7047
  const blocked = missing.length === 0 ? bwrapStartError() : null;
@@ -6911,7 +7053,20 @@ async function open2(cmd) {
6911
7053
  const daemon = await ensureDaemon();
6912
7054
  if (!daemon)
6913
7055
  return 1;
6914
- const file = explicit && existsSync12(target) && statSync5(target).isFile();
7056
+ if (clone && cmd.path !== null) {
7057
+ const res = await post("/clone", daemon.token, { url: cmd.path, parent: process.cwd() });
7058
+ if (!res.ok) {
7059
+ console.error(`could not open ${cmd.path}: ${await res.text()}`);
7060
+ return 1;
7061
+ }
7062
+ console.log(`toyon: ${clone.name} is ready to clone into ${process.cwd()}; press return in Toyon to start`);
7063
+ if ((await res.json()).delivered)
7064
+ focusShell();
7065
+ else
7066
+ openShell(daemon);
7067
+ return 0;
7068
+ }
7069
+ const file = explicit && existsSync13(target) && statSync5(target).isFile();
6915
7070
  if (file) {
6916
7071
  const res = await post("/open", daemon.token, { path: target });
6917
7072
  if (!res.ok) {