@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 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=build-cli.d.ts.map
@@ -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
+ }