mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 22:19:42 +02:00
feat: OpenCode integration Phases 1-3 — types, CLI resolver, tmux spawn
Phase 1: Type system - Add SessionMode = 'claude' | 'shell' | 'opencode' to types.ts (single source of truth) - Add OpenCodeConfig interface (model, autoAllowTools, continueSession, etc.) - Add openCodeConfig to SessionState - Import SessionMode in session.ts, mux-interface.ts (remove duplicates) - Refactor createSession/respawnPane to options objects (CreateSessionOptions, RespawnPaneOptions) Phase 2: CLI resolver - New opencode-cli-resolver.ts mirroring claude-cli-resolver.ts - Searches ~/.opencode/bin, ~/.local/bin, /usr/local/bin, ~/go/bin, etc. - Exported via utils/index.ts Phase 3: Tmux spawn - Extract buildSpawnCommand() shared helper (eliminates duplication) - Add buildOpenCodeCommand() for opencode CLI flags - Add setOpenCodeEnvVars() — API keys via tmux setenv (not visible in ps) - Add setOpenCodeConfigContent() — JSON config via tmux setenv (prevents shell injection) - Gate _processExpensiveParsers() for opencode mode (skip Claude-specific parsers) - OpenCode-specific TUI ready detection (timeout-based, no ❯ prompt scanning) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,3 +33,4 @@ export {
|
||||
export { assertNever } from './type-safety.js';
|
||||
export { wrapWithNice } from './nice-wrapper.js';
|
||||
export { findClaudeDir, getAugmentedPath } from './claude-cli-resolver.js';
|
||||
export { resolveOpenCodeDir, isOpenCodeAvailable, getOpenCodeAugmentedPath } from './opencode-cli-resolver.js';
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @fileoverview Resolve the OpenCode CLI binary across common install paths.
|
||||
*
|
||||
* Mirrors claude-cli-resolver.ts pattern. Finds the `opencode` binary
|
||||
* and provides an augmented PATH string for tmux sessions.
|
||||
*
|
||||
* @module utils/opencode-cli-resolver
|
||||
*/
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { delimiter, dirname, join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
/** Timeout for exec commands (5 seconds) */
|
||||
const EXEC_TIMEOUT_MS = 5000;
|
||||
|
||||
/** Common directories where the OpenCode CLI binary may be installed */
|
||||
const OPENCODE_SEARCH_DIRS = [
|
||||
join(homedir(), '.opencode', 'bin'), // Default install location
|
||||
join(homedir(), '.local', 'bin'), // Alternative install location
|
||||
'/usr/local/bin', // Homebrew / system
|
||||
join(homedir(), 'go', 'bin'), // Go install
|
||||
join(homedir(), '.bun', 'bin'), // Bun global
|
||||
join(homedir(), '.npm-global', 'bin'), // npm global
|
||||
join(homedir(), 'bin'), // User bin
|
||||
];
|
||||
|
||||
/** Cached directory containing the opencode binary (empty string = searched but not found) */
|
||||
let _openCodeDir: string | null = null;
|
||||
|
||||
/**
|
||||
* Finds the directory containing the `opencode` binary.
|
||||
* Checks `which opencode` first, then falls back to common install locations.
|
||||
* Result is cached for subsequent calls.
|
||||
*
|
||||
* @returns Directory path, or null if not found
|
||||
*/
|
||||
export function resolveOpenCodeDir(): string | null {
|
||||
if (_openCodeDir !== null) return _openCodeDir || null;
|
||||
|
||||
// Try `which` first (respects current PATH)
|
||||
try {
|
||||
const result = execSync('which opencode', { encoding: 'utf-8', timeout: EXEC_TIMEOUT_MS }).trim();
|
||||
if (result && existsSync(result)) {
|
||||
_openCodeDir = dirname(result);
|
||||
return _openCodeDir;
|
||||
}
|
||||
} catch {
|
||||
// OpenCode not in PATH, will check common locations
|
||||
}
|
||||
|
||||
// Fallback: check common installation directories
|
||||
for (const dir of OPENCODE_SEARCH_DIRS) {
|
||||
if (existsSync(join(dir, 'opencode'))) {
|
||||
_openCodeDir = dir;
|
||||
return _openCodeDir;
|
||||
}
|
||||
}
|
||||
|
||||
_openCodeDir = ''; // mark as searched, not found
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if OpenCode CLI is available on the system.
|
||||
*/
|
||||
export function isOpenCodeAvailable(): boolean {
|
||||
return resolveOpenCodeDir() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a PATH string that includes the directory containing `opencode`.
|
||||
*
|
||||
* Finds the opencode binary (via `which` or common install locations), then
|
||||
* prepends its directory to the current PATH if not already present.
|
||||
* Result is cached for subsequent calls.
|
||||
*/
|
||||
export function getOpenCodeAugmentedPath(): string {
|
||||
const currentPath = process.env.PATH || '';
|
||||
const dir = resolveOpenCodeDir();
|
||||
|
||||
if (dir && !currentPath.split(delimiter).includes(dir)) {
|
||||
return `${dir}${delimiter}${currentPath}`;
|
||||
}
|
||||
|
||||
return currentPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset cached resolution (for testing).
|
||||
*/
|
||||
export function resetOpenCodeCache(): void {
|
||||
_openCodeDir = null;
|
||||
}
|
||||
Reference in New Issue
Block a user