toyon 0.0.0 → 0.2.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 +116 -2
- package/bin/toyon.js +46 -0
- package/cloud/Dockerfile +32 -0
- package/cloud/entrypoint.sh +66 -0
- package/dist/bridge.js +1 -0
- package/dist/cli.js +1466 -0
- package/dist/daemon.js +25838 -0
- package/dist/shell/assets/Editor-CB2SckMT.css +1 -0
- package/dist/shell/assets/Editor-D-PlbMSY.js +928 -0
- package/dist/shell/assets/XTerm-6GBZ9nXN.css +32 -0
- package/dist/shell/assets/XTerm-CM_FJEwN.js +40 -0
- package/dist/shell/assets/abap-DLDM7-KI.js +1 -0
- package/dist/shell/assets/apex-DNDY2TF8.js +1 -0
- package/dist/shell/assets/azcli-Y6nb8tq_.js +1 -0
- package/dist/shell/assets/bat-BwHxbl9M.js +1 -0
- package/dist/shell/assets/bicep-CFznDFnq.js +2 -0
- package/dist/shell/assets/cameligo-Bf6VGUru.js +1 -0
- package/dist/shell/assets/clojure-Dnu-v4kV.js +1 -0
- package/dist/shell/assets/codicon-Brq4_Ui5.ttf +0 -0
- package/dist/shell/assets/coffee-Bd8akH9Z.js +1 -0
- package/dist/shell/assets/cpp-BbWJElDN.js +1 -0
- package/dist/shell/assets/csharp-Co3qMtFm.js +1 -0
- package/dist/shell/assets/csp-D-4FJmMZ.js +1 -0
- package/dist/shell/assets/css-DdJfP1eB.js +3 -0
- package/dist/shell/assets/css.worker-CqwWTjeG.js +97 -0
- package/dist/shell/assets/cssMode-Bkqyr9Lr.js +1 -0
- package/dist/shell/assets/cypher-cTPe9QuQ.js +1 -0
- package/dist/shell/assets/dart-BOtBlQCF.js +1 -0
- package/dist/shell/assets/dockerfile-BG73LgW2.js +1 -0
- package/dist/shell/assets/ecl-BEgZUVRK.js +1 -0
- package/dist/shell/assets/editor.worker-DCKwvLbM.js +30 -0
- package/dist/shell/assets/elixir-BkW5O-1t.js +1 -0
- package/dist/shell/assets/flow9-BeJ5waoc.js +1 -0
- package/dist/shell/assets/freemarker2-DVAK5zoJ.js +3 -0
- package/dist/shell/assets/fsharp-PahG7c26.js +1 -0
- package/dist/shell/assets/go-acbASCJo.js +1 -0
- package/dist/shell/assets/graphql-BxJiqAUM.js +1 -0
- package/dist/shell/assets/handlebars-Bdtj7uMl.js +1 -0
- package/dist/shell/assets/hcl-DtV1sZF8.js +1 -0
- package/dist/shell/assets/html-B1pqTvyA.js +1 -0
- package/dist/shell/assets/html.worker-DhJ4VZMp.js +474 -0
- package/dist/shell/assets/htmlMode-CBtvzewS.js +1 -0
- package/dist/shell/assets/index-GBQLHG7z.css +1 -0
- package/dist/shell/assets/index-jy_zeFGs.js +230 -0
- package/dist/shell/assets/ini-Kd9XrMLS.js +1 -0
- package/dist/shell/assets/java-CXBNlu9o.js +1 -0
- package/dist/shell/assets/javascript-FNavUzsm.js +1 -0
- package/dist/shell/assets/json.worker-BF9eV5mS.js +62 -0
- package/dist/shell/assets/jsonMode-HgW1GXQH.js +7 -0
- package/dist/shell/assets/julia-cl7-CwDS.js +1 -0
- package/dist/shell/assets/kotlin-s7OhZKlX.js +1 -0
- package/dist/shell/assets/less-9HpZscsL.js +2 -0
- package/dist/shell/assets/lexon-OrD6JF1K.js +1 -0
- package/dist/shell/assets/liquid-6wkVlojW.js +1 -0
- package/dist/shell/assets/lspLanguageFeatures-CvNG6b_1.js +4 -0
- package/dist/shell/assets/lua-Cyyb5UIc.js +1 -0
- package/dist/shell/assets/m3-B8OfTtLu.js +1 -0
- package/dist/shell/assets/markdown-BFxVWTOG.js +1 -0
- package/dist/shell/assets/mdx-C-byKky3.js +1 -0
- package/dist/shell/assets/mips-CiqrrVzr.js +1 -0
- package/dist/shell/assets/msdax-DmeGPVcC.js +1 -0
- package/dist/shell/assets/mysql-C_tMU-Nz.js +1 -0
- package/dist/shell/assets/objective-c-BDtDVThU.js +1 -0
- package/dist/shell/assets/pascal-vHIfCaH5.js +1 -0
- package/dist/shell/assets/pascaligo-DtZ0uQbO.js +1 -0
- package/dist/shell/assets/perl-Ub6l9XKa.js +1 -0
- package/dist/shell/assets/pgsql-BlNEE0v7.js +1 -0
- package/dist/shell/assets/php-BBUBE1dy.js +1 -0
- package/dist/shell/assets/pla-DSh2-awV.js +1 -0
- package/dist/shell/assets/postiats-CocnycG-.js +1 -0
- package/dist/shell/assets/powerquery-tScXyioY.js +1 -0
- package/dist/shell/assets/powershell-COWaemsV.js +1 -0
- package/dist/shell/assets/protobuf-Brw8urJB.js +2 -0
- package/dist/shell/assets/pug-8SOpv6rk.js +1 -0
- package/dist/shell/assets/python-BEJMbP4i.js +1 -0
- package/dist/shell/assets/qsharp-Bw9ernYp.js +1 -0
- package/dist/shell/assets/r-j7ic8hl3.js +1 -0
- package/dist/shell/assets/razor-DeXWW25R.js +1 -0
- package/dist/shell/assets/redis-Bu5POkcn.js +1 -0
- package/dist/shell/assets/redshift-Bs9aos_-.js +1 -0
- package/dist/shell/assets/restructuredtext-CqXO7rUv.js +1 -0
- package/dist/shell/assets/ruby-zBfavPgS.js +1 -0
- package/dist/shell/assets/rust-BzKRNQWT.js +1 -0
- package/dist/shell/assets/sb-BBc9UKZt.js +1 -0
- package/dist/shell/assets/scala-D9hQfWCl.js +1 -0
- package/dist/shell/assets/scheme-BPhDTwHR.js +1 -0
- package/dist/shell/assets/scss-CBJaRo0y.js +3 -0
- package/dist/shell/assets/shell-DiJ1NA_G.js +1 -0
- package/dist/shell/assets/solidity-Db0IVjzk.js +1 -0
- package/dist/shell/assets/sophia-CnS9iZB_.js +1 -0
- package/dist/shell/assets/sparql-CJmd_6j2.js +1 -0
- package/dist/shell/assets/sql-ClhHkBeG.js +1 -0
- package/dist/shell/assets/st-CHwy0fLd.js +1 -0
- package/dist/shell/assets/swift-Bqt4WxQ4.js +3 -0
- package/dist/shell/assets/systemverilog-Bs9z6M-B.js +1 -0
- package/dist/shell/assets/tcl-Dm6ycUr_.js +1 -0
- package/dist/shell/assets/ts.worker-DTZAwq0V.js +67735 -0
- package/dist/shell/assets/tsMode-DMwJnvD-.js +11 -0
- package/dist/shell/assets/twig-Csy3S7wG.js +1 -0
- package/dist/shell/assets/typescript-Cj57ccSp.js +1 -0
- package/dist/shell/assets/typespec-Btyra-wh.js +1 -0
- package/dist/shell/assets/vb-Db0cS2oM.js +1 -0
- package/dist/shell/assets/wgsl-BTesnYfV.js +298 -0
- package/dist/shell/assets/workers-BZi9jqBp.js +1 -0
- package/dist/shell/assets/xml-DUDeQI0I.js +1 -0
- package/dist/shell/assets/yaml-BCD8Shg5.js +1 -0
- package/dist/shell/icon.svg +3 -0
- package/dist/shell/index.html +34 -0
- package/dist/shell/manifest.json +21 -0
- package/dist/shell/sw.js +4 -0
- package/package.json +42 -12
- package/LICENSE-APACHE +0 -201
- package/index.js +0 -2
- /package/{LICENSE-MIT → LICENSE} +0 -0
package/README.md
CHANGED
|
@@ -1,3 +1,117 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Toyon
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Checking an agent's work means running it. Toyon gives every chat its own copy of your web app, with a live preview.
|
|
4
|
+
|
|
5
|
+
Each copy runs on your own machine, and the next one is pre-warmed. Works with Claude Code, Codex and OpenCode.
|
|
6
|
+
|
|
7
|
+
**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
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
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
|
+
|
|
13
|
+
In Toyon you type what you want, and the chat opens with your app already running in the middle of the window. Point at a button in the page and ask for it to be bigger; watch it change while the agent works; keep the change when it looks right.
|
|
14
|
+
|
|
15
|
+
## How it works
|
|
16
|
+
|
|
17
|
+
- Every idea gets its own copy of your project, with the app running.
|
|
18
|
+
- You judge the work by using the app first, then read the change.
|
|
19
|
+
- When you're not sure, try more than one version.
|
|
20
|
+
- Watch and steer while it works, or leave and come back to your app already running.
|
|
21
|
+
- Fix the small thing yourself. A one-line change does not need another prompt.
|
|
22
|
+
- Nothing is kept until you say so.
|
|
23
|
+
- Your editor stays yours.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
cd your-project
|
|
29
|
+
npx toyon
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Needs git and Node 18 or newer; bun comes with the package. On Linux the agents' sandbox also needs bubblewrap and socat (`sudo apt install bubblewrap socat`), and `toyon doctor` checks both and prints the AppArmor profile Ubuntu 24.04 and later ask for. `npm i -g toyon` puts `toyon` on your PATH for good, after which `toyon`, `toyon .` and `toyon ~/projects/app` all open a project, and anywhere else opens Toyon without one, ready to start a new one. `toyon --help` lists the rest.
|
|
33
|
+
|
|
34
|
+
Each agent signs in with its own login, from the chat, and MCP servers run as each agent runs them, outside Toyon's sandbox.
|
|
35
|
+
|
|
36
|
+
- **Claude Code** is installed on first start. It loads your user, project and local settings, so permission rules, hooks, slash commands, plugins and MCP servers work as they do in the terminal.
|
|
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.
|
|
39
|
+
|
|
40
|
+
## What you get
|
|
41
|
+
|
|
42
|
+
- **A copy of your project per chat.** Type what you want; Toyon makes a branch and a git worktree, clones the dependencies, starts your dev servers and opens the agent in it. One spare per project is kept warm, so the next chat's app is usually already running. A second chat right after, or several at once, waits for its own setup.
|
|
43
|
+
- **Your app at the centre.** Each copy runs its own servers behind its own preview address. Switch between chats like tabs; a chat you come back to soon keeps its page state.
|
|
44
|
+
- **Connected to its code.** Point at anything on the page to talk about it or open the line that draws it. Hover a change and it is outlined in the page. A design pane maps the page back to your tokens and components.
|
|
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.
|
|
48
|
+
|
|
49
|
+
Built for web apps. A project without a page still works; the chat takes the middle instead of the preview.
|
|
50
|
+
|
|
51
|
+
## How is this different from Conductor or Superset?
|
|
52
|
+
|
|
53
|
+
They are good tools for running many agents at once, built for teams on GitHub. 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 can land work on your own machine without GitHub.
|
|
54
|
+
|
|
55
|
+
## Under the hood
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
browser (shell UI) ──HTTP/WS──> daemon (one per machine)
|
|
59
|
+
├─ worktree manager (git worktree add/prune, CoW dep clone)
|
|
60
|
+
├─ process supervisor (per-worktree dev servers, $PORT contract)
|
|
61
|
+
├─ per-worktree reverse proxy (live preview iframe, HMR passthrough)
|
|
62
|
+
├─ agent sessions (Claude Code, Codex or OpenCode over ACP, one per worktree)
|
|
63
|
+
└─ git ops (status/diff, ref watcher, land)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
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` and asks you to confirm it; other stacks start from an empty guess the agent can fill in. A tool that takes its port from a flag, Vite among them, gets the flag added by the guess. If a server still comes up on some other port, Toyon follows it there and says which flag to add, and if it never listens at all the preview says so instead of waiting.
|
|
67
|
+
|
|
68
|
+
Copies start when you open them, not when the daemon boots. A copy nobody has looked at for two hours stops running its servers (five minutes on a deployed machine) and starts again when you open it or visit its preview; the chat and the agent keep going. `TOYON_PROC_SLEEP_MS` changes the delay, and `off` turns it off. `toyon stop` stops the daemon and everything it runs. `toyon doctor` says what is running and why a page cannot connect.
|
|
69
|
+
|
|
70
|
+
## What it does not do
|
|
71
|
+
|
|
72
|
+
- It is not an editor. There is no file tree, no multi-file editing, no debugger, no extensions, no inline completion. The diff is editable and there is a one-keystroke jump to the editor you already use.
|
|
73
|
+
- It does not commit, push or open pull requests on its own. The agent is told not to push or delete branches.
|
|
74
|
+
- 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` is in the environment of every command and terminal so a project can keep them apart (`app_$TOYON_WORKTREE` as the database name), and `TOYON_ROOT` is the main checkout, so a setup step can copy over what git leaves behind and Toyon does not already copy (`cp "$TOYON_ROOT/data/dev.db" data/`; `node_modules` and the `.env` files come along on their own).
|
|
76
|
+
- 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
|
+
|
|
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
|
+
## Somewhere other than your laptop
|
|
83
|
+
|
|
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.
|
|
85
|
+
|
|
86
|
+
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
|
+
|
|
88
|
+
## Trust
|
|
89
|
+
|
|
90
|
+
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
|
+
|
|
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.
|
|
93
|
+
|
|
94
|
+
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
|
+
|
|
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 every agent is told not to push. 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
|
+
|
|
98
|
+
## Uninstall
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
toyon uninstall
|
|
102
|
+
npm uninstall -g toyon
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The first command lists what it will remove and asks before doing it: the daemon, everything under `~/.toyon` (state, transcripts, the agent adapters, and the worktree directories, removed through git so each repository's worktree list stays clean), and `~/Applications/Toyon.app` if you installed it. It keeps your projects, every branch Toyon made under `toyon/`, and Toyon's settings in each project. `--yes` skips the question. It does not delete a machine you deployed to Fly; `toyon deploy fly destroy <name>` does, and should run first.
|
|
106
|
+
|
|
107
|
+
## Telemetry
|
|
108
|
+
|
|
109
|
+
None. Toyon makes no network calls of its own. The only 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
|
+
|
|
111
|
+
## Help build it
|
|
112
|
+
|
|
113
|
+
Toyon is early, and there is more worth doing than there are hands. The most useful thing right now is an issue: what you ran, what broke, and what your project is built with, especially anything that is not a Node app. Pull requests are welcome too; [CONTRIBUTING.md](https://github.com/toyon-dev/toyon/blob/main/CONTRIBUTING.md) has the setup, and `bun run check` is the bar.
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT
|
package/bin/toyon.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The npm entry. Everything toyon does runs under bun; this finds one and hands over. The `bun`
|
|
3
|
+
// package in our dependencies carries the platform binary, so `npx toyon` works on a machine
|
|
4
|
+
// with only Node; a bun already on PATH is the fallback when that optional download failed.
|
|
5
|
+
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { createRequire } from "node:module";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
12
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
|
|
15
|
+
function bundledBun() {
|
|
16
|
+
try {
|
|
17
|
+
// the package's postinstall leaves the real binary at bin/bun.exe on every platform
|
|
18
|
+
const exe = join(dirname(require.resolve("bun/package.json")), "bin", "bun.exe");
|
|
19
|
+
return existsSync(exe) ? exe : null;
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function pathBun() {
|
|
26
|
+
const which = process.platform === "win32" ? "where" : "which";
|
|
27
|
+
const r = spawnSync(which, ["bun"], { encoding: "utf8" });
|
|
28
|
+
const found = r.status === 0 ? r.stdout.split("\n")[0].trim() : "";
|
|
29
|
+
return found || null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const bun = bundledBun() ?? pathBun();
|
|
33
|
+
if (!bun) {
|
|
34
|
+
console.error(
|
|
35
|
+
"toyon: bun is not installed and npm could not fetch it for this platform.\n" +
|
|
36
|
+
"install it from https://bun.sh and run toyon again.",
|
|
37
|
+
);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const r = spawnSync(bun, [join(here, "..", "dist", "cli.js"), ...process.argv.slice(2)], { stdio: "inherit" });
|
|
42
|
+
if (r.error) {
|
|
43
|
+
console.error(`toyon: could not start bun at ${bun}: ${r.error.message}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
process.exit(r.status ?? 1);
|
package/cloud/Dockerfile
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# The toyon machine: the CLI's bundled daemon and shell, run under bun. The build context is the one
|
|
2
|
+
# packages/cli/src/deploy/context.ts writes (dist/, package.json, this file, entrypoint.sh), so the
|
|
3
|
+
# image holds exactly the package that built it. BUN_VERSION comes from the CLI's own dependency.
|
|
4
|
+
#
|
|
5
|
+
# A glibc base, so bun-pty and the agent adapters pick their linux-x64 builds. bubblewrap and socat:
|
|
6
|
+
# Claude Code sandboxes Bash and refuses to run without them, Codex's workspace-write sandbox uses
|
|
7
|
+
# bubblewrap, and so does toyon's own sandbox for an agent that brings none.
|
|
8
|
+
ARG BUN_VERSION
|
|
9
|
+
FROM oven/bun:${BUN_VERSION}-debian
|
|
10
|
+
|
|
11
|
+
RUN apt-get update \
|
|
12
|
+
&& apt-get install -y --no-install-recommends git curl ca-certificates openssh-client bubblewrap socat \
|
|
13
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
14
|
+
|
|
15
|
+
# Claude Code refuses bypassPermissions as root, so the daemon runs as `toyon`.
|
|
16
|
+
RUN useradd --create-home --uid 1001 --shell /bin/bash toyon
|
|
17
|
+
|
|
18
|
+
WORKDIR /app
|
|
19
|
+
COPY package.json ./
|
|
20
|
+
RUN bun install --production
|
|
21
|
+
COPY dist ./dist
|
|
22
|
+
COPY entrypoint.sh /entrypoint.sh
|
|
23
|
+
RUN chmod 0755 /entrypoint.sh && chown -R toyon:toyon /app
|
|
24
|
+
|
|
25
|
+
# The agent adapters install on first boot, onto the volume under TOYON_HOME, and survive restarts.
|
|
26
|
+
ENV TOYON_CLOUD=1 \
|
|
27
|
+
TOYON_PORT=4141 \
|
|
28
|
+
TOYON_HOME=/data/toyon \
|
|
29
|
+
HOME=/data/home
|
|
30
|
+
|
|
31
|
+
EXPOSE 4141 10001 10002 10003 10004 10005 10006 10007 10008
|
|
32
|
+
ENTRYPOINT ["/entrypoint.sh"]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Runs as root only long enough to own the volume and move the secrets out of the environment, then
|
|
3
|
+
# drops to `toyon`. /data is the volume: state, transcripts, worktrees, the repo, the agent adapters,
|
|
4
|
+
# and HOME (so ~/.claude and bun's cache survive restarts).
|
|
5
|
+
set -eu
|
|
6
|
+
|
|
7
|
+
export TOYON_HOME="${TOYON_HOME:-/data/toyon}"
|
|
8
|
+
mkdir -p /data/home "$TOYON_HOME"
|
|
9
|
+
|
|
10
|
+
# Platform secrets arrive in the environment, and everything the daemon starts inherits its
|
|
11
|
+
# environment. The token and the git credentials go to files in TOYON_HOME, which the daemon reads
|
|
12
|
+
# and no agent may (packages/daemon/src/agent/sandbox.ts), and leave the environment before it starts.
|
|
13
|
+
if [ -n "${TOYON_TOKEN:-}" ]; then
|
|
14
|
+
(umask 077 && printf %s "$TOYON_TOKEN" > "$TOYON_HOME/token")
|
|
15
|
+
fi
|
|
16
|
+
if [ -n "${GITHUB_TOKEN:-}" ]; then
|
|
17
|
+
(umask 077 && printf 'https://x-access-token:%s@github.com\n' "$GITHUB_TOKEN" > "$TOYON_HOME/git-credentials")
|
|
18
|
+
fi
|
|
19
|
+
unset TOYON_TOKEN GITHUB_TOKEN
|
|
20
|
+
chown -R toyon:toyon /data
|
|
21
|
+
|
|
22
|
+
exec runuser -u toyon -- /bin/sh -c '
|
|
23
|
+
set -eu
|
|
24
|
+
export HOME=/data/home
|
|
25
|
+
|
|
26
|
+
git config --global user.name >/dev/null 2>&1 || git config --global user.name "toyon"
|
|
27
|
+
git config --global user.email >/dev/null 2>&1 || git config --global user.email "toyon@localhost"
|
|
28
|
+
git config --global init.defaultBranch main
|
|
29
|
+
if [ -f "$TOYON_HOME/git-credentials" ]; then
|
|
30
|
+
git config --global credential.helper "store --file=$TOYON_HOME/git-credentials"
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
REPO=/data/repo
|
|
34
|
+
if [ ! -d "$REPO/.git" ]; then
|
|
35
|
+
if [ -n "${TOYON_REPO_URL:-}" ]; then
|
|
36
|
+
echo "cloning $TOYON_REPO_URL"
|
|
37
|
+
git clone "$TOYON_REPO_URL" "$REPO"
|
|
38
|
+
else
|
|
39
|
+
echo "scaffolding a vite react-ts starter"
|
|
40
|
+
(cd /data && bunx --bun create-vite@latest repo --template react-ts < /dev/null)
|
|
41
|
+
# the run contract: listen on $PORT (vite ignores the variable by default)
|
|
42
|
+
cat > "$REPO/vite.config.ts" <<EOF
|
|
43
|
+
import { defineConfig } from "vite";
|
|
44
|
+
import react from "@vitejs/plugin-react";
|
|
45
|
+
|
|
46
|
+
export default defineConfig({
|
|
47
|
+
plugins: [react()],
|
|
48
|
+
server: {
|
|
49
|
+
port: Number(process.env.PORT) || 5173,
|
|
50
|
+
strictPort: true,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
EOF
|
|
54
|
+
# explicit settings: without them the daemon treats the detected procs as a guess and starts
|
|
55
|
+
# nothing until the first-run card confirms
|
|
56
|
+
mkdir -p "$REPO/.toyon"
|
|
57
|
+
cat > "$REPO/.toyon/settings.json" <<EOF
|
|
58
|
+
{ "setup": ["bun install"], "run": { "web": "bun run dev" } }
|
|
59
|
+
EOF
|
|
60
|
+
(cd "$REPO" && bun install)
|
|
61
|
+
(cd "$REPO" && git init -q && git add -A && git commit -qm "vite react-ts starter")
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
exec bun /app/dist/daemon.js "$REPO"
|
|
66
|
+
'
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +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}});
|