@flout/paf-flout 0.1.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 +63 -0
- package/dist/src/build-cli.d.ts +2 -0
- package/dist/src/build-cli.d.ts.map +1 -0
- package/dist/src/build-cli.js +23 -0
- package/dist/src/build-cli.js.map +1 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +89 -0
- package/dist/src/index.js.map +1 -0
- package/package.json +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# @flout/paf-flout
|
|
2
|
+
|
|
3
|
+
> **Fork-only.** Not part of upstream `bgrgicak/flout`. This package ships a batteries-included Docker image specific to the PaF (phone-a-friend) integration.
|
|
4
|
+
|
|
5
|
+
A monorepo package that holds the Dockerfile for the `paf-flout:<hash>` image and a small build helper. The Dockerfile is embedded as a TypeScript string constant — same shape as `@flout/sandbox`'s `EMBEDDED_DOCKERFILE` — so the image tag is content-addressed (sha256 of the Dockerfile text). Any edit to the embedded Dockerfile produces a new tag automatically.
|
|
6
|
+
|
|
7
|
+
## What's inside the image
|
|
8
|
+
|
|
9
|
+
| Layer | Contents |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Base | `node:22-slim` |
|
|
12
|
+
| System | bash, curl, git, sudo, tmux |
|
|
13
|
+
| User | `dev` at UID 1000 (replaces node:22-slim's `node` user) |
|
|
14
|
+
| Backend CLIs (stable layer) | `@openai/codex`, `@google/gemini-cli`, `opencode-ai` |
|
|
15
|
+
| Claude | `claude.ai/install.sh` + `/usr/local/bin/claude` symlink |
|
|
16
|
+
| PaF (volatile layer, last) | `@freibergergarcia/phone-a-friend@2.3.1` |
|
|
17
|
+
| PaF plugin shim | `phone-a-friend plugin install --opencode` |
|
|
18
|
+
| Auth dirs | pre-created at `~/.claude`, `~/.codex`, `~/.gemini`, `~/.local/share/opencode`, `~/.config/{opencode,phone-a-friend}` |
|
|
19
|
+
| Shell entry | `phone-a-friend doctor` runs on shell entry via `.bashrc` |
|
|
20
|
+
|
|
21
|
+
Layer ordering: PaF sits last so PaF version bumps don't invalidate the heavier backend install layers (codex/gemini/opencode/claude all stay cached).
|
|
22
|
+
|
|
23
|
+
## Build
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm run build # tsc compile of this package
|
|
27
|
+
npm run build-image # docker build -t paf-flout:<sha256-prefix>
|
|
28
|
+
|
|
29
|
+
# also tag the build with convenience aliases:
|
|
30
|
+
npm run build-image -- paf-flout:r3 paf-flout:latest
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Override the engine:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
PAF_FLOUT_ENGINE_BINARY=podman npm run build-image
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Use with flout
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# get the content-addressed tag programmatically
|
|
43
|
+
PAF_FLOUT_IMAGE=$(node -e "import('./packages/paf-flout/dist/src/index.js').then(m => console.log(m.IMAGE_NAME))")
|
|
44
|
+
|
|
45
|
+
flout sandbox start --image $PAF_FLOUT_IMAGE --clean --name devbox -- \
|
|
46
|
+
-p 127.0.0.1:4096:4096 \
|
|
47
|
+
-e OPENCODE_SERVER_PASSWORD=$(openssl rand -hex 16)
|
|
48
|
+
|
|
49
|
+
flout sandbox shell devbox
|
|
50
|
+
# inside: opencode serve --hostname 0.0.0.0 --port 4096
|
|
51
|
+
# host browser → http://localhost:4096
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Why this is a separate package (and fork-only)
|
|
55
|
+
|
|
56
|
+
- **Right layer.** `@flout/sandbox` is the generic container lifecycle (knows nothing about specific tools). This package owns the image-specific knowledge (which CLIs to bundle, which versions, layer order).
|
|
57
|
+
- **Doesn't go upstream.** Bero's flout is a generic agent-session tool. The PaF integration is one specific use case — it lives in the fork.
|
|
58
|
+
- **Content-addressed tagging.** Edit the Dockerfile string → automatic new tag on next build, mirroring how `@flout/sandbox` invalidates its `flout-sandbox:<hash>` image.
|
|
59
|
+
|
|
60
|
+
## Related
|
|
61
|
+
|
|
62
|
+
- Eventual home: dedicated `paf-flout` repo with CI multi-arch builds and GHCR publishing — see the portability plan.
|
|
63
|
+
- Predecessor: loose Dockerfile at `~/sandbox-experiments/paf-flout-track-a/Dockerfile` (now superseded by this package).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-cli.d.ts","sourceRoot":"","sources":["../../src/build-cli.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Thin CLI wrapper around buildImage(). Invoked via `npm run build-image`.
|
|
2
|
+
//
|
|
3
|
+
// npm run build-image # tags paf-flout:<sha256-prefix>
|
|
4
|
+
// npm run build-image -- paf-flout:r3 paf-flout:latest # also applies those extra tags
|
|
5
|
+
// PAF_FLOUT_ENGINE_BINARY=podman npm run build-image # build with a different engine
|
|
6
|
+
import { buildImage, IMAGE_NAME } from './index.js';
|
|
7
|
+
const engineBinary = process.env.PAF_FLOUT_ENGINE_BINARY || 'docker';
|
|
8
|
+
const extraTags = process.argv.slice(2);
|
|
9
|
+
// Extra tags become `-t <tag>` args prepended BEFORE the build-context path in
|
|
10
|
+
// the spawned `docker build` invocation. Reject anything that could be parsed
|
|
11
|
+
// by docker as a flag (any arg starting with '-') — legitimate image tags
|
|
12
|
+
// never start with '-', so this blocks injection like '--push' or '-f /etc/...'.
|
|
13
|
+
for (const tag of extraTags) {
|
|
14
|
+
if (tag.startsWith('-')) {
|
|
15
|
+
console.error(`Refusing tag '${tag}': image tags cannot start with '-'. ` +
|
|
16
|
+
`Allowed shape: registry/repo:tag, e.g. paf-flout:r3 or ghcr.io/user/img:latest.`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
buildImage({ engineBinary, extraTags });
|
|
21
|
+
const tagsApplied = [IMAGE_NAME, ...extraTags];
|
|
22
|
+
console.log(`Built ${tagsApplied.join(', ')}`);
|
|
23
|
+
//# sourceMappingURL=build-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-cli.js","sourceRoot":"","sources":["../../src/build-cli.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,EAAE;AACF,+FAA+F;AAC/F,8FAA8F;AAC9F,8FAA8F;AAC9F,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,QAAQ,CAAC;AACrE,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAExC,+EAA+E;AAC/E,8EAA8E;AAC9E,0EAA0E;AAC1E,iFAAiF;AACjF,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;IAC5B,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,KAAK,CACX,iBAAiB,GAAG,uCAAuC;YAC3D,iFAAiF,CAClF,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,UAAU,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC,CAAC;AAExC,MAAM,WAAW,GAAG,CAAC,UAAU,EAAE,GAAG,SAAS,CAAC,CAAC;AAC/C,OAAO,CAAC,GAAG,CAAC,SAAS,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const EMBEDDED_DOCKERFILE = "FROM node:22-slim\n\nRUN apt-get update && apt-get install -y \\\n bash curl git sudo tmux \\\n && rm -rf /var/lib/apt/lists/*\n\n# node:22-slim ships a 'node' user at UID 1000. Remove it so 'dev' can\n# take UID 1000 \u2014 bind-mounted credential files (~/.claude, ~/.local/share/opencode,\n# ~/.codex, ~/.gemini) are then readable/writable by dev without permission issues.\nRUN userdel -r node 2>/dev/null || true \\\n && useradd -m -s /bin/bash -u 1000 dev \\\n && adduser dev sudo \\\n && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers\n\n# Backend CLIs (stable layer). Bumped less often than PaF, so they sit in their\n# own RUN to keep their cache hit when PaF version changes.\nRUN npm install -g \\\n @openai/codex \\\n @google/gemini-cli \\\n opencode-ai\n\nUSER dev\nWORKDIR /home/dev\nENV PATH=\"/home/dev/.local/bin:${PATH}\"\n\n# Claude \u2014 native installer writes ~/.local/bin/claude. Symlink into\n# /usr/local/bin so bash -lc (login shells used in docker exec) finds it.\nRUN curl -fsSL https://claude.ai/install.sh | bash \\\n && sudo ln -sf /home/dev/.local/bin/claude /usr/local/bin/claude\n\n# PaF installed LAST (volatile layer). When PaF version bumps, only this layer\n# and the plugin-install layer below rebuild \u2014 the backend CLIs above stay cached.\nUSER root\nRUN npm install -g @freibergergarcia/phone-a-friend@2.3.1\nUSER dev\n\n# PaF OpenCode shim install at build time (host shims are nvm-symlinks, can't mount).\nRUN phone-a-friend plugin install --opencode\n\n# Pre-create auth dirs as dev so bind mounts don't break writes to adjacent paths.\nRUN mkdir -p \\\n /home/dev/.claude \\\n /home/dev/.codex \\\n /home/dev/.gemini \\\n /home/dev/.local/share/opencode \\\n /home/dev/.config/opencode \\\n /home/dev/.config/phone-a-friend\n\nRUN echo 'echo \"\"; phone-a-friend doctor 2>&1 | head -30 || true; echo \"\"' >> /home/dev/.bashrc\n\nCMD [\"sleep\", \"infinity\"]\n";
|
|
2
|
+
export declare const IMAGE_BASE = "paf-flout";
|
|
3
|
+
export declare const DOCKERFILE_TAG: string;
|
|
4
|
+
export declare const IMAGE_NAME: string;
|
|
5
|
+
export interface BuildOptions {
|
|
6
|
+
engineBinary?: string;
|
|
7
|
+
extraTags?: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare function buildImage(options?: BuildOptions): void;
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,mBAAmB,i8DAmD/B,CAAC;AAEF,eAAO,MAAM,UAAU,cAAc,CAAC;AAItC,eAAO,MAAM,cAAc,QAIZ,CAAC;AAEhB,eAAO,MAAM,UAAU,QAAoC,CAAC;AAE5D,MAAM,WAAW,YAAY;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,wBAAgB,UAAU,CAAC,OAAO,GAAE,YAAiB,GAAG,IAAI,CAY3D"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { execFileSync } from 'child_process';
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
// Dockerfile embedded as a string constant. Mirrors the pattern from
|
|
7
|
+
// @flout/sandbox so the image tag is content-addressed (sha256 of the
|
|
8
|
+
// Dockerfile text) — any edit here automatically invalidates the cached image
|
|
9
|
+
// on next build, no manual version bumping needed.
|
|
10
|
+
//
|
|
11
|
+
// Layer ordering note: backend CLIs (codex/gemini/opencode/claude) sit BEFORE
|
|
12
|
+
// phone-a-friend so PaF version bumps don't invalidate the (larger) backend
|
|
13
|
+
// install layers. `phone-a-friend plugin install --opencode` must come after
|
|
14
|
+
// both PaF and opencode are installed.
|
|
15
|
+
export const EMBEDDED_DOCKERFILE = `FROM node:22-slim
|
|
16
|
+
|
|
17
|
+
RUN apt-get update && apt-get install -y \\
|
|
18
|
+
bash curl git sudo tmux \\
|
|
19
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
20
|
+
|
|
21
|
+
# node:22-slim ships a 'node' user at UID 1000. Remove it so 'dev' can
|
|
22
|
+
# take UID 1000 — bind-mounted credential files (~/.claude, ~/.local/share/opencode,
|
|
23
|
+
# ~/.codex, ~/.gemini) are then readable/writable by dev without permission issues.
|
|
24
|
+
RUN userdel -r node 2>/dev/null || true \\
|
|
25
|
+
&& useradd -m -s /bin/bash -u 1000 dev \\
|
|
26
|
+
&& adduser dev sudo \\
|
|
27
|
+
&& echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
|
|
28
|
+
|
|
29
|
+
# Backend CLIs (stable layer). Bumped less often than PaF, so they sit in their
|
|
30
|
+
# own RUN to keep their cache hit when PaF version changes.
|
|
31
|
+
RUN npm install -g \\
|
|
32
|
+
@openai/codex \\
|
|
33
|
+
@google/gemini-cli \\
|
|
34
|
+
opencode-ai
|
|
35
|
+
|
|
36
|
+
USER dev
|
|
37
|
+
WORKDIR /home/dev
|
|
38
|
+
ENV PATH="/home/dev/.local/bin:\${PATH}"
|
|
39
|
+
|
|
40
|
+
# Claude — native installer writes ~/.local/bin/claude. Symlink into
|
|
41
|
+
# /usr/local/bin so bash -lc (login shells used in docker exec) finds it.
|
|
42
|
+
RUN curl -fsSL https://claude.ai/install.sh | bash \\
|
|
43
|
+
&& sudo ln -sf /home/dev/.local/bin/claude /usr/local/bin/claude
|
|
44
|
+
|
|
45
|
+
# PaF installed LAST (volatile layer). When PaF version bumps, only this layer
|
|
46
|
+
# and the plugin-install layer below rebuild — the backend CLIs above stay cached.
|
|
47
|
+
USER root
|
|
48
|
+
RUN npm install -g @freibergergarcia/phone-a-friend@2.3.1
|
|
49
|
+
USER dev
|
|
50
|
+
|
|
51
|
+
# PaF OpenCode shim install at build time (host shims are nvm-symlinks, can't mount).
|
|
52
|
+
RUN phone-a-friend plugin install --opencode
|
|
53
|
+
|
|
54
|
+
# Pre-create auth dirs as dev so bind mounts don't break writes to adjacent paths.
|
|
55
|
+
RUN mkdir -p \\
|
|
56
|
+
/home/dev/.claude \\
|
|
57
|
+
/home/dev/.codex \\
|
|
58
|
+
/home/dev/.gemini \\
|
|
59
|
+
/home/dev/.local/share/opencode \\
|
|
60
|
+
/home/dev/.config/opencode \\
|
|
61
|
+
/home/dev/.config/phone-a-friend
|
|
62
|
+
|
|
63
|
+
RUN echo 'echo ""; phone-a-friend doctor 2>&1 | head -30 || true; echo ""' >> /home/dev/.bashrc
|
|
64
|
+
|
|
65
|
+
CMD ["sleep", "infinity"]
|
|
66
|
+
`;
|
|
67
|
+
export const IMAGE_BASE = 'paf-flout';
|
|
68
|
+
// 12-char prefix of sha256(EMBEDDED_DOCKERFILE) — matches @flout/sandbox's tag
|
|
69
|
+
// shape so the look-and-feel is consistent across flout-built images.
|
|
70
|
+
export const DOCKERFILE_TAG = crypto
|
|
71
|
+
.createHash('sha256')
|
|
72
|
+
.update(EMBEDDED_DOCKERFILE)
|
|
73
|
+
.digest('hex')
|
|
74
|
+
.slice(0, 12);
|
|
75
|
+
export const IMAGE_NAME = `${IMAGE_BASE}:${DOCKERFILE_TAG}`;
|
|
76
|
+
export function buildImage(options = {}) {
|
|
77
|
+
const engineBinary = options.engineBinary ?? 'docker';
|
|
78
|
+
const extraTags = options.extraTags ?? [];
|
|
79
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'paf-flout-build-'));
|
|
80
|
+
try {
|
|
81
|
+
fs.writeFileSync(path.join(tmpDir, 'Dockerfile'), EMBEDDED_DOCKERFILE);
|
|
82
|
+
const tagArgs = [IMAGE_NAME, ...extraTags].flatMap(t => ['-t', t]);
|
|
83
|
+
execFileSync(engineBinary, ['build', ...tagArgs, tmpDir], { stdio: 'inherit' });
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,qEAAqE;AACrE,sEAAsE;AACtE,8EAA8E;AAC9E,mDAAmD;AACnD,EAAE;AACF,8EAA8E;AAC9E,4EAA4E;AAC5E,6EAA6E;AAC7E,uCAAuC;AACvC,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmDlC,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG,WAAW,CAAC;AAEtC,+EAA+E;AAC/E,sEAAsE;AACtE,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM;KACjC,UAAU,CAAC,QAAQ,CAAC;KACpB,MAAM,CAAC,mBAAmB,CAAC;KAC3B,MAAM,CAAC,KAAK,CAAC;KACb,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAEhB,MAAM,CAAC,MAAM,UAAU,GAAG,GAAG,UAAU,IAAI,cAAc,EAAE,CAAC;AAO5D,MAAM,UAAU,UAAU,CAAC,UAAwB,EAAE;IACnD,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,QAAQ,CAAC;IACtD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;IAE1C,MAAM,MAAM,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAC1E,IAAI,CAAC;QACH,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,mBAAmB,CAAC,CAAC;QACvE,MAAM,OAAO,GAAG,CAAC,UAAU,EAAE,GAAG,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACnE,YAAY,CAAC,YAAY,EAAE,CAAC,OAAO,EAAE,GAAG,OAAO,EAAE,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAClF,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flout/paf-flout",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"private": false,
|
|
6
|
+
"description": "Batteries-included image for flout sandboxes (PaF + codex + gemini + opencode + claude pre-installed). Fork-only — not part of upstream flout.",
|
|
7
|
+
"main": "dist/src/index.js",
|
|
8
|
+
"types": "dist/src/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/src"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc --build",
|
|
14
|
+
"build-image": "node dist/src/build-cli.js"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=20"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"license": "GPL-2.0-only"
|
|
23
|
+
}
|