Skip to content

Commit 9d08d59

Browse files
authored
Merge pull request #3 from Mike-Jenkins-Org/gimli/fix-linux-portability
fix: enable linux-portable mode on Linux/macOS hosts
2 parents 5eb4244 + cd76d4f commit 9d08d59

4 files changed

Lines changed: 116 additions & 2 deletions

File tree

profiles/profiles.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
"ANTHROPIC_API_KEY"
1111
]
1212
]
13+
},
14+
"local": {
15+
"description": "Local tools profile (no external env required)",
16+
"env_files": [],
17+
"required_env": []
1318
}
1419
}
1520
}

scripts/adapters/catalog.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,11 @@
44
"default_profile": "anthropic",
55
"command_env": "PCODER_CLAUDE_CMD",
66
"candidate_commands": ["claude"]
7+
},
8+
"pwsh": {
9+
"display_name": "PowerShell",
10+
"default_profile": "local",
11+
"command_env": "PCODER_PWSH_CMD",
12+
"candidate_commands": ["pwsh", "powershell"]
713
}
814
}

scripts/pcoder.cjs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -743,11 +743,15 @@ function runInLinuxPortableVm(options) {
743743
toolArgs,
744744
noSyncBack,
745745
skipProjectSync,
746-
authMode
746+
authMode,
747+
settings
747748
} = options;
748749

750+
// On non-Windows hosts, linux-portable mode uses portable host-native execution
751+
// with isolated auth state instead of a VM. This provides portability without
752+
// requiring Docker/Podman.
749753
if (process.platform !== 'win32') {
750-
fail('linux-portable mode is currently implemented for Windows hosts only.');
754+
return runPortableHostNative(options);
751755
}
752756

753757
loadJsonSafe(vmManifestPath, 'vm manifest');
@@ -845,6 +849,47 @@ function runInLinuxPortableVm(options) {
845849
process.exitCode = typeof runResult.status === 'number' ? runResult.status : 1;
846850
}
847851

852+
/**
853+
* Run tool in portable host-native mode (non-Windows hosts).
854+
* Uses isolated auth state in state/auth/<tool>/host/ but runs the tool
855+
* directly on the host without a VM. This provides portability on Linux/macOS
856+
* where a VM isn't needed for Linux tools.
857+
*/
858+
function runPortableHostNative(options) {
859+
const {
860+
tool,
861+
adapter,
862+
projectPath,
863+
mergedEnv,
864+
toolArgs,
865+
authMode,
866+
settings
867+
} = options;
868+
869+
// Resolve the runner
870+
const runner = resolveRunner(adapter, mergedEnv);
871+
if (!runner) {
872+
fail(`No executable found for tool '${tool}'. Set ${adapter.command_env} or install one of: ${adapter.candidate_commands.join(', ')}`);
873+
}
874+
875+
// Apply portable auth environment (isolates auth state to state/auth/<tool>/host/)
876+
const env = applyPortableHostAuthEnv(tool, { ...mergedEnv }, settings);
877+
878+
console.log(`[portable-native] Running ${tool} with isolated auth state...`);
879+
880+
const result = cp.spawnSync(runner, toolArgs, {
881+
cwd: projectPath,
882+
stdio: 'inherit',
883+
env
884+
});
885+
886+
if (result.error) {
887+
fail(`Failed to launch '${runner}': ${result.error.message}`);
888+
}
889+
890+
process.exitCode = typeof result.status === 'number' ? result.status : 1;
891+
}
892+
848893
function startWindowsVm() {
849894
const startScript = path.join(repoRoot, 'scripts', 'runtime', 'windows', 'start-vm.cmd');
850895
if (!fs.existsSync(startScript)) {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/env bash
2+
# Portable Coder smoke test for Linux/macOS
3+
# Run this to verify the portable launcher is working
4+
5+
set -euo pipefail
6+
7+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8+
REPO_ROOT="$(cd "${SCRIPT_DIR}/../../../" && pwd)"
9+
PCODER="${REPO_ROOT}/scripts/pcoder"
10+
11+
echo "=== PortableCoder Smoke Test ==="
12+
echo "Repo root: ${REPO_ROOT}"
13+
echo ""
14+
15+
# Check if pcoder exists
16+
if [[ ! -x "${PCODER}" ]]; then
17+
echo "FAIL: pcoder launcher not found or not executable: ${PCODER}"
18+
exit 1
19+
fi
20+
echo "OK: pcoder launcher found"
21+
22+
# Check if settings are initialized
23+
if ! "${PCODER}" doctor >/dev/null 2>&1; then
24+
echo "INFO: Settings not initialized, running setup --init..."
25+
"${PCODER}" setup --init
26+
fi
27+
28+
echo ""
29+
echo "=== Running doctor ==="
30+
if ! "${PCODER}" doctor; then
31+
echo "FAIL: Doctor check failed"
32+
exit 1
33+
fi
34+
35+
echo ""
36+
echo "=== Testing codex in portable-native mode ==="
37+
if "${PCODER}" run codex --mode linux-portable -- --version 2>&1 | grep -q "codex-cli"; then
38+
echo "OK: codex works in portable-native mode"
39+
else
40+
echo "FAIL: codex portable-native mode failed"
41+
exit 1
42+
fi
43+
44+
echo ""
45+
echo "=== Testing claude in portable-native mode ==="
46+
if "${PCODER}" run claude --mode linux-portable -- --version 2>&1 | grep -q "Claude Code"; then
47+
echo "OK: claude works in portable-native mode"
48+
else
49+
echo "FAIL: claude portable-native mode failed"
50+
exit 1
51+
fi
52+
53+
echo ""
54+
echo "=== Auth status ==="
55+
"${PCODER}" auth status
56+
57+
echo ""
58+
echo "=== All smoke tests passed ==="

0 commit comments

Comments
 (0)