diff --git a/profiles/profiles.json b/profiles/profiles.json index 1b6512d..9709459 100644 --- a/profiles/profiles.json +++ b/profiles/profiles.json @@ -10,6 +10,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 15011e9..f31084c 100644 --- a/scripts/adapters/catalog.json +++ b/scripts/adapters/catalog.json @@ -4,5 +4,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"] } } diff --git a/scripts/pcoder.cjs b/scripts/pcoder.cjs index be1ac38..ba655f5 100755 --- a/scripts/pcoder.cjs +++ b/scripts/pcoder.cjs @@ -743,11 +743,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'); @@ -845,6 +849,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 ==="