toyon 0.2.0 → 0.3.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 +62 -39
- package/dist/bridge.js +1 -1
- package/dist/cli.js +233 -70
- package/dist/daemon.js +1067 -210
- package/dist/shell/assets/{Editor-D-PlbMSY.js → Editor-Lle8R8mY.js} +3 -3
- package/dist/shell/assets/{XTerm-CM_FJEwN.js → XTerm-DcWpV1FL.js} +1 -1
- package/dist/shell/assets/{cssMode-Bkqyr9Lr.js → cssMode-Ck0mAapo.js} +1 -1
- package/dist/shell/assets/{freemarker2-DVAK5zoJ.js → freemarker2-B5OkSZOW.js} +1 -1
- package/dist/shell/assets/{handlebars-Bdtj7uMl.js → handlebars-C6vrFwYj.js} +1 -1
- package/dist/shell/assets/{html-B1pqTvyA.js → html-CE92cnIr.js} +1 -1
- package/dist/shell/assets/{htmlMode-CBtvzewS.js → htmlMode-CfM52A9Q.js} +1 -1
- package/dist/shell/assets/index-DOf1M9v5.js +233 -0
- package/dist/shell/assets/index-b4LMTPcs.css +1 -0
- package/dist/shell/assets/{javascript-FNavUzsm.js → javascript-C7Q1bCJn.js} +1 -1
- package/dist/shell/assets/{jsonMode-HgW1GXQH.js → jsonMode-Cq43R9DW.js} +1 -1
- package/dist/shell/assets/{liquid-6wkVlojW.js → liquid-CnxQAGJm.js} +1 -1
- package/dist/shell/assets/{lspLanguageFeatures-CvNG6b_1.js → lspLanguageFeatures-BtjVqb14.js} +1 -1
- package/dist/shell/assets/{mdx-C-byKky3.js → mdx-ycMLgnto.js} +1 -1
- package/dist/shell/assets/{python-BEJMbP4i.js → python-se_0lOPN.js} +1 -1
- package/dist/shell/assets/{razor-DeXWW25R.js → razor-DjByizBq.js} +1 -1
- package/dist/shell/assets/{tsMode-DMwJnvD-.js → tsMode-D1mOZbLC.js} +1 -1
- package/dist/shell/assets/{typescript-Cj57ccSp.js → typescript-B-dZdvXG.js} +1 -1
- package/dist/shell/assets/{workers-BZi9jqBp.js → workers-DXwGNRtC.js} +1 -1
- package/dist/shell/assets/{xml-DUDeQI0I.js → xml-CoqNFco9.js} +1 -1
- package/dist/shell/assets/{yaml-BCD8Shg5.js → yaml-D5Qru8sI.js} +1 -1
- package/dist/shell/index.html +2 -2
- package/dist/shell/manifest.json +1 -1
- package/package.json +2 -2
- package/dist/shell/assets/index-GBQLHG7z.css +0 -1
- package/dist/shell/assets/index-jy_zeFGs.js +0 -230
package/README.md
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
# Toyon
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Toyon is where you ask for changes to your web app and try them as they happen. Every chat gets its own running copy with a live preview.
|
|
4
4
|
|
|
5
|
-
Each copy runs on your own machine, and the next one is pre-warmed. Works with Claude Code, Codex and OpenCode.
|
|
5
|
+
Each copy runs on your own machine, and the next one is pre-warmed. Works with Claude Code, Codex and OpenCode over the Agent Client Protocol (ACP).
|
|
6
|
+
|
|
7
|
+

|
|
6
8
|
|
|
7
9
|
**Status: pre-alpha, built in the open.** Runs on macOS and Linux. Windows through WSL2 is next. Expect rough edges, and say so in an issue.
|
|
8
10
|
|
|
11
|
+
[How it works](#how-it-works) · [Install](#install) · [Under the hood](#under-the-hood) · [Trust](#trust) · [Uninstall](#uninstall)
|
|
12
|
+
|
|
9
13
|
## Why
|
|
10
14
|
|
|
11
15
|
A diff says what changed, not whether the page still looks right. A preview link at the end of a run shows you the result after the agent has stopped, too late to steer it. And with more than one agent going it gets worse: terminal splits, copies of the repo made by hand, dev servers fighting over a port.
|
|
12
16
|
|
|
13
|
-
In Toyon you
|
|
17
|
+
In Toyon you point at a button in the page and ask for it to be bigger, watch it change while the agent works, and keep the change when it looks right.
|
|
14
18
|
|
|
15
19
|
## How it works
|
|
16
20
|
|
|
@@ -22,78 +26,97 @@ In Toyon you type what you want, and the chat opens with your app already runnin
|
|
|
22
26
|
- Nothing is kept until you say so.
|
|
23
27
|
- Your editor stays yours.
|
|
24
28
|
|
|
29
|
+
Each copy is a branch and a git worktree with your dependencies and `.env` files; one spare per project is kept warm.
|
|
30
|
+
|
|
31
|
+
Point at anything on the page to talk about it or open the line that draws it, and the design pane maps it to your tokens and components.
|
|
32
|
+
|
|
33
|
+
One Toyon holds every project you open, and the list of chats shows which one needs you.
|
|
34
|
+
|
|
35
|
+
When your check passes, land merges into main on your machine by default, or pushes, or opens a pull request.
|
|
36
|
+
|
|
37
|
+
Built for web apps. A project without a page still works; the chat takes the middle instead of the preview.
|
|
38
|
+
|
|
25
39
|
## Install
|
|
26
40
|
|
|
41
|
+
Try it in a project:
|
|
42
|
+
|
|
27
43
|
```sh
|
|
28
44
|
cd your-project
|
|
29
45
|
npx toyon
|
|
30
46
|
```
|
|
31
47
|
|
|
32
|
-
|
|
48
|
+
Or install it globally:
|
|
33
49
|
|
|
34
|
-
|
|
50
|
+
```sh
|
|
51
|
+
npm i -g toyon
|
|
52
|
+
toyon ~/projects/app
|
|
53
|
+
```
|
|
35
54
|
|
|
36
|
-
|
|
37
|
-
- **Codex** is installed on first start and reads its own config file.
|
|
38
|
-
- **OpenCode** is a larger download, installed from its row's menu. It signs in with `opencode auth login` in the chat's terminal and uses whichever providers you have signed it into. It runs on a config Toyon writes, so a repository's `opencode.json` cannot loosen it, and with less than the other two: no subagents, no effort levels, and no steering mid-turn.
|
|
55
|
+
`toyon .` opens the folder you are in and `toyon --help` lists the rest. A global install keeps itself updated; `TOYON_UPDATES=off` stops it. Run outside a project, Toyon opens with none: describe a new one in a sentence, or give the project picker a git URL to clone.
|
|
39
56
|
|
|
40
|
-
|
|
57
|
+
You need:
|
|
41
58
|
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
- **More than one version.** Send one prompt to several copies and compare the results in the app.
|
|
46
|
-
- **Keep it your way.** When a turn is done and your check passes, the composer offers to land it: merged into main on your machine, pushed, or opened as a pull request, whichever your project's settings name. Nothing is committed or pushed until you press it.
|
|
47
|
-
- **The rest of the loop.** A terminal per chat, quick-open and search, a diff you can edit, themes including your VS Code ones, and an installable app window.
|
|
59
|
+
- git and Node 18 or newer. Bun comes with the package.
|
|
60
|
+
- An account for the agent you pick: a Claude plan or an Anthropic API key for Claude Code, a ChatGPT plan or an OpenAI API key for Codex, or any provider OpenCode signs into.
|
|
61
|
+
- On Linux, bubblewrap and socat for the agents' sandbox (`sudo apt install bubblewrap socat`). `toyon doctor` checks both and prints the AppArmor profile Ubuntu 24.04 and later ask for.
|
|
48
62
|
|
|
49
|
-
|
|
63
|
+
Each agent signs in from the chat. Claude Code and Codex install on first start; OpenCode is a larger download with no subagents, effort levels or steering mid-turn. What each one reads and can do is in [docs/agents.md](https://github.com/toyon-dev/toyon/blob/main/docs/agents.md).
|
|
50
64
|
|
|
51
|
-
## How is this different from
|
|
65
|
+
## How is this different from what you use now?
|
|
52
66
|
|
|
53
|
-
|
|
67
|
+
- **Claude Code or Codex in a terminal.** Toyon runs that same agent under your own login and config; for Claude Code that includes your permission rules, hooks, slash commands and MCP servers. What changes is around it: each chat gets its own copy of the project with the app running, and nothing lands until you press.
|
|
68
|
+
- **Cursor, Zed or VS Code.** Toyon sits beside your editor, not in place of it. Their agent panels and worktrees live in the editor, with a browser you open when you want it; in Toyon the running app is the centre of every chat, wired to its source. Your checkout stays yours, and any file opens back in your editor from its menu.
|
|
69
|
+
- **Conductor or Superset.** They are good tools for running many agents at once. Toyon is built around the app instead of the agents: every chat opens with your app running at the centre, connected to its code, so you see each change before you keep it. It is MIT licensed, needs no account, sends no telemetry, runs in your browser on macOS or Linux, and lands work as a pull request on GitHub or on your own machine without it.
|
|
54
70
|
|
|
55
71
|
## Under the hood
|
|
56
72
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
73
|
+
The contract for anything Toyon runs: *stay in the foreground, listen on `$PORT`, reload yourself however you like.* A `.toyon/settings.json` in the project names the setup commands, the commands to `run`, an optional `check` and how work lands. The first open guesses one from `package.json`, asks you to confirm it, and writes it into the project; other stacks start from an empty guess the agent can fill in. If a server comes up on some other port anyway, the preview follows it there and says which flag to add.
|
|
74
|
+
|
|
75
|
+
A project with a page and an API behind it:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"setup": ["npm install"],
|
|
80
|
+
"run": {
|
|
81
|
+
"web": "vite --port $PORT",
|
|
82
|
+
"api": "node --watch server.js"
|
|
83
|
+
},
|
|
84
|
+
"check": "npm test",
|
|
85
|
+
"land": { "route": "pr" }
|
|
86
|
+
}
|
|
64
87
|
```
|
|
65
88
|
|
|
66
|
-
|
|
89
|
+
Each command gets its own `$PORT`, and the addresses of the others as `<NAME>_URL` (`API_URL` here), so the page can proxy to its API. The preview shows `web`.
|
|
67
90
|
|
|
68
|
-
Copies start when you open them,
|
|
91
|
+
Copies start when you open them, and stop their servers when nobody has looked at them for a while; the chat and the agent keep going. `toyon stop` stops the daemon and everything it runs. `toyon doctor` says what is running and why a page cannot connect. Every setting, the port rules and the sleep delay are in [docs/settings.md](https://github.com/toyon-dev/toyon/blob/main/docs/settings.md).
|
|
69
92
|
|
|
70
93
|
## What it does not do
|
|
71
94
|
|
|
72
|
-
- It is not an editor.
|
|
73
|
-
-
|
|
95
|
+
- It is not an editor. You can browse the files, read and edit a diff and run a terminal, but there is no multi-file editing, no debugger, no extensions, no inline completion.
|
|
96
|
+
- Only land pushes, when you press it. The agent is told not to push or delete branches.
|
|
74
97
|
- It works on git repositories, and the product uses git's words for what it does. A new project starts one for you.
|
|
75
|
-
- It does not keep copies apart from services they share. Each copy runs your setup and your commands on its own, so a database or a compose stack they all point at is shared, migrations included. `TOYON_WORKTREE`
|
|
98
|
+
- It does not keep copies apart from services they share. Each copy runs your setup and your commands on its own, so a database or a compose stack they all point at is shared, migrations included. `TOYON_WORKTREE` in every command's environment lets a project give each copy its own (`app_$TOYON_WORKTREE` as the database name); [docs/settings.md](https://github.com/toyon-dev/toyon/blob/main/docs/settings.md#keeping-copies-apart) has the recipes.
|
|
76
99
|
- It does not carry a login between copies. Each preview has its own cookies, so a new chat's app starts signed out, and an OAuth provider cannot redirect into a preview at all.
|
|
77
100
|
|
|
78
|
-
## Disk
|
|
79
|
-
|
|
80
|
-
Each copy is a real `git worktree` under `~/.toyon/worktrees.noindex` with its own `node_modules`, cloned from the main checkout with copy-on-write where the filesystem has it: `cp -c` on APFS, `--reflink=auto` on btrfs and XFS. A new copy then costs seconds and almost no space until files diverge. On ext4 and other filesystems without reflinks it is a plain copy, and each one costs a full `node_modules`. The setup log says which path ran. Nothing is deleted without you.
|
|
81
|
-
|
|
82
101
|
## Somewhere other than your laptop
|
|
83
102
|
|
|
84
|
-
Toyon can also run on a box you open from your phone, or on your own Fly account with the laptop closed. None of it passes through a Toyon server; there is none. `toyon remote` puts a box on your own domain or your tailnet, `toyon deploy fly up` builds a machine on your Fly account and prints the link, and the Dockerfile in the package runs on any host with a disk and a port range. [toyon.cloud](https://toyon.cloud) keeps a list of your machines in your browser, and nothing else.
|
|
103
|
+
Toyon can also run on a box you open from your phone, or on your own Fly account with the laptop closed. This moves Toyon and its copies off your laptop; it does not publish your app. None of it passes through a Toyon server; there is none. `toyon remote` puts a box on your own domain behind Caddy or on your Tailscale tailnet, `toyon deploy fly up` builds a machine on your Fly account and prints the link, and the Dockerfile in the package runs on any host with a disk and a port range. A Fly machine measured about $4-6 a month in ordinary use, plus whatever your agents spend, and its first chat can sign in with your Claude plan. [toyon.cloud](https://toyon.cloud) keeps a list of your machines in your browser, and nothing else.
|
|
85
104
|
|
|
86
105
|
These routes are new, and the shell has no phone layout yet. How to set each one up, and what it has been checked with, is in [docs/remote.md](https://github.com/toyon-dev/toyon/blob/main/docs/remote.md).
|
|
87
106
|
|
|
107
|
+
## Disk
|
|
108
|
+
|
|
109
|
+
Each copy is a real `git worktree` under `~/.toyon/worktrees.noindex` with its own `node_modules`, cloned from the main checkout with copy-on-write where the filesystem has it: `cp -c` on APFS, `--reflink=auto` on btrfs and XFS. A new copy then costs seconds and almost no space until files diverge. On ext4 and other filesystems without reflinks it is a plain copy, and each one costs a full `node_modules`. The setup log says which path ran. Nothing is deleted without you.
|
|
110
|
+
|
|
88
111
|
## Trust
|
|
89
112
|
|
|
90
113
|
The daemon listens on loopback only and every request carries a per-machine token. Nothing is exposed to your network, and turning on remote access admits one name over https and nothing else.
|
|
91
114
|
|
|
92
|
-
Agents run confined. Everything a shell command spawns runs inside an OS sandbox (Seatbelt on macOS, bubblewrap on Linux) that can write to the worktree, the temp directories and package-manager caches, and nothing else, and the file tools are held to the same boundary. An agent cannot widen its own sandbox or another's. Opening a repository runs its setup, its dev server and whatever it configures for the agents, so open the ones you trust.
|
|
115
|
+
Agents run confined. Everything a shell command spawns runs inside an OS sandbox (Seatbelt on macOS, bubblewrap on Linux) that can write to the worktree, the temp directories and package-manager caches, and nothing else, and the file tools are held to the same boundary. An agent cannot widen its own sandbox or another's. MCP servers run as each agent runs them, outside Toyon's sandbox. Opening a repository runs its setup, its dev server and whatever it configures for the agents, so open the ones you trust.
|
|
93
116
|
|
|
94
117
|
Each chat has a permission mode, shown next to the prompt. **auto**, the default, lets edits and sandboxed commands run and asks only when the agent proposes a plan. **ask** turns every edit and every command into a card in the chat before it runs. **plan** puts the agent in its read-only mode, and approving its plan chooses whether the work runs in auto or ask.
|
|
95
118
|
|
|
96
|
-
One limit worth knowing: the sandbox cannot tell a commit from a push. `git push`, branch deletion and `gh pr` are refused by rules in the configuration Toyon gives Claude Code and OpenCode, Codex has no equivalent list, and
|
|
119
|
+
One limit worth knowing: the sandbox cannot tell a commit from a push. `git push`, branch deletion and `gh pr` are refused by rules in the configuration Toyon gives Claude Code and OpenCode, and every agent is told not to push. Codex has no equivalent list, and its shell commands can read Toyon's token. That is a command filter, not a wall: if your credentials are on the machine, a determined agent could still find a spelling that pushes. The whole boundary, per agent, is in [docs/trust.md](https://github.com/toyon-dev/toyon/blob/main/docs/trust.md).
|
|
97
120
|
|
|
98
121
|
## Uninstall
|
|
99
122
|
|
|
@@ -102,11 +125,11 @@ toyon uninstall
|
|
|
102
125
|
npm uninstall -g toyon
|
|
103
126
|
```
|
|
104
127
|
|
|
105
|
-
The first command lists what it will remove
|
|
128
|
+
The first command lists what it will remove from `~/.toyon` and asks before doing it. It keeps your projects, every branch Toyon made under `toyon/`, and Toyon's settings in each project. It does not delete a machine you deployed to Fly; run `toyon deploy fly destroy <name>` first.
|
|
106
129
|
|
|
107
130
|
## Telemetry
|
|
108
131
|
|
|
109
|
-
None. Toyon
|
|
132
|
+
None: nothing about you or your work is sent anywhere. Toyon's one call of its own is an update check every six hours against the registry npm is set up for, and `TOYON_UPDATES=off` stops it. The rest of the traffic is to the agents you sign into, to npm on first start to fetch the Claude Code and Codex adapters and again when you first pick OpenCode, to Fly when you deploy there, and to whatever your own dev servers, `git fetch` and `git push` talk to.
|
|
110
133
|
|
|
111
134
|
## Help build it
|
|
112
135
|
|
package/dist/bridge.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var T=[{id:"quick-open",key:"p"},{id:"commands",key:"p",shift:!0,bareAlias:"F1"},{id:"search",key:"f",shift:!0},{id:"chats",key:"g"},{id:"changes",key:"b",ctrlAlias:{key:"g",shift:!0}},{id:"composer",key:"l"},{id:"rail",key:"k",shift:!0},{id:"keys",key:","},{id:"terminal",key:"j",ctrlAlias:{key:"`"}},{id:"design",key:"d"},{id:"term-tab",key:"`",ctrl:!0,shift:!0},{id:"pick",key:"e"},{id:"inspect",key:"i"},{id:"zen",key:"."},{id:"new",key:"k",aliases:["n"]},{id:"worktree",key:"1-9"},{id:"wt-prev",key:"ArrowUp",alt:!0,ctrlAlias:{key:"Tab",shift:!0},cmdShiftAlias:["[","{"]},{id:"wt-next",key:"ArrowDown",alt:!0,ctrlAlias:{key:"Tab"},cmdShiftAlias:["]","}"]},{id:"wt-unseen-prev",key:"ArrowUp",alt:!0,shift:!0},{id:"wt-unseen-next",key:"ArrowDown",alt:!0,shift:!0},{id:"mark-unread",key:"u",shift:!0},{id:"project",key:"o",ctrlAlias:{key:"r",hostOnly:!0},cmdShiftAlias:["o"]},{id:"refs",key:"g",shift:!0},{id:"routes",key:"u"},{id:"reload",key:"r"}];function H(e,{guest:t=!1}={}){if(e.altKey){if(e.metaKey||e.ctrlKey)return null;let r=T.find((i)=>i.alt&&i.key===e.key&&!!i.shift===e.shiftKey);return r&&r.id!=="worktree"?{id:r.id}:null}if(!e.metaKey&&!e.ctrlKey){if(e.shiftKey||t)return null;let r=T.find((i)=>i.bareAlias===e.key);return r&&r.id!=="worktree"?{id:r.id}:null}if(!!e.ctrlKey===e.metaKey)return null;let n=e.key.length===1?e.key.toLowerCase():e.key;if(e.metaKey&&!e.shiftKey&&n>="1"&&n<="9")return{id:"worktree",digit:Number(n)};for(let r of T){if(r.id==="worktree")continue;let i=r.ctrlAlias;if(i&&e.ctrlKey&&i.key===n&&!!i.shift===e.shiftKey&&!(t&&i.hostOnly))return{id:r.id};if(e.metaKey&&e.shiftKey&&r.cmdShiftAlias?.includes(n))return{id:r.id};if(r.alt)continue;if(e.ctrlKey&&!r.ctrl||!!r.shift!==e.shiftKey)continue;if(r.key===n||r.aliases?.includes(n))return{id:r.id}}return null}var G=Symbol.for("react.forward_ref"),V=Symbol.for("react.memo");function R(e){if(e._debugSource?.fileName)return{file:e._debugSource.fileName,line:e._debugSource.lineNumber};let t=e._debugStack?.stack;if(t){let n=t.match(/https?:\/\/[^/]+(\/src\/[^)\s?]+)(?:\?[^:)\s]*)?:(\d+):\d+/);if(n?.[1])return{file:n[1],line:Number(n[2])||void 0}}return null}function _(e){for(let t=e;t;t=t.return){let n=R(t);if(n)return n}return null}function C(e){if(typeof e==="function")return e.displayName||e.name||null;let t=e;if(t?.$$typeof===G)return t.displayName||C(t.render);if(t?.$$typeof===V)return t.displayName||C(t.type);return null}var J=(e)=>typeof e.type==="string"||e.tag===6,Q=(e,t)=>!!e&&(e===t||!!e.stateNode&&e.stateNode===t.stateNode);function Z(e){let t=null,n=(r)=>{for(let i=r.child??null;i;i=i.sibling??null)if(J(i)){if(t)return!1;t=i}else if(!n(i))return!1;return!0};return n(e)?t:null}function ee(e){for(let t=e;t;t=t.return){let n=R(t);if(n)return t._debugOwner===null?null:n}return null}function K(e){let t=_(e),n=null,r=null,i=!1;for(let o=e;o;o=o.return){let s=C(o.type);if(!s)continue;let l=!!e&&Q(Z(o),e),a=ee(o);if(n&&!(l&&a))break;if(n=s,r=a,i=l,!l)break}if(r&&t&&r.file===t.file&&r.line===t.line)r=null;return{src:t,comp:n,call:r,wraps:i}}function L({src:e,call:t,wraps:n},r){let i=n?t:e,o=n?e:t;return(r?o??i:i??o)??null}if(window.__toyonBridge)throw Error("Toyon bridge already installed");window.__toyonBridge=!0;var b=window.__toyonShellOrigins??[],O=null,c=(e)=>{try{let t=O?[O]:b.length?b:["*"];for(let n of t)window.parent.postMessage({__toyon:!0,...e},n)}catch{}};c({type:"loaded",url:location.href,title:document.title});var w="#ff4929",W="#1d2021";window.addEventListener("vite:afterUpdate",()=>c({type:"hmr"}));window.addEventListener("error",(e)=>{c({type:"page-error",message:String(e.message),source:e.filename,line:e.lineno})});window.addEventListener("unhandledrejection",(e)=>{c({type:"page-error",message:`unhandled rejection: ${String(e.reason)}`})});var S=()=>c({type:"navigated",url:location.href,title:document.title}),te=history.pushState.bind(history);history.pushState=(...e)=>{te(...e),S()};var ne=history.replaceState.bind(history);history.replaceState=(...e)=>{ne(...e),S()};window.addEventListener("popstate",S);window.addEventListener("hashchange",S);var F=document.title;if(document.head)new MutationObserver(()=>{if(document.title===F)return;F=document.title,c({type:"title",title:F})}).observe(document.head,{childList:!0,subtree:!0,characterData:!0});var j=!1;window.addEventListener("keydown",(e)=>{if(window.__toyonShell)return;let t=H(e,{guest:!0});if(j){if(t?.id!=="zen")return}else if(e.key==="Escape"){c({type:"key",key:"Escape",meta:!1});return}else if(!t)return;e.preventDefault(),e.stopPropagation(),c({type:"key",key:e.key,meta:e.metaKey,ctrl:e.ctrlKey,shift:e.shiftKey,alt:e.altKey})},!0);var q=(e)=>!e.defaultPrevented&&!!e.dataTransfer?.types.includes("Files");window.addEventListener("dragover",(e)=>{if(!q(e))return;if(e.preventDefault(),e.dataTransfer)e.dataTransfer.dropEffect="none";c({type:"drag-files"})});window.addEventListener("drop",(e)=>{if(!q(e))return;e.preventDefault(),c({type:"drop-files"})});function P(e){for(let t of Object.keys(e))if(t.startsWith("__reactFiber$"))return e[t]??null;return null}var p=null;function re(){if(p?.isConnected)return p;return p=document.createElement("div"),p.style.cssText="position:fixed;inset:0;pointer-events:none;z-index:2147483647;",document.documentElement.appendChild(p),p}function m(){if(p)p.innerHTML=""}var N=20;function E(e,t,n=!0){let r=document.createElement("div");if(r.style.cssText=`position:fixed;left:${e.left}px;top:${e.top}px;width:${e.width}px;height:${e.height}px;outline:2px solid ${w};outline-offset:-1px;${n?`background:${w}14;`:""}border-radius:2px;`,t){let i=e.top>=N,o=e.bottom+N<=window.innerHeight;if(t.style.top=i?`${-N}px`:o?`${e.height+4}px`:"2px",!i&&!o)t.style.left="2px";r.appendChild(t)}if(re().appendChild(r),t){let i=t.getBoundingClientRect(),o=i.right-(window.innerWidth-2);if(o>0){let s=Number.parseFloat(t.style.left||"0");t.style.left=`${s-Math.min(o,Math.max(0,i.left-2))}px`}}}function g(e,t){let n=document.createElement("div");if(n.textContent=e,n.style.cssText=`position:absolute;left:0;background:${w};color:${W};font:11px -apple-system,sans-serif;padding:1px 6px;border-radius:3px;white-space:nowrap;`,t){let r=document.createElement("span");r.textContent=` ${t}`,r.style.opacity="0.55",n.appendChild(r)}return n}var x=!1,d=null,h=!1,f=!1,y=!1,M=(e)=>`${U(e.file)}${e.line?`:${e.line}`:""}`;function A(e){d=e,m();let t=K(P(e)),{src:n,comp:r,call:i}=t,o=L(t,f),s=o===n?i:n,l=h!==y,a=(...k)=>k.filter(Boolean).join(" ")||void 0,u=l?o?g(`open ${M(o)}`,a(s&&`⇧ ${M(s)}`,y&&"⌥ chat")):g(`find ${I(e)}`,a(y&&"⌥ chat")):g(r?`<${r} />${o?` · ${M(o)}`:""}`:I(e),a(!y&&"⌥ code"));E(e.getBoundingClientRect(),u,l)}function I(e){let t=e.tagName.toLowerCase(),n=e.classList[0];return(e.id?`${t}#${e.id}`:n?`${t}.${n}`:t).slice(0,48)}var ie=new Set(["name","placeholder","aria-label","title","alt","for","href","type","role","src"]);function oe(e){let t="";for(let r of Array.from(e.childNodes))if(r.nodeType===Node.TEXT_NODE)t+=r.textContent??"";let n=[];for(let r of Array.from(e.attributes)){if(n.length===12)break;if(r.value&&(ie.has(r.name)||r.name.startsWith("data-")))n.push([r.name.slice(0,40),r.value.slice(0,200)])}return{tag:e.tagName.toLowerCase().slice(0,40),id:e.id.slice(0,200),classes:Array.from(e.classList).slice(0,16).map((r)=>r.slice(0,120)),text:t.replace(/\s+/g," ").trim().slice(0,200),attrs:n}}function z(e){let t=document.elementFromPoint(e.clientX,e.clientY);if(!t)return;let n=t!==d||e.altKey!==h||e.shiftKey!==f;if(h=e.altKey,f=e.shiftKey,n)A(t)}function X(e){e.preventDefault(),e.stopPropagation();let t=d??document.elementFromPoint(e.clientX,e.clientY),n=K(t?P(t):null),{src:r,comp:i,call:o}=n;f=e.shiftKey;let s=L(n,f),l=e.altKey!==y;if(D(),!t)return;c({type:"picked",verb:l?"code":"chat",site:s&&s===o?"call":"source",component:i,file:r?.file??null,line:r?.line??null,callFile:o?.file??null,callLine:o?.line??null,selector:se(t),tag:t.tagName.toLowerCase(),classes:t.className?.toString?.().slice(0,200)??"",text:t.textContent?.trim().slice(0,120)??"",html:t.outerHTML.slice(0,600),route:location.pathname+location.search,element:oe(t)})}function v(e){if(e.key==="Escape"&&e.type==="keydown"){e.preventDefault(),e.stopPropagation(),D(),c({type:"pick-cancel"});return}if((e.key==="Alt"||e.key==="Shift")&&(h!==e.altKey||f!==e.shiftKey)){if(h=e.altKey,f=e.shiftKey,d)A(d)}}function le(e){if(y=e,x){if(d)A(d);return}x=!0,d=null,h=!1,f=!1,document.addEventListener("mousemove",z,!0),document.addEventListener("click",X,!0),document.addEventListener("keydown",v,!0),document.addEventListener("keyup",v,!0),document.documentElement.style.cursor="crosshair"}function D(){x=!1,y=!1,d=null,h=!1,f=!1,m(),document.removeEventListener("mousemove",z,!0),document.removeEventListener("click",X,!0),document.removeEventListener("keydown",v,!0),document.removeEventListener("keyup",v,!0),document.documentElement.style.cursor=""}function se(e){let t=[],n=e;for(let r=0;n&&n!==document.documentElement&&r<6;r++){if(n.id){t.unshift(`#${CSS.escape(n.id)}`);break}let i=n.parentElement,o=i?Array.from(i.children).indexOf(n)+1:1;t.unshift(`${n.tagName.toLowerCase()}:nth-child(${o})`),n=i}return t.join(" > ")}function U(e){let t=e.lastIndexOf("/src/");return t>=0?e.slice(t+1):e.split("/").slice(-2).join("/")}function ae(e,t){return e.endsWith(t)||t.endsWith(U(e))}function ce(e,t){let n=[],r=0;for(let s of Array.from(document.querySelectorAll("*"))){let l=_(P(s));if(l)r++;if(!l||!ae(l.file,e))continue;n.push({el:s,line:l.line??-1})}if(!t||t.length===0)return{matched:n,fromFile:n,withSource:r};if(t.length===1&&t[0][0]===t[0][1]){let s=t[0][0],l=n.filter((u)=>u.line===s);if(l.length>0)return{matched:l,fromFile:n,withSource:r};let a=n.filter((u)=>u.line>0&&u.line<s);return a.sort((u,k)=>k.line-u.line),{matched:a.slice(0,1),fromFile:n,withSource:r}}return{matched:n.filter((s)=>t.some(([l,a])=>s.line>=l-8&&s.line<=a+1)),fromFile:n,withSource:r}}function ue(e,t){m();let{matched:n,fromFile:r,withSource:i}=ce(e,t),o=0;for(let{el:s}of n){if(o>=40)break;let l=s.getBoundingClientRect();if(l.width>0&&l.height>0)E(l),o++}if(o===0)c({type:"highlight-miss",path:e,fileMatched:r.length,withSource:i,ranges:t})}window.addEventListener("message",(e)=>{if(e.source!==window.parent)return;if(b.length&&!b.includes(e.origin))return;let t=e.data;if(!t?.__toyon)return;O=e.origin;let n=t;switch(n.type){case"reload":location.reload();break;case"navigate":if(/^[/?#]/.test(n.path))location.assign(n.path);break;case"back":history.back();break;case"forward":history.forward();break;case"links":{let r=new Set,i=[];for(let o of Array.from(document.querySelectorAll("a[href]"))){if(o.origin!==location.origin||o.target==="_blank"||o.hasAttribute("download"))continue;let s=o.pathname+(o.hash.startsWith("#/")?o.hash:"");if(s.startsWith("/__toyon")||r.has(s))continue;r.add(s);let l=(o.textContent||o.getAttribute("aria-label")||"").replace(/\s+/g," ").trim();if(i.push({path:s,text:l.slice(0,80)}),i.length>=200)break}c({type:"links",links:i});break}case"pick-start":le(n.verb==="code");break;case"pick-mods":if(x&&(h!==n.alt||f!==n.shift)){if(h=n.alt,f=n.shift,d)A(d)}break;case"pick-cancel":D();break;case"highlight-file":ue(n.path,n.ranges??null);break;case"highlight-computed":{m();let r=/^(color|font|line-height|letter-|text-|word-)/,i=(l)=>{if(/^(INPUT|TEXTAREA|SELECT)$/.test(l.tagName))return!0;for(let a=l.firstChild;a;a=a.nextSibling)if(a.nodeType===3&&a.textContent&&a.textContent.trim())return!0;return!1},o=document.querySelectorAll("*"),s=!0;for(let l=0;l<o.length;l++){let a=getComputedStyle(o[l]),u=([B,Y])=>a.getPropertyValue(B)===Y&&(!r.test(B)||i(o[l]));if(n.match==="any"?n.props.some(u):n.props.every(u))E(o[l].getBoundingClientRect(),s&&n.label?g(n.label):void 0),s=!1}break}case"highlight-selector":{m();try{let r=document.querySelectorAll(n.selector);for(let i=0;i<r.length;i++)E(r[i].getBoundingClientRect(),i===0&&n.label?g(n.label):void 0)}catch{}break}case"highlight-clear":m();break;case"zen":j=n.on;break;case"theme":if(/^#[0-9a-f]{3,8}$/i.test(n.accent))w=n.accent;if(/^#[0-9a-f]{3,8}$/i.test(n.accentFg))W=n.accentFg;break}});
|
|
1
|
+
var T=[{id:"quick-open",key:"p"},{id:"commands",key:"p",shift:!0,bareAlias:"F1"},{id:"search",key:"f",shift:!0},{id:"chats",key:"g"},{id:"changes",key:"b",ctrlAlias:{key:"g",shift:!0}},{id:"files",key:"e",shift:!0},{id:"composer",key:"l"},{id:"rail",key:"k",shift:!0},{id:"keys",key:","},{id:"terminal",key:"j",ctrlAlias:{key:"`"}},{id:"design",key:"d"},{id:"term-tab",key:"`",ctrl:!0,shift:!0},{id:"pick",key:"e"},{id:"inspect",key:"i"},{id:"zen",key:"."},{id:"new",key:"k",aliases:["n"]},{id:"worktree",key:"1-9"},{id:"wt-prev",key:"ArrowUp",alt:!0,ctrlAlias:{key:"Tab",shift:!0},cmdShiftAlias:["[","{"]},{id:"wt-next",key:"ArrowDown",alt:!0,ctrlAlias:{key:"Tab"},cmdShiftAlias:["]","}"]},{id:"wt-unseen-prev",key:"ArrowUp",alt:!0,shift:!0},{id:"wt-unseen-next",key:"ArrowDown",alt:!0,shift:!0},{id:"mark-unread",key:"u",shift:!0},{id:"project",key:"o",ctrlAlias:{key:"r",hostOnly:!0},cmdShiftAlias:["o"]},{id:"refs",key:"g",shift:!0},{id:"routes",key:"u"},{id:"reload",key:"r"}];function H(e,{guest:t=!1}={}){if(e.altKey){if(e.metaKey||e.ctrlKey)return null;let r=T.find((i)=>i.alt&&i.key===e.key&&!!i.shift===e.shiftKey);return r&&r.id!=="worktree"?{id:r.id}:null}if(!e.metaKey&&!e.ctrlKey){if(e.shiftKey||t)return null;let r=T.find((i)=>i.bareAlias===e.key);return r&&r.id!=="worktree"?{id:r.id}:null}if(!!e.ctrlKey===e.metaKey)return null;let n=e.key.length===1?e.key.toLowerCase():e.key;if(e.metaKey&&!e.shiftKey&&n>="1"&&n<="9")return{id:"worktree",digit:Number(n)};for(let r of T){if(r.id==="worktree")continue;let i=r.ctrlAlias;if(i&&e.ctrlKey&&i.key===n&&!!i.shift===e.shiftKey&&!(t&&i.hostOnly))return{id:r.id};if(e.metaKey&&e.shiftKey&&r.cmdShiftAlias?.includes(n))return{id:r.id};if(r.alt)continue;if(e.ctrlKey&&!r.ctrl||!!r.shift!==e.shiftKey)continue;if(r.key===n||r.aliases?.includes(n))return{id:r.id}}return null}var G=Symbol.for("react.forward_ref"),V=Symbol.for("react.memo");function R(e){if(e._debugSource?.fileName)return{file:e._debugSource.fileName,line:e._debugSource.lineNumber};let t=e._debugStack?.stack;if(t){let n=t.match(/https?:\/\/[^/]+(\/src\/[^)\s?]+)(?:\?[^:)\s]*)?:(\d+):\d+/);if(n?.[1])return{file:n[1],line:Number(n[2])||void 0}}return null}function _(e){for(let t=e;t;t=t.return){let n=R(t);if(n)return n}return null}function C(e){if(typeof e==="function")return e.displayName||e.name||null;let t=e;if(t?.$$typeof===G)return t.displayName||C(t.render);if(t?.$$typeof===V)return t.displayName||C(t.type);return null}var J=(e)=>typeof e.type==="string"||e.tag===6,Q=(e,t)=>!!e&&(e===t||!!e.stateNode&&e.stateNode===t.stateNode);function Z(e){let t=null,n=(r)=>{for(let i=r.child??null;i;i=i.sibling??null)if(J(i)){if(t)return!1;t=i}else if(!n(i))return!1;return!0};return n(e)?t:null}function ee(e){for(let t=e;t;t=t.return){let n=R(t);if(n)return t._debugOwner===null?null:n}return null}function K(e){let t=_(e),n=null,r=null,i=!1;for(let o=e;o;o=o.return){let s=C(o.type);if(!s)continue;let l=!!e&&Q(Z(o),e),a=ee(o);if(n&&!(l&&a))break;if(n=s,r=a,i=l,!l)break}if(r&&t&&r.file===t.file&&r.line===t.line)r=null;return{src:t,comp:n,call:r,wraps:i}}function L({src:e,call:t,wraps:n},r){let i=n?t:e,o=n?e:t;return(r?o??i:i??o)??null}if(window.__toyonBridge)throw Error("Toyon bridge already installed");window.__toyonBridge=!0;var b=window.__toyonShellOrigins??[],O=null,c=(e)=>{try{let t=O?[O]:b.length?b:["*"];for(let n of t)window.parent.postMessage({__toyon:!0,...e},n)}catch{}};c({type:"loaded",url:location.href,title:document.title});var w="#ff4929",W="#1d2021";window.addEventListener("vite:afterUpdate",()=>c({type:"hmr"}));window.addEventListener("error",(e)=>{c({type:"page-error",message:String(e.message),source:e.filename,line:e.lineno})});window.addEventListener("unhandledrejection",(e)=>{c({type:"page-error",message:`unhandled rejection: ${String(e.reason)}`})});var S=()=>c({type:"navigated",url:location.href,title:document.title}),te=history.pushState.bind(history);history.pushState=(...e)=>{te(...e),S()};var ne=history.replaceState.bind(history);history.replaceState=(...e)=>{ne(...e),S()};window.addEventListener("popstate",S);window.addEventListener("hashchange",S);var F=document.title;if(document.head)new MutationObserver(()=>{if(document.title===F)return;F=document.title,c({type:"title",title:F})}).observe(document.head,{childList:!0,subtree:!0,characterData:!0});var j=!1;window.addEventListener("keydown",(e)=>{if(window.__toyonShell)return;let t=H(e,{guest:!0});if(j){if(t?.id!=="zen")return}else if(e.key==="Escape"){c({type:"key",key:"Escape",meta:!1});return}else if(!t)return;e.preventDefault(),e.stopPropagation(),c({type:"key",key:e.key,meta:e.metaKey,ctrl:e.ctrlKey,shift:e.shiftKey,alt:e.altKey})},!0);var q=(e)=>!e.defaultPrevented&&!!e.dataTransfer?.types.includes("Files");window.addEventListener("dragover",(e)=>{if(!q(e))return;if(e.preventDefault(),e.dataTransfer)e.dataTransfer.dropEffect="none";c({type:"drag-files"})});window.addEventListener("drop",(e)=>{if(!q(e))return;e.preventDefault(),c({type:"drop-files"})});function P(e){for(let t of Object.keys(e))if(t.startsWith("__reactFiber$"))return e[t]??null;return null}var p=null;function re(){if(p?.isConnected)return p;return p=document.createElement("div"),p.style.cssText="position:fixed;inset:0;pointer-events:none;z-index:2147483647;",document.documentElement.appendChild(p),p}function m(){if(p)p.innerHTML=""}var N=20;function E(e,t,n=!0){let r=document.createElement("div");if(r.style.cssText=`position:fixed;left:${e.left}px;top:${e.top}px;width:${e.width}px;height:${e.height}px;outline:2px solid ${w};outline-offset:-1px;${n?`background:${w}14;`:""}border-radius:2px;`,t){let i=e.top>=N,o=e.bottom+N<=window.innerHeight;if(t.style.top=i?`${-N}px`:o?`${e.height+4}px`:"2px",!i&&!o)t.style.left="2px";r.appendChild(t)}if(re().appendChild(r),t){let i=t.getBoundingClientRect(),o=i.right-(window.innerWidth-2);if(o>0){let s=Number.parseFloat(t.style.left||"0");t.style.left=`${s-Math.min(o,Math.max(0,i.left-2))}px`}}}function g(e,t){let n=document.createElement("div");if(n.textContent=e,n.style.cssText=`position:absolute;left:0;background:${w};color:${W};font:11px -apple-system,sans-serif;padding:1px 6px;border-radius:3px;white-space:nowrap;`,t){let r=document.createElement("span");r.textContent=` ${t}`,r.style.opacity="0.55",n.appendChild(r)}return n}var x=!1,d=null,h=!1,f=!1,y=!1,M=(e)=>`${U(e.file)}${e.line?`:${e.line}`:""}`;function A(e){d=e,m();let t=K(P(e)),{src:n,comp:r,call:i}=t,o=L(t,f),s=o===n?i:n,l=h!==y,a=(...k)=>k.filter(Boolean).join(" ")||void 0,u=l?o?g(`open ${M(o)}`,a(s&&`⇧ ${M(s)}`,y&&"⌥ chat")):g(`find ${I(e)}`,a(y&&"⌥ chat")):g(r?`<${r} />${o?` · ${M(o)}`:""}`:I(e),a(!y&&"⌥ code"));E(e.getBoundingClientRect(),u,l)}function I(e){let t=e.tagName.toLowerCase(),n=e.classList[0];return(e.id?`${t}#${e.id}`:n?`${t}.${n}`:t).slice(0,48)}var ie=new Set(["name","placeholder","aria-label","title","alt","for","href","type","role","src"]);function oe(e){let t="";for(let r of Array.from(e.childNodes))if(r.nodeType===Node.TEXT_NODE)t+=r.textContent??"";let n=[];for(let r of Array.from(e.attributes)){if(n.length===12)break;if(r.value&&(ie.has(r.name)||r.name.startsWith("data-")))n.push([r.name.slice(0,40),r.value.slice(0,200)])}return{tag:e.tagName.toLowerCase().slice(0,40),id:e.id.slice(0,200),classes:Array.from(e.classList).slice(0,16).map((r)=>r.slice(0,120)),text:t.replace(/\s+/g," ").trim().slice(0,200),attrs:n}}function z(e){let t=document.elementFromPoint(e.clientX,e.clientY);if(!t)return;let n=t!==d||e.altKey!==h||e.shiftKey!==f;if(h=e.altKey,f=e.shiftKey,n)A(t)}function X(e){e.preventDefault(),e.stopPropagation();let t=d??document.elementFromPoint(e.clientX,e.clientY),n=K(t?P(t):null),{src:r,comp:i,call:o}=n;f=e.shiftKey;let s=L(n,f),l=e.altKey!==y;if(D(),!t)return;c({type:"picked",verb:l?"code":"chat",site:s&&s===o?"call":"source",component:i,file:r?.file??null,line:r?.line??null,callFile:o?.file??null,callLine:o?.line??null,selector:se(t),tag:t.tagName.toLowerCase(),classes:t.className?.toString?.().slice(0,200)??"",text:t.textContent?.trim().slice(0,120)??"",html:t.outerHTML.slice(0,600),route:location.pathname+location.search,element:oe(t)})}function v(e){if(e.key==="Escape"&&e.type==="keydown"){e.preventDefault(),e.stopPropagation(),D(),c({type:"pick-cancel"});return}if((e.key==="Alt"||e.key==="Shift")&&(h!==e.altKey||f!==e.shiftKey)){if(h=e.altKey,f=e.shiftKey,d)A(d)}}function le(e){if(y=e,x){if(d)A(d);return}x=!0,d=null,h=!1,f=!1,document.addEventListener("mousemove",z,!0),document.addEventListener("click",X,!0),document.addEventListener("keydown",v,!0),document.addEventListener("keyup",v,!0),document.documentElement.style.cursor="crosshair"}function D(){x=!1,y=!1,d=null,h=!1,f=!1,m(),document.removeEventListener("mousemove",z,!0),document.removeEventListener("click",X,!0),document.removeEventListener("keydown",v,!0),document.removeEventListener("keyup",v,!0),document.documentElement.style.cursor=""}function se(e){let t=[],n=e;for(let r=0;n&&n!==document.documentElement&&r<6;r++){if(n.id){t.unshift(`#${CSS.escape(n.id)}`);break}let i=n.parentElement,o=i?Array.from(i.children).indexOf(n)+1:1;t.unshift(`${n.tagName.toLowerCase()}:nth-child(${o})`),n=i}return t.join(" > ")}function U(e){let t=e.lastIndexOf("/src/");return t>=0?e.slice(t+1):e.split("/").slice(-2).join("/")}function ae(e,t){return e.endsWith(t)||t.endsWith(U(e))}function ce(e,t){let n=[],r=0;for(let s of Array.from(document.querySelectorAll("*"))){let l=_(P(s));if(l)r++;if(!l||!ae(l.file,e))continue;n.push({el:s,line:l.line??-1})}if(!t||t.length===0)return{matched:n,fromFile:n,withSource:r};if(t.length===1&&t[0][0]===t[0][1]){let s=t[0][0],l=n.filter((u)=>u.line===s);if(l.length>0)return{matched:l,fromFile:n,withSource:r};let a=n.filter((u)=>u.line>0&&u.line<s);return a.sort((u,k)=>k.line-u.line),{matched:a.slice(0,1),fromFile:n,withSource:r}}return{matched:n.filter((s)=>t.some(([l,a])=>s.line>=l-8&&s.line<=a+1)),fromFile:n,withSource:r}}function ue(e,t){m();let{matched:n,fromFile:r,withSource:i}=ce(e,t),o=0;for(let{el:s}of n){if(o>=40)break;let l=s.getBoundingClientRect();if(l.width>0&&l.height>0)E(l),o++}if(o===0)c({type:"highlight-miss",path:e,fileMatched:r.length,withSource:i,ranges:t})}window.addEventListener("message",(e)=>{if(e.source!==window.parent)return;if(b.length&&!b.includes(e.origin))return;let t=e.data;if(!t?.__toyon)return;O=e.origin;let n=t;switch(n.type){case"reload":location.reload();break;case"navigate":if(/^[/?#]/.test(n.path))location.assign(n.path);break;case"back":history.back();break;case"forward":history.forward();break;case"links":{let r=new Set,i=[];for(let o of Array.from(document.querySelectorAll("a[href]"))){if(o.origin!==location.origin||o.target==="_blank"||o.hasAttribute("download"))continue;let s=o.pathname+(o.hash.startsWith("#/")?o.hash:"");if(s.startsWith("/__toyon")||r.has(s))continue;r.add(s);let l=(o.textContent||o.getAttribute("aria-label")||"").replace(/\s+/g," ").trim();if(i.push({path:s,text:l.slice(0,80)}),i.length>=200)break}c({type:"links",links:i});break}case"pick-start":le(n.verb==="code");break;case"pick-mods":if(x&&(h!==n.alt||f!==n.shift)){if(h=n.alt,f=n.shift,d)A(d)}break;case"pick-cancel":D();break;case"highlight-file":ue(n.path,n.ranges??null);break;case"highlight-computed":{m();let r=/^(color|font|line-height|letter-|text-|word-)/,i=(l)=>{if(/^(INPUT|TEXTAREA|SELECT)$/.test(l.tagName))return!0;for(let a=l.firstChild;a;a=a.nextSibling)if(a.nodeType===3&&a.textContent&&a.textContent.trim())return!0;return!1},o=document.querySelectorAll("*"),s=!0;for(let l=0;l<o.length;l++){let a=getComputedStyle(o[l]),u=([B,Y])=>a.getPropertyValue(B)===Y&&(!r.test(B)||i(o[l]));if(n.match==="any"?n.props.some(u):n.props.every(u))E(o[l].getBoundingClientRect(),s&&n.label?g(n.label):void 0),s=!1}break}case"highlight-selector":{m();try{let r=document.querySelectorAll(n.selector);for(let i=0;i<r.length;i++)E(r[i].getBoundingClientRect(),i===0&&n.label?g(n.label):void 0)}catch{}break}case"highlight-clear":m();break;case"zen":j=n.on;break;case"theme":if(/^#[0-9a-f]{3,8}$/i.test(n.accent))w=n.accent;if(/^#[0-9a-f]{3,8}$/i.test(n.accentFg))W=n.accentFg;break}});
|
package/dist/cli.js
CHANGED
|
@@ -3,75 +3,14 @@
|
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
5
|
import { existsSync as existsSync10 } from "fs";
|
|
6
|
-
import { join as
|
|
7
|
-
// package.json
|
|
8
|
-
var package_default = {
|
|
9
|
-
name: "toyon",
|
|
10
|
-
version: "0.2.0",
|
|
11
|
-
description: "Checking an agent's work means running it. Toyon gives every chat its own copy of your web app, 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.2",
|
|
51
|
-
"bun-pty": "0.4.10"
|
|
52
|
-
},
|
|
53
|
-
devDependencies: {
|
|
54
|
-
"@toyon/shared": "workspace:*"
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// src/app.ts
|
|
59
|
-
import { spawn as spawn2, spawnSync } from "child_process";
|
|
60
|
-
import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync, 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 existsSync2, mkdirSync, openSync, readFileSync } from "fs";
|
|
67
|
-
import { homedir } from "os";
|
|
68
|
-
import { join as join2 } from "path";
|
|
6
|
+
import { join as join8, resolve } from "path";
|
|
69
7
|
|
|
70
8
|
// ../shared/src/daemon.ts
|
|
71
9
|
var DAEMON_FILES = {
|
|
72
10
|
token: "token",
|
|
73
11
|
pid: "daemon.pid",
|
|
74
12
|
state: "state.json",
|
|
13
|
+
drafts: "drafts.json",
|
|
75
14
|
log: "daemon.log",
|
|
76
15
|
remote: "remote.json"
|
|
77
16
|
};
|
|
@@ -153,6 +92,114 @@ function bwrapBlockedAdvice(error, restricted) {
|
|
|
153
92
|
`);
|
|
154
93
|
}
|
|
155
94
|
|
|
95
|
+
// ../shared/src/update.ts
|
|
96
|
+
function installMethod(packageJson) {
|
|
97
|
+
if (!packageJson)
|
|
98
|
+
return "none";
|
|
99
|
+
const p = packageJson.replaceAll("\\", "/");
|
|
100
|
+
if (p.includes("/_npx/"))
|
|
101
|
+
return "npx";
|
|
102
|
+
if (p.includes("/.bun/install/global/"))
|
|
103
|
+
return "bun";
|
|
104
|
+
if (p.includes("/node_modules/toyon/"))
|
|
105
|
+
return "npm";
|
|
106
|
+
return "none";
|
|
107
|
+
}
|
|
108
|
+
function installCommand(method, version) {
|
|
109
|
+
if (method === "npm")
|
|
110
|
+
return ["npm", "install", "-g", `toyon@${version}`];
|
|
111
|
+
if (method === "bun")
|
|
112
|
+
return ["bun", "add", "-g", `toyon@${version}`];
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
function newer(a, b) {
|
|
116
|
+
const pa = parseVersion(a);
|
|
117
|
+
const pb = parseVersion(b);
|
|
118
|
+
if (!pa || !pb)
|
|
119
|
+
return false;
|
|
120
|
+
for (let i = 0;i < 3; i++) {
|
|
121
|
+
const x = pa.nums[i] ?? 0;
|
|
122
|
+
const y = pb.nums[i] ?? 0;
|
|
123
|
+
if (x !== y)
|
|
124
|
+
return x > y;
|
|
125
|
+
}
|
|
126
|
+
if (pa.pre === pb.pre)
|
|
127
|
+
return false;
|
|
128
|
+
if (pa.pre === null)
|
|
129
|
+
return true;
|
|
130
|
+
if (pb.pre === null)
|
|
131
|
+
return false;
|
|
132
|
+
return pa.pre > pb.pre;
|
|
133
|
+
}
|
|
134
|
+
function parseVersion(v) {
|
|
135
|
+
const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(v.trim());
|
|
136
|
+
if (!m)
|
|
137
|
+
return null;
|
|
138
|
+
return { nums: [Number(m[1]), Number(m[2]), Number(m[3])], pre: m[4] ?? null };
|
|
139
|
+
}
|
|
140
|
+
// package.json
|
|
141
|
+
var package_default = {
|
|
142
|
+
name: "toyon",
|
|
143
|
+
version: "0.3.0",
|
|
144
|
+
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.",
|
|
145
|
+
license: "MIT",
|
|
146
|
+
type: "module",
|
|
147
|
+
homepage: "https://toyon.dev",
|
|
148
|
+
repository: {
|
|
149
|
+
type: "git",
|
|
150
|
+
url: "git+https://github.com/toyon-dev/toyon.git"
|
|
151
|
+
},
|
|
152
|
+
bugs: "https://github.com/toyon-dev/toyon/issues",
|
|
153
|
+
keywords: [
|
|
154
|
+
"ai",
|
|
155
|
+
"agents",
|
|
156
|
+
"claude-code",
|
|
157
|
+
"codex",
|
|
158
|
+
"opencode",
|
|
159
|
+
"git",
|
|
160
|
+
"worktree",
|
|
161
|
+
"preview",
|
|
162
|
+
"dev-server"
|
|
163
|
+
],
|
|
164
|
+
bin: {
|
|
165
|
+
toyon: "bin/toyon.js"
|
|
166
|
+
},
|
|
167
|
+
files: [
|
|
168
|
+
"bin",
|
|
169
|
+
"dist",
|
|
170
|
+
"cloud"
|
|
171
|
+
],
|
|
172
|
+
engines: {
|
|
173
|
+
node: ">=18"
|
|
174
|
+
},
|
|
175
|
+
os: [
|
|
176
|
+
"darwin",
|
|
177
|
+
"linux"
|
|
178
|
+
],
|
|
179
|
+
scripts: {
|
|
180
|
+
prepublishOnly: "bun ../../scripts/pack.ts"
|
|
181
|
+
},
|
|
182
|
+
dependencies: {
|
|
183
|
+
bun: "1.4.2",
|
|
184
|
+
"bun-pty": "0.4.10"
|
|
185
|
+
},
|
|
186
|
+
devDependencies: {
|
|
187
|
+
"@toyon/shared": "workspace:*"
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// src/app.ts
|
|
192
|
+
import { spawn as spawn2, spawnSync } from "child_process";
|
|
193
|
+
import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
|
|
194
|
+
import { homedir as homedir2 } from "os";
|
|
195
|
+
import { join as join3 } from "path";
|
|
196
|
+
|
|
197
|
+
// src/daemon.ts
|
|
198
|
+
import { spawn } from "child_process";
|
|
199
|
+
import { existsSync as existsSync2, mkdirSync, openSync, readFileSync } from "fs";
|
|
200
|
+
import { homedir } from "os";
|
|
201
|
+
import { join as join2 } from "path";
|
|
202
|
+
|
|
156
203
|
// src/layout.ts
|
|
157
204
|
import { existsSync } from "fs";
|
|
158
205
|
import { dirname, join } from "path";
|
|
@@ -404,7 +451,18 @@ exec /bin/bash "$(dirname "$0")/../Resources/launch.sh"
|
|
|
404
451
|
}
|
|
405
452
|
|
|
406
453
|
// src/args.ts
|
|
407
|
-
var VERBS = new Set([
|
|
454
|
+
var VERBS = new Set([
|
|
455
|
+
"stop",
|
|
456
|
+
"restart",
|
|
457
|
+
"update",
|
|
458
|
+
"doctor",
|
|
459
|
+
"logs",
|
|
460
|
+
"version",
|
|
461
|
+
"uninstall",
|
|
462
|
+
"remote",
|
|
463
|
+
"deploy",
|
|
464
|
+
"help"
|
|
465
|
+
]);
|
|
408
466
|
var DEFAULT_LOG_LINES = 100;
|
|
409
467
|
function parseArgs(argv) {
|
|
410
468
|
if (argv.includes("--help") || argv.includes("-h"))
|
|
@@ -416,6 +474,7 @@ function parseArgs(argv) {
|
|
|
416
474
|
switch (first) {
|
|
417
475
|
case "stop":
|
|
418
476
|
case "restart":
|
|
477
|
+
case "update":
|
|
419
478
|
case "doctor":
|
|
420
479
|
case "version":
|
|
421
480
|
case "help":
|
|
@@ -544,8 +603,10 @@ usage
|
|
|
544
603
|
toyon [path] start the daemon if it is not running, register the repo at path
|
|
545
604
|
(default: the current directory) and open the shell
|
|
546
605
|
toyon stop stop the daemon and every dev server and agent it runs
|
|
547
|
-
toyon restart stop the daemon and start it again
|
|
548
|
-
|
|
606
|
+
toyon restart stop the daemon and start it again from what is installed now; every
|
|
607
|
+
shell reconnects on its own
|
|
608
|
+
toyon update install the newest Toyon the way this one was installed, then restart
|
|
609
|
+
the daemon onto it once no chat is mid-reply
|
|
549
610
|
toyon doctor check the daemon, the token, the shell build and the tools Toyon needs
|
|
550
611
|
toyon logs [-f] [-n N] print the daemon log; -f keeps following it, -n sets how many lines
|
|
551
612
|
toyon version print the CLI version, and the daemon's if one is running
|
|
@@ -574,6 +635,7 @@ options for toyon [path]
|
|
|
574
635
|
environment
|
|
575
636
|
TOYON_HOME where state lives (default ~/.toyon)
|
|
576
637
|
TOYON_PORT the daemon's port (default 4141)
|
|
638
|
+
TOYON_UPDATES off stops Toyon checking for and installing updates on this machine
|
|
577
639
|
`;
|
|
578
640
|
|
|
579
641
|
// src/deploy/fly.ts
|
|
@@ -977,7 +1039,12 @@ async function doctor() {
|
|
|
977
1039
|
lines.push(line(true, "running", `${running} of ${total} worktrees started; the rest start when opened`));
|
|
978
1040
|
}
|
|
979
1041
|
if (version !== package_default.version) {
|
|
980
|
-
lines.push(line(false, "version", `cli is ${package_default.version} and the daemon is ${version}; \`toyon
|
|
1042
|
+
lines.push(line(false, "version", `cli is ${package_default.version} and the daemon is ${version}; \`toyon restart\` brings them level`));
|
|
1043
|
+
}
|
|
1044
|
+
if (h.updates?.managed) {
|
|
1045
|
+
lines.push(line(true, "updates", "off for this machine (TOYON_UPDATES=off)"));
|
|
1046
|
+
} else if (h.updates?.unreachable) {
|
|
1047
|
+
lines.push(line(false, "updates", `npm could not get toyon from ${h.updates.unreachable}, so Toyon cannot update itself; if you may use the public registry, run npm install -g toyon --registry=https://registry.npmjs.org/`));
|
|
981
1048
|
}
|
|
982
1049
|
}
|
|
983
1050
|
const token = readToken();
|
|
@@ -1373,11 +1440,97 @@ async function uninstall(opts) {
|
|
|
1373
1440
|
return 0;
|
|
1374
1441
|
}
|
|
1375
1442
|
|
|
1443
|
+
// src/update.ts
|
|
1444
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
1445
|
+
import { join as join7 } from "path";
|
|
1446
|
+
var WAIT_MS2 = 15000;
|
|
1447
|
+
async function update() {
|
|
1448
|
+
if (process.env.TOYON_UPDATES === "off") {
|
|
1449
|
+
console.error("updates are turned off for this machine (TOYON_UPDATES=off)");
|
|
1450
|
+
return 1;
|
|
1451
|
+
}
|
|
1452
|
+
const method = installMethod(packaged ? join7(here, "..", "package.json") : null);
|
|
1453
|
+
if (method === "none") {
|
|
1454
|
+
console.error("toyon update works on a global npm or bun install, and this Toyon is not one; update it the way it was installed");
|
|
1455
|
+
return 1;
|
|
1456
|
+
}
|
|
1457
|
+
const latest = latestVersion();
|
|
1458
|
+
if (latest === null) {
|
|
1459
|
+
console.error("could not ask npm for the newest Toyon; check that npm is on your PATH and its registry answers");
|
|
1460
|
+
return 1;
|
|
1461
|
+
}
|
|
1462
|
+
const behind = newer(latest, package_default.version);
|
|
1463
|
+
if (method === "npx") {
|
|
1464
|
+
console.log(behind ? `toyon ${latest} is out; npx runs the version you name, so run npx toyon@${latest}` : `toyon ${package_default.version} is the newest`);
|
|
1465
|
+
return 0;
|
|
1466
|
+
}
|
|
1467
|
+
if (behind) {
|
|
1468
|
+
const command = installCommand(method, latest) ?? [];
|
|
1469
|
+
const [cmd, ...args] = command;
|
|
1470
|
+
if (!cmd)
|
|
1471
|
+
return 1;
|
|
1472
|
+
console.log(`installing toyon ${latest}: ${command.join(" ")}`);
|
|
1473
|
+
const r = spawnSync2(cmd, args, { stdio: "inherit" });
|
|
1474
|
+
if (r.status !== 0) {
|
|
1475
|
+
console.error(`the install did not finish; run ${command.join(" ")} yourself to see why`);
|
|
1476
|
+
return r.status ?? 1;
|
|
1477
|
+
}
|
|
1478
|
+
} else {
|
|
1479
|
+
console.log(`toyon ${package_default.version} is the newest`);
|
|
1480
|
+
}
|
|
1481
|
+
return restartOnto(behind ? latest : package_default.version);
|
|
1482
|
+
}
|
|
1483
|
+
function latestVersion() {
|
|
1484
|
+
const r = spawnSync2("npm", ["view", "toyon", "version", "--json"], { encoding: "utf8" });
|
|
1485
|
+
if (r.status !== 0)
|
|
1486
|
+
return null;
|
|
1487
|
+
try {
|
|
1488
|
+
const v = JSON.parse(r.stdout);
|
|
1489
|
+
return typeof v === "string" ? v : null;
|
|
1490
|
+
} catch {
|
|
1491
|
+
return null;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
async function restartOnto(version) {
|
|
1495
|
+
const h = await health();
|
|
1496
|
+
if (!h) {
|
|
1497
|
+
console.log(`Toyon is not running; \`toyon\` starts ${version}`);
|
|
1498
|
+
return 0;
|
|
1499
|
+
}
|
|
1500
|
+
if (h.version === version) {
|
|
1501
|
+
console.log(`the daemon is already ${version}`);
|
|
1502
|
+
return 0;
|
|
1503
|
+
}
|
|
1504
|
+
const token = readToken();
|
|
1505
|
+
if (!token) {
|
|
1506
|
+
console.error("the daemon token is missing, so the daemon cannot be asked to restart; `toyon restart` does it by hand");
|
|
1507
|
+
return 1;
|
|
1508
|
+
}
|
|
1509
|
+
const res = await fetch(`${base}/restart?token=${token}`, { method: "POST" }).catch(() => null);
|
|
1510
|
+
if (res && res.status === 409) {
|
|
1511
|
+
console.error(await res.text());
|
|
1512
|
+
return 1;
|
|
1513
|
+
}
|
|
1514
|
+
const deadline = Date.now() + WAIT_MS2;
|
|
1515
|
+
while (Date.now() < deadline) {
|
|
1516
|
+
await Bun.sleep(500);
|
|
1517
|
+
if ((await health())?.version === version) {
|
|
1518
|
+
console.log(`restarted Toyon on ${version}`);
|
|
1519
|
+
return 0;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
console.log(`Toyon restarts on ${version} once the chat replying now finishes`);
|
|
1523
|
+
return 0;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1376
1526
|
// src/cli.ts
|
|
1527
|
+
function behind(daemonVersion) {
|
|
1528
|
+
return daemonVersion === undefined || newer(package_default.version, daemonVersion);
|
|
1529
|
+
}
|
|
1377
1530
|
async function open(cmd) {
|
|
1378
1531
|
const explicit = cmd.path !== null;
|
|
1379
1532
|
const target = resolve(cmd.path ?? process.cwd());
|
|
1380
|
-
const register = explicit || existsSync10(
|
|
1533
|
+
const register = explicit || existsSync10(join8(target, ".git"));
|
|
1381
1534
|
if (process.platform === "linux") {
|
|
1382
1535
|
const missing = missingSandboxTools();
|
|
1383
1536
|
const blocked = missing.length === 0 ? bwrapStartError() : null;
|
|
@@ -1386,12 +1539,15 @@ async function open(cmd) {
|
|
|
1386
1539
|
else if (blocked)
|
|
1387
1540
|
console.log(bwrapBlockedAdvice(blocked, userNamespacesRestricted()));
|
|
1388
1541
|
}
|
|
1389
|
-
|
|
1542
|
+
const running = await health();
|
|
1543
|
+
if (!running) {
|
|
1390
1544
|
console.log("starting Toyon daemon\u2026");
|
|
1391
1545
|
if (!await startDaemon()) {
|
|
1392
1546
|
console.error(`daemon failed to start; \`toyon logs\` shows why (${logFile})`);
|
|
1393
1547
|
return 1;
|
|
1394
1548
|
}
|
|
1549
|
+
} else if (behind(running.version)) {
|
|
1550
|
+
console.log(`toyon: the running daemon is ${running.version ?? "older"}; \`toyon restart\` starts ${package_default.version}`);
|
|
1395
1551
|
}
|
|
1396
1552
|
const token = readToken();
|
|
1397
1553
|
if (!token) {
|
|
@@ -1442,13 +1598,20 @@ async function run(cmd) {
|
|
|
1442
1598
|
return 2;
|
|
1443
1599
|
case "version": {
|
|
1444
1600
|
const h = await health();
|
|
1445
|
-
|
|
1601
|
+
if (!h)
|
|
1602
|
+
console.log(`toyon ${package_default.version}`);
|
|
1603
|
+
else if (!behind(h.version))
|
|
1604
|
+
console.log(`toyon ${package_default.version} (daemon ${h.version} running)`);
|
|
1605
|
+
else
|
|
1606
|
+
console.log(`toyon ${package_default.version} (daemon ${h.version ?? "?"} running; \`toyon restart\` to update)`);
|
|
1446
1607
|
return 0;
|
|
1447
1608
|
}
|
|
1448
1609
|
case "stop":
|
|
1449
1610
|
return stop();
|
|
1450
1611
|
case "restart":
|
|
1451
1612
|
return restart();
|
|
1613
|
+
case "update":
|
|
1614
|
+
return update();
|
|
1452
1615
|
case "doctor":
|
|
1453
1616
|
return doctor();
|
|
1454
1617
|
case "logs":
|