Skip to content

Commit c7428fb

Browse files
committed
init: repo structure — README, CONTRIBUTING, .gitignore, VERSION
- .gitignore: workspace/, doc/archive/, runtime dirs - VERSION: 0.0.1-lpb - .env.example: stack environment variables - .dockerignore: Docker build exclusions - README.md: devstack overview - CONTRIBUTING.md: development guidelines
0 parents  commit c7428fb

53 files changed

Lines changed: 9934 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Docker build ignore patterns
2+
3+
# Git
4+
.git
5+
.gitignore
6+
7+
# Node
8+
node_modules
9+
package-lock.json
10+
*.tsbuildinfo
11+
12+
# IDE
13+
.vscode
14+
.idea
15+
*.swp
16+
*.swo
17+
*~
18+
19+
# OS
20+
.DS_Store
21+
Thumbs.db
22+
23+
# Docs (not needed in image)
24+
*.md
25+
26+
# Env files
27+
.env
28+
.env.*
29+
!.env.example
30+
31+
# Biome
32+
biome.base.json
33+
34+
# Local build artifacts
35+
support/node_modules
36+
*.tgz

.env.example

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Copy this file to .env (same directory) and fill in real values.
2+
# .env is gitignored — never commit real keys.
3+
#
4+
# All variables use the LPB_ (LocalPibox) prefix to avoid conflicts with
5+
# user environment variables. Loaded by:
6+
# - start.sh (sources .env from workspace directory)
7+
# - podman -e LPB_* flags (via podman run command)
8+
9+
# ─── API Keys ────────────────────────────────────────────────────────────────
10+
11+
# Exa MCP search API key
12+
LPB_EXA_API_KEY=
13+
14+
# ─── Editor ──────────────────────────────────────────────────────────────────
15+
16+
# OpenVSCode connection token — leave empty for a random token on every start.
17+
# Set to a custom value for persistence across restarts.
18+
# WARNING: If LPB_EDITOR_HOST=0.0.0.0 (default), editor is LAN-accessible.
19+
LPB_CONNECTION_TOKEN=
20+
21+
# Editor host — use 0.0.0.0 for LAN access, 127.0.0.1 for local-only.
22+
LPB_EDITOR_HOST=0.0.0.0
23+
24+
# Editor port
25+
LPB_ED_PORT=3000
26+
27+
# ─── Browser ─────────────────────────────────────────────────────────────────
28+
29+
# Agent-browser session — uses PI_WORKTREE_ID for worktree isolation.
30+
# Subagent sessions get unique IDs via session-uuid command.
31+
LPB_AGENT_BROWSER_SESSION=${PI_WORKTREE_ID}
32+
33+
# Agent-browser persistent storage on host
34+
# Chrome download, browser profiles, and session state survive container rebuilds.
35+
# Default: ~/.localpibox/agent-browser/
36+
# LPB_AGENT_BROWSER_DIR=$HOME/.localpibox/agent-browser
37+
38+
# Chrome launch args (container-safe defaults)
39+
LPB_AGENT_BROWSER_ARGS="--no-sandbox,--no-first-run,--disable-gpu,--disable-crashpad"
40+
41+
# Max output length to prevent context flooding
42+
LPB_AGENT_BROWSER_MAX_OUTPUT=4000
43+
44+
# LLM safety markers in content extraction
45+
LPB_AGENT_BROWSER_CONTENT_BOUNDARIES=true
46+
47+
# Action categories requiring approval
48+
LPB_AGENT_BROWSER_CONFIRM_ACTIONS="delete,download,cookie_delete,file_access"
49+
50+
# Daemon idle timeout (5 minutes) — auto-cleanup after inactivity
51+
LPB_AGENT_BROWSER_IDLE_TIMEOUT_MS=300000
52+
53+
# ─── Optional ────────────────────────────────────────────────────────────────
54+
55+
# OpenRouter API key (overflow provider — not needed with Lemonade)
56+
# LPB_OPENROUTER_API_KEY=
57+
58+
# Max tokens as a fraction of context window (lemonade-pi-plugin).
59+
# Computed as: max_tokens = context_window * LPB_MAX_TOKENS_CONTEXT_RATIO
60+
# Default: 0.06 (6 %) — reduced from 0.125 for Qwen thinking models.
61+
# At 0.125, Qwen thinking models generate 10-20k-token thinking blocks that
62+
# push prompt+max_tokens past the 262k window limit, causing overflow errors.
63+
# 0.06 × 262k = ~15.7k maxTokens — leaves room for thinking blocks within window.
64+
# Examples:
65+
# 0.06 → 262K context → 15.7K max tokens
66+
# 0.125 → 262K context → 32.8K max tokens (may overflow with thinking)
67+
# 0.20 → 262K context → 52.4K max tokens
68+
# LPB_MAX_TOKENS_CONTEXT_RATIO=0.06
69+
70+
# ─── Persistence ─────────────────────────────────────────────────────────────
71+
72+
# Persist gh CLI auth to ~/.config/gh inside container, stored in
73+
# ~/.localpibox/state/gh-config on the host (in the state volume).
74+
# LPB_PERSIST_GH_CONFIG=true
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# ═══════════════════════════════════════════════════════════
2+
# LocalPibox Devstack — CI/CD Build & Publish
3+
# ═══════════════════════════════════════════════════════════
4+
# Builds on GitHub (fast connection) → publishes to GHCR
5+
# Extensions update at runtime via pi update --extensions
6+
#
7+
# Triggers:
8+
# - Push to main (Dockerfile, support/, lpb.stack.env, lpb.conf.env)
9+
# - Weekly cron (Monday 3am UTC) — keep image fresh
10+
# - Manual dispatch
11+
#
12+
# Images built:
13+
# ghcr.io/localpibox/devstack:cli — Base dev environment + Pi CLI
14+
# ghcr.io/localpibox/devstack:web — Extends cli + VSCodium server
15+
#
16+
# Actions: all latest major versions (Node.js 24 native)
17+
# actions/checkout@v6 · docker/build-push-action@v7
18+
# docker/setup-buildx-action@v4 · docker/setup-qemu-action@v4
19+
# docker/login-action@v4 · docker/metadata-action@v6
20+
# actions/upload-artifact@v7
21+
22+
name: Build & Publish Devstack
23+
24+
on:
25+
push:
26+
branches: [main]
27+
paths:
28+
- 'Dockerfile'
29+
- 'support/**'
30+
- 'lpb.stack.env'
31+
- 'lpb.conf.env'
32+
- '.github/workflows/*.yml'
33+
- 'scripts/**'
34+
pull_request:
35+
branches: [main]
36+
paths:
37+
- 'Dockerfile'
38+
- 'support/**'
39+
- 'lpb.stack.env'
40+
- 'lpb.conf.env'
41+
- '.github/workflows/*.yml'
42+
- 'scripts/**'
43+
schedule:
44+
- cron: '0 3 * * 1'
45+
workflow_dispatch:
46+
inputs:
47+
publish_latest:
48+
description: 'Publish as :latest tag'
49+
type: boolean
50+
default: true
51+
no_cache:
52+
description: 'Full rebuild with no cache (pick up fork-branch changes)'
53+
type: boolean
54+
default: false
55+
56+
permissions:
57+
contents: read
58+
packages: write
59+
60+
env:
61+
IMAGE_NAME: ghcr.io/localpibox/devstack
62+
63+
jobs:
64+
test-lpb:
65+
name: Run lpb.py unit tests
66+
runs-on: ubuntu-latest
67+
steps:
68+
- name: Checkout
69+
uses: actions/checkout@v6
70+
71+
- name: Run test suite
72+
run: |
73+
python3 scripts/test_lpb.py
74+
python3 scripts/test_localpibox.py
75+
76+
build-cli:
77+
name: Build & publish cli image
78+
runs-on: ubuntu-latest
79+
timeout-minutes: 45
80+
needs: [test-lpb]
81+
steps:
82+
- name: Checkout
83+
uses: actions/checkout@v6
84+
85+
- name: Set up QEMU
86+
uses: docker/setup-qemu-action@v4
87+
88+
- name: Set up buildx
89+
uses: docker/setup-buildx-action@v4
90+
91+
- name: Login to GHCR
92+
uses: docker/login-action@v4
93+
with:
94+
registry: ghcr.io
95+
username: ${{ github.actor }}
96+
password: ${{ secrets.GITHUB_TOKEN }}
97+
98+
- name: Load stack config
99+
id: config
100+
run: |
101+
set -a
102+
source lpb.stack.env
103+
set +a
104+
echo "PI_FORK=$LPB_PI_FORK" >> "$GITHUB_OUTPUT"
105+
echo "PI_REF=$LPB_PI_REF" >> "$GITHUB_OUTPUT"
106+
echo "CONFIG_FORK=$LPB_CONFIG_FORK" >> "$GITHUB_OUTPUT"
107+
echo "CONFIG_REF=$LPB_CONFIG_REF" >> "$GITHUB_OUTPUT"
108+
echo "NODE_VERSION=$LPB_NODE_VERSION" >> "$GITHUB_OUTPUT"
109+
echo "VSCODIUM_VERSION=$LPB_VSCODIUM_VERSION" >> "$GITHUB_OUTPUT"
110+
echo "LPB_VERSION=$(cat VERSION 2>/dev/null || echo unknown)" >> "$GITHUB_OUTPUT"
111+
MAX_TOKENS=$(grep -E '^LPB_MAX_TOKENS_CONTEXT_RATIO=' lpb.conf.env | cut -d= -f2- | tr -d '"' 2>/dev/null || true)
112+
echo "MAX_TOKENS=${MAX_TOKENS:-0.06}" >> "$GITHUB_OUTPUT"
113+
sha=$(git ls-remote "$LPB_PI_FORK" "refs/heads/$LPB_PI_REF" | awk '{print $1}')
114+
# Read stack version from config repo
115+
cfg_repo=$(echo "$LPB_CONFIG_FORK" | sed -E 's#https://github.com/([^/]+)/([^./]+)\.git#\1/\2#')
116+
stack_ver=$(curl -sf "https://raw.githubusercontent.com/$cfg_repo/$LPB_CONFIG_REF/VERSION" 2>/dev/null || echo "unknown")
117+
echo "sha=${sha:-unknown}" >> "$GITHUB_OUTPUT"
118+
echo "stack_version=${stack_ver}" >> "$GITHUB_OUTPUT"
119+
120+
- name: Build & push cli
121+
uses: docker/build-push-action@v7
122+
with:
123+
context: .
124+
file: ./Dockerfile
125+
target: cli
126+
push: ${{ github.event_name != 'pull_request' }}
127+
cache-from: type=gha
128+
cache-to: type=gha,mode=max
129+
build-args: |
130+
PI_FORK=${{ steps.config.outputs.PI_FORK }}
131+
PI_REF=${{ steps.config.outputs.PI_REF }}
132+
CONFIG_FORK=${{ steps.config.outputs.CONFIG_FORK }}
133+
CONFIG_REF=${{ steps.config.outputs.CONFIG_REF }}
134+
NODE_VERSION=${{ steps.config.outputs.NODE_VERSION }}
135+
VSCODIUM_VERSION=${{ steps.config.outputs.VSCODIUM_VERSION }}
136+
PI_HEAD_SHA=${{ steps.config.outputs.sha }}
137+
LPB_VERSION=${{ steps.config.outputs.LPB_VERSION }}
138+
LPB_MAX_TOKENS_CONTEXT_RATIO=${{ steps.config.outputs.MAX_TOKENS }}
139+
no-cache: ${{ github.event.inputs.no_cache == 'true' }}
140+
tags: |
141+
${{ env.IMAGE_NAME }}:cli
142+
${{ env.IMAGE_NAME }}:main-cli
143+
${{ env.IMAGE_NAME }}:${{ github.sha }}-cli
144+
${{ env.IMAGE_NAME }}:${{ steps.config.outputs.stack_version }}-cli
145+
${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && format('{0}:latest', env.IMAGE_NAME) || '' }}
146+
${{ github.event.inputs.publish_latest && format('{0}:latest', env.IMAGE_NAME) || '' }}
147+
${{ github.event_name == 'schedule' && format('{0}:weekly-cli', env.IMAGE_NAME) || '' }}
148+
provenance: false
149+
platforms: linux/amd64
150+
151+
build-web:
152+
name: Build & publish web image
153+
runs-on: ubuntu-latest
154+
timeout-minutes: 45
155+
needs: [build-cli]
156+
steps:
157+
- name: Checkout
158+
uses: actions/checkout@v6
159+
160+
- name: Set up QEMU
161+
uses: docker/setup-qemu-action@v4
162+
163+
- name: Set up buildx
164+
uses: docker/setup-buildx-action@v4
165+
166+
- name: Login to GHCR
167+
uses: docker/login-action@v4
168+
with:
169+
registry: ghcr.io
170+
username: ${{ github.actor }}
171+
password: ${{ secrets.GITHUB_TOKEN }}
172+
173+
- name: Load stack config
174+
id: config
175+
run: |
176+
set -a
177+
source lpb.stack.env
178+
set +a
179+
echo "PI_FORK=$LPB_PI_FORK" >> "$GITHUB_OUTPUT"
180+
echo "PI_REF=$LPB_PI_REF" >> "$GITHUB_OUTPUT"
181+
echo "CONFIG_FORK=$LPB_CONFIG_FORK" >> "$GITHUB_OUTPUT"
182+
echo "CONFIG_REF=$LPB_CONFIG_REF" >> "$GITHUB_OUTPUT"
183+
echo "NODE_VERSION=$LPB_NODE_VERSION" >> "$GITHUB_OUTPUT"
184+
echo "VSCODIUM_VERSION=$LPB_VSCODIUM_VERSION" >> "$GITHUB_OUTPUT"
185+
echo "LPB_VERSION=$(cat VERSION 2>/dev/null || echo unknown)" >> "$GITHUB_OUTPUT"
186+
MAX_TOKENS=$(grep -E '^LPB_MAX_TOKENS_CONTEXT_RATIO=' lpb.conf.env | cut -d= -f2- | tr -d '"' 2>/dev/null || true)
187+
echo "MAX_TOKENS=${MAX_TOKENS:-0.06}" >> "$GITHUB_OUTPUT"
188+
sha=$(git ls-remote "$LPB_PI_FORK" "refs/heads/$LPB_PI_REF" | awk '{print $1}')
189+
# Read stack version from config repo
190+
cfg_repo=$(echo "$LPB_CONFIG_FORK" | sed -E 's#https://github.com/([^/]+)/([^./]+)\.git#\1/\2#')
191+
stack_ver=$(curl -sf "https://raw.githubusercontent.com/$cfg_repo/$LPB_CONFIG_REF/VERSION" 2>/dev/null || echo "unknown")
192+
echo "sha=${sha:-unknown}" >> "$GITHUB_OUTPUT"
193+
echo "stack_version=${stack_ver}" >> "$GITHUB_OUTPUT"
194+
195+
- name: Build & push web
196+
uses: docker/build-push-action@v7
197+
with:
198+
context: .
199+
file: ./Dockerfile
200+
target: web
201+
push: ${{ github.event_name != 'pull_request' }}
202+
cache-from: type=gha
203+
cache-to: type=gha,mode=max
204+
build-args: |
205+
PI_FORK=${{ steps.config.outputs.PI_FORK }}
206+
PI_REF=${{ steps.config.outputs.PI_REF }}
207+
CONFIG_FORK=${{ steps.config.outputs.CONFIG_FORK }}
208+
CONFIG_REF=${{ steps.config.outputs.CONFIG_REF }}
209+
NODE_VERSION=${{ steps.config.outputs.NODE_VERSION }}
210+
VSCODIUM_VERSION=${{ steps.config.outputs.VSCODIUM_VERSION }}
211+
PI_HEAD_SHA=${{ steps.config.outputs.sha }}
212+
LPB_VERSION=${{ steps.config.outputs.LPB_VERSION }}
213+
LPB_MAX_TOKENS_CONTEXT_RATIO=${{ steps.config.outputs.MAX_TOKENS }}
214+
no-cache: ${{ github.event.inputs.no_cache == 'true' }}
215+
tags: |
216+
${{ env.IMAGE_NAME }}:web
217+
${{ env.IMAGE_NAME }}:main-web
218+
${{ env.IMAGE_NAME }}:${{ github.sha }}-web
219+
${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && format('{0}:latest-web', env.IMAGE_NAME) || '' }}
220+
${{ github.event.inputs.publish_latest && format('{0}:latest-web', env.IMAGE_NAME) || '' }}
221+
${{ github.event_name == 'schedule' && format('{0}:weekly-web', env.IMAGE_NAME) || '' }}
222+
provenance: false
223+
platforms: linux/amd64
224+
225+
status:
226+
name: Build status
227+
needs: [build-cli, build-web]
228+
if: always()
229+
runs-on: ubuntu-latest
230+
steps:
231+
- run: |
232+
if [ "${{ needs.build-cli.result }}" != "success" ] || [ "${{ needs.build-web.result }}" != "success" ]; then
233+
echo "Build failed!"
234+
exit 1
235+
fi
236+
echo "Build complete — both cli and web images pushed"

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# ── Devstack workspace/ ─────────────────────────────────────────────────
2+
# Stack repo clones/symlinks — managed by ./support/version.sh
3+
# NOT tracked in git. Recreated by:
4+
# ./support/version.sh workspace sync
5+
/workspace
6+
# ── Documentation archive (moved, not regenerated yet) ─────────────────────
7+
doc/archive/

0 commit comments

Comments
 (0)