Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions src/commands/autopilot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,40 @@ export async function runAutopilot(engine: BrainEngine, args: string[]) {

// --- Install/Uninstall ---

/**
* Resolve the gbrain CLI path for wrapper scripts.
* process.execPath returns the bun runtime binary, not the gbrain CLI,
* so the wrapper script fails with "Script not found".
*/
function resolveGbrainPath(): string {
const home = process.env.HOME || '';

// Check common install locations for the gbrain binary/symlink
const candidates = [
process.env.BUN_INSTALL ? join(process.env.BUN_INSTALL, 'bin', 'gbrain') : '',
join(home, '.bun', 'bin', 'gbrain'),
'/usr/local/bin/gbrain',
].filter(Boolean);

for (const candidate of candidates) {
if (existsSync(candidate)) return candidate;
}

// Try `which gbrain` for non-standard install locations
try {
const result = execSync('which gbrain', { encoding: 'utf-8', timeout: 5000 }).trim();
if (result) return result;
} catch {}

// Dev mode fallback: running via `bun run src/cli.ts` with no installed binary
const argv1 = process.argv[1] || '';
if (argv1.endsWith('cli.ts') && existsSync(argv1)) {
return `${process.execPath} ${argv1}`;
}

return process.execPath;
}

function plistPath(): string {
return join(process.env.HOME || '', 'Library', 'LaunchAgents', 'com.gbrain.autopilot.plist');
}
Expand All @@ -176,15 +210,19 @@ async function installDaemon(engine: BrainEngine, args: string[]) {
// Write a wrapper script that sources the user's shell profile for API keys
// instead of baking secrets into plist/crontab (#2: no plaintext keys in config files)
const wrapperPath = join(gbrainDir, 'autopilot-run.sh');
const gbrainPath = process.execPath;
const gbrainPath = resolveGbrainPath();
// Shell-escape values to prevent command injection (#1)
const safeRepoPath = repoPath.replace(/'/g, "'\\''");
const safeGbrainPath = gbrainPath.replace(/'/g, "'\\''");
// Dev mode returns "bun /path/cli.ts" (two tokens); installed mode returns a single path.
const isDevMode = gbrainPath.includes(' ');
const execLine = isDevMode
? `exec ${gbrainPath} autopilot --repo '${safeRepoPath}'`
: `exec '${gbrainPath.replace(/'/g, "'\\''")}' autopilot --repo '${safeRepoPath}'`;
const wrapper = `#!/bin/bash
# Auto-generated by gbrain autopilot --install
# Sources shell profile for API keys, then runs autopilot
source ~/.zshrc 2>/dev/null || source ~/.bashrc 2>/dev/null || true
exec '${safeGbrainPath}' autopilot --repo '${safeRepoPath}'
${execLine}
`;
writeFileSync(wrapperPath, wrapper, { mode: 0o755 });

Expand Down