Claude
Skills
Sign in
Back

electron-cli-integration

Included with Lifetime
$97 forever

Integrate external CLI tools (Claude, Node, npx) in Electron apps with proper PATH handling

Backend & APIs

What this skill does


# Electron CLI Integration Skill

When Electron apps launch from Finder/Explorer, the PATH is minimal. This skill covers detecting and using CLI tools reliably.

## The PATH Problem

When launched from terminal:
```
PATH=/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin:~/.nvm/versions/node/v20/bin:...
```

When launched from Finder (macOS):
```
PATH=/usr/bin:/bin:/usr/sbin:/sbin
```

## CLI Detector Pattern

```typescript
// electron/cli-detector.ts
import { exec } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';

const execAsync = promisify(exec);

export interface CliStatus {
  found: boolean;
  path?: string;
  version?: string;
  error?: string;
}

// Common installation paths for various tools
function getCommonPaths(toolName: string): string[] {
  const home = os.homedir();

  return [
    // Homebrew (macOS)
    `/usr/local/bin/${toolName}`,
    `/opt/homebrew/bin/${toolName}`,

    // User-local installations
    path.join(home, '.local', 'bin', toolName),

    // Tool-specific locations
    path.join(home, '.claude', 'bin', toolName),

    // Node.js via nvm
    path.join(home, '.nvm', 'versions', 'node', 'current', 'bin', toolName),

    // System paths
    `/usr/bin/${toolName}`,

    // Windows-specific (if on Windows)
    path.join(home, 'AppData', 'Roaming', 'npm', `${toolName}.cmd`),
    `C:\\Program Files\\nodejs\\${toolName}.cmd`,
  ];
}

function isExecutable(filePath: string): boolean {
  try {
    fs.accessSync(filePath, fs.constants.X_OK);
    return true;
  } catch {
    return false;
  }
}

function findInCommonPaths(toolName: string): string | null {
  for (const toolPath of getCommonPaths(toolName)) {
    if (fs.existsSync(toolPath) && isExecutable(toolPath)) {
      return toolPath;
    }
  }
  return null;
}

export async function detectCli(toolName: string): Promise<CliStatus> {
  // First, check common installation paths
  let toolPath = findInCommonPaths(toolName);

  // If not found, try 'which' command (works if terminal has full PATH)
  if (!toolPath) {
    try {
      const which = await import('which');
      toolPath = await which.default(toolName);
    } catch {
      // Not found in PATH either
    }
  }

  if (!toolPath) {
    return {
      found: false,
      error: `${toolName} not found. Check common installation paths or ensure it's in your PATH.`,
    };
  }

  // Get version
  try {
    const { stdout } = await execAsync(`"${toolPath}" --version`, {
      timeout: 5000,
    });
    const version = stdout.trim();

    return {
      found: true,
      path: toolPath,
      version,
    };
  } catch {
    // Found but couldn't get version
    return {
      found: true,
      path: toolPath,
      error: `Found ${toolName} but could not determine version`,
    };
  }
}

// Specific detectors for common tools
export async function detectClaudeCli(): Promise<CliStatus> {
  return detectCli('claude');
}

export async function detectNode(): Promise<CliStatus> {
  return detectCli('node');
}

export async function detectNpx(): Promise<CliStatus> {
  return detectCli('npx');
}
```

## Using CLI Tools at Runtime

### Spawning with Safe Paths

```typescript
// src/server/services/cli-runner.ts
import { spawn } from 'child_process';
import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs';

interface SpawnOptions {
  args: string[];
  cwd?: string;
  env?: NodeJS.ProcessEnv;
  onStdout?: (data: string) => void;
  onStderr?: (data: string) => void;
}

// Get safe working directory (not root!)
function getSafeWorkingDir(): string {
  if (process.env.ELECTRON_DB_PATH) {
    return path.dirname(process.env.ELECTRON_DB_PATH);
  }
  return os.tmpdir();
}

// Find executable in common paths
function findExecutable(name: string): string {
  const home = os.homedir();
  const paths = [
    `/usr/local/bin/${name}`,
    `/opt/homebrew/bin/${name}`,
    path.join(home, '.local', 'bin', name),
    path.join(home, '.nvm/versions/node/current/bin', name),
    `/usr/bin/${name}`,
  ];

  for (const p of paths) {
    try {
      fs.accessSync(p, fs.constants.X_OK);
      return p;
    } catch {}
  }

  // Fallback - hope it's in PATH
  return name;
}

export async function runCli(
  toolName: string,
  options: SpawnOptions
): Promise<{ exitCode: number; stdout: string; stderr: string }> {
  return new Promise((resolve) => {
    const toolPath = process.env[`${toolName.toUpperCase()}_PATH`] || findExecutable(toolName);
    const cwd = options.cwd || getSafeWorkingDir();

    let stdout = '';
    let stderr = '';

    // Use bash to ensure proper environment
    const fullCommand = `"${toolPath}" ${options.args.map((a) => `"${a}"`).join(' ')}`;

    const proc = spawn('/bin/bash', ['-c', fullCommand], {
      cwd,
      env: { ...process.env, ...options.env },
      stdio: ['pipe', 'pipe', 'pipe'],
    });

    proc.stdout.on('data', (data) => {
      const text = data.toString();
      stdout += text;
      options.onStdout?.(text);
    });

    proc.stderr.on('data', (data) => {
      const text = data.toString();
      stderr += text;
      options.onStderr?.(text);
    });

    proc.on('close', (code) => {
      resolve({
        exitCode: code ?? 1,
        stdout,
        stderr,
      });
    });

    proc.on('error', (err) => {
      resolve({
        exitCode: 1,
        stdout,
        stderr: err.message,
      });
    });
  });
}
```

### Claude CLI Integration

```typescript
// src/server/services/claude-cli.ts
import { spawn } from 'child_process';
import * as path from 'path';
import * as os from 'os';

function getClaudePath(): string {
  // Check environment variable first (set by main process)
  if (process.env.CLAUDE_PATH) {
    return process.env.CLAUDE_PATH;
  }

  // Search common paths
  const home = os.homedir();
  const paths = [
    '/usr/local/bin/claude',
    '/opt/homebrew/bin/claude',
    path.join(home, '.local', 'bin', 'claude'),
    path.join(home, '.claude', 'bin', 'claude'),
  ];

  for (const p of paths) {
    try {
      require('fs').accessSync(p, require('fs').constants.X_OK);
      return p;
    } catch {}
  }

  return 'claude'; // Fallback
}

function getSafeWorkingDir(): string {
  if (process.env.ELECTRON_DB_PATH) {
    return path.dirname(process.env.ELECTRON_DB_PATH);
  }
  return os.tmpdir();
}

export async function runClaudeCommand(
  prompt: string
): Promise<{ success: boolean; output: string; error?: string }> {
  return new Promise((resolve) => {
    const claudePath = getClaudePath();

    const proc = spawn('/bin/bash', ['-c', `"${claudePath}" --print --output-format text`], {
      cwd: getSafeWorkingDir(),
      env: { ...process.env },
      stdio: ['pipe', 'pipe', 'pipe'],
    });

    let output = '';
    let error = '';

    // Write prompt to stdin
    proc.stdin.write(prompt);
    proc.stdin.end();

    proc.stdout.on('data', (data) => {
      output += data.toString();
    });

    proc.stderr.on('data', (data) => {
      error += data.toString();
    });

    proc.on('close', (code) => {
      if (code === 0 && output.trim()) {
        resolve({ success: true, output: output.trim() });
      } else {
        resolve({
          success: false,
          output,
          error: error || 'Command failed',
        });
      }
    });

    proc.on('error', (err) => {
      resolve({
        success: false,
        output: '',
        error: err.message,
      });
    });
  });
}
```

### Running npx Without .bin Directory

In packaged apps, `node_modules/.bin` doesn't exist. Call CLI scripts directly:

```typescript
// src/server/services/remotion-runner.ts
import { spawn } from 'child_process';
import * as path from 'path';
import * as os from 'os';

function findNodePath(): string {
  const home = os.homedir();
  const paths = [
    '/usr/local/bin/node',
    '/opt/homebrew/bin/node',
    path.join(home, '.nvm/versions/node/current/bin/node'),
  ];

  const 

Related in Backend & APIs