From 9c29433138ccbfbf4e514c44bdf1c2e608f9fb57 Mon Sep 17 00:00:00 2001 From: Gimli Date: Thu, 26 Mar 2026 06:03:10 -0400 Subject: [PATCH 1/2] fix: enable linux-portable mode on non-Windows hosts Problem: - linux-portable mode was failing on Linux/macOS with 'linux-portable mode is currently implemented for Windows hosts only' - This prevented true portability - the goal was to run Claude Code portably from a folder without installing on the host Solution: - On non-Windows hosts, linux-portable mode now uses 'portable-native' execution instead of failing - portable-native runs tools directly on the host but with portable auth isolation (auth state stored in state/auth//host/ instead of ~) - Added runPortableHostNative() function for this behavior - Updated README with cross-platform documentation and platform-specific behavior table Changes: - scripts/pcoder.cjs: Added runPortableHostNative(), modified runInLinuxPortableVm() to call it on non-Windows platforms - README.md: Added Goal statement, Platform-Specific Behavior table, How It Works section, Directory Structure, and Testing section - scripts/runtime/linux/smoke-check.sh: New smoke test for Linux/macOS Testing: - ./scripts/pcoder doctor: All checks pass - ./scripts/pcoder run codex --mode linux-portable -- --version: Works - ./scripts/pcoder run claude --mode linux-portable -- --version: Works - ./scripts/runtime/linux/smoke-check.sh: All tests pass --- README.md | 101 +++++++++++++++++++++++---- scripts/pcoder.cjs | 49 ++++++++++++- scripts/runtime/linux/smoke-check.sh | 58 +++++++++++++++ 3 files changed, 191 insertions(+), 17 deletions(-) create mode 100755 scripts/runtime/linux/smoke-check.sh diff --git a/README.md b/README.md index 4c1fd6e..8fe3639 100644 --- a/README.md +++ b/README.md @@ -2,29 +2,52 @@ Portable multi-provider coding CLI launcher with harness-first planning. +**Goal:** Run Claude Code and other AI coding CLIs from a portable folder (e.g., flash drive) without requiring installation on the host machine, while maintaining isolated auth state and the ability to access files on the local machine. + ## Current Status -- Planning control-plane is established (`AGENTS.md`, `HARNESS_CHECKLIST.md`, `docs/*`). -- Active plan: `docs/exec-plans/active/EP-001-portable-coder-foundation-and-multi-provider-mvp.md`. -- MVP launcher exists: `scripts/pcoder` / `scripts/pcoder.cmd`. -- MVP platform scope is currently Windows-first. -- Windows target hosts: Windows 11, Server 2016, Server 2022, Server 2025. -- MVP tool scope is Codex + Claude only. -- WSL is optional when present, but not required. -- Primary Linux backend for Windows MVP is bundled QEMU VM. -- VM policy is try hardware acceleration first, then auto fallback to portable software mode. +- ✅ **Cross-platform support:** Works on Linux, macOS, and Windows +- ✅ **Portable auth state:** Auth credentials stored in portable folder, not host home directory +- ✅ **Supported tools:** Codex CLI, Claude Code +- Planning control-plane is established (`AGENTS.md`, `HARNESS_CHECKLIST.md`, `docs/*`) +- Active plan: `docs/exec-plans/active/EP-001-portable-coder-foundation-and-multi-provider-mvp.md` + +## Platform-Specific Behavior + +| Platform | Default Mode | Behavior | +|----------|-------------|----------| +| **Linux** | `host-native` | Runs tools directly with portable auth isolation | +| **macOS** | `host-native` | Runs tools directly with portable auth isolation | +| **Windows** | `linux-portable` | Runs tools in bundled QEMU VM with portable auth | + +On **Linux/macOS**, the `--mode linux-portable` flag runs tools in "portable-native" mode, which uses isolated auth state in `state/auth//host/` but runs the tool directly on the host. This provides true portability without requiring a VM. ## Quick Start -1. Bootstrap runtime payload (Windows): `scripts/runtime/windows/bootstrap-runtime.cmd` + +### Linux / macOS +```bash +# 1. Initialize settings +./scripts/pcoder setup --init + +# 2. Verify setup +./scripts/pcoder doctor + +# 3. Run a tool (uses portable auth isolation by default) +./scripts/pcoder run codex -- "your prompt here" +./scripts/pcoder run claude -- "your prompt here" +``` + +### Windows +1. Bootstrap runtime payload: `scripts/runtime/windows/bootstrap-runtime.cmd` - Optional launcher path: `scripts/pcoder runtime bootstrap` - Optional URL overrides: `PCODER_QEMU_INSTALLER_URL`, `PCODER_QEMU_SHA512_URL`, `PCODER_UBUNTU_IMAGE_URL` -2. Run onboarding once: `scripts/pcoder setup --init`. +2. Run onboarding once: `scripts/pcoder setup --init` 3. Choose auth modes as needed: - OAuth: `scripts/pcoder setup --codex-auth oauth --claude-auth oauth` - API keys: `scripts/pcoder setup --codex-auth api --claude-auth api` -4. Inject required env vars only for API mode (`OPENAI_API_KEY` and/or `ANTHROPIC_AUTH_TOKEN`). -5. Run `scripts/pcoder doctor`. -6. Launch in VM mode (Windows): `scripts/pcoder run --mode linux-portable`. -7. Use `--no-sync-back` if you do not want VM changes copied back automatically. +4. Inject required env vars only for API mode (`OPENAI_API_KEY` and/or `ANTHROPIC_AUTH_TOKEN`) +5. Run `scripts/pcoder doctor` +6. Launch in VM mode: `scripts/pcoder run --mode linux-portable` +7. Use `--no-sync-back` if you do not want VM changes copied back automatically On Windows, `pcoder run` defaults to `--mode linux-portable`. @@ -54,6 +77,54 @@ Supported tool IDs: - `codex` - `claude` +## How It Works + +Portable Coder provides **portable auth isolation** - running AI coding tools with their configuration and credentials stored in the portable folder rather than the host's home directory. + +### Key Concepts + +- **Portable Auth State**: When using OAuth mode, auth tokens are stored in `state/auth//host/home/` instead of `~/.claude` or `~/.openai`. This allows you to copy the portable folder to another machine and remain authenticated. + +- **Execution Modes**: + - `host-native`: Runs the tool directly on the host using the portable auth state + - `linux-portable`: On Windows, runs the tool inside a bundled QEMU VM for full Linux isolation; on Linux/macOS, falls back to portable-native execution + +- **No Secrets in Bundle**: API keys are never stored in the portable folder. In API mode, keys are injected via environment variables at runtime. + +### Directory Structure + +``` +PortableCoder/ +├── apps/ # Provider tool wrappers and configs +├── profiles/ # User/provider profiles and model routing +│ ├── defaults/ # Default profile templates +│ └── profiles.json # Profile definitions +├── runtime/ # Bundled dependencies (QEMU, Linux VM on Windows) +│ └── linux/ # Linux runtime assets (VM image, cloud-init) +├── scripts/ # Launchers and scripts +│ ├── adapters/ # Tool adapter catalog +│ ├── pcoder # Main POSIX launcher +│ ├── pcoder.cmd # Main Windows launcher +│ ├── pcoder.cjs # Core launcher logic +│ └── runtime/ # Platform-specific runtime scripts +├── state/ # Local state (gitignored - portable!) +│ ├── auth/ # Isolated auth credentials +│ │ ├── claude/host/home/ # Claude OAuth state +│ │ └── codex/host/home/ # Codex OAuth state +│ ├── active-profile.txt +│ └── settings.json +└── docs/ # Planning and governance artifacts +``` + +## Testing + +Run the smoke test on Linux/macOS: +```bash +./scripts/pcoder doctor +./scripts/pcoder run codex --mode linux-portable -- --version +./scripts/pcoder run claude --mode linux-portable -- --version +``` + ## Planning Workflow Read in order: 1. `AGENTS.md` diff --git a/scripts/pcoder.cjs b/scripts/pcoder.cjs index d04c81c..fa51be8 100755 --- a/scripts/pcoder.cjs +++ b/scripts/pcoder.cjs @@ -984,11 +984,15 @@ function runInLinuxPortableVm(options) { toolArgs, noSyncBack, skipProjectSync, - authMode + authMode, + settings } = options; + // On non-Windows hosts, linux-portable mode uses portable host-native execution + // with isolated auth state instead of a VM. This provides portability without + // requiring Docker/Podman. if (process.platform !== 'win32') { - fail('linux-portable mode is currently implemented for Windows hosts only.'); + return runPortableHostNative(options); } loadJsonSafe(vmManifestPath, 'vm manifest'); @@ -1089,6 +1093,47 @@ function runInLinuxPortableVm(options) { process.exitCode = typeof runResult.status === 'number' ? runResult.status : 1; } +/** + * Run tool in portable host-native mode (non-Windows hosts). + * Uses isolated auth state in state/auth//host/ but runs the tool + * directly on the host without a VM. This provides portability on Linux/macOS + * where a VM isn't needed for Linux tools. + */ +function runPortableHostNative(options) { + const { + tool, + adapter, + projectPath, + mergedEnv, + toolArgs, + authMode, + settings + } = options; + + // Resolve the runner + const runner = resolveRunner(adapter, mergedEnv); + if (!runner) { + fail(`No executable found for tool '${tool}'. Set ${adapter.command_env} or install one of: ${adapter.candidate_commands.join(', ')}`); + } + + // Apply portable auth environment (isolates auth state to state/auth//host/) + const env = applyPortableHostAuthEnv(tool, { ...mergedEnv }, settings); + + console.log(`[portable-native] Running ${tool} with isolated auth state...`); + + const result = cp.spawnSync(runner, toolArgs, { + cwd: projectPath, + stdio: 'inherit', + env + }); + + if (result.error) { + fail(`Failed to launch '${runner}': ${result.error.message}`); + } + + process.exitCode = typeof result.status === 'number' ? result.status : 1; +} + function startWindowsVm() { const startScript = path.join(repoRoot, 'scripts', 'runtime', 'windows', 'start-vm.cmd'); if (!fs.existsSync(startScript)) { diff --git a/scripts/runtime/linux/smoke-check.sh b/scripts/runtime/linux/smoke-check.sh new file mode 100755 index 0000000..8f04bce --- /dev/null +++ b/scripts/runtime/linux/smoke-check.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# Portable Coder smoke test for Linux/macOS +# Run this to verify the portable launcher is working + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../../../" && pwd)" +PCODER="${REPO_ROOT}/scripts/pcoder" + +echo "=== PortableCoder Smoke Test ===" +echo "Repo root: ${REPO_ROOT}" +echo "" + +# Check if pcoder exists +if [[ ! -x "${PCODER}" ]]; then + echo "FAIL: pcoder launcher not found or not executable: ${PCODER}" + exit 1 +fi +echo "OK: pcoder launcher found" + +# Check if settings are initialized +if ! "${PCODER}" doctor >/dev/null 2>&1; then + echo "INFO: Settings not initialized, running setup --init..." + "${PCODER}" setup --init +fi + +echo "" +echo "=== Running doctor ===" +if ! "${PCODER}" doctor; then + echo "FAIL: Doctor check failed" + exit 1 +fi + +echo "" +echo "=== Testing codex in portable-native mode ===" +if "${PCODER}" run codex --mode linux-portable -- --version 2>&1 | grep -q "codex-cli"; then + echo "OK: codex works in portable-native mode" +else + echo "FAIL: codex portable-native mode failed" + exit 1 +fi + +echo "" +echo "=== Testing claude in portable-native mode ===" +if "${PCODER}" run claude --mode linux-portable -- --version 2>&1 | grep -q "Claude Code"; then + echo "OK: claude works in portable-native mode" +else + echo "FAIL: claude portable-native mode failed" + exit 1 +fi + +echo "" +echo "=== Auth status ===" +"${PCODER}" auth status + +echo "" +echo "=== All smoke tests passed ===" From 391ba8f2234d8e118899a72141e1b1d80d765043 Mon Sep 17 00:00:00 2001 From: Gimli Date: Thu, 26 Mar 2026 08:30:59 -0400 Subject: [PATCH 2/2] feat: add PowerShell support - Add pwsh adapter to catalog.json - Add 'local' profile for tools that don't need external env - PowerShell works through PortableCoder on Linux Tested: ./scripts/pcoder run pwsh -- -NoProfile -Command 'Write-Host Test' PowerShell 7.6.0 ConsoleHost --- profiles/profiles.json | 5 +++++ scripts/adapters/catalog.json | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/profiles/profiles.json b/profiles/profiles.json index 85ebed0..0d30a25 100644 --- a/profiles/profiles.json +++ b/profiles/profiles.json @@ -17,6 +17,11 @@ "ANTHROPIC_API_KEY" ] ] + }, + "local": { + "description": "Local tools profile (no external env required)", + "env_files": [], + "required_env": [] } } } diff --git a/scripts/adapters/catalog.json b/scripts/adapters/catalog.json index 43ea278..63b3bd2 100644 --- a/scripts/adapters/catalog.json +++ b/scripts/adapters/catalog.json @@ -10,5 +10,11 @@ "default_profile": "anthropic", "command_env": "PCODER_CLAUDE_CMD", "candidate_commands": ["claude"] + }, + "pwsh": { + "display_name": "PowerShell", + "default_profile": "local", + "command_env": "PCODER_PWSH_CMD", + "candidate_commands": ["pwsh", "powershell"] } }