toyon 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/CHANGELOG.md +71 -0
- package/dist/cli.js +114 -47
- package/dist/daemon.js +1835 -759
- package/dist/shell/assets/{Editor-BATywBIx.js → Editor-CU91PIeX.js} +175 -175
- package/dist/shell/assets/{XTerm-So8-Vafj.js → XTerm-y7vQoOEu.js} +1 -1
- package/dist/shell/assets/{cssMode-DlHcrkXj.js → cssMode-B7sFzM6K.js} +1 -1
- package/dist/shell/assets/{freemarker2-D3xOnVxc.js → freemarker2-ChTLQoDC.js} +1 -1
- package/dist/shell/assets/{grammar-RZJ5oG33.js → grammar-xXcxTc3V.js} +1 -1
- package/dist/shell/assets/{handlebars-UbIG7080.js → handlebars-DPJSozQx.js} +1 -1
- package/dist/shell/assets/{html-D92eLK6U.js → html-Cog2rLcD.js} +1 -1
- package/dist/shell/assets/{htmlMode-DsZIJzoj.js → htmlMode-Xm5_ujKb.js} +1 -1
- package/dist/shell/assets/index-BpW1Kufz.css +1 -0
- package/dist/shell/assets/index-CdY8WF4P.js +270 -0
- package/dist/shell/assets/{javascript-DUkgFtV2.js → javascript-Dcv6lw7b.js} +1 -1
- package/dist/shell/assets/{jsonMode-BiIooPg9.js → jsonMode-D5_V0TW5.js} +1 -1
- package/dist/shell/assets/{liquid-DFFjZ5TH.js → liquid-C_zXIF5F.js} +1 -1
- package/dist/shell/assets/{lspLanguageFeatures-C7Ug4qkg.js → lspLanguageFeatures-CkiTdLmp.js} +1 -1
- package/dist/shell/assets/{mdx-DdjSgAuG.js → mdx-gvbILQkr.js} +1 -1
- package/dist/shell/assets/{python-DPUF4OK9.js → python-CZTrOkh_.js} +1 -1
- package/dist/shell/assets/{razor-IRYJ0pOA.js → razor-DowlBj1i.js} +1 -1
- package/dist/shell/assets/{tsMode-CljE7MPO.js → tsMode-BNl2cV_K.js} +1 -1
- package/dist/shell/assets/{xml-D8Zv3Gd0.js → xml-DIiuHFHw.js} +1 -1
- package/dist/shell/assets/{yaml-Dxx9PL0B.js → yaml-CBnMyn4j.js} +1 -1
- package/dist/shell/icon-192.png +0 -0
- package/dist/shell/icon-512.png +0 -0
- package/dist/shell/icon-maskable-512.png +0 -0
- package/dist/shell/index.html +94 -13
- package/dist/shell/manifest.json +19 -1
- package/package.json +1 -1
- package/dist/shell/assets/index-BvYzBRq_.css +0 -1
- package/dist/shell/assets/index-CHQwm2uN.js +0 -268
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ Point at anything on the page to talk about it or open the line that draws it, a
|
|
|
28
28
|
|
|
29
29
|
One Toyon holds every project you open, and the list of chats shows which one needs you.
|
|
30
30
|
|
|
31
|
-
When your check passes, land merges into main on your machine by default, or pushes, or
|
|
31
|
+
When your check passes, land merges into main on your machine by default, or pushes, or creates a pull request.
|
|
32
32
|
|
|
33
33
|
Built for web apps. A project without a page still works; the chat takes the middle instead of the preview.
|
|
34
34
|
|
package/dist/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,77 @@
|
|
|
2
2
|
|
|
3
3
|
What changed in each release of Toyon, newest first.
|
|
4
4
|
|
|
5
|
+
## 0.18.0 (2026-10-23)
|
|
6
|
+
|
|
7
|
+
Stop and the ask cards say what they are doing while they do it, and the phone rail's swipe reads as one gesture with the rungs the row actually offers.
|
|
8
|
+
|
|
9
|
+
### New
|
|
10
|
+
- A plan card can take a note with its answer. Open the field under a row, write the note, and Enter answers with that row and sends the words after it, so a plan is accepted with a condition in one step. The draft stays with the card when you switch worktrees.
|
|
11
|
+
- Up in a blank composer walks onto the card answers you wrote a note for, and puts that note back in the box, in with your messages and bang commands.
|
|
12
|
+
- When Claude runs on Amazon Bedrock and the AWS sign-in has lapsed, the turn ends on a card instead of an error row. Its first button sets Claude Code to refresh the sign-in itself from then on; the others run a one-off login or send the message again.
|
|
13
|
+
- On a phone, tapping the usage ring opens the meters with the menu beneath them.
|
|
14
|
+
- An installed app and its splash screen get a full icon on one colour in place of the monogram.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- Pressing stop shows at once: the button breathes and the rail dot pulses red until the turn has really ended. Before, nothing moved for as long as the cancel took, up to half a minute, so the press read as ignored.
|
|
18
|
+
- An option's preview on an ask card sits under its own row and scrolls past twelve lines, instead of in a band that followed the cursor and resized the card.
|
|
19
|
+
- The editor pane head leads with the file name and dims the directory after it. The directory shortens and then drops when the pane is narrow, and copy path and reveal moved into the head's right-click menu.
|
|
20
|
+
- The phone rail's swipe follows one ladder, land, then archive, then more, and shows only the rungs the row's menu offers. A landed and clean row has no land rung, so archive takes the short drag. The well behind the row darkens with the slide and fades as the row settles.
|
|
21
|
+
- Opening a file under Toyon's home works for transcripts, logs, themes and launcher sources. Only the daemon's own state and credentials are refused, and the refusal says so.
|
|
22
|
+
- A slow first connection no longer flips the window to a reason: the shell tries twice before naming one, so the list stays on screen, and a wrong token is still named at once. The boot page draws the four-square mark after a short delay.
|
|
23
|
+
- A queued message shows its tag on a line above the text, and the commit row's tip sits on the chat's side of the row as a rail row's does.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- With Fable at its weekly cap, the usage ring drew the next fullest window and the out-until warning matched no row. The Fable week is now a row of its own in the usage view, the ring and the warning.
|
|
27
|
+
- A message sent mid-turn that the agent went on to answer past a stop was queued and run a second time.
|
|
28
|
+
- A reply that ended in an error or on a sign-in card left its row marked as running, in the moment and in the stored chat.
|
|
29
|
+
- An image attached to a card answer was lost; it now reaches the agent with the words.
|
|
30
|
+
- A chat that paused to ask you something was listed twice on the updated card, as asking and as finished. It shows once, as asking.
|
|
31
|
+
- Typing a full @ worktree name and a space now closes the mention menu, and a trailing ?, ! or . no longer keeps it open or gets swallowed by Enter.
|
|
32
|
+
- Pressing go on after a stop or error now moves the row up the rail like any send.
|
|
33
|
+
- On a phone, the status bar matches the screen behind it once the app has booted, instead of a different shade above the body.
|
|
34
|
+
- A mostly vertical scroll in a diff no longer drifts it a pixel sideways.
|
|
35
|
+
- A code chip inside a table cell no longer wraps at its spaces; a table of chips scrolls sideways on a phone instead.
|
|
36
|
+
- The offer line under the composer clipped the focus ring on a long offer, and the sync note sat beside the behind-main count while a sync ran instead of at the row's end.
|
|
37
|
+
|
|
38
|
+
### Not yet
|
|
39
|
+
- The app icon is a stand-in until the final one is drawn.
|
|
40
|
+
|
|
41
|
+
## 0.17.0 (2026-10-21)
|
|
42
|
+
|
|
43
|
+
The composer's @ menu reaches the other worktrees of the project, the editor pane can wrap long lines, and a git command that hangs no longer holds up every later landing.
|
|
44
|
+
|
|
45
|
+
### New
|
|
46
|
+
- The composer's @ menu lists the project's other worktrees. A bare @ offers the three you sent to most recently, each with its status dot, title and the time since the last send, and typing part of a title searches them all. The pick puts the worktree's name in the message so the agent can read that chat.
|
|
47
|
+
- The editor pane has a wrap button that folds long lines at the pane's edge or goes back to sideways scrolling. The choice holds for the window and applies to the diff view too.
|
|
48
|
+
- Up and down walk the archived and discovered rows of the rail as they already walked your own. Stepping onto an archived row opens its page and keeps the focus on the row.
|
|
49
|
+
- `toyon <file>` with a window already open shows the file there and brings the window forward instead of opening a second one, and the project picker takes a file as well as a folder.
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
- On the PR route the button reads create PR until a pull request exists, since open read as opening its page.
|
|
53
|
+
- The plan row in the transcript shows the plan's first heading, as the row above the composer does, and keeps the path in its tooltip and menu.
|
|
54
|
+
- The chats palette leads each group with the chat's name and the age of its newest hit, and shows the chat's ask beneath it on up to two lines.
|
|
55
|
+
- Project search stops at a page of results so a lockfile cannot fill it, and a new query or a closed palette cancels the running one. The footer says when not every match is shown. The symbol lookup on cmd+I stops reading once it has enough rows, so a common name no longer drains the whole project.
|
|
56
|
+
- A plain git command inside a landing or sync has a ten-minute ceiling. Before, one hung process could hold the project's lock and silently queue every later land or sync; now it is killed and the row says so.
|
|
57
|
+
- The daemon's log rotates at 10 MB, is readable only by you, opens each run with a line naming the version, and no longer prints the signed-in link with its token. Refusals, git failures and closed connections take one line each instead of a stack trace.
|
|
58
|
+
- Agent settings, hooks and plugin code are denied to the agent file by file instead of by folder, so its skills, commands and prompts are ordinary edits again. The refusal row names what it guards.
|
|
59
|
+
- Archiving the row you are on moves you to the nearest row above it that is waiting, done, unseen, working or has its app up, and only looks below when none qualifies, so old worktrees parked at the bottom of the rail are not pulled into view.
|
|
60
|
+
- While a commit or land runs, the commit box shows the step's line where the message was and keeps its height, so the foot does not jump. The typed message is held and clears only once the tree is clean, so a refused hook gives the words back to edit.
|
|
61
|
+
- Waiting lines in the files tree, history, design pane, terminal, boot and import views and the pickers show as live text instead of a spinner beside the words.
|
|
62
|
+
- The archived row's menu item reads delete instead of delete for good; the confirm dialog still spells out what goes with it.
|
|
63
|
+
- When a sync finds that the changes carried on the row no longer fit over main, it asks the agent to fix them and names the clashing files, instead of leaving a bare line on the row.
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
- A reply containing a raw HTML tag such as `<title>` or `<script>` drew nothing after the tag; the tags now show as text.
|
|
67
|
+
- A table wider than the chat column scrolls inside itself, with a fade at the edge that has more behind it, instead of pushing the whole transcript sideways.
|
|
68
|
+
- Switching worktrees quickly could paint the previous chat's text in the new one for a moment.
|
|
69
|
+
- The caret in an empty composer was drawn behind the ghost text; it shows over it now.
|
|
70
|
+
- Sideways scrolling in the editor that ran off a line's end no longer triggers the browser's back or forward swipe.
|
|
71
|
+
- Clicking a tool row left a lone accent line across the top of the transcript on the next keypress; the focus ring now draws whole inside the row.
|
|
72
|
+
- The time tip on a daemon row followed the pointer across the next message; it sits above the word it belongs to.
|
|
73
|
+
- The Dock helper wrote the signed-in link, token included, to its launcher log. The link is printed only to a terminal now; a file gets the bare address and the token file's path.
|
|
74
|
+
- A landing mark could stay on a row after its record had moved on, and a failed Tailscale status check raised an unhandled error instead of reading as not running.
|
|
75
|
+
|
|
5
76
|
## 0.16.0 (2026-10-18)
|
|
6
77
|
|
|
7
78
|
Streamed replies are written to the transcript a few characters at a time instead of a line at a time, the plan card follows the file Claude Code keeps itself, and the checks and preview in the PR line are links.
|
package/dist/cli.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
|
-
import { existsSync as
|
|
5
|
+
import { existsSync as existsSync12, statSync as statSync5 } from "fs";
|
|
6
6
|
import { join as join7, resolve } from "path";
|
|
7
7
|
// package.json
|
|
8
8
|
var package_default = {
|
|
9
9
|
name: "toyon",
|
|
10
|
-
version: "0.
|
|
10
|
+
version: "0.18.0",
|
|
11
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
12
|
license: "MIT",
|
|
13
13
|
type: "module",
|
|
@@ -57,13 +57,13 @@ var package_default = {
|
|
|
57
57
|
|
|
58
58
|
// src/app.ts
|
|
59
59
|
import { spawn as spawn3, spawnSync } from "child_process";
|
|
60
|
-
import { existsSync as
|
|
60
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
61
61
|
import { homedir as homedir2 } from "os";
|
|
62
62
|
import { join as join3 } from "path";
|
|
63
63
|
|
|
64
64
|
// src/daemon.ts
|
|
65
65
|
import { spawn } from "child_process";
|
|
66
|
-
import { existsSync as
|
|
66
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync } from "fs";
|
|
67
67
|
import { homedir } from "os";
|
|
68
68
|
import { join as join2 } from "path";
|
|
69
69
|
|
|
@@ -76,6 +76,8 @@ var isTailnetName = (host) => host.endsWith(".ts.net");
|
|
|
76
76
|
var APP_SCHEME = "toyon";
|
|
77
77
|
var START_LINK = `${APP_SCHEME}://start`;
|
|
78
78
|
var LSREGISTER = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister";
|
|
79
|
+
var FD_LIMIT_LOW = 4096;
|
|
80
|
+
var fdLimitLine = (n) => `open-file limit is ${n}; raise it with ulimit -n 8192 before toyon starts`;
|
|
79
81
|
var DAEMON_FILES = {
|
|
80
82
|
token: "token",
|
|
81
83
|
helper: "Toyon.app",
|
|
@@ -5249,19 +5251,31 @@ function parseVersion(v) {
|
|
|
5249
5251
|
return { nums: [Number(m[1]), Number(m[2]), Number(m[3])], pre: m[4] ?? null };
|
|
5250
5252
|
}
|
|
5251
5253
|
|
|
5254
|
+
// ../shared/src/logfile.ts
|
|
5255
|
+
import { chmodSync, existsSync, openSync, renameSync, statSync } from "fs";
|
|
5256
|
+
var LOG_ROTATE_BYTES = 10 * 1024 * 1024;
|
|
5257
|
+
var previousLog = (path) => `${path}.1`;
|
|
5258
|
+
function openLog(path, maxBytes = LOG_ROTATE_BYTES) {
|
|
5259
|
+
if (existsSync(path) && statSync(path).size > maxBytes)
|
|
5260
|
+
renameSync(path, previousLog(path));
|
|
5261
|
+
const fd = openSync(path, "a", 384);
|
|
5262
|
+
chmodSync(path, 384);
|
|
5263
|
+
return fd;
|
|
5264
|
+
}
|
|
5265
|
+
|
|
5252
5266
|
// src/layout.ts
|
|
5253
|
-
import { existsSync, realpathSync } from "fs";
|
|
5267
|
+
import { existsSync as existsSync2, realpathSync } from "fs";
|
|
5254
5268
|
import { delimiter, dirname, join } from "path";
|
|
5255
5269
|
import { fileURLToPath } from "url";
|
|
5256
5270
|
var here = dirname(fileURLToPath(import.meta.url));
|
|
5257
|
-
var packaged =
|
|
5271
|
+
var packaged = existsSync2(join(here, "daemon.js"));
|
|
5258
5272
|
var method = installMethod(packaged ? join(here, "..", "package.json") : null);
|
|
5259
5273
|
function installOnPath() {
|
|
5260
5274
|
for (const dir of (process.env.PATH ?? "").split(delimiter)) {
|
|
5261
5275
|
if (dir === "" || dir.includes("/_npx/"))
|
|
5262
5276
|
continue;
|
|
5263
5277
|
const bin = join(dir, "toyon");
|
|
5264
|
-
if (!
|
|
5278
|
+
if (!existsSync2(bin))
|
|
5265
5279
|
continue;
|
|
5266
5280
|
try {
|
|
5267
5281
|
return installMethod(realpathSync(bin));
|
|
@@ -5308,13 +5322,13 @@ async function daemonFetch(path, init = {}) {
|
|
|
5308
5322
|
}
|
|
5309
5323
|
}
|
|
5310
5324
|
function readToken() {
|
|
5311
|
-
if (!
|
|
5325
|
+
if (!existsSync3(tokenFile))
|
|
5312
5326
|
return null;
|
|
5313
5327
|
const t = readFileSync(tokenFile, "utf8").trim();
|
|
5314
5328
|
return t === "" ? null : t;
|
|
5315
5329
|
}
|
|
5316
5330
|
function readPid() {
|
|
5317
|
-
if (!
|
|
5331
|
+
if (!existsSync3(pidFile))
|
|
5318
5332
|
return null;
|
|
5319
5333
|
const n = Number(readFileSync(pidFile, "utf8").trim());
|
|
5320
5334
|
return Number.isInteger(n) && n > 0 ? n : null;
|
|
@@ -5329,7 +5343,7 @@ function alive(pid) {
|
|
|
5329
5343
|
}
|
|
5330
5344
|
async function startDaemon() {
|
|
5331
5345
|
mkdirSync(home, { recursive: true });
|
|
5332
|
-
const logFd =
|
|
5346
|
+
const logFd = openLog(logFile);
|
|
5333
5347
|
const child = spawn(process.execPath, ["run", daemonEntry], {
|
|
5334
5348
|
detached: true,
|
|
5335
5349
|
stdio: ["ignore", logFd, logFd],
|
|
@@ -5377,6 +5391,9 @@ var shellOrigins = ["http://toyon.localhost", `http://toyon.localhost:${port}`];
|
|
|
5377
5391
|
function shellUrl(token, branded) {
|
|
5378
5392
|
return `${branded ? shellOrigins[0] : shellOrigins[1]}/#token=${token}`;
|
|
5379
5393
|
}
|
|
5394
|
+
function shellLine(url, tty = process.stdout.isTTY === true) {
|
|
5395
|
+
return tty ? `toyon: ${url}` : `toyon: ${url.replace(/#token=.*$/, "")}; token read from ${tokenFile}`;
|
|
5396
|
+
}
|
|
5380
5397
|
|
|
5381
5398
|
// src/openUrl.ts
|
|
5382
5399
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -5397,16 +5414,16 @@ var CHROMIUMS = [
|
|
|
5397
5414
|
var pwaCacheDir = join3(home, "pwa");
|
|
5398
5415
|
function profilesOf(browser) {
|
|
5399
5416
|
const root = join3(homedir2(), "Library", "Application Support", browser.dataDir);
|
|
5400
|
-
if (!
|
|
5417
|
+
if (!existsSync4(root))
|
|
5401
5418
|
return [];
|
|
5402
5419
|
return readdirSync(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join3(root, d.name));
|
|
5403
5420
|
}
|
|
5404
5421
|
function pwaStillInstalled(browser, id) {
|
|
5405
|
-
return profilesOf(browser).some((p) =>
|
|
5422
|
+
return profilesOf(browser).some((p) => existsSync4(join3(p, "Web Applications", "Manifest Resources", id)));
|
|
5406
5423
|
}
|
|
5407
5424
|
function findPwaId(browser) {
|
|
5408
5425
|
const cache = join3(pwaCacheDir, `${browser.name.replace(/\W+/g, "-")}.id`);
|
|
5409
|
-
if (
|
|
5426
|
+
if (existsSync4(cache)) {
|
|
5410
5427
|
const id = readFileSync2(cache, "utf8").trim();
|
|
5411
5428
|
if (/^[a-p]{32}$/.test(id) && pwaStillInstalled(browser, id))
|
|
5412
5429
|
return id;
|
|
@@ -5415,7 +5432,7 @@ function findPwaId(browser) {
|
|
|
5415
5432
|
const re = new RegExp(`web_apps-dt-([a-p]{32})[\\s\\S]{0,16}?(?:${ids})`);
|
|
5416
5433
|
for (const p of profilesOf(browser)) {
|
|
5417
5434
|
const dir = join3(p, "Sync Data", "LevelDB");
|
|
5418
|
-
if (!
|
|
5435
|
+
if (!existsSync4(dir))
|
|
5419
5436
|
continue;
|
|
5420
5437
|
for (const f of readdirSync(dir)) {
|
|
5421
5438
|
if (!/\.(log|ldb)$/.test(f))
|
|
@@ -5431,7 +5448,7 @@ function findPwaId(browser) {
|
|
|
5431
5448
|
return null;
|
|
5432
5449
|
}
|
|
5433
5450
|
function openShell(daemon) {
|
|
5434
|
-
console.log(
|
|
5451
|
+
console.log(shellLine(daemon.url));
|
|
5435
5452
|
const found = process.platform === "darwin" ? launchInstalledApp() : { kind: "none" };
|
|
5436
5453
|
if (found.kind === "launched")
|
|
5437
5454
|
return;
|
|
@@ -5441,6 +5458,41 @@ function openShell(daemon) {
|
|
|
5441
5458
|
const where = defaultBrowserInstalls(readLinkHandlers()) ? "" : `open it in ${found.browser} to `;
|
|
5442
5459
|
console.log(`tip: ${where}install Toyon as an app from the top bar; toyon then opens in its own window`);
|
|
5443
5460
|
}
|
|
5461
|
+
function focusShell() {
|
|
5462
|
+
if (process.platform !== "darwin")
|
|
5463
|
+
return;
|
|
5464
|
+
const bundle = installedAppBundle();
|
|
5465
|
+
if (!bundle)
|
|
5466
|
+
return;
|
|
5467
|
+
const child = spawn3("open", [bundle], { stdio: "ignore" });
|
|
5468
|
+
child.on("error", () => {});
|
|
5469
|
+
child.unref();
|
|
5470
|
+
}
|
|
5471
|
+
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)) : [];
|
|
5474
|
+
for (const app of CHROMIUMS) {
|
|
5475
|
+
const id = findPwaId(app);
|
|
5476
|
+
if (!id)
|
|
5477
|
+
continue;
|
|
5478
|
+
const kept = profilesOf(app).map((p) => join3(p, "Web Applications", `_crx_${id}`));
|
|
5479
|
+
const hit = [...shelves, ...kept].flatMap(bundlesIn).find((b) => {
|
|
5480
|
+
try {
|
|
5481
|
+
return readFileSync2(join3(b, "Contents", "Info.plist"), "latin1").includes(id);
|
|
5482
|
+
} catch {
|
|
5483
|
+
return false;
|
|
5484
|
+
}
|
|
5485
|
+
});
|
|
5486
|
+
if (hit)
|
|
5487
|
+
return hit;
|
|
5488
|
+
}
|
|
5489
|
+
return null;
|
|
5490
|
+
}
|
|
5491
|
+
function bundlesIn(dir) {
|
|
5492
|
+
if (!existsSync4(dir))
|
|
5493
|
+
return [];
|
|
5494
|
+
return readdirSync(dir).filter((n) => n.endsWith(".app")).map((n) => join3(dir, n));
|
|
5495
|
+
}
|
|
5444
5496
|
function launchInstalledApp() {
|
|
5445
5497
|
let seen = null;
|
|
5446
5498
|
for (const app of CHROMIUMS) {
|
|
@@ -5671,12 +5723,12 @@ policy
|
|
|
5671
5723
|
// src/deploy/fly.ts
|
|
5672
5724
|
import { randomBytes } from "crypto";
|
|
5673
5725
|
import { Resolver } from "dns/promises";
|
|
5674
|
-
import { existsSync as
|
|
5726
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, mkdtempSync, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
5675
5727
|
import { tmpdir } from "os";
|
|
5676
5728
|
import { join as join5 } from "path";
|
|
5677
5729
|
|
|
5678
5730
|
// ../shared/src/managed-load.ts
|
|
5679
|
-
import { readFileSync as readFileSync3, statSync } from "fs";
|
|
5731
|
+
import { readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
5680
5732
|
import { userInfo } from "os";
|
|
5681
5733
|
async function plutilJson(path) {
|
|
5682
5734
|
const p = Bun.spawn(["plutil", "-convert", "json", "-o", "-", path], { stdout: "pipe", stderr: "pipe" });
|
|
@@ -5698,7 +5750,7 @@ async function loadManaged(opts = {}) {
|
|
|
5698
5750
|
for (const s of sources) {
|
|
5699
5751
|
let st;
|
|
5700
5752
|
try {
|
|
5701
|
-
st =
|
|
5753
|
+
st = statSync2(s.path);
|
|
5702
5754
|
} catch {
|
|
5703
5755
|
reads.push(s);
|
|
5704
5756
|
continue;
|
|
@@ -5731,12 +5783,12 @@ function refusedByPolicy(verb, m) {
|
|
|
5731
5783
|
}
|
|
5732
5784
|
|
|
5733
5785
|
// src/deploy/context.ts
|
|
5734
|
-
import { cpSync, existsSync as
|
|
5786
|
+
import { cpSync, existsSync as existsSync5, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
5735
5787
|
import { join as join4 } from "path";
|
|
5736
5788
|
var bunVersion = package_default.dependencies.bun;
|
|
5737
5789
|
function machineSources() {
|
|
5738
5790
|
const dist = packaged ? here : join4(here, "..", "dist");
|
|
5739
|
-
return
|
|
5791
|
+
return existsSync5(join4(dist, "daemon.js")) ? { dist, cloud: join4(dist, "..", "cloud") } : null;
|
|
5740
5792
|
}
|
|
5741
5793
|
function machinePackageJson() {
|
|
5742
5794
|
const manifest = {
|
|
@@ -5841,7 +5893,7 @@ function secret(env, file) {
|
|
|
5841
5893
|
if (fromEnv)
|
|
5842
5894
|
return fromEnv;
|
|
5843
5895
|
const path = join5(cloudDir, file);
|
|
5844
|
-
return
|
|
5896
|
+
return existsSync6(path) ? readFileSync4(path, "utf8").trim() || null : null;
|
|
5845
5897
|
}
|
|
5846
5898
|
async function capture(bin, args, input) {
|
|
5847
5899
|
const p = Bun.spawn([bin, ...args], {
|
|
@@ -5906,7 +5958,7 @@ async function up(cmd, bin) {
|
|
|
5906
5958
|
throw new DeployError("could not tell which Fly region is closest; pass --region (`fly platform regions` lists them)");
|
|
5907
5959
|
console.log(cmd.region ? `region ${region}` : `region ${region}, the closest to you; --region picks another`);
|
|
5908
5960
|
mkdirSync4(cloudDir, { recursive: true, mode: 448 });
|
|
5909
|
-
if (!
|
|
5961
|
+
if (!existsSync6(tokenFile2(cmd.name)))
|
|
5910
5962
|
writeFileSync3(tokenFile2(cmd.name), randomBytes(32).toString("hex"), { mode: 384 });
|
|
5911
5963
|
const token = readFileSync4(tokenFile2(cmd.name), "utf8").trim();
|
|
5912
5964
|
const app = ["-a", cmd.name];
|
|
@@ -6014,7 +6066,7 @@ async function deploy(cmd) {
|
|
|
6014
6066
|
await up(cmd, bin);
|
|
6015
6067
|
else if (cmd.action === "destroy")
|
|
6016
6068
|
return await destroy(cmd, bin);
|
|
6017
|
-
else if (
|
|
6069
|
+
else if (existsSync6(tokenFile2(cmd.name))) {
|
|
6018
6070
|
await printLinks(cmd.name, readFileSync4(tokenFile2(cmd.name), "utf8").trim());
|
|
6019
6071
|
} else {
|
|
6020
6072
|
throw new DeployError(`${cmd.name} was not deployed from this machine; its token is not in ${cloudDir}`);
|
|
@@ -6029,7 +6081,7 @@ async function deploy(cmd) {
|
|
|
6029
6081
|
}
|
|
6030
6082
|
|
|
6031
6083
|
// src/doctor.ts
|
|
6032
|
-
import { existsSync as
|
|
6084
|
+
import { existsSync as existsSync7, statSync as statSync3 } from "fs";
|
|
6033
6085
|
|
|
6034
6086
|
// src/sandboxDeps.ts
|
|
6035
6087
|
import { readFileSync as readFileSync5 } from "fs";
|
|
@@ -6147,6 +6199,8 @@ async function doctor() {
|
|
|
6147
6199
|
} else {
|
|
6148
6200
|
const version = h.version ?? "?";
|
|
6149
6201
|
lines.push(line(true, "daemon", `${version} at ${base}, pid ${h.pid ?? "?"}${h.branded ? ", also http://toyon.localhost" : ""}` + (h.lag ? `, event loop worst ${h.lag.max}ms${h.lag.maxCause ? ` (${h.lag.maxCause})` : ""}` : "")));
|
|
6202
|
+
if (h.fdLimit !== undefined && h.fdLimit < FD_LIMIT_LOW)
|
|
6203
|
+
lines.push(line(false, "limits", fdLimitLine(h.fdLimit)));
|
|
6150
6204
|
if (h.remote) {
|
|
6151
6205
|
lines.push(line(true, "remote", `https://${h.remote.host}/ through a TLS front on 127.0.0.1:${port}, previews at ${h.remote.previews}`));
|
|
6152
6206
|
}
|
|
@@ -6189,9 +6243,11 @@ async function doctor() {
|
|
|
6189
6243
|
lines.push(line(false, "shell", `could not fetch: ${e.message}`));
|
|
6190
6244
|
}
|
|
6191
6245
|
}
|
|
6192
|
-
if (
|
|
6193
|
-
const kb = Math.round(
|
|
6194
|
-
|
|
6246
|
+
if (existsSync7(logFile)) {
|
|
6247
|
+
const kb = (p) => Math.round(statSync3(p).size / 1024);
|
|
6248
|
+
const before = previousLog(logFile);
|
|
6249
|
+
const kept = existsSync7(before) ? `, and ${kb(before)} KB kept from before` : "";
|
|
6250
|
+
lines.push(line(true, "log", `${logFile} (${kb(logFile)} KB${kept})`));
|
|
6195
6251
|
}
|
|
6196
6252
|
const width = Math.max(...lines.map((l) => l.label.length));
|
|
6197
6253
|
for (const l of lines) {
|
|
@@ -6226,19 +6282,25 @@ async function helper(args) {
|
|
|
6226
6282
|
return 1;
|
|
6227
6283
|
if (plan.kind === "start")
|
|
6228
6284
|
return 0;
|
|
6285
|
+
let shown = true;
|
|
6229
6286
|
for (const path of plan.paths) {
|
|
6230
6287
|
const res = await post("/open", daemon.token, { path });
|
|
6231
6288
|
if (!res.ok)
|
|
6232
6289
|
console.error(`could not open ${path}: ${await res.text()}`);
|
|
6290
|
+
else if (!(await res.json()).delivered)
|
|
6291
|
+
shown = false;
|
|
6233
6292
|
}
|
|
6234
|
-
|
|
6293
|
+
if (shown)
|
|
6294
|
+
focusShell();
|
|
6295
|
+
else
|
|
6296
|
+
openShell(daemon);
|
|
6235
6297
|
return 0;
|
|
6236
6298
|
}
|
|
6237
6299
|
|
|
6238
6300
|
// src/logs.ts
|
|
6239
|
-
import { existsSync as
|
|
6301
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
6240
6302
|
async function logs(opts) {
|
|
6241
|
-
if (!
|
|
6303
|
+
if (!existsSync8(logFile)) {
|
|
6242
6304
|
console.log(`no log yet at ${logFile}; it appears once \`toyon\` has started the daemon`);
|
|
6243
6305
|
return opts.follow ? 0 : 1;
|
|
6244
6306
|
}
|
|
@@ -6254,12 +6316,12 @@ async function logs(opts) {
|
|
|
6254
6316
|
`);
|
|
6255
6317
|
if (!opts.follow)
|
|
6256
6318
|
return 0;
|
|
6257
|
-
let offset =
|
|
6319
|
+
let offset = statSync4(logFile).size;
|
|
6258
6320
|
for (;; ) {
|
|
6259
6321
|
await Bun.sleep(300);
|
|
6260
|
-
if (!
|
|
6322
|
+
if (!existsSync8(logFile))
|
|
6261
6323
|
continue;
|
|
6262
|
-
const size =
|
|
6324
|
+
const size = statSync4(logFile).size;
|
|
6263
6325
|
if (size < offset)
|
|
6264
6326
|
offset = 0;
|
|
6265
6327
|
if (size === offset)
|
|
@@ -6280,17 +6342,17 @@ function confirm(question) {
|
|
|
6280
6342
|
}
|
|
6281
6343
|
|
|
6282
6344
|
// src/remote.ts
|
|
6283
|
-
import { existsSync as
|
|
6345
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
6284
6346
|
|
|
6285
6347
|
// ../shared/src/tailscale.ts
|
|
6286
|
-
import { existsSync as
|
|
6348
|
+
import { existsSync as existsSync9, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
6287
6349
|
var dnsName = (raw) => (raw ?? "").replace(/\.$/, "").toLowerCase();
|
|
6288
6350
|
|
|
6289
6351
|
class TailscaleError extends Error {
|
|
6290
6352
|
}
|
|
6291
6353
|
var MAC_APP_CLI = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
|
|
6292
6354
|
function findTailscale() {
|
|
6293
|
-
return Bun.which("tailscale") ?? (
|
|
6355
|
+
return Bun.which("tailscale") ?? (existsSync9(MAC_APP_CLI) ? MAC_APP_CLI : null);
|
|
6294
6356
|
}
|
|
6295
6357
|
var NOT_INSTALLED = "Tailscale is not installed here (no tailscale on PATH); https://tailscale.com/download";
|
|
6296
6358
|
function tailscaleCli(bin = findTailscale()) {
|
|
@@ -6456,7 +6518,7 @@ async function turnRemoteOff(ts, daemonPort, file, was) {
|
|
|
6456
6518
|
|
|
6457
6519
|
// src/remote.ts
|
|
6458
6520
|
function saved() {
|
|
6459
|
-
if (!
|
|
6521
|
+
if (!existsSync10(remoteFile))
|
|
6460
6522
|
return null;
|
|
6461
6523
|
return parseRemote(readFileSync7(remoteFile, "utf8"));
|
|
6462
6524
|
}
|
|
@@ -6685,11 +6747,11 @@ async function restart() {
|
|
|
6685
6747
|
}
|
|
6686
6748
|
|
|
6687
6749
|
// src/uninstall.ts
|
|
6688
|
-
import { existsSync as
|
|
6750
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8, rmSync as rmSync4 } from "fs";
|
|
6689
6751
|
import { join as join6 } from "path";
|
|
6690
6752
|
function readState() {
|
|
6691
6753
|
const file = join6(home, DAEMON_FILES.state);
|
|
6692
|
-
if (!
|
|
6754
|
+
if (!existsSync11(file))
|
|
6693
6755
|
return {};
|
|
6694
6756
|
try {
|
|
6695
6757
|
return JSON.parse(readFileSync8(file, "utf8"));
|
|
@@ -6718,7 +6780,7 @@ async function uninstall(opts) {
|
|
|
6718
6780
|
const repo = repos.find((r) => r.id === w.repoId);
|
|
6719
6781
|
console.log(` ${w.path} (worktree of ${repo?.path ?? "?"})`);
|
|
6720
6782
|
}
|
|
6721
|
-
if (
|
|
6783
|
+
if (existsSync11(helper))
|
|
6722
6784
|
console.log(` ${helper} (the hidden helper that starts Toyon from the Dock app)`);
|
|
6723
6785
|
console.log("and stops the daemon if it is running.");
|
|
6724
6786
|
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).");
|
|
@@ -6730,16 +6792,16 @@ async function uninstall(opts) {
|
|
|
6730
6792
|
await stop();
|
|
6731
6793
|
for (const w of worktrees) {
|
|
6732
6794
|
const repo = repos.find((r) => r.id === w.repoId);
|
|
6733
|
-
if (repo &&
|
|
6795
|
+
if (repo && existsSync11(repo.path)) {
|
|
6734
6796
|
const ok = await git(repo.path, "worktree", "remove", "--force", w.path);
|
|
6735
|
-
if (!ok &&
|
|
6797
|
+
if (!ok && existsSync11(w.path))
|
|
6736
6798
|
rmSync4(w.path, { recursive: true, force: true });
|
|
6737
6799
|
await git(repo.path, "worktree", "prune");
|
|
6738
|
-
} else if (
|
|
6800
|
+
} else if (existsSync11(w.path)) {
|
|
6739
6801
|
rmSync4(w.path, { recursive: true, force: true });
|
|
6740
6802
|
}
|
|
6741
6803
|
}
|
|
6742
|
-
if (
|
|
6804
|
+
if (existsSync11(helper)) {
|
|
6743
6805
|
try {
|
|
6744
6806
|
await Bun.spawn([LSREGISTER, "-u", helper], { stdout: "ignore", stderr: "ignore" }).exited;
|
|
6745
6807
|
} catch {}
|
|
@@ -6837,7 +6899,7 @@ async function restartOnto(version) {
|
|
|
6837
6899
|
async function open2(cmd) {
|
|
6838
6900
|
const explicit = cmd.path !== null;
|
|
6839
6901
|
const target = resolve(cmd.path ?? process.cwd());
|
|
6840
|
-
const register = explicit ||
|
|
6902
|
+
const register = explicit || existsSync12(join7(target, ".git"));
|
|
6841
6903
|
if (process.platform === "linux") {
|
|
6842
6904
|
const missing = missingSandboxTools();
|
|
6843
6905
|
const blocked = missing.length === 0 ? bwrapStartError() : null;
|
|
@@ -6849,7 +6911,7 @@ async function open2(cmd) {
|
|
|
6849
6911
|
const daemon = await ensureDaemon();
|
|
6850
6912
|
if (!daemon)
|
|
6851
6913
|
return 1;
|
|
6852
|
-
const file = explicit &&
|
|
6914
|
+
const file = explicit && existsSync12(target) && statSync5(target).isFile();
|
|
6853
6915
|
if (file) {
|
|
6854
6916
|
const res = await post("/open", daemon.token, { path: target });
|
|
6855
6917
|
if (!res.ok) {
|
|
@@ -6857,6 +6919,11 @@ async function open2(cmd) {
|
|
|
6857
6919
|
return 1;
|
|
6858
6920
|
}
|
|
6859
6921
|
const opened = await res.json();
|
|
6922
|
+
if (opened.delivered) {
|
|
6923
|
+
console.log(`toyon: ${target} is open in Toyon`);
|
|
6924
|
+
focusShell();
|
|
6925
|
+
return 0;
|
|
6926
|
+
}
|
|
6860
6927
|
console.log(opened.kind === "file" ? `toyon: opening ${target} in its project` : `toyon: opening ${target}`);
|
|
6861
6928
|
} else if (register) {
|
|
6862
6929
|
const res = await post("/register", daemon.token, { path: target });
|
|
@@ -6872,7 +6939,7 @@ async function start() {
|
|
|
6872
6939
|
const daemon = await ensureDaemon();
|
|
6873
6940
|
if (!daemon)
|
|
6874
6941
|
return 1;
|
|
6875
|
-
console.log(
|
|
6942
|
+
console.log(shellLine(daemon.url));
|
|
6876
6943
|
return 0;
|
|
6877
6944
|
}
|
|
6878
6945
|
async function run(cmd) {
|