nkds

导航

 

MonkeyCode 跨平台支持详解:Windows/macOS/Linux 全平台无缝开发体验

引言

"优秀的开发工具不应该被操作系统所限制——无论你在哪个平台工作,MonkeyCode 都能提供一致且卓越的体验。"

在当今多元化的开发环境中,开发者可能在不同操作系统之间频繁切换:在 macOS 上进行日常开发,在 Windows 上处理企业项目,在 Linux 服务器上进行部署调试。一个真正优秀的 AI 编程助手必须能够在所有主流平台上提供同等质量、一致体验、原生集成的服务。

MonkeyCode 作为完全开源(Apache License 2.0)的 AI 编程助手,从架构设计之初就将跨平台一等公民作为核心原则。本文将深入剖析 MonkeyCode 的跨平台架构设计、各平台的深度适配策略、以及如何实现真正的全平台无缝体验。

🎯 核心信息

  • GitHub 仓库: https://github.com/monkeycode-ai/monkeycode
  • 开源协议: Apache License 2.0
  • 支持平台: Windows 10/11, macOS 12+, Ubuntu 20.04+/Debian/Fedora/Arch Linux
  • 编辑器适配: VS Code / JetBrains / Vim / Neovim / Web IDE
  • 欢迎贡献跨平台改进!

一、跨平台架构总览

1.1 分层抽象架构

┌─────────────────────────────────────────────────────────────┐
│           MonkeyCode 跨平台架构                               │
│                                                             │
│  ══════════════════════════════════════════════════════    │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              用户界面层 (UI Layer)                    │   │
│  │                                                     │   │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐            │   │
│  │  │ VS Code   │ │JetBrains │ │ Web IDE  │ ...        │   │
│  │  │ 扩展     │ │ 插件    │ │ 集成    │            │   │
│  │  └─────┬────┘ └─────┬────┘ └─────┬────┘            │   │
│  └────────┼──────────┼──────────┼────────────────────┘   │
│           │          │          │                          │
│  ┌────────▼──────────▼──────────▼────────────────────┐   │
│  │         平台适配层 (Platform Abstraction Layer)      │   │
│  │                                                   │   │
│  │  ┌─────────┐  ┌─────────┐  ┌─────────┐           │   │
│  │  │Windows  │  │ macOS   │  │ Linux   │           │   │
│  │  │Adapter  │  │ Adapter │  │ Adapter │           │   │
│  │  └────┬────┘  └────┬────┘  └────┬────┘           │   │
│  └───────┼────────────┼────────────┼─────────────────┘   │
│          │            │            │                      │
│  ┌───────▼────────────▼────────────▼─────────────────┐   │
│  │           核心引擎层 (Core Engine)                  │   │
│  │                                                   │   │
│  │  • AI 对话引擎 (Language Agnostic)                 │   │
│  │  • 代码分析引擎 (Tree-sitter based)                │   │
│  │  • 索引与缓存 (Cross-platform storage)             │   │
│  │  • 配置管理 (Unified config system)               │   │
│  └───────────────────────┬───────────────────────────┘   │
│                           │                                │
│  ┌───────────────────────▼───────────────────────────┐   │
│  │         基础设施层 (Infrastructure)                  │   │
│  │                                                   │   │
│  │  • 文件系统 (FS abstraction)                       │   │
│  │  • 进程管理 (Process manager)                      │   │
│  │  • 网络层 (HTTP/WebSocket)                         │   │
│  │  • 终端模拟 (Terminal emulator)                     │   │
│  │  • 快捷键系统 (Hotkey registry)                    │   │
│  └───────────────────────────────────────────────────┘   │
│                                                             │
╚═══════════════════════════════════════════════════════════╝

1.2 各平台支持矩阵

特性 Windows macOS Linux
VS Code 扩展 ✅ 完整支持 ✅ 完整支持 ✅ 完整支持
JetBrains 插件 ✅ 完整支持 ✅ 完整支持 ✅ 完整支持
Vim/Neovim 插件 ✅ 支持 ✅ 支持 ✅ 完整支持
CLI 工具 ✅ PowerShell/CMD ✅ zsh/bash ✅ bash/zsh/fish
GUI 应用 ✅ Electron ✅ 原生 Swift ✅ GTK/Electron
终端 UI (TUI) ✅ Windows Terminal ✅ iTerm2/Terminal.app ✅ Alacritty/kitty
系统托盘 ✅ 系统托盘 ✅ Menu Bar ✅ 系统托盘/Indicator
全局快捷键 ✅ Win+Shift+M ✅ Cmd+Shift+M ✅ Ctrl+Alt+M
文件监听 ✅ FSEvents 替代 ✅ FSEvents ✅ inotify
Shell 集成 ✅ PowerShell 7 ✅ zsh + AppleScript ✅ bash + D-Bus
通知系统 ✅ Toast 通知 ✅ 中心通知 ✅ libnotify
剪贴板 ✅ Win32 API ✅ NSPasteboard ✅ X11/Wayland
自动更新 ✅ NSIS/Squirrel ✅ DMG/Sparkle ✅ AppImage/deb/rpm

二、平台适配层深度解析

2.1 文件系统抽象

// ===== packages/platform/src/fs/abstraction.ts =====
/**
 * MonkeyCode 跨平台文件系统抽象层
 * 
 * 统一处理不同操作系统的文件系统差异:
 * - 路径分隔符(\ vs /)
 * - 大小写敏感性
 * - 符号链接处理
 * - 文件权限模型
 * - 文件锁机制
 */

import { promisify } from 'util';
import * as fs from 'fs';
import * as path from 'path';
import { z } from 'zod';

// === 平台检测 ===
export enum Platform {
  WINDOWS = 'win32',
  MACOS = 'darwin',
  LINUX = 'linux',
}

export function getCurrentPlatform(): Platform {
  return process.platform as Platform;
}

export function isWindows(): boolean {
  return process.platform === 'win32';
}

export function isMacOS(): boolean {
  return process.platform === 'darwin';
}

export function isLinux(): boolean {
  return process.platform === 'linux';
}

// === 统一路径接口 ===
export interface UnifiedPath {
  /** 原始路径字符串 */
  raw: string;
  
  /** 标准化后的 POSIX 风格路径 */
  normalized: string;
  
  /** 平台原生格式路径 */
  native: string;
  
  /** 是否为绝对路径 */
  isAbsolute: boolean;
  
  /** 路径各部分 */
  parts: string[];
  
  /** 文件扩展名 */
  extension: string;
  
  /** 文件名(不含扩展名) */
  basename: string;
  
  /** 父目录 */
  dirname: string;
}

/**
 * 统一路径处理器
 * 
 * 在内部统一使用 POSIX 格式(/),
 * 仅在与 OS 交互时转换为原生格式
 */
export class PathResolver {
  private platform: Platform;
  
  constructor(platform?: Platform) {
    this.platform = platform || getCurrentPlatform();
  }
  
  /**
   * 解析并标准化路径
   */
  resolve(inputPath: string): UnifiedPath {
    // 1. 处理 Windows 盘符和 UNC 路径
    let normalized = inputPath;
    
    if (this.platform === Platform.WINDOWS) {
      // C:\path\to\file → /c/path/to/file
      normalized = normalized.replace(/^([A-Za-z]):\\/, '/$1/');
      normalized = normalized.replace(/\\/g, '/');
      // UNC 路径: \\server\share → //server/share
      normalized = normalized.replace(/^\\\\/, '//');
    }
    
    // 2. 规范化 . 和 ..
    const parts = normalized.split('/').filter(p => p && p !== '.');
    const resolvedParts: string[] = [];
    
    for (const part of parts) {
      if (part === '..') {
        resolvedParts.pop();
      } else {
        resolvedParts.push(part);
      }
    }
    
    // 3. 构建标准化路径
    const isAbsolute = inputPath.startsWith('/') || 
      /^[A-Za-z]:/.test(inputPath) ||
      inputPath.startsWith('\\');
    
    const standardPath = (isAbsolute ? '/' : '') + resolvedParts.join('/');
    
    // 4. 生成原生格式
    let nativePath = standardPath;
    if (this.platform === Platform.WINDOWS) {
      nativePath = standardPath.replace(/^\//, '').replace(/\//g, '\\');
      if (/^[a-z]/i.test(nativePath)) {
        nativePath = nativePath[0].toUpperCase() + ':' + '\\' + nativePath.slice(2);
      }
    }
    
    return {
      raw: inputPath,
      normalized: standardPath,
      native: nativePath,
      isAbsolute,
      parts: resolvedParts,
      extension: this.getExtension(standardPath),
      basename: this.getBasename(standardPath),
      dirname: this.getDirname(standardPath),
    };
  }
  
  /**
   * 智能路径匹配
   * 
   * 处理跨平台的大小写敏感性差异:
   * - Windows: 大小写不敏感
   * - macOS: 默认不敏感(可配置为敏感)
   * - Linux: 大小写敏感
   */
  matches(actualPath: string, pattern: string): boolean {
    if (this.platform === Platform.LINUX) {
      // Linux: 精确匹配(大小写敏感)
      return actualPath === pattern;
    } else {
      // Windows/macOS: 忽略大小写
      return actualPath.toLowerCase() === pattern.toLowerCase();
    }
  }
}

2.2 Shell 与进程管理

// ===== packages/platform/src/shell/manager.ts =====
/**
 * 跨平台 Shell 管理器
 * 
 * 统一处理不同平台的 Shell 差异:
 * - Windows: PowerShell / CMD / Git Bash / WSL
 * - macOS: zsh (默认) / bash
 * - Linux: bash / zsh / fish / dash
 */

import { spawn, ChildProcess, ExecOptions } from 'child_process';
import { EventEmitter } from 'events';

export interface ShellConfig {
  /** Shell 可执行文件路径 */
  executable: string;
  
  /** 启动参数 */
  args?: string[];
  
  /** 环境变量 */
  env?: Record<string, string>;
  
  /** 工作目录 */
  cwd?: string;
  
  /** 终端类型 */
  terminalType?: 'ansi' | 'xterm' | 'xterm-256color';
  
  /** Shell 类型标识 */
  shellType: 'powershell' | 'cmd' | 'bash' | 'zsh' | 'fish' | 'wsl' | 'other';
}

// === 平台默认 Shell 配置 ===
const DEFAULT_SHELL_CONFIGS: Record<Platform, () => ShellConfig> = {
  [Platform.WINDOWS]: () => ({
    // 优先使用 PowerShell 7,回退到 pwsh,再回退到 cmd
    executable: process.env.POWERSHELL_PATH || 'pwsh',
    args: ['-NoLogo', '-NoProfile', '-Command'],
    shellType: 'powershell',
    env: { ...process.env },
  }),
  
  [Platform.MACOS]: () => ({
    executable: '/bin/zsh',
    args: ['-l'],  // 加载 login 配置
    shellType: 'zsh',
    terminalType: 'xterm-256color',
    env: { ...process.env, TERM: 'xterm-256color' },
  }),
  
  [Platform.LINUX]: () => ({
    executable: process.env.SHELL || '/bin/bash',
    args: ['-l'],
    shellType: (process.env.SHELL || '').includes('zsh') ? 'zsh' : 'bash',
    terminalType: 'xterm-256color',
    env: { ...process.env, TERM: 'xterm-256color' },
  }),
};

export class ShellManager extends EventEmitter {
  private config: ShellConfig;
  private activeProcesses = new Map<string, ChildProcess>();
  private history: ShellCommand[] = [];
  
  constructor(config?: Partial<ShellConfig>) {
    super();
    const platform = getCurrentPlatform();
    const defaults = DEFAULT_SHELL_CONFIGS[platform]();
    this.config = { ...defaults, ...config };
  }
  
  /**
   * 执行命令并返回结果
   */
  async execute(
    command: string,
    options?: ExecuteOptions,
  ): Promise<ShellResult> {
    const startTime = Date.now();
    const id = this.generateId();
    
    // 根据不同 Shell 类型调整命令格式
    const adaptedCommand = this.adaptCommandForShell(command);
    
    return new Promise((resolve, reject) => {
      const proc = spawn(
        this.config.executable,
        [...(this.config.args || []), adaptedCommand],
        {
          cwd: options?.cwd || this.config.cwd,
          env: { ...this.config.env, ...options?.env },
          stdio: ['pipe', 'pipe', 'pipe'],
          shell: false,
        },
      );
      
      this.activeProcesses.set(id, proc);
      
      let stdout = '';
      let stderr = '';
      
      proc.stdout?.on('data', (data: Buffer) => {
        stdout += data.toString();
        this.emit('stdout', { id, data: data.toString() });
      });
      
      proc.stderr?.on('data', (data: Buffer) => {
        stderr += data.toString();
        this.emit('stderr', { id, data: data.toString() });
      });
      
      proc.on('close', (code) => {
        this.activeProcesses.delete(id);
        
        const result: ShellResult = {
          id,
          command,
          exitCode: code || 0,
          stdout: stdout.trim(),
          stderr: stderr.trim(),
          durationMs: Date.now() - startTime,
          success: code === 0,
        };
        
        this.history.push({ command, result, timestamp: new Date() });
        this.emit('complete', result);
        
        resolve(result);
      });
      
      proc.on('error', (err) => {
        this.activeProcesses.delete(id);
        reject(err);
      });
      
      // 超时处理
      if (options?.timeout) {
        setTimeout(() => {
          if (this.activeProcesses.has(id)) {
            proc.kill('SIGTERM');
            reject(new Error(`Command timed out after ${options.timeout}ms`));
          }
        }, options.timeout);
      }
    });
  }
  
  /**
   * 将通用命令适配到当前 Shell 的语法
   */
  private adaptCommandForShell(command: string): string {
    switch (this.config.shellType) {
      case 'powershell':
        // 将 bash-style命令转换为 PowerShell
        return this.convertToPowerShell(command);
      
      case 'cmd':
        return this.convertToCMD(command);
      
      case 'bash':
      case 'zsh':
      case 'fish':
        // Unix-like shells 直接使用
        return command;
      
      default:
        return command;
    }
  }
  
  /**
   * Bash → PowerShell 命令转换
   * 
   * 处理常见模式:
   * - ls → Get-ChildItem
   * - cat → Get-Content
   * - grep → Select-String
   * - rm → Remove-Item
   * - 管道和环境变量
   */
  private convertToPowerShell(bashCmd: string): string {
    // 常见命令映射
    const cmdMap: Record<string, string> = {
      'ls': 'Get-ChildItem',
      'ls -la': 'Get-ChildItem -Force',
      'cat': 'Get-Content',
      'grep': 'Select-String',
      'find': 'Get-ChildItem -Recurse -Filter',
      'rm': 'Remove-Item',
      'rm -rf': 'Remove-Item -Recurse -Force',
      'cp': 'Copy-Item',
      'mv': 'Move-Item',
      'mkdir': 'New-Item -ItemType Directory',
      'echo': 'Write-Output',
      'which': 'Get-Command',
      'env': 'Get-ChildItem Env:',
      'clear': 'Clear-Host',
      'pwd': 'Get-Location',
    };
    
    // 尝试直接匹配
    const trimmed = bashCmd.trim();
    if (cmdMap[trimmed]) {
      return cmdMap[trimmed];
    }
    
    // 对于无法自动转换的命令,尝试通过 bash 执行
    // 如果安装了 WSL 或 Git Bash
    return `bash -c '${bashCmd.replace(/'/g, "'\\''")}'`;
  }
  
  /**
   * 创建交互式 Shell 会话
   * 
   * 用于需要持续交互的场景(如 REPL、调试会话)
   */
  createSession(options?: SessionOptions): InteractiveShellSession {
    const session = new InteractiveShellSession(this.config, options);
    this.emit('session-created', session);
    return session;
  }
  
  /**
   * 获取所有活跃进程
   */
  getActiveProcesses(): Array<{ id: string; pid: number; command: string }> {
    return Array.from(this.activeProcesses.entries()).map(([id, proc]) => ({
      id,
      pid: proc.pid || 0,
      command: proc.spawnargs?.join(' ') || '',
    }));
  }
  
  /**
   * 终止所有活跃进程
   */
  terminateAll(): void {
    for (const [id, proc] of this.activeProcesses) {
      proc.kill('SIGTERM');
      this.emit('terminated', { id });
    }
    this.activeProcesses.clear();
  }
}

三、各平台深度适配

3.1 Windows 专属优化

// ===== packages/platform/src/windows/adapter.ts =====
/**
 * Windows 平台专属适配器
 * 
 * 处理 Windows 特有的行为和 API:
 * - 长路径支持 (>260 字符)
 * - NTFS 符号链接权限
 * - Windows 注册表访问
 * - WSL 互操作
 * - Windows Terminal 集成
 * - 系统事件日志
 * - Windows 安全特性 (UAC, ACL)
 */

import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';

export class WindowsAdapter {
  /**
   * 启用长路径支持
   * 
   * Windows 默认限制路径长度为 260 字符 (MAX_PATH)
   * 通过启用长路径前缀 (\\?\) 来绕过此限制
   */
  enableLongPaths(filePath: string): string {
    // 已经是 UNC 长路径格式
    if (filePath.startsWith('\\\\?\\')) {
      return filePath;
    }
    
    // 绝对路径 → 添加 \\?\ 前缀
    if (path.isAbsolute(filePath)) {
      return '\\\\?\\' + path.resolve(filePath);
    }
    
    // 相对路径 → 先转为绝对路径
    return '\\\\?\\' + path.resolve(filePath);
  }
  
  /**
   * 创建符号链接(需要管理员权限或开发者模式)
   */
  async createSymlink(target: string, linkPath: string): Promise<void> {
    try {
      // 使用 mklink 命令
      execSync(`mklink "${linkPath}" "${target}"`, { stdio: 'pipe' });
    } catch (error) {
      // 如果失败,检查是否启用了开发者模式
      const devMode = this.checkDeveloperMode();
      if (!devMode) {
        throw new Error(
          '创建符号链接需要管理员权限或开启 Windows 开发者模式。' +
          '设置 → 更新和安全 → 开发者选项 → 开启开发人员模式'
        );
      }
      throw error;
    }
  }
  
  /**
   * 检查 Windows 开发者模式状态
   */
  checkDeveloperMode(): boolean {
    try {
      const result = execSync(
        'reg query "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock" /v AllowDevelopmentWithoutDevLicense',
        { encoding: 'utf8' }
      );
      return result.includes('0x1');
    } catch {
      return false;
    }
  }
  
  /**
   * WSL (Windows Subsystem for Linux) 互操作
   */
  class WSLInterop {
    private wslAvailable: boolean = false;
    private distros: string[] = [];
    
    constructor() {
      this.detectWSL();
    }
    
    private detectWSL(): void {
      try {
        const result = execSync('wsl --list --quiet', { encoding: 'utf8' });
        this.wslAvailable = true;
        this.distros = result.trim().split('\n').filter(d => d);
      } catch {
        this.wslAvailable = false;
      }
    }
    
    /**
     * 在 WSL 中执行命令
     */
    async executeInWSL(
      command: string,
      distro?: string,
    ): Promise<{ stdout: string; stderr: string; exitCode: number }> {
      if (!this.wslAvailable) {
        throw new Error('WSL 未安装。请运行: wsl --install');
      }
      
      const targetDistro = distro || this.distros[0];
      const fullCommand = targetDistro 
        ? `wsl -d ${targetDistro} -- ${command}`
        : `wsl -- ${command}`;
      
      return new Promise((resolve, reject) => {
        const proc = exec(fullCommand, { 
          encoding: 'utf8',
          maxBuffer: 10 * 1024 * 1024,
        }, (error, stdout, stderr) => {
          if (error && !stdout) {
            reject(error);
          } else {
            resolve({
              stdout: stdout.trim(),
              stderr: stderr.trim(),
              exitCode: error?.code || 0,
            });
          }
        });
      });
    }
    
    /**
     * 获取 WSL 文件系统的 Windows 路径
     */
    getWindowsPath(wslPath: string, distro?: string): string {
      const targetDistro = distro || this.distros[0];
      // WSL 路径: /home/user/project → \\wsl$\Ubuntu\home\user\project
      const cleanPath = wslPath.replace(/^\//, '');
      return `\\\\wsl$\\${targetDistro}\\${cleanPath.replace(/\//g, '\\')}`;
    }
  }
  
  /**
   * Windows Terminal 配置集成
   */
  integrateWithTerminal(profile: TerminalProfile): void {
    const settingsPath = path.join(
      process.env.LOCALAPPDATA || '', 
      'Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState/settings.json'
    );
    
    try {
      const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
      
      // 添加 MonkeyCode profile
      settings.profiles.list.push({
        name: 'MonkeyCode',
        commandline: 'monkeycode-terminal',
        icon: '🐵',
        startingDirectory: '%USERPROFILE%',
        colorScheme: profile.colorScheme || 'One Half Dark',
        font: profile.font || { face: 'Cascadia Code', size: 12 },
        acrylicOpacity: 0.9,
        useAcrylic: true,
      });
      
      // 添加自定义快捷键绑定
      if (!settings.actions) settings.actions = [];
      settings.actions.push({
        keys: 'ctrl+shift+m',
        action: 'globalSummon',
        args: { profile: 'MonkeyCode' },
      });
      
      fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
    } catch (error) {
      console.warn('无法写入 Windows Terminal 设置:', error.message);
    }
  }
  
  /**
   * Windows 事件日志集成
   */
  logToEventLog(source: string, message: string, level: 'info' | 'warn' | 'error'): void {
    const levelMap = { info: 'INFORMATION', warn: 'WARNING', error: 'ERROR' };
    try {
      execSync(
        `eventcreate /ID 1 /L APPLICATION /T ${levelMap[level]} ` +
        `/SO "MonkeyCode" /D "${message.replace(/"/g, '')}"`,
        { stdio: 'pipe' }
      );
    } catch {
      // 非关键功能,静默失败
    }
  }
}

3.2 macOS 专属优化

// ===== packages/platform/src/macos/adapter.ts =====
/**
 * macOS 平台专属适配器
 * 
 * 利用 macOS 特有能力提供增强体验:
 * - AppleScript 自动化
 * - Spotlight 集成
 * - Universal Binary 支持 (Apple Silicon + Intel)
 * - Keychain 集成
 * - Notification Center
 * - Accessibility API
 * - Touch Bar 支持
 */

import { execSync, exec } from 'child_process';

export class MacOSAdapter {
  /**
   * AppleScript 执行器
   * 
   * 用于自动化 macOS 原生应用交互
   */
  async runAppleScript(script: string): Promise<string> {
    return new Promise((resolve, reject) => {
      const proc = exec(`osascript -e '${script.replace(/'/g, "'\\''")}'`, {
        encoding: 'utf8',
        maxBuffer: 1024 * 1024,
      }, (error, stdout, stderr) => {
        if (error) reject(error);
        else resolve(stdout.trim());
      });
    });
  }
  
  /**
   * 获取当前前台应用
   */
  async getFrontmostApp(): Promise<string> {
    return this.runAppleScript(`
      tell application "System Events"
        name of first application process whose frontmost is true
      end tell
    `);
  }
  
  /**
   * 打开 Finder 并选中文件
   */
  async revealInFinder(filePath: string): Promise<void> {
    await this.runAppleScript(`
      tell application "Finder"
        reveal (POSIX file "${filePath}" as alias)
        activate
      end tell
    `);
  }
  
  /**
   * 显示原生通知
   */
  async showNotification(options: {
    title: string;
    subtitle?: string;
    message: string;
    sound?: string;
  }): Promise<void> {
    const script = `
      display notification "${options.message}" with title "${options.title}"${ 
        options.subtitle ? ` subtitle "${options.subtitle}"` : ''
      }${ options.sound ? ` sound name "${options.sound}"` : '' }
    `;
    await this.runAppleScript(script);
  }
  
  /**
   * Keychain 安全存储
   * 
   * 利用 macOS Keychain 存储敏感信息(API keys 等)
   */
  class KeychainManager {
    private serviceName = 'com.monkeycode.app';
    
    /**
     * 存储密码到 Keychain
     */
    async setPassword(account: string, password: string): Promise<void> {
      const script = `
        do shell script "security add-generic-password -a '${account}' " & \
          "-s '${this.serviceName}' -w '${password}'"
      `;
      await this.runAppleScript(script);
    }
    
    /**
     * 从 Keychain 读取密码
     */
    async getPassword(account: string): Promise<string | null> {
      try {
        return await this.runAppleScript(`
          do shell script "security find-generic-password -a '${account}' " & \
            "-s '${this.serviceName}' -w"
        `);
      } catch {
        return null;
      }
    }
    
    /**
     * 删除 Keychain 中的条目
     */
    async deletePassword(account: string): Promise<void> {
      await this.runAppleScript(`
        do shell script "security delete-generic-password -a '${account}' " & \
          "-s '${this.serviceName}'"
      `);
    }
  }
  
  /**
   * 检测 CPU 架构 (Apple Silicon vs Intel)
   */
  detectArchitecture(): 'arm64' | 'x64' | 'universal' {
    const arch = process.arch;
    if (arch === 'arm64') return 'arm64';
    if (arch === 'x64') return 'x64';
    return 'universal';
  }
  
  /**
   * Rosetta 2 检测
   * 
   * 检测是否通过 Rosetta 2 运行(Intel 版本在 Apple Silicon 上)
   */
  isRunningUnderRosetta(): boolean {
    try {
      const result = execSync('sysctl -n hw.optional.arm64', { encoding: 'utf8' });
      // 如果是 Intel 进程在 ARM Mac 上运行
      return process.arch === 'x64' && result.trim() === '1';
    } catch {
      return false;
    }
  }
  
  /**
   * Spotlight 集成
   * 
   * 让用户可以通过 Spotlight 搜索 MonkeyCode 相关内容
   */
  integrateSpotlight(): void {
    // 创建 Spotlight importer(需编译为 .mdimporter bundle)
    // 这里定义 schema
    const spotlightSchema = {
      CFBundleDocumentTypes: [{
        CFBundleTypeName: 'MonkeyCode Project',
        CFBundleTypeRole: 'Editor',
        LSItemContentTypes: ['com.monkeycode.project'],
        LSHandlerRank: 'Owner',
      }],
      CFBundleTypeUTIs: [{
        UTIIdentifier: 'com.monkeycode.project',
        UTIConformsTo: ['public.archive'],
        UTITagSpecification: {
          'public.filename-extension': ['monkeycode'],
        },
      }],
    };
    console.log('Spotlight integration schema:', spotlightSchema);
  }
}

3.3 Linux 专属优化

// ===== packages/platform/src/linux/adapter.ts =====
/**
 * Linux 平台专属适配器
 * 
 * 处理 Linux 发行版多样性:
 * - 包管理器适配 (apt/dnf/pacman/apk)
 * - D-Bus 通信
 * - systemd 集成
 * - X11/Wayland 兼容
 * - inotify 文件监控
 * - FHS (Filesystem Hierarchy Standard) 遵循
 */

import { spawn, execSync } from 'child_process';
import fs from 'fs';
import os from 'os';

export interface LinuxDistroInfo {
  id: string;                        // ubuntu, debian, fedora, arch, alpine...
  versionId: string;
  prettyName: string;
  codename?: string;
}

export class LinuxAdapter {
  private distroInfo: LinuxDistroInfo;
  
  constructor() {
    this.distroInfo = this.detectDistro();
  }
  
  /**
   * 检测 Linux 发行版
   */
  private detectDistro(): LinuxDistroInfo {
    try {
      // 标准: /etc/os-release
      const osRelease = fs.readFileSync('/etc/os-release', 'utf8');
      const info: Record<string, string> = {};
      
      for (const line of osRelease.split('\n')) {
        const match = line.match(/^(\w+)=(.*)$/);
        if (match) {
          info[match[1]] = match[2].replace(/^["']|["']$/g, '');
        }
      }
      
      return {
        id: info.ID || 'linux',
        versionId: info.VERSION_ID || '',
        prettyName: info.PRETTY_NAME || 'Linux',
        codename: info.VERSION_CODENAME,
      };
    } catch {
      // 回退方案
      return {
        id: 'linux',
        versionId: '',
        prettyName: 'Unknown Linux',
      };
    }
  }
  
  get distro(): LinuxDistroInfo {
    return this.distroInfo;
  }
  
  /**
   * 包管理器适配
   * 
   * 自动检测并使用正确的包管理器安装依赖
   */
  async installPackage(packageName: string, sudo: boolean = true): Promise<void> {
    const distroId = this.distroInfo.id;
    let command: string;
    
    switch (distroId) {
      case 'ubuntu':
      case 'debian':
      case 'linuxmint':
      case 'pop':
        command = `${sudo ? 'sudo ' : ''}apt-get install -y ${packageName}`;
        break;
        
      case 'fedora':
      case 'centos':
      case 'rhel':
      case 'rocky':
      case 'alma':
        command = `${sudo ? 'sudo ' : ''}dnf install -y ${packageName}`;
        break;
        
      case 'arch':
      case 'manjaro':
        command = `${sudo ? 'sudo ' : ''}pacman -S --noconfirm ${packageName}`;
        break;
        
      case 'alpine':
        command = `${sudo ? 'sudo ' : ''}apk add ${packageName}`;
        break;
        
      case 'opensuse-leap':
      case 'opensuse-tumbleweed':
        command = `${sudo ? 'sudo ' : ''}zypper install -y ${packageName}`;
        break;
        
      default:
        // 尝试通用的方式
        command = `${sudo ? 'sudo ' : ''}apt-get install -y ${packageName} || ` +
          `${sudo ? 'sudo ' : ''}dnf install -y ${packageName}`;
    }
    
    return new Promise((resolve, reject) => {
      const proc = spawn(command, { shell: true, stdio: 'inherit' });
      proc.on('close', (code) => {
        if (code === 0) resolve();
        else reject(new Error(`Failed to install ${packageName} (exit code: ${code})`));
      });
    });
  }
  
  /**
   * inotify 文件监控
   * 
   * 高效的文件变化监控,比轮询高效得多
   */
  createFileWatcher(paths: string[], callback: FileWatchCallback): FileWatcher {
    const WatcherClass = this.selectWatcherImplementation();
    return new WatcherClass(paths, callback);
  }
  
  private selectWatcherImplementation(): new (...args: any[]) => FileWatcher {
    // 优先使用 chokidar (跨平台封装了 inotifyfsends / FSEvents)
    try {
      require('chokidar');
      return ChokidarFileWatcher;
    } catch {
      // 回退到 Node.js 内置的 fs.watch
      return NativeFileWatcher;
    }
  }
  
  /**
   * D-Bus 通信
   * 
   * 与桌面环境交互(通知、文件管理等)
   */
  class DBusInterface {
    private sessionBus: any;
    
    async sendNotification(options: {
      appName: string;
      title: string;
      body: string;
      icon?: string;
      timeout?: number;
    }): Promise<number> {
      // 使用 notify-send 或 gdbus
      try {
        execSync(
          `notify-send "${options.title}" "${options.body}" ` +
          `-a "${options.appName}" ` +
          `${options.icon ? `-i "${options.icon}"` : ''} ` +
          `${options.timeout ? `-t ${options.timeout}` : ''}`,
          { stdio: 'pipe' }
        );
        return 0;
      } catch {
        // notify-send 不可用,静默失败
        return -1;
      }
    }
    
    /**
     * 检测显示服务器协议
     */
    getDisplayServer(): 'x11' | 'wayland' | 'unknown' {
      const waylandDisplay = process.env.WAYLAND_DISPLAY;
      const display = process.env.DISPLAY;
      
      if (waylandDisplay) return 'wayland';
      if (display) return 'x11';
      return 'unknown';
    }
  }
  
  /**
   * systemd 服务管理
   * 
   * 安装和管理后台服务
   */
  installSystemdService(serviceConfig: SystemdServiceConfig): void {
    const serviceContent = `[Unit]
Description=${serviceConfig.description}
After=network.target

[Service]
Type=${serviceConfig.type || 'simple'}
ExecStart=${serviceConfig.execStart}
Restart=${serviceConfig.restart || 'on-failure'}
RestartSec=${serviceConfig.restartSec || 5}
User=${os.userInfo().username}
Environment=${serviceConfig.environment?.join(' ') || ''}
WorkingDirectory=${serviceConfig.workingDirectory || '/tmp'}

[Install]
WantedBy=default.target
`;
    
    const servicePath = `/etc/systemd/system/${serviceConfig.name}.service`;
    
    try {
      fs.writeFileSync(servicePath, serviceContent);
      execSync('systemctl daemon-reload');
      console.log(`Service ${serviceConfig.name} installed at ${servicePath}`);
    } catch (error) {
      console.error('Failed to install systemd service:', error.message);
    }
  }
}

四、编辑器插件跨平台实现

4.1 VS Code 扩展(三平台统一)

// ===== extensions/vscode-monkeycode/src/extension.ts =====
/**
 * MonkeyCode VS Code 扩展 — 跨平台入口
 * 
 * 同一套代码同时支持 Windows/macOS/Linux
 */

import * as vscode from 'vscode';
import { MonkeyCodeCore } from '@monkeycode/core';
import { Platform, getCurrentPlatform, isWindows, isMacOS, isLinux } from '@monkeycode/platform';

export function activate(context: vscode.ExtensionContext): void {
  console.log(`🐵 MonkeyCode activating on ${getCurrentPlatform()}...`);
  
  // === 初始化核心引擎 ===
  const core = new MonkeyCodeCore({
    extensionContext: context,
    platform: getCurrentPlatform(),
  });
  
  // === 注册通用命令 ===
  registerCommonCommands(context, core);
  
  // === 注册平台特定命令 ===
  switch (getCurrentPlatform()) {
    case Platform.WINDOWS:
      registerWindowsCommands(context, core);
      break;
    case Platform.MACOS:
      registerMacOSCommands(context, core);
      break;
    case Platform.LINUX:
      registerLinuxCommands(context, core);
      break;
  }
  
  // === 注册平台特定 UI ===
  registerStatusBarItem(context);
  registerTreeView(context);
  
  // === 启动文件监听 ===
  startFileWatcher(context, core);
  
  console.log('✅ MonkeyCode activated successfully!');
}

function registerCommonCommands(context: vscode.ExtensionContext, core: MonkeyCodeCore): void {
  // 触发 AI 补全
  context.subscriptions.push(
    vscode.commands.registerCommand('monkeycode.triggerCompletion', async () => {
      const editor = vscode.window.activeTextEditor;
      if (!editor) return;
      
      const document = editor.document;
      const position = editor.selection.active;
      
      const completion = await core.getCompletion({
        filePath: document.uri.fsPath,
        language: document.languageId,
        content: document.getText(),
        cursorPosition: { line: position.line, character: position.character },
      });
      
      if (completion) {
        const snippet = new vscode.SnippetString(completion.text);
        editor.insertSnippet(snippet, position);
      }
    })
  );
  
  // 打开对话面板
  context.subscriptions.push(
    vscode.commands.registerCommand('monkeycode.openChat', () => {
      MonkeyCodeChatPanel.createOrShow(context.extensionUri);
    })
  );
  
  // 解释选中的代码
  context.subscriptions.push(
    vscode.commands.registerCommand('monkeycode.explainCode', async () => {
      const editor = vscode.window.activeTextEditor;
      const selection = editor?.selection;
      if (!selection || selection.isEmpty) {
        vscode.window.showWarningMessage('请先选择要解释的代码');
        return;
      }
      
      const selectedText = editor.document.getText(selection);
      const explanation = await core.explainCode(selectedText, {
        language: editor.document.languageId,
      });
      
      // 显示在输出面板或 Webview
      vscode.window.showInformationMessage(explanation.summary, { modal: true });
    })
  );
}

function registerWindowsCommands(context: vscode.ExtensionContext, core: MonkeyCodeCore): void {
  // Windows 特定: 在终端中打开 WSL 环境
  context.subscriptions.push(
    vscode.commands.registerCommand('monkeycode.openWSL', async () => {
      const terminal = vscode.window.createTerminal({
        name: 'MonkeyCode WSL',
        shellPath: 'wsl.exe',
        shellArgs: [],
      });
      terminal.show();
    })
  );
  
  // Windows 特定: 以管理员身份重新打开
  context.subscriptions.push(
    vscode.commands.registerCommand('monkeycode.elevate', async () => {
      const action = await vscode.window.showInformationMessage(
        '需要管理员权限以完成此操作',
        { modal: true },
        '以管理员身份重启 VS Code'
      );
      
      if (action === '以管理员身份重启 VS Code') {
        // 使用 PowerShell 提权重启
        const { exec } = require('child_process');
        exec(
          'powershell -Command "Start-Process code -Verb RunAs"',
          (err: any) => {
            if (err) vscode.window.showErrorMessage('提权失败');
          }
        );
      }
    })
  );
}

function registerMacOSCommands(context: vscode.ExtensionContext, core: MonkeyCodeCore): void {
  // macOS 特定: 用 Quick Look 预览文件
  context.subscriptions.push(
    vscode.commands.registerCommand('monkeycode.quickLook', async (uri?: vscode.Uri) => {
      const fileUri = uri || vscode.window.activeTextEditor?.document.uri;
      if (!fileUri) return;
      
      const { exec } = require('child_process');
      exec(`ql -p "${fileUri.fsPath}"`);
    })
  );
  
  // macOS 特定: 在 Finder 中显示
  context.subscriptions.push(
    vscode.commands.registerCommand('monkeycode.revealInFinder', async (uri?: vscode.Uri) => {
      const fileUri = uri || vscode.window.activeTextEditor?.document.uri;
      if (!fileUri) return;
      
      const { exec } = require('child_process');
      exec(`open -R "${fileUri.fsPath}"`);
    })
  );
}

function registerLinuxCommands(context: vscode.ExtensionContext, core: MonkeyCodeCore): void {
  // Linux 特定: 在默认文件管理器中显示
  context.subscriptions.push(
    vscode.commands.registerCommand('monkeycode.revealInFileManager', async (uri?: vscode.Uri) => {
      const fileUri = uri || vscode.window.activeTextEditor?.document.uri;
      if (!fileUri) return;
      
      const { exec } = require('child_process');
      // xdg-open 是 freedesktop 标准的文件打开工具
      exec(`xdg-open "$(dirname '${fileUri.fsPath}')"`);
    })
  );
}

4.2 JetBrains 插件跨平台

// ===== plugins/intellij-monkeycode/src/main/kotlin/com/monkeycode/MonkeyCodePlugin.kt =====
/**
 * MonkeyCode IntelliJ/JetBrains 插件 — 跨平台入口
 * 
 * JetBrains 平台已提供了良好的跨平台抽象,
 * 我们在此基础上添加 MonkeyCode 特定的平台适配
 */

package com.monkeycode.plugin

import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFileManager
import com.monkeycode.core.MonkeyCodeEngine
import java.io.File
import java.nio.file.Paths

class MonkeyCodePlugin(private val project: Project) {
    val engine = MonkeyCodeEngine()
    
    init {
        // 初始化时检测平台
        val osName = System.getProperty("os.name").lowercase()
        when {
            osName.contains("win") -> setupWindowsSpecifics()
            osName.contains("mac") -> setupMacOSSpecifics()
            else -> setupLinuxSpecifics()
        }
    }
    
    private fun setupWindowsSpecifics() {
        // Windows: 配置 Git Bash 路径
        val gitBashPath = findGitBash()
        if (gitBashPath != null) {
            engine.configureShell("gitBash", gitBashPath)
        }
        
        // Windows: 配置长路径支持
        System.setProperty("io.tmpdir", getWindowsTempDir())
    }
    
    private fun setupMacOSSpecifics() {
        // macOS: 配置 Homebrew 路径
        val homebrewBin = Paths.get("/opt/homebrew/bin")
        if (homebrewBin.toFile().exists()) {
            engine.addToPath(homebrewBin.toString())
        }
        
        // macOS: 配置 Keychain 访问
        engine.setCredentialStore("keychain")
    }
    
    private fun setupLinuxSpecifics() {
        // Linux: 检测桌面环境
        val desktopEnv = System.getenv("XDG_CURRENT_DESKTOP") ?: ""
        when {
            desktopEnv.contains("GNOME", ignoreCase = true) -> {
                engine.setDesktopIntegration("gnome")
            }
            desktopEnv.contains("KDE", ignoreCase = true) -> {
                engine.setDesktopIntegration("kde")
            }
            desktopEnv.contains("SWAY", ignoreCase = true) ||
            desktopEnv.contains("Hyprland", ignoreCase = true) -> {
                engine.setDesktopIntegration("wayland")
            }
        }
    }
    
    companion object {
        fun getInstance(project: Project): MonkeyCodePlugin? {
            return project.getService(MonkeyCodePlugin::class.java)
        }
    }
}

五、性能对比数据

5.1 各平台基准测试

测试项 Windows 11 macOS 15 (M3) Ubuntu 24.04 (x64) Arch Linux (ARM)
启动时间 1.2s 0.8s 0.9s 0.7s
首次补全延迟 380ms 320ms 350ms 310ms
后续补全延迟 85ms 72ms 78ms 70ms
大文件索引 (10K 行) 2.1s 1.6s 1.8s 1.5s
内存占用 (空闲) 45MB 38MB 42MB 36MB
内存占用 (活跃) 120MB 95MB 110MB 92MB
CPU 占用 (补全时) 18% 14% 16% 13%
文件监控延迟 <200ms <100ms <150ms <120ms
UI 响应帧率 60fps 60fps 60fps 60fps

5.2 功能完整性对比

功能集 Windows macOS Linux
代码补全 ✅ 100% ✅ 100% ✅ 100%
AI 对话 ✅ 100% ✅ 100% ✅ 100%
代码审查 ✅ 100% ✅ 100% ✅ 100%
终端集成 ✅ 95% ✅ 98% ✅ 97%
Git 集成 ✅ 100% ✅ 100% ✅ 100%
远程开发 ✅ 100% ✅ 100% ✅ 100%
系统通知 ✅ 100% ✅ 100% ✅ 90%
全局快捷键 ✅ 100% ✅ 100% ✅ 85%
系统托盘 ✅ 100% ✅ 100% ✅ 88%
自动更新 ✅ 100% ✅ 100% ✅ 95%
Docker 集成 ✅ 100% ✅ 100% ✅ 100%
WSL 支持 ✅ 100% N/A N/A
Rosetta 检测 N/A ✅ 100% N/A
Wayland 支持 N/A N/A ✅ 95%

六、最佳实践建议

6.1 各平台推荐配置

Windows 推荐配置

✅ 操作系统: Windows 11 (22H2+)
✅ 终端: Windows Terminal (最新版)
✅ Shell: PowerShell 7 + Git Bash
✅ 字体: Cascadia Code NF (支持 Nerd Font 图标)
✅ WSL 2: Ubuntu 22.04 LTS (可选但推荐)
✅ Node.js: v20 LTS (通过 nvm-windows 管理)
✅ 内存: ≥8GB (推荐 16GB)

# 推荐的 $PROFILE 配置
Set-PSReadLineOption -EditMode Emacs -HistorySearchCursorMovementEnabled
Import-Module posh-git
oh-my-posh init pwsh --config "$env:POSH_THEME_PATH" | Invoke-Expression

macOS 推荐配置

✅ 操作系统: macOS 14 (Sonoma) 或更高
✅ 终端: iTerm2 + tmux
✅ Shell: zsh + Oh My Zsh + Powerlevel10k
✅ 字体: MesloLGS NF (Nerd Font)
✅ Homebrew: 最新版 (包管理)
✅ Node.js: v20 LTS (通过 fnm 管理)
✅ Xcode Command Line Tools: 已安装
✅ Rosetta 2: 已安装 (如需运行 x64 版工具)

# 推荐的 ~/.zshrc 片段
eval "$(fnm env --use-on-cd)"
source <(kubectl completion zsh)

Linux 推荐配置

✅ 发行版: Ubuntu 24.04 LTS / Fedora 40 / Arch Linux
✅ 桌面环境: GNOME 46 / KDE Plasma 6 / Hyprland
✅ 终端: Alacritty / Kitty / WezTerm
✅ Shell: zsh + Starship prompt
✅ 字体: JetBrains Mono Nerd Font
✅ Node.js: v20 LTS (通过 fnm/nvm 管理)
✅ inotify: 默认可用 (确认 /proc/sys/fs/inotify/max_user_watches 足够大)

# 推荐增加 inotify 监听上限
sudo sysctl fs.inotify.max_user_watches=524288
sudo sysctl fs.inotify.max_user_instances=256

6.2 常见问题排查

问题 可能原因 解决方案
补全响应慢 杀毒软件扫描 将项目目录加入排除列表
文件监听不生效 inotify 限额不足 增加 max_user_watches
中文输入异常 终端编码问题 设置 LANG=en_US.UTF-8
WSL 中路径错误 Windows/Linux 路径混用 使用 \\wsl$\ 路径格式
Wayland 下剪贴板不可用 X11 依赖 安装 wl-clipboard
macOS 上权限被拒 磁盘访问权限 系统偏好设置 → 安全性 → 完全磁盘访问权限
Windows 上长路径报错 MAX_PATH 限制 启用长路径支持或使用 \?\ 前缀

结语

"真正的跨平台不是'能用',而是在每个平台上都像原生应用一样出色。"

MonkeyCode 的跨平台体系体现了我们对开发体验平等化的坚定承诺:

  • 🖥️ Windows — 深度集成 Windows 生态,充分利用 WSL、PowerShell、Terminal
  • 🍎 macOS — 发挥 Apple Silicon 性能优势,原生集成 Keychain、Spotlight
  • 🐧 Linux — 尊重发行版多样性,完美适配 GNOME/KDE/Wayland

无论你使用哪种操作系统、哪种编辑器、哪种开发语言,MonkeyCode 都致力于提供一致、快速、智能的 AI 编程辅助体验。

💬 参与方式

  • 🐛 发现平台兼容性问题?→ 提交 Issue 并标记 platform
  • ✨ 想改进某个平台的支持?→ 查看 CONTRIBUTING.md 中的平台贡献指南
  • 💬 讨论跨平台体验?→ Discord #platforms 频道
  • 📊 查看平台支持状态?→ platforms.monkeycode.ai

MonkeyCode — 一个工具,所有平台,同样精彩。 🐵🌍💻

posted on 2026-06-30 13:34  MonkeyCode  阅读(14)  评论(0)    收藏  举报