bottleship 0.1.0-prototype.0 → 0.1.0-prototype.2
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/CHANGELOG.md +17 -0
- package/README.md +118 -113
- package/dist/src/boxes.d.ts +41 -39
- package/dist/src/boxes.js +181 -179
- package/dist/src/cli.js +298 -83
- package/dist/src/config.d.ts +3 -6
- package/dist/src/config.js +19 -85
- package/dist/src/init.d.ts +3 -4
- package/dist/src/init.js +120 -205
- package/dist/src/local.d.ts +8 -0
- package/dist/src/local.js +24 -0
- package/dist/src/onboard.d.ts +5 -0
- package/dist/src/onboard.js +25 -0
- package/dist/src/paths.d.ts +0 -1
- package/dist/src/paths.js +0 -1
- package/dist/src/provider.d.ts +16 -0
- package/dist/src/provider.js +81 -0
- package/dist/src/repo.d.ts +21 -0
- package/dist/src/repo.js +75 -0
- package/dist/src/review/comments.d.ts +66 -0
- package/dist/src/review/comments.js +344 -0
- package/dist/src/review/contract.d.ts +82 -0
- package/dist/src/review/contract.js +189 -0
- package/dist/src/review/github.d.ts +120 -0
- package/dist/src/review/github.js +160 -0
- package/dist/src/review/local.d.ts +14 -0
- package/dist/src/review/local.js +70 -0
- package/dist/src/review/loop.d.ts +12 -0
- package/dist/src/review/loop.js +119 -0
- package/dist/src/review/pr.d.ts +14 -0
- package/dist/src/review/pr.js +68 -0
- package/dist/src/review/prompt.d.ts +15 -0
- package/dist/src/review/prompt.js +48 -0
- package/dist/src/review/serve.d.ts +1 -0
- package/dist/src/review/serve.js +8 -0
- package/dist/src/review/server.d.ts +14 -0
- package/dist/src/review/server.js +484 -0
- package/dist/src/review/setting-up.d.ts +4 -0
- package/dist/src/review/setting-up.js +74 -0
- package/dist/src/review/start.d.ts +32 -0
- package/dist/src/review/start.js +140 -0
- package/dist/src/review/target.d.ts +20 -0
- package/dist/src/review/target.js +66 -0
- package/dist/src/review/template.d.ts +3 -0
- package/dist/src/review/template.js +13 -0
- package/dist/src/review/terminal.d.ts +4 -0
- package/dist/src/review/terminal.js +91 -0
- package/dist/src/secrets.d.ts +1 -0
- package/dist/src/secrets.js +11 -1
- package/dist/src/types.d.ts +1 -71
- package/dist/src/ui.d.ts +6 -1
- package/dist/src/ui.js +22 -23
- package/dist/src/util.d.ts +11 -0
- package/dist/src/util.js +40 -0
- package/package.json +7 -14
- package/presets/agents/claude.json +1 -5
- package/presets/agents/codex.json +1 -10
- package/presets/agents/cursor.json +1 -9
- package/presets/agents/opencode.json +1 -14
- package/presets/providers/docker/create +12 -0
- package/presets/providers/docker/destroy +2 -0
- package/presets/providers/docker/exec +2 -0
- package/presets/providers/docker/list +4 -0
- package/presets/providers/docker/status +2 -0
- package/presets/providers/docker/url +4 -0
- package/review-app/KIT.md +106 -0
- package/review-app/WRITING.md +93 -0
- package/review-app/examples/rply-16.tsx +98 -0
- package/review-app/index.html +15 -0
- package/review-app/package-lock.json +2539 -0
- package/review-app/package.json +24 -0
- package/review-app/playground/fixtures/excalidraw-12602/data/box.json +1 -0
- package/review-app/playground/fixtures/excalidraw-12602/data/pr.diff +91 -0
- package/review-app/playground/fixtures/excalidraw-12602/data/pr.json +62 -0
- package/review-app/playground/fixtures/excalidraw-12602/data/review-comments.json +44 -0
- package/review-app/playground/fixtures/excalidraw-12602/events.json +1 -0
- package/review-app/playground/fixtures/excalidraw-12602/files/shots/base-ar-zoom.png +0 -0
- package/review-app/playground/fixtures/excalidraw-12602/files/shots/base-en-US-zoom.png +0 -0
- package/review-app/playground/fixtures/excalidraw-12602/files/shots/base-he-IL-zoom.png +0 -0
- package/review-app/playground/fixtures/excalidraw-12602/files/shots/base-he-dark.png +0 -0
- package/review-app/playground/fixtures/excalidraw-12602/files/shots/base-he-hover.png +0 -0
- package/review-app/playground/fixtures/excalidraw-12602/files/shots/base-he-page.png +0 -0
- package/review-app/playground/fixtures/excalidraw-12602/files/shots/base-he-tip.png +0 -0
- package/review-app/playground/fixtures/excalidraw-12602/files/shots/base-he-zen.png +0 -0
- package/review-app/playground/fixtures/excalidraw-12602/files/shots/head-ar-zoom.png +0 -0
- package/review-app/playground/fixtures/excalidraw-12602/files/shots/head-en-US-zoom.png +0 -0
- package/review-app/playground/fixtures/excalidraw-12602/files/shots/head-he-IL-zoom.png +0 -0
- package/review-app/playground/fixtures/excalidraw-12602/files/shots/head-he-dark.png +0 -0
- package/review-app/playground/fixtures/excalidraw-12602/files/shots/head-he-hover.png +0 -0
- package/review-app/playground/fixtures/excalidraw-12602/files/shots/head-he-page.png +0 -0
- package/review-app/playground/fixtures/excalidraw-12602/files/shots/head-he-tip.png +0 -0
- package/review-app/playground/fixtures/excalidraw-12602/files/shots/head-he-zen.png +0 -0
- package/review-app/playground/fixtures/excalidraw-12602/instances.json +1 -0
- package/review-app/playground/fixtures/excalidraw-12602/review.tsx +103 -0
- package/review-app/playground/fixtures/rply-16/data/box.json +1 -0
- package/review-app/playground/fixtures/rply-16/data/hooks.diff +28 -0
- package/review-app/playground/fixtures/rply-16/data/pr.diff +114 -0
- package/review-app/playground/fixtures/rply-16/data/pr.json +68 -0
- package/review-app/playground/fixtures/rply-16/data/review-comments.json +1 -0
- package/review-app/playground/fixtures/rply-16/events.json +156 -0
- package/review-app/playground/fixtures/rply-16/instances.json +1 -0
- package/review-app/playground/fixtures/rply-16/review.tsx +98 -0
- package/review-app/playground/gallery.tsx +127 -0
- package/review-app/playground/index.html +15 -0
- package/review-app/playground/main.tsx +26 -0
- package/review-app/playground/mock.ts +215 -0
- package/review-app/playground/playground.css +18 -0
- package/review-app/playground/vite.config.ts +32 -0
- package/review-app/scripts/check.cjs +47 -0
- package/review-app/scripts/clip.cjs +104 -0
- package/review-app/scripts/shot.cjs +66 -0
- package/review-app/src/App.tsx +132 -0
- package/review-app/src/data/box.json +1 -0
- package/review-app/src/data/hooks.diff +28 -0
- package/review-app/src/data/pr.diff +114 -0
- package/review-app/src/data/pr.json +68 -0
- package/review-app/src/data/review-comments.json +1 -0
- package/review-app/src/kit/agent.tsx +287 -0
- package/review-app/src/kit/chapter.tsx +178 -0
- package/review-app/src/kit/code.tsx +217 -0
- package/review-app/src/kit/comments.tsx +377 -0
- package/review-app/src/kit/finish.tsx +274 -0
- package/review-app/src/kit/flow.tsx +184 -0
- package/review-app/src/kit/index.ts +10 -0
- package/review-app/src/kit/kit.css +696 -0
- package/review-app/src/kit/live.ts +91 -0
- package/review-app/src/kit/media.tsx +348 -0
- package/review-app/src/kit/overview.tsx +161 -0
- package/review-app/src/kit/pr.ts +100 -0
- package/review-app/src/kit/pullrequest.tsx +298 -0
- package/review-app/src/kit/review.ts +74 -0
- package/review-app/src/kit/theme.ts +30 -0
- package/review-app/src/kit/ui.tsx +127 -0
- package/review-app/src/main.tsx +6 -0
- package/review-app/src/review.tsx +22 -0
- package/review-app/tsconfig.json +17 -0
- package/review-app/vite.config.ts +12 -0
- package/dist/presets/providers/command.d.ts +0 -3
- package/dist/presets/providers/command.js +0 -39
- package/dist/presets/providers/daytona.d.ts +0 -3
- package/dist/presets/providers/daytona.js +0 -173
- package/dist/presets/providers/docker.d.ts +0 -3
- package/dist/presets/providers/docker.js +0 -31
- package/dist/presets/providers/e2b.d.ts +0 -3
- package/dist/presets/providers/e2b.js +0 -124
- package/dist/presets/providers/exe.d.ts +0 -3
- package/dist/presets/providers/exe.js +0 -28
- package/dist/presets/providers/http.example.d.ts +0 -3
- package/dist/presets/providers/http.example.js +0 -47
- package/dist/presets/providers/local.d.ts +0 -3
- package/dist/presets/providers/local.js +0 -52
- package/dist/presets/providers/modal.d.ts +0 -3
- package/dist/presets/providers/modal.js +0 -117
- package/dist/presets/providers/ssh.d.ts +0 -3
- package/dist/presets/providers/ssh.js +0 -49
- package/dist/presets/providers/util.d.ts +0 -13
- package/dist/presets/providers/util.js +0 -102
- package/presets/providers/command.ts +0 -43
- package/presets/providers/daytona.ts +0 -181
- package/presets/providers/docker.ts +0 -38
- package/presets/providers/e2b.ts +0 -130
- package/presets/providers/exe.ts +0 -35
- package/presets/providers/http.example.ts +0 -64
- package/presets/providers/local.ts +0 -56
- package/presets/providers/modal.ts +0 -121
- package/presets/providers/ssh.ts +0 -54
- package/presets/providers/util.ts +0 -112
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
Add a section on top with a new version to release it: pushing it to main publishes that version to npm.
|
|
4
4
|
Prerelease versions like `0.1.0-prototype.1` go to the dist-tag named after them (`prototype`), plain versions to `latest`.
|
|
5
5
|
|
|
6
|
+
## 0.1.0-prototype.2
|
|
7
|
+
|
|
8
|
+
- `bottleship review <pr>` brings a PR up in a box, with its base and head running side by side. An agent there builds a guided tour that opens in your browser: an overview, chapters that show the change in the running app, and the diff in tour order. Your comments go into your pending review on GitHub, and you submit it from the tour with a summary and a verdict. The Pull request page shows the checks and merges when you are allowed to.
|
|
9
|
+
- `bottleship review` with no PR does the same for your checkout's local changes, and leaves the checkout untouched.
|
|
10
|
+
- The tour has a chat with an agent. It knows the page you are on and takes selections, comments and screenshots as context. In a PR review the box agent answers, and commits fixes to the PR's branch when you ask. In a review of local changes your own agent answers, changes your checkout, and the tour updates.
|
|
11
|
+
- `bottleship init` also sets up the repo you run it in: your local agent works out how the repo runs in a box and writes `.bottleship/`, checking its work in real boxes. On a machine that is already set up, it goes straight to the repo.
|
|
12
|
+
- `bottleship box list` shows the boxes on this machine with their reviews, and `bottleship box rm` removes one.
|
|
13
|
+
- Breaking: the commands for coding agents moved under `bottleship agent`: `new`, `status`, `ask`, `exec` and `rm`, plus the new `list`, `review`, `review next` and `onboard`. `bottleship agent help` is their reference. To update your agent, replace the "## Remote agents with bottleship" section in its instructions file with the one in the README.
|
|
14
|
+
- Providers can have a `list` script, which `box list` uses. The builtin `docker` provider has one.
|
|
15
|
+
|
|
16
|
+
## 0.1.0-prototype.1
|
|
17
|
+
|
|
18
|
+
- The last step of `bottleship init` opens your local agent (Claude Code, Codex, opencode or Cursor) to add bottleship to its global instructions, instead of showing text to copy.
|
|
19
|
+
- `bottleship init --reset` starts over: sets the config aside as `config.json.bak` and asks everything again, and on a terminal offers to forget saved logins.
|
|
20
|
+
- Fixed: a status check could read a box's state while it was being written and report a waiting login as running, or a finished agent as stopped.
|
|
21
|
+
- Fixed: removing a box that was still waiting for its device login left the login process running.
|
|
22
|
+
|
|
6
23
|
## 0.1.0-prototype.0
|
|
7
24
|
|
|
8
25
|
First prototype. `bottleship init` sets up a provider and a coding agent once, then your local agent runs other agents in remote boxes with `new`, `status`, `ask`, `exec` and `rm`.
|
package/README.md
CHANGED
|
@@ -1,134 +1,92 @@
|
|
|
1
1
|
# bottleship (prototype)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Review a pull request by watching it run. `bottleship review 17` starts the PR in a box, an isolated dev environment with base and head running side by side. An agent there builds a guided tour that opens in your browser: what the PR does, shown in the running app, then the diff in tour order. You comment, chat with the agent, and submit your review to GitHub from the tour.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The same boxes let your coding agent hand work to other agents, like subagents that each have their own copy of the app.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Quick start
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
```sh
|
|
12
|
-
npm install -g <path or git URL of this package> # will be `npm i -g bottleship` once published
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Needs Node >= 22.18 on this machine. Installing from a git URL or a checkout builds the TypeScript sources (`npm install` runs `npm run build`).
|
|
16
|
-
|
|
17
|
-
## Onboarding
|
|
18
|
-
|
|
19
|
-
Once per machine, in a terminal:
|
|
9
|
+
You need Node 22.18 or newer, Docker, and the GitHub CLI logged in (`gh auth login`).
|
|
20
10
|
|
|
21
11
|
```sh
|
|
12
|
+
npm install -g bottleship@prototype
|
|
13
|
+
cd your-repo
|
|
22
14
|
bottleship init
|
|
15
|
+
bottleship review 17
|
|
23
16
|
```
|
|
24
17
|
|
|
25
|
-
|
|
18
|
+
`bottleship init` asks a few questions, checks that a box can run an agent, then has your local coding agent set up the repo with you. The first box builds the base image, which takes a few minutes.
|
|
26
19
|
|
|
27
|
-
|
|
20
|
+
## Commands
|
|
28
21
|
|
|
29
22
|
```sh
|
|
30
|
-
bottleship init --
|
|
23
|
+
bottleship init [--reset] # set up this machine, then the repo you are in
|
|
24
|
+
bottleship review [<pr>] # review a PR, or this checkout's changes without a PR
|
|
25
|
+
bottleship review --base <ref> # review the changes against another base
|
|
26
|
+
bottleship box list # the boxes on this machine, with their reviews
|
|
27
|
+
bottleship box rm [<id>] # remove a box; without an id, pick one
|
|
28
|
+
bottleship help
|
|
31
29
|
```
|
|
32
30
|
|
|
33
|
-
|
|
31
|
+
A PR is a number in the current repo, `owner/name#17`, or a link to the PR.
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
Your coding agent has its own commands under `bottleship agent`. See [For your coding agent](#for-your-coding-agent).
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
## Reviewing a PR
|
|
38
36
|
|
|
39
|
-
|
|
37
|
+
`bottleship review 17` reads the PR from GitHub, starts a box, opens the review in your browser, and shows progress in the terminal until the tour is ready. Ctrl-C stops watching, and the review keeps going.
|
|
40
38
|
|
|
41
|
-
|
|
39
|
+
The tour has four parts, in the rail on the left:
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
| `{ "env": "CORP_KEY" }` | an environment variable of the bottleship process |
|
|
41
|
+
- **Overview.** What the PR does in one sentence, before and after, where it happens, and what deserves a close look. Every claim in the description, comments and reviews is checked in the box.
|
|
42
|
+
- **Chapters.** The change in the running app. Each chapter says what should happen on base and on head, runs a step, and shows the evidence: live instances, screenshots side by side, and flows traced through the code.
|
|
43
|
+
- **Changes.** The diff grouped by chapter, each group with why it exists, then the files no chapter covers.
|
|
44
|
+
- **Finish review** and **Pull request.** Your comments, a summary and a verdict, then the PR's checks and a merge button when you are allowed to merge.
|
|
48
45
|
|
|
49
|
-
|
|
46
|
+
Comments work as on GitHub, and land there as you write them. Press the + next to a line, or drag over several lines, to comment or suggest a change. Add single comment posts at once. Start a review puts the comment in your pending review, which you submit from Finish review.
|
|
50
47
|
|
|
51
|
-
|
|
48
|
+
The Agent button in the footer opens a chat with the agent in the box. It knows the page you are on, and takes what you select, any comment, and any screenshot as context. It answers questions, and commits fixes to the PR's branch when you ask. It leaves comments on GitHub only when you ask, and only in your pending review.
|
|
52
49
|
|
|
53
|
-
|
|
50
|
+
Only review PRs you trust. The box runs the PR's code with your GitHub token, so code in the PR could push, comment or approve as you, in every repo the token reaches.
|
|
54
51
|
|
|
55
|
-
|
|
56
|
-
- `device`: the agent logs in inside each box. `new` (and `ask`, if the box's login expired) starts the login and returns its URL and code for the user to approve in a browser; the task starts once they do. Nothing is stored on this machine.
|
|
57
|
-
- `none`: no login.
|
|
52
|
+
## Reviewing local changes
|
|
58
53
|
|
|
59
|
-
|
|
60
|
-
|---|---|---|
|
|
61
|
-
| `claude` | token: `CLAUDE_CODE_OAUTH_TOKEN` from `claude setup-token` | Anthropic's documented way to use a Claude subscription headless ([docs](https://code.claude.com/docs/en/authentication)). Boxes never get `ANTHROPIC_API_KEY` unless you configure it, as it would win over the token. |
|
|
62
|
-
| `codex` | device: `codex login --device-auth` per box; token: `CODEX_API_KEY` (an OpenAI API key) | One ChatGPT login (`auth.json`) must not be shared across machines or parallel runs ([docs](https://learn.chatgpt.com/docs/auth/ci-cd-auth)), so each box logs in itself. Once, first: in ChatGPT, Settings, Security, turn on device code login for Codex. |
|
|
63
|
-
| `cursor` (no reply tested yet) | token: `CURSOR_API_KEY` from https://cursor.com/dashboard/api (bills to your Cursor plan); device: `cursor-agent login` per box | API keys are Cursor's documented headless login ([docs](https://cursor.com/docs/cli/reference/authentication)). |
|
|
64
|
-
| `opencode` | none: free models; device: a ChatGPT plan via `opencode auth login openai --method chatgpt-headless` per box; token: `OPENCODE_API_KEY` (OpenCode Zen), `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` | Per-box logins and provider API keys ([docs](https://opencode.ai/docs/)). Never a Claude subscription: Anthropic allows those only in Claude Code. |
|
|
65
|
-
|
|
66
|
-
On `new`, the `.env*` files at the root of the current repo (not `*.example`, `*.sample`, `*.template`) are copied into the box workspace at the same paths.
|
|
54
|
+
Run `bottleship review` without a PR to review your checkout's changes: commits, uncommitted and untracked files, against where your branch left the default branch. The box gets a copy, and your checkout stays as it is. `.env` files never go into the diff.
|
|
67
55
|
|
|
68
|
-
|
|
56
|
+
Your coding agent starts these reviews too, after a big change it made. Then the chat in the tour goes to that agent: it changes your checkout, the box picks up the new state, and the tour updates. When you press Looks good, your agent does what your note says.
|
|
69
57
|
|
|
70
|
-
##
|
|
58
|
+
## Boxes
|
|
71
59
|
|
|
72
|
-
|
|
60
|
+
A box is a Docker container by default. Each one has the repo, its dependencies and services, and the app running. `bottleship box list` shows every box with its age and, for reviews, the review link. Boxes stay until you remove them with `bottleship box rm`.
|
|
73
61
|
|
|
74
|
-
|
|
75
|
-
- `exe`: [exe.dev](https://exe.dev) VMs over ssh; needs `ssh exe.dev` working. No box template: exe.dev boots VMs only from registry images, so boxes install the agent at start.
|
|
76
|
-
- `e2b` (untested): [e2b.dev](https://e2b.dev) cloud sandboxes; API key from https://e2b.dev/dashboard.
|
|
77
|
-
- `daytona` (untested): [daytona.io](https://daytona.io) cloud sandboxes; API key from https://app.daytona.io/dashboard/keys.
|
|
78
|
-
- `modal` (untested): [modal.com](https://modal.com) cloud sandboxes; token id and secret from `modal token new` or https://modal.com/settings/tokens.
|
|
79
|
-
- `ssh` (untested on a real host): any machine you can `ssh` into without a password prompt; each box is a directory `~/bottleship-boxes/<id>` there, used as its `$HOME`. No isolation: agents run as that user with permission prompts off. Options: `host` (required, anything ssh accepts, e.g. a `~/.ssh/config` alias; `init --host`), `root`.
|
|
80
|
-
- `local`: git worktrees on this machine. The box workspace is a `git worktree` of your local repo on branch `bottleship/<id>` (from your `HEAD` commit, without uncommitted changes); `rm` removes the worktree and keeps the branch. No isolation: agents run as you, with your files and logins, with permission prompts off. Boxes stop when the laptop sleeps.
|
|
81
|
-
- `custom`: your company's sandboxes, see below.
|
|
62
|
+
## Setting up a repo
|
|
82
63
|
|
|
83
|
-
|
|
64
|
+
A repo describes its box in `.bottleship/`, committed with the repo. `bottleship init` in the repo has your coding agent write it with you and check it in real boxes.
|
|
84
65
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
For a company sandbox system with only an API or a CLI. A human runs `bottleship init` and picks `custom` (an agent: `bottleship init --yes --provider custom --name <name>`). That copies [presets/providers/http.example.ts](presets/providers/http.example.ts) to `~/.bottleship/providers/<name>.ts` and adds `"<name>": { "module": "providers/<name>.ts" }` under `providers` in `~/.bottleship/config.json`. Then, as the agent:
|
|
92
|
-
|
|
93
|
-
1. Replace every TODO in `~/.bottleship/providers/<name>.ts` with the real API calls, or, if the system has a CLI, run its commands with `kit.run`. Find out how the system creates a sandbox, runs a command in it, and deletes it (its docs, its CLI's `--help`, or ask the user).
|
|
94
|
-
2. Put the module's options in its config entry, with secrets as secret sources, never as plain values:
|
|
95
|
-
|
|
96
|
-
```json
|
|
97
|
-
"providers": {
|
|
98
|
-
"corp": { "module": "providers/corp.ts", "baseUrl": "https://sandboxes.corp.example/api", "apiKey": { "store": "corp-sandbox" } }
|
|
99
|
-
}
|
|
100
|
-
```
|
|
66
|
+
| File | What it does |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `.bottleship/Dockerfile` | Optional. Lines added to bottleship's base image (Debian bookworm, Node 22, git, gh, curl, ripgrep, a user `box` without sudo), run as root. No `FROM`, `COPY` or `ADD`. `EXPOSE <port>` marks the ports to open; bottleship publishes them on 127.0.0.1 and waits for them. |
|
|
69
|
+
| `.bottleship/setup.sh` | Runs once as `box` in `~/workspace`: dependencies, databases, seed data, build. Must not prompt. |
|
|
70
|
+
| `.bottleship/start.sh` | Starts the services, then the app in the foreground. It must listen on 0.0.0.0. The log is in `~/.bottleship/app.log`. |
|
|
101
71
|
|
|
102
|
-
|
|
72
|
+
Boxes also get the `.env` files at the root of your checkout, but not `.env.example` and the like. The image is built once per Dockerfile and reused after that.
|
|
103
73
|
|
|
104
|
-
|
|
74
|
+
## Configuration
|
|
105
75
|
|
|
106
|
-
|
|
76
|
+
`bottleship init` writes `~/.bottleship/config.json`. Run it again to fill in anything missing, or with `--reset` to start over. The old config is kept as `config.json.bak`.
|
|
107
77
|
|
|
108
|
-
|
|
78
|
+
### Agents
|
|
109
79
|
|
|
110
|
-
|
|
111
|
-
bottleship new "<task>" [--repo owner/name | --no-repo] [--provider P] [--agent A]
|
|
112
|
-
# { "id": "docker:bs-fix-login-bug-3fa2", "provider": "docker", "agent": "claude", "repo": "acme/web", "next": "..." }
|
|
113
|
-
# device login route: { "id", "provider", "agent", "repo", "login": { "url", "code"?, "agent" }, "next": "show the user this URL and code; ..." }
|
|
114
|
-
|
|
115
|
-
bottleship status <id> [--wait 600] [--lines 40]
|
|
116
|
-
# { "id", "box": "running", "agent": "running" | "exited" | "stopped", "exitCode"?, "log": "<last lines>", "next" }
|
|
117
|
-
# { "id", "box": "running", "agent": "waiting_for_login", "login": { "url", "code"?, "agent" }, "log", "next" }
|
|
118
|
-
# { "id", "box": "running", "agent": "login_failed", "exitCode", "log": "<the login's output>", "next" }
|
|
119
|
-
# { "id", "box": "gone", "next" }
|
|
120
|
-
|
|
121
|
-
bottleship ask <id> "<follow-up>" # continues the same conversation; refused while the agent runs
|
|
122
|
-
# { "id", "agent": "running", "next" }, or with an expired device login { "id", "agent": "waiting_for_login", "login", "next" }
|
|
123
|
-
bottleship exec <id> -- git log --oneline -5
|
|
124
|
-
bottleship exec <id> -- "npm test 2>&1 | tail -20" # one argument is a script, several are argv
|
|
125
|
-
bottleship exec -i <id> -- <cmd> # streams stdin/stdout, e.g. for an IDE attaching over ACP
|
|
126
|
-
bottleship rm <id>
|
|
127
|
-
```
|
|
80
|
+
The agent in a box is `claude`, `codex`, `cursor` or `opencode`. Each gets one long-lived login, which init asks for:
|
|
128
81
|
|
|
129
|
-
|
|
82
|
+
| Agent | Env var | Where it comes from |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `claude` | `CLAUDE_CODE_OAUTH_TOKEN` | `claude setup-token`, to use a Claude subscription headless ([docs](https://code.claude.com/docs/en/authentication)). Boxes never get `ANTHROPIC_API_KEY` unless you configure it, since it would win over the token. |
|
|
85
|
+
| `codex` | `CODEX_API_KEY` | an OpenAI API key, billed to the API, not a ChatGPT plan |
|
|
86
|
+
| `cursor` | `CURSOR_API_KEY` | an API key from https://cursor.com/dashboard/api, billed to your Cursor plan |
|
|
87
|
+
| `opencode` | `OPENCODE_API_KEY` | an OpenCode Zen key from https://opencode.ai/console |
|
|
130
88
|
|
|
131
|
-
|
|
89
|
+
An agent is a set of shell templates. The presets are in [presets/agents](presets/agents), and an entry under `agents` in the config overrides only the fields it names:
|
|
132
90
|
|
|
133
91
|
```json
|
|
134
92
|
"agents": {
|
|
@@ -138,47 +96,94 @@ Agents are configured as shell templates (`{prompt}` is filled in shell-quoted):
|
|
|
138
96
|
"resume": "claude -p {prompt} --continue --dangerously-skip-permissions",
|
|
139
97
|
"env": { "IS_SANDBOX": "1" },
|
|
140
98
|
"unset": ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"],
|
|
141
|
-
"login": { "
|
|
99
|
+
"login": { "env": "CLAUDE_CODE_OAUTH_TOKEN", "store": "claude", "command": "claude setup-token" }
|
|
142
100
|
}
|
|
143
101
|
}
|
|
144
102
|
```
|
|
145
103
|
|
|
146
|
-
|
|
104
|
+
### Secrets and tokens
|
|
147
105
|
|
|
148
|
-
|
|
106
|
+
Boxes get `GH_TOKEN`, which git and gh both use, and the agent's login. Other env vars go under `env` in the config, as a plain string or one of these sources:
|
|
149
107
|
|
|
150
|
-
|
|
108
|
+
| Source | Meaning |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `{ "store": "claude" }` | bottleship's store: the macOS Keychain, or a 0600 file under `~/.bottleship/secrets` |
|
|
111
|
+
| `{ "command": "gh auth token" }` | the output of a command on this machine |
|
|
112
|
+
| `{ "env": "CORP_KEY" }` | an environment variable of the bottleship process |
|
|
113
|
+
|
|
114
|
+
Init asks for a [fine-grained personal access token](https://github.com/settings/personal-access-tokens/new) limited to the repos boxes work on, with Contents and Pull requests set to read and write. Your full gh login (`gh auth token`) is used only if you pick it and confirm a warning. Either way, protect `main` so a box reaches it only through a reviewed PR.
|
|
115
|
+
|
|
116
|
+
The agent in a box can do anything its tokens allow, so keep them narrow. A box that only runs the repo's scripts, such as a setup check, gets no secrets. A proxy that keeps tokens out of boxes is not built yet.
|
|
151
117
|
|
|
152
|
-
|
|
118
|
+
## Providers
|
|
119
|
+
|
|
120
|
+
A provider decides where boxes run. It is a directory of bash scripts. `docker` ships in [presets/providers/docker](presets/providers/docker). For anything else, such as a company's sandboxes, a cloud sandbox service or a VM over ssh, put the same scripts in `~/.bottleship/providers/<name>/` and pick it in `bottleship init`. A provider there with a builtin's name replaces the builtin.
|
|
153
121
|
|
|
154
|
-
|
|
122
|
+
| Script | Contract |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `create <id> <dockerfile> <image>` | Build the box from the Dockerfile, cached as `<image>`, which changes whenever the Dockerfile does. Start it with every `EXPOSE`d port reachable from this machine, and exit 0 once it runs commands. The box stays up until `destroy`. |
|
|
125
|
+
| `exec <id> <script>` | Run `bash -c <script>` in the box as its non-root user, with `$HOME` its home. Pass stdin, stdout, stderr and the exit code through, binary-clean and streaming. |
|
|
126
|
+
| `status <id>` | Print `running` or `gone`. |
|
|
127
|
+
| `destroy <id>` | Remove the box and everything it runs. |
|
|
128
|
+
| `url <id> <port>` | Optional. Print a URL on this machine for a port the box exposes. |
|
|
129
|
+
| `list` | Optional. Print the boxes bottleship made, one per line: the id, then optionally a space and the creation time in ISO 8601. Without it, `box list` shows none of this provider's boxes. |
|
|
155
130
|
|
|
156
|
-
|
|
131
|
+
The scripts run on this machine with bottleship's environment. They read the credentials they need themselves, from an env var, the keychain or a CLI's own login, and never hardcode them.
|
|
157
132
|
|
|
158
|
-
##
|
|
133
|
+
## For your coding agent
|
|
159
134
|
|
|
160
|
-
`bottleship init`
|
|
135
|
+
`bottleship init` adds this section to your agent's global instructions, such as `~/.claude/CLAUDE.md` or `~/.codex/AGENTS.md`. If you skipped that step, add it yourself:
|
|
161
136
|
|
|
162
137
|
```md
|
|
163
138
|
## Remote agents with bottleship
|
|
164
139
|
|
|
165
|
-
|
|
140
|
+
The `bottleship` CLI runs coding agents in boxes, isolated dev environments for a repo (Docker containers by default), and builds guided reviews of PRs and of local changes, which the user opens in their browser. Your commands are under `bottleship agent`; run `bottleship agent help` before you first use them in a session, for what each does and how to use it.
|
|
166
141
|
|
|
167
|
-
-
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
-
|
|
171
|
-
- `bottleship ask <id> "<follow-up>"` continues the same conversation once the agent has finished.
|
|
172
|
-
- `bottleship exec <id> -- <cmd>` inspects the box (git log, test output, files).
|
|
173
|
-
- `bottleship rm <id>` when the work is merged or abandoned. If a box is gone, start a new one on the pushed branch.
|
|
174
|
-
- If the user wants to watch or steer a box from their IDE, find out how their IDE attaches to an agent over a command (ACP) and give it `bottleship exec -i <id> -- <the agent's ACP command>`.
|
|
142
|
+
- Use a box for work that can run on its own while you do something else, and treat it as a subagent you manage.
|
|
143
|
+
- When the user wants to review a PR, start the review for them and give them its url.
|
|
144
|
+
- Reviews of your own work: after a big change you made on your own (many files, or behaviour that is hard to see in the diff), start a local review before you hand over and give the user its url, unless the user has said otherwise. Skip it for small changes.
|
|
145
|
+
- If the user stops or interrupts you while you start a review, or turns one down, ask them once when they want reviews (after every big change, only when they ask, never, or something else). Then rewrite the line above in this section of your instructions file to say exactly what they chose, and follow it from then on.
|
|
175
146
|
- Secrets: never ask the user to paste a secret to you. If bottleship reports one missing, ask them to run `bottleship init` in their terminal.
|
|
176
147
|
```
|
|
177
148
|
|
|
149
|
+
`bottleship agent help` is the full reference for agents. Each command prints JSON, errors are `{ "error", "message", "fix" }` with exit code 1, and most results say in `next` what to do next. In short:
|
|
150
|
+
|
|
151
|
+
- `agent new`, `status`, `ask`, `exec`, `list` and `rm` run another agent in a box and manage it.
|
|
152
|
+
- `agent review` starts a review and returns its link. For local changes, `agent review next` waits for what you send from the tour's chat and sends back the agent's updates.
|
|
153
|
+
- `agent onboard` gives the instructions for setting up a repo, and `agent onboard --check` tries them in a box.
|
|
154
|
+
- `agent init --yes` sets up the machine without prompts.
|
|
155
|
+
|
|
156
|
+
## How a review works
|
|
157
|
+
|
|
158
|
+
These details are for working on bottleship or on a tour.
|
|
159
|
+
|
|
160
|
+
In a review box, `~/workspace` is the PR's head and `~/base` is the commit it branched off. The box environment is the PR's `.bottleship/` if it has one, otherwise your local checkout's, otherwise none and the agent works it out.
|
|
161
|
+
|
|
162
|
+
The review server listens on port 7777 from the start, so the link works at once and shows that the review is being set up. Ports 7781 to 7784 are free for extra instances, such as base. The server keeps the review's state in the box: `~/review/comments.json`, `verdict.json` and `updates.json`.
|
|
163
|
+
|
|
164
|
+
`~/review/app` is a copy of [review-app](review-app), a Vite and React app with the PR's data in `src/data`. The agent writes `src/review.tsx`, step scripts in `~/review/steps/<id>.sh`, and screenshots in `~/review/shots`. [review-app/KIT.md](review-app/KIT.md) tells it how to build a tour from the kit, and [review-app/WRITING.md](review-app/WRITING.md) how to write one. `examples/rply-16.tsx` is a complete tour.
|
|
165
|
+
|
|
166
|
+
Steps and review hooks, which are temporary code in the box's copy of the app, report what happens by posting events to `$BOTTLESHIP_REVIEW_URL/api/events`:
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{ "instance": "base", "kind": "hook", "at": "push/index.ts:47", "text": "setPresence key=…/ada-7f3c",
|
|
170
|
+
"flow": { "id": "presence", "edge": ["sync", "pres"], "node": "pres", "status": "fail", "label": "…/ada-7f3c → deploys" },
|
|
171
|
+
"overlay": { "title": "No push for Ada", "status": "fail" } }
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`kind` is `request`, `hook`, `ok` or `fail`. `flow` moves a dot along an edge and sets a node's state and label. `overlay` shows a note over that instance. The server tags each event with the step that was running, and answers 400 with the problems when an event is malformed.
|
|
175
|
+
|
|
176
|
+
For GitHub Enterprise, log gh in to that host (`gh auth login --hostname <host>`) and make `GH_TOKEN` a token for it.
|
|
177
|
+
|
|
178
178
|
## Development
|
|
179
179
|
|
|
180
180
|
```sh
|
|
181
181
|
npm test # build, then all tests
|
|
182
|
-
npx vitest run test/cli.test.ts # one file
|
|
183
|
-
grep -rn 'ponytail:' src
|
|
182
|
+
npx vitest run test/cli.test.ts # one file, after a build
|
|
183
|
+
grep -rn 'ponytail:' src # deliberate shortcuts and their upgrade paths
|
|
184
|
+
cd review-app && npm run playground # the review kit against recorded reviews, gallery at #/gallery
|
|
184
185
|
```
|
|
186
|
+
|
|
187
|
+
Tests run boxes with the `dir` provider in [test/fixtures/providers/dir](test/fixtures/providers/dir), which uses directories on this machine, so no Docker is needed.
|
|
188
|
+
|
|
189
|
+
To release, add a section with the new version on top of [CHANGELOG.md](CHANGELOG.md) and set the same version in `package.json`. Merging it to `main` publishes it to npm.
|
package/dist/src/boxes.d.ts
CHANGED
|
@@ -1,64 +1,66 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
url: string;
|
|
5
|
-
label: string;
|
|
6
|
-
}
|
|
1
|
+
import { type Provider } from "./provider.ts";
|
|
2
|
+
import type { Repo, RepoEnv } from "./repo.ts";
|
|
3
|
+
import type { Config } from "./types.ts";
|
|
7
4
|
export interface Box {
|
|
8
5
|
id: string;
|
|
9
|
-
provider:
|
|
6
|
+
provider: Provider;
|
|
10
7
|
box: string;
|
|
11
|
-
warning?: string;
|
|
12
|
-
login?: Login;
|
|
13
|
-
}
|
|
14
|
-
export interface Login {
|
|
15
|
-
url: string;
|
|
16
|
-
code?: string;
|
|
17
|
-
agent: string;
|
|
18
8
|
}
|
|
19
9
|
export type AgentState = {
|
|
20
10
|
agent: "running";
|
|
11
|
+
env?: string;
|
|
21
12
|
log: string;
|
|
22
13
|
} | {
|
|
23
14
|
agent: "exited";
|
|
24
15
|
exitCode: number;
|
|
16
|
+
env?: string;
|
|
25
17
|
log: string;
|
|
26
18
|
} | {
|
|
27
19
|
agent: "stopped";
|
|
20
|
+
env?: string;
|
|
28
21
|
log: string;
|
|
29
|
-
} | {
|
|
30
|
-
agent: "waiting_for_login";
|
|
31
|
-
login?: Login;
|
|
32
|
-
log: string;
|
|
33
|
-
} | {
|
|
34
|
-
agent: "login_failed";
|
|
35
|
-
exitCode: number;
|
|
36
|
-
log: string;
|
|
37
22
|
};
|
|
38
|
-
export declare const goneFix = "the box is gone. Start a new one with `bottleship new \"<task>\"` and tell its agent to continue on the branch the old box pushed";
|
|
39
|
-
export declare function
|
|
40
|
-
|
|
23
|
+
export declare const goneFix = "the box is gone. Start a new one with `bottleship agent new \"<task>\"` and tell its agent to continue on the branch the old box pushed";
|
|
24
|
+
export declare function boxImage(cfg: Config, agentName: string, repoEnv?: RepoEnv, extra?: string): {
|
|
25
|
+
dockerfile: string;
|
|
26
|
+
image: string;
|
|
27
|
+
};
|
|
41
28
|
export declare function parseId(cfg: Config, handle: string): {
|
|
42
29
|
provider: string;
|
|
43
30
|
box: string;
|
|
44
31
|
};
|
|
45
|
-
export declare function newBoxId(
|
|
32
|
+
export declare function newBoxId(label: string): string;
|
|
46
33
|
export declare function openBox(cfg: Config, handle: string): Promise<Box>;
|
|
47
|
-
export interface
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
34
|
+
export interface BoxOptions {
|
|
35
|
+
provider: string;
|
|
36
|
+
label: string;
|
|
37
|
+
agent?: string;
|
|
38
|
+
workspace?: {
|
|
39
|
+
clone: Repo;
|
|
40
|
+
} | {
|
|
41
|
+
tar: Buffer;
|
|
42
|
+
};
|
|
43
|
+
repoEnv?: RepoEnv;
|
|
51
44
|
dotenv?: Record<string, string>;
|
|
45
|
+
pr?: {
|
|
46
|
+
number: number;
|
|
47
|
+
base: string;
|
|
48
|
+
};
|
|
49
|
+
env?: Record<string, string>;
|
|
50
|
+
dockerfile?: string;
|
|
52
51
|
}
|
|
53
|
-
export declare function
|
|
54
|
-
export declare function
|
|
55
|
-
export declare function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
export declare function createBox(cfg: Config, opts: BoxOptions): Promise<Box>;
|
|
53
|
+
export declare function putTar(b: Box, dir: string, tar: Buffer): Promise<void>;
|
|
54
|
+
export declare function putFiles(b: Box, files: Record<string, string>): Promise<void>;
|
|
55
|
+
export declare const runIn: (b: Box, script: string) => Promise<import("./types.ts").ExecResult>;
|
|
56
|
+
export type Stage = "setup" | "start" | "ready" | "failed";
|
|
57
|
+
export declare function prepare(b: Box, seconds?: number): Promise<{
|
|
58
|
+
stage: Stage;
|
|
59
|
+
message?: string;
|
|
60
|
+
log?: string;
|
|
61
|
+
}>;
|
|
62
|
+
export declare function startAgent(b: Box, prompt: string): Promise<void>;
|
|
60
63
|
export declare function agentState(b: Box, lines?: number): Promise<AgentState>;
|
|
61
|
-
export declare function loginState(b: Box, setup?: string, seconds?: number): Promise<AgentState>;
|
|
62
64
|
export declare function waitAgent(b: Box, seconds: number, lines?: number): Promise<AgentState>;
|
|
63
|
-
export declare function ask(b: Box, prompt: string): Promise<
|
|
65
|
+
export declare function ask(b: Box, prompt: string): Promise<void>;
|
|
64
66
|
export declare const inBox: (script: string) => string;
|