garaje 0.1.0 → 0.1.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/README.md +110 -0
- package/dist/cli.js +0 -0
- package/package.json +1 -1
- package/dist/host/compose.js +0 -65
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# garaje
|
|
2
|
+
|
|
3
|
+
**Park your codebases. Let the agent drive.**
|
|
4
|
+
|
|
5
|
+
`garaje` is a command-line tool for running the [pi](https://pi.dev) coding
|
|
6
|
+
agent against the codebases you actually work on — each one containerized with
|
|
7
|
+
its own datastores, alongside the agent, in a single Docker Compose project
|
|
8
|
+
called a **garaje**. You scaffold a garaje, hand it your repos, and the
|
|
9
|
+
in-container agent wires each one up for agentic development.
|
|
10
|
+
|
|
11
|
+
The design goal is **portability**: your dev environment is declared as data
|
|
12
|
+
(a manifest + per-codebase recipes), the agent harness is a swappable package,
|
|
13
|
+
and the same garaje runs locally on Docker today (Kubernetes is on the roadmap).
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g garaje
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
> Requires **Docker** (Desktop or Engine) on the host. The agent and its tools
|
|
20
|
+
> run in containers; `garaje` itself is a thin, dependency-light wrapper over
|
|
21
|
+
> `docker compose`.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Quick start
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# 1. Scaffold a new garaje (a git repo that hosts your parked codebases)
|
|
29
|
+
garaje init my-workspace
|
|
30
|
+
cd my-workspace
|
|
31
|
+
|
|
32
|
+
# 2. Start it — builds the agent container and brings the project up
|
|
33
|
+
garaje up --build
|
|
34
|
+
|
|
35
|
+
# 3. Drop into the agent
|
|
36
|
+
garaje pi
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then, from inside the agent, point it at a codebase and ask it to **park** it —
|
|
40
|
+
the agent introspects the repo, writes a `recipe.yaml` describing how it runs
|
|
41
|
+
(toolchain, processes, datastores, env), generates the container config, and
|
|
42
|
+
commits it. From then on that codebase is a first-class, live-reloading,
|
|
43
|
+
agent-assisted dev target in your garaje.
|
|
44
|
+
|
|
45
|
+
Stop without losing your work or your credentials:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
garaje down # refuses -v, so the agent's auth + provisioned tokens survive
|
|
49
|
+
garaje up
|
|
50
|
+
garaje pi -c # continue your most recent session
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Concepts
|
|
54
|
+
|
|
55
|
+
| Term | What it is |
|
|
56
|
+
|------|-----------|
|
|
57
|
+
| **garaje** | A git repo + Docker Compose project that runs the pi agent next to the codebases it works on. One per developer, or one shared per team. |
|
|
58
|
+
| **bay** | A parked codebase inside a garaje (`bays/<name>`), materialized from the manifest. The garaje commits the *manifest*, not the code. |
|
|
59
|
+
| **`recipe.yaml`** | A platform-neutral description of how one codebase runs — its toolchain, app processes, backing services, env, and secrets. Lives in the codebase; the agent authors it by introspection. |
|
|
60
|
+
| **[`@garaje/base`](https://www.npmjs.com/package/@garaje/base)** | The agent-side substrate — the persona, rules, roles, and skills the in-container agent loads. Pinned declaratively in `.pi/settings.json`; installed automatically in-container. |
|
|
61
|
+
|
|
62
|
+
## Commands
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
garaje init <name> [dir] scaffold a new garaje
|
|
66
|
+
garaje up [--build] build + start the garaje (host-only)
|
|
67
|
+
garaje down stop it (refuses -v; auth/tokens persist)
|
|
68
|
+
garaje pi [-c] open the pi agent TUI (-c continues the last session)
|
|
69
|
+
garaje attach attach to the running agent
|
|
70
|
+
garaje logs tail container logs
|
|
71
|
+
garaje doctor health-check the garaje
|
|
72
|
+
garaje sync [name] clone the manifest's codebases into bays/
|
|
73
|
+
garaje list list parked bays
|
|
74
|
+
garaje park [name] regenerate the compose from the bays' recipes
|
|
75
|
+
garaje bay <up|down|logs|ps> operate a parked bay's services
|
|
76
|
+
garaje auth <add|list|remove> provision git / gh / integration credentials
|
|
77
|
+
garaje upgrade [--to <v>] bump the framework + base pins, flag override drift
|
|
78
|
+
garaje sessions-index index the agent's session transcripts
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## How it works
|
|
82
|
+
|
|
83
|
+
- **Compose split by ownership.** A thin top-level `docker-compose.yml` includes
|
|
84
|
+
the framework layer (the agent container); parked codebases live in a
|
|
85
|
+
generated `compose.bays.yaml`. The agent shapes services by editing recipes,
|
|
86
|
+
never the compose files it can't reconfigure itself.
|
|
87
|
+
- **Bind-mounted, live-reloading.** The repo is mounted into the containers, so
|
|
88
|
+
edits the agent makes take effect immediately — no image rebuilds for code
|
|
89
|
+
changes.
|
|
90
|
+
- **Least-privilege Docker.** The agent reaches Docker only through a
|
|
91
|
+
default-deny socket proxy, never the raw socket.
|
|
92
|
+
- **Layered, declarative config.** The shared `@garaje/base` package is pinned
|
|
93
|
+
and consumed read-only; a garaje customizes by layering local overrides on
|
|
94
|
+
top — so upgrading the framework is a pin bump, not a merge.
|
|
95
|
+
|
|
96
|
+
## Framework & harness portability
|
|
97
|
+
|
|
98
|
+
garaje separates the *dev environment* (yours, committed as data) from the
|
|
99
|
+
*agent harness* (a package). Today the harness is pi via `@garaje/base`; the
|
|
100
|
+
seam is deliberately thin so a different harness is a different base package,
|
|
101
|
+
not a rewrite. The runtime is Docker Compose locally, with a Kubernetes driver
|
|
102
|
+
planned for the cloud — the same recipe, two runtimes.
|
|
103
|
+
|
|
104
|
+
`garaje` and `@garaje/base` are released in lockstep. Upgrade a garaje with
|
|
105
|
+
`garaje upgrade`, which bumps the pins and flags any local override that now
|
|
106
|
+
shadows or orphans a changed base resource.
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
MIT
|
package/dist/cli.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
package/dist/host/compose.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
/**
|
|
5
|
-
* The argv to pass after `docker compose`. Mirrors bin/_lib.sh `compose`:
|
|
6
|
-
* always `-f docker-compose.yml`, layer `-f compose.bays.yaml` when present,
|
|
7
|
-
* and prepend `-p <name>` when the garaje declares one.
|
|
8
|
-
*/
|
|
9
|
-
export function composeArgs(ctx, extra) {
|
|
10
|
-
const files = ["-f", "docker-compose.yml"];
|
|
11
|
-
if (ctx.hasBaysCompose)
|
|
12
|
-
files.push("-f", "compose.bays.yaml");
|
|
13
|
-
const project = ctx.name ? ["-p", ctx.name] : [];
|
|
14
|
-
return [...project, ...files, ...extra];
|
|
15
|
-
}
|
|
16
|
-
/** The committed compose project name — the first top-level `name:` line. */
|
|
17
|
-
export function readProjectName(root) {
|
|
18
|
-
const path = join(root, "garaje.yaml");
|
|
19
|
-
if (!existsSync(path))
|
|
20
|
-
return undefined;
|
|
21
|
-
for (const line of readFileSync(path, "utf8").split(/\r?\n/)) {
|
|
22
|
-
const m = /^name:\s*(.+?)\s*$/.exec(line);
|
|
23
|
-
if (m)
|
|
24
|
-
return m[1].replace(/^["']|["']$/g, "");
|
|
25
|
-
}
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|
|
28
|
-
export function composeContext(root) {
|
|
29
|
-
return {
|
|
30
|
-
name: readProjectName(root),
|
|
31
|
-
hasBaysCompose: existsSync(join(root, "compose.bays.yaml")),
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
/** Run `docker compose` from the repo root, inheriting stdio; returns exit code. */
|
|
35
|
-
export function compose(root, extra) {
|
|
36
|
-
const args = composeArgs(composeContext(root), extra);
|
|
37
|
-
return spawnSync("docker", ["compose", ...args], { cwd: root, stdio: "inherit" }).status ?? 1;
|
|
38
|
-
}
|
|
39
|
-
/** Run `docker compose` and capture stdout/stderr instead of inheriting. */
|
|
40
|
-
export function composeCapture(root, extra) {
|
|
41
|
-
const args = composeArgs(composeContext(root), extra);
|
|
42
|
-
const r = spawnSync("docker", ["compose", ...args], { cwd: root, encoding: "utf8" });
|
|
43
|
-
return { status: r.status ?? 1, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
44
|
-
}
|
|
45
|
-
/** Like composeCapture, but feeds `input` to the process's stdin. */
|
|
46
|
-
export function composeCaptureInput(root, extra, input) {
|
|
47
|
-
const args = composeArgs(composeContext(root), extra);
|
|
48
|
-
const r = spawnSync("docker", ["compose", ...args], { cwd: root, encoding: "utf8", input });
|
|
49
|
-
return { status: r.status ?? 1, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Start the stack if the `pi` service isn't already running. Returns 0 when pi
|
|
53
|
-
* is (or becomes) running, else the non-zero `up -d` exit code so callers can
|
|
54
|
-
* bail cleanly instead of exec-ing against a stack that failed to start. The
|
|
55
|
-
* running-check is inlined (rather than importing pi.ts's parseRunningServices)
|
|
56
|
-
* to avoid a compose ↔ pi import cycle — pi.ts already imports from compose.ts.
|
|
57
|
-
*/
|
|
58
|
-
export function ensurePiRunning(root) {
|
|
59
|
-
const ps = composeCapture(root, ["ps", "--status", "running", "--services"]);
|
|
60
|
-
const running = ps.stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
61
|
-
if (running.includes("pi"))
|
|
62
|
-
return 0;
|
|
63
|
-
process.stderr.write("(pi container not running; starting the stack)\n");
|
|
64
|
-
return compose(root, ["up", "-d"]);
|
|
65
|
-
}
|