basepad 0.0.0-stage → 0.0.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/LICENSE +21 -0
- package/README.md +83 -2
- package/dist/catalogue/mailpit.js +82 -0
- package/dist/catalogue/postgres.js +632 -0
- package/dist/catalogue/redis.js +135 -0
- package/dist/catalogue.js +13 -0
- package/dist/claude-hooks.js +33 -0
- package/dist/cli.js +182 -0
- package/dist/commands.js +989 -0
- package/dist/config.js +217 -0
- package/dist/env-files.js +123 -0
- package/dist/forward.js +53 -0
- package/dist/hosts.js +79 -0
- package/dist/index.js +11 -0
- package/dist/init.js +88 -0
- package/dist/internal/exec.js +58 -0
- package/dist/internal/ports.js +63 -0
- package/dist/internal/processes.js +42 -0
- package/dist/internal/service-user.js +53 -0
- package/dist/internal/table.js +7 -0
- package/dist/launcher.js +53 -0
- package/dist/layout.js +234 -0
- package/dist/mission-control/act.js +91 -0
- package/dist/mission-control/feed.js +254 -0
- package/dist/mission-control/frame.js +799 -0
- package/dist/mission-control/launch.js +362 -0
- package/dist/mission-control/model.js +325 -0
- package/dist/mission-control/run.js +211 -0
- package/dist/mission-control/work.js +162 -0
- package/dist/setup.js +280 -0
- package/dist/supervisor.js +477 -0
- package/dist/tls.js +99 -0
- package/dist/tools.js +32 -0
- package/dist/versions.js +19 -0
- package/dist/worktrees.js +186 -0
- package/package.json +50 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aysnc Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,84 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/rocket.svg" width="220" alt="basepad's rocket on its launch pad">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">basepad</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
A local environment for coding agents.<br>
|
|
9
|
+
On your machine, or in a cloud sandbox.
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Tell basepad what your project runs:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
// basepad.config.ts
|
|
18
|
+
import { defineConfig, mailpit, postgres, redis } from "basepad";
|
|
19
|
+
|
|
20
|
+
export default defineConfig({
|
|
21
|
+
name: "shop",
|
|
22
|
+
services: { db: postgres(), cache: redis(), mail: mailpit() },
|
|
23
|
+
apps: {
|
|
24
|
+
web: { run: "next dev" },
|
|
25
|
+
api: { run: "php artisan serve --port=$PORT" },
|
|
26
|
+
},
|
|
27
|
+
env: ({ db, cache, mail, web }) => ({
|
|
28
|
+
".env.local": {
|
|
29
|
+
DATABASE_URL: db.url,
|
|
30
|
+
REDIS_URL: cache.url,
|
|
31
|
+
MAIL_URL: mail.url,
|
|
32
|
+
APP_URL: web.url,
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`basepad up` starts it. Every agent working in a worktree gets its own copy, with its own data and its own URLs:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
https://web.shop.localhost you, on main
|
|
42
|
+
https://web.worktree-1.shop.localhost the agent fixing checkout
|
|
43
|
+
https://web.worktree-2.shop.localhost the agent rewriting search
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Mail it sends lands in a local inbox at `https://mail.shop.localhost`, and `basepad db save` snapshots its database before it tries something risky.
|
|
47
|
+
|
|
48
|
+
## Try it
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
curl https://mise.run | sh # basepad installs everything else through mise
|
|
52
|
+
npm install --save-dev basepad
|
|
53
|
+
npx basepad init # writes a starter config
|
|
54
|
+
npx basepad up
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Run `npx basepad setup` once per machine and your browser will trust the local HTTPS domains. It's worth it for the rocket alone.
|
|
58
|
+
|
|
59
|
+
Works on macOS (Apple Silicon) and Linux (x64 and ARM).
|
|
60
|
+
|
|
61
|
+
## Commands
|
|
62
|
+
|
|
63
|
+
| | |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| `basepad up` | Start everything and open mission control (`-d` skips it) |
|
|
66
|
+
| `basepad down` | Stop everything. Data and env files stay put |
|
|
67
|
+
| `basepad status` | Every app's URL and state |
|
|
68
|
+
| `basepad worktree add` | A new branch with its own environment |
|
|
69
|
+
| `basepad worktree rm <name>` | Throw one away. The branch survives |
|
|
70
|
+
| `basepad db save`, `db load` | Snapshot this environment's database, and put it back |
|
|
71
|
+
| `basepad setup` | Once per machine, for trusted URLs with no port |
|
|
72
|
+
|
|
73
|
+
The [guide](docs/guide.md) has the rest: every config option, how worktrees copy data, the Claude Code hooks, CI and sandboxes, and every key in mission control.
|
|
74
|
+
|
|
75
|
+
## Hacking on basepad
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
mise install && npm ci
|
|
79
|
+
npm run check
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The tests run the real thing against real mise, pitchfork and Postgres. [`AGENTS.md`](AGENTS.md) explains how to keep a test run away from your own setup.
|
|
83
|
+
|
|
84
|
+
MIT licensed.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { BasepadError, shellQuote } from "../internal/exec.js";
|
|
5
|
+
import { answers, freePort, waitFor } from "../internal/ports.js";
|
|
6
|
+
import { recordRunning, runningPort } from "../internal/processes.js";
|
|
7
|
+
import { install } from "../tools.js";
|
|
8
|
+
import { MAILPIT_VERSION } from "../versions.js";
|
|
9
|
+
/**
|
|
10
|
+
* Mailpit: one inbox per project, which catches every message the project's environments send.
|
|
11
|
+
* Its inbox is its domain; its SMTP server is on loopback. Main and every worktree share it, and
|
|
12
|
+
* each sends as its own SMTP user, which Mailpit tags each message with. Its messages are kept in
|
|
13
|
+
* the service's folder, so they survive a restart.
|
|
14
|
+
*/
|
|
15
|
+
const HOST = "127.0.0.1";
|
|
16
|
+
/** Mailpit accepts any password, but some mailers authenticate only with one. */
|
|
17
|
+
const PASSWORD = "basepad";
|
|
18
|
+
/** How long `up` waits for its SMTP server to answer before it gives up. */
|
|
19
|
+
const READY_MS = 30_000;
|
|
20
|
+
/**
|
|
21
|
+
* Catches all mail the project sends. Its inbox is at `https://<key>.<name>.localhost`; each
|
|
22
|
+
* message is tagged with the environment that sent it: `main`, or a worktree's suffix.
|
|
23
|
+
*/
|
|
24
|
+
export function mailpit() {
|
|
25
|
+
return { kind: "mailpit" };
|
|
26
|
+
}
|
|
27
|
+
export const mailpitKind = {
|
|
28
|
+
schema: z.strictObject({ kind: z.literal("mailpit") }),
|
|
29
|
+
web: true,
|
|
30
|
+
names(_service, { dir, suffix }) {
|
|
31
|
+
return { user: suffix ?? "main", data: join(dir, "mailpit.db"), pid: join(dir, "mailpit.pid") };
|
|
32
|
+
},
|
|
33
|
+
async prepare(_service, place) {
|
|
34
|
+
const bin = (await install("mailpit", MAILPIT_VERSION, place.machine.home)).bin("mailpit");
|
|
35
|
+
await mkdir(place.dir, { recursive: true });
|
|
36
|
+
const smtp = (await runningPort(place.pid)) ?? (await freePort(place.port));
|
|
37
|
+
const argv = [
|
|
38
|
+
bin,
|
|
39
|
+
"--smtp",
|
|
40
|
+
`${HOST}:${smtp}`,
|
|
41
|
+
"--database",
|
|
42
|
+
place.data,
|
|
43
|
+
// Any user and password, over plain SMTP on loopback: the user becomes the message's tag.
|
|
44
|
+
"--smtp-auth-accept-any",
|
|
45
|
+
"--smtp-auth-allow-insecure",
|
|
46
|
+
"--tags-username",
|
|
47
|
+
"--smtp-disable-rdns",
|
|
48
|
+
"--disable-version-check",
|
|
49
|
+
];
|
|
50
|
+
return {
|
|
51
|
+
run: `${recordRunning(place.pid, smtp)} && exec ${argv.map(shellQuote).join(" ")} --listen "${HOST}:$PORT"`,
|
|
52
|
+
dir: place.dir,
|
|
53
|
+
port: webOf(place).port,
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
async provision(_service, place, { stopped }) {
|
|
57
|
+
const port = await waitFor(async () => {
|
|
58
|
+
const port = await runningPort(place.pid);
|
|
59
|
+
if (port === undefined)
|
|
60
|
+
throw new Error("It has not started.");
|
|
61
|
+
if (!(await answers(port, [HOST])))
|
|
62
|
+
throw new Error(`Nothing answers on port ${port}.`);
|
|
63
|
+
return port;
|
|
64
|
+
}, `Mailpit for \`${place.key}\` didn't answer within ${READY_MS / 1000} s.`, READY_MS, stopped);
|
|
65
|
+
return {
|
|
66
|
+
url: `smtp://${place.user}:${PASSWORD}@${HOST}:${port}`,
|
|
67
|
+
host: HOST,
|
|
68
|
+
port,
|
|
69
|
+
user: place.user,
|
|
70
|
+
password: PASSWORD,
|
|
71
|
+
inbox: webOf(place).url,
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
async address(_service, place) {
|
|
75
|
+
return webOf(place).url;
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
function webOf(place) {
|
|
79
|
+
if (place.web === undefined)
|
|
80
|
+
throw new BasepadError(`\`${place.key}\` has no domain.`);
|
|
81
|
+
return place.web;
|
|
82
|
+
}
|