AionUi - Cowork with Your CLI AI Agent

Version   License   Platform

GitHub Trending


✨ 项目概述

AionUi 是一个基于 Electron 框架构建的跨平台桌面应用,旨在为用户提供一个统一、可视化的图形界面,用于与多种 AI 终端代理(例如 Gemini CLI、Claude Code、Qwen Code、Goose CLI、Auggie 等)进行协作。它遵循模块化架构原则,优先考虑用户体验、本地数据安全和开发者可维护性。

✨ 核心特性

  • 多AI代理集成:支持通过标准化适配器连接多种AI终端代理,每个代理可独立管理和配置。
  • 跨平台兼容:支持 macOS、Windows 和 Linux 操作系统。
  • 模块化架构:采用桥接模式处理进程间通信,核心功能(如对话框、文件系统、会话管理)均实现为独立的、可测试的模块。
  • 可视化交互界面:提供直观的聊天式界面,支持文件拖放、多会话管理和工作区集成。
  • 本地数据安全:对话历史和设置默认存储在本地,API密钥经加密管理,未经用户明确同意不传输数据。
  • WebUI模式:可通过嵌入式Web服务器启动,允许通过浏览器远程访问应用。
  • 开发者友好:采用 TypeScript 保证类型安全,配置了 ESLint、Prettier、Jest 等工具确保代码质量和一致性,并集成了 Git Hooks 和规范化的提交信息格式。
  • 丰富的技能库(Skills):内置支持 PDF、PPTX、DOCX、XLSX 等文档的创建、编辑与分析,并提供诸如“Manus风格文件规划”、“HUMAN 3.0 发展教练”等多种工作流技能。

🚀 安装指南

环境要求

  • Node.js: (版本建议参考 package.json 中的 engines 字段)
  • npmyarn
  • 系统: macOS / Windows / Linux

安装步骤

  1. 克隆仓库

    git clone https://github.com/iOfficeAI/AionUi.git
    cd AionUi
    
  2. 安装依赖

    npm install
    

    安装过程会自动运行 postinstall 脚本,处理原生模块的安装。

  3. 开发环境运行

    npm start
    

    这会启动 Electron 应用,并运行在开发模式下(支持热重载)。

  4. 构建与打包
    项目使用 Electron Forge 和 electron-builder 进行构建。

    • 开发构建:
      npm run build
      
    • 生产环境打包:
      npm run dist
      
      特定平台或架构的打包脚本可在 package.jsonscripts 部分找到,如 dist:windist:macdist:linux

原生模块重建

项目依赖一些原生模块(如 better-sqlite3bcrypt 等)。跨架构构建或特定平台下,脚本会自动处理重建。如需手动强制重建,可设置环境变量:

FORCE_NATIVE_REBUILD=true npm run dist

📖 使用说明

基础使用

  1. 启动应用

    • 桌面模式:直接运行可执行文件或使用 npm start
    • WebUI 模式
      # Windows
      "C:\Program Files\AionUi\AionUi.exe" --webui
      # macOS
      /Applications/AionUi.app/Contents/MacOS/AionUi --webui
      
      然后通过浏览器访问 http://localhost:3000(端口可能根据配置变化)。
  2. 连接AI代理
    应用启动时会自动检测系统中已安装的、支持的AI CLI工具(如Gemini CLI, Claude Code等)。你可以在设置界面查看和管理已连接的代理。

  3. 开始协作
    在主界面的聊天窗口中输入指令,选择你想要协作的AI代理,即可开始工作。支持文件拖拽上传、多轮对话和上下文管理。

技能(Skills)使用

AionUi 集成了强大的 Cowork 模式,支持通过预定义的“技能”执行复杂任务。当你的请求中包含特定关键词时,对应的技能会被自动激活。

例如

  • 处理 PDF 文件时,可使用 pdf 技能进行合并、拆分、填写表单或转换图片。
  • 需要创建 PPT 时,可使用 pptx 技能,遵循 html2pptx.jspptxgenjs 的工作流生成幻灯片。
  • 需要进行复杂项目规划时,可激活 “Planning with Files - Manus风格文件规划” 技能,它将引导你创建 task_plan.mdfindings.mdprogress.md 文件来系统化管理任务。

开发与配置

  • 代码风格:项目使用 ESLint 和 Prettier。在提交代码前,Git Hooks 会自动运行检查和格式化。

    # 手动运行代码检查
    npm run lint
    # 自动修复可修复的问题
    npm run lint:fix
    # 格式化代码
    npm run format
    
  • 环境变量:可以通过环境变量覆盖开发服务器端口,例如:

    AIONUI_DEV_PORT=4000 npm start
    

💻 核心代码

1. 主进程入口 (main.ts)

此文件是 Electron 应用的主进程入口,负责初始化应用、创建浏览器窗口、设置全局错误处理、启动 Web 服务器和初始化与 AI 代理的通信桥梁。

/**
 * @license
 * Copyright 2025 AionUi (aionui.com)
 * SPDX-License-Identifier: Apache-2.0
 */
import './utils/configureChromium';
import { app, BrowserWindow, screen } from 'electron';
import fixPath from 'fix-path';
import * as fs from 'fs';
import * as path from 'path';
import { initMainAdapterWithWindow } from './adapter/main'; // 初始化主进程与渲染进程的通信桥梁
import { ipcBridge } from './common';
import { initializeProcess } from './process';
import { initializeAcpDetector } from './process/bridge'; // 初始化 ACP (AI 代理协议) 检测器
import { registerWindowMaximizeListeners } from './process/bridge/windowControlsBridge';
import WorkerManage from './process/WorkerManage';
import { startWebServer } from './webserver'; // 启动 WebUI 模式的服务器
import { SERVER_CONFIG } from './webserver/config/constants';
import { applyZoomToWindow } from './process/utils/zoom';
// 处理 Windows 安装器事件
import electronSquirrelStartup from 'electron-squirrel-startup';

// 修复 macOS/Linux 下 GUI 应用的 PATH 环境变量
if (process.platform === 'darwin' || process.platform === 'linux') {
  fixPath();
}

// 处理 Squirrel 启动事件 (Windows)
if (electronSquirrelStartup) {
  app.quit();
}

// 全局未捕获异常处理器
process.on('uncaughtException', (_error) => {
  // 生产环境可记录日志或上报
  if (process.env.NODE_ENV !== 'development') {
    // TODO: 添加错误处理逻辑
  }
});

// 应用准备就绪后创建窗口
app.whenReady().then(() => {
  createWindow();
  initializeAcpDetector(); // 检测可用的 AI 代理
  // ... 其他初始化逻辑
});

// 创建浏览器窗口函数
function createWindow(): void {
  const { width, height } = screen.getPrimaryDisplay().workAreaSize;
  const mainWindow = new BrowserWindow({
    width: Math.min(1440, width),
    height: Math.min(900, height),
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'), // 预加载脚本
      contextIsolation: true,
      nodeIntegration: false,
    },
  });
  // 加载 Webpack 打包的入口
  mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
  // 初始化与该窗口的通信桥梁
  initMainAdapterWithWindow(mainWindow);
}

2. 预加载脚本 (preload.ts)

预加载脚本在渲染进程加载网页之前运行,负责将安全的、受控的 Electron API 暴露给渲染进程,是实现进程间通信(IPC)的关键。

/**
 * @license
 * Copyright 2025 AionUi (aionui.com)
 * SPDX-License-Identifier: Apache-2.0
 */
import { contextBridge, ipcRenderer, webUtils } from 'electron';
import { ADAPTER_BRIDGE_EVENT_KEY } from './adapter/constant';

// 将 `electronAPI` 对象安全地暴露给渲染进程的 window 对象
contextBridge.exposeInMainWorld('electronAPI', {
  // 发送消息到主进程
  emit: (name: string, data: any) => {
    return ipcRenderer
      .invoke(
        ADAPTER_BRIDGE_EVENT_KEY,
        JSON.stringify({
          name: name,
          data: data,
        })
      )
      .catch((error) => {
        console.error('IPC invoke error:', error);
        throw error;
      });
  },
  // 监听来自主进程的消息
  on: (callback: any) => {
    const handler = (event: any, value: any) => {
      callback({ event, value });
    };
    ipcRenderer.on(ADAPTER_BRIDGE_EVENT_KEY, handler);
    return () => {
      ipcRenderer.off(ADAPTER_BRIDGE_EVENT_KEY, handler);
    };
  },
  // 获取拖拽文件的绝对路径(用于 WebUI 模式下的文件选择)
  getPathForFile: (file: File) => webUtils.getPathForFile(file),
});

3. AI代理连接与适配器 (AcpConnection.ts)

此类负责与支持 ACP (AI 代理协议) 的外部 CLI 工具建立连接、管理 JSON-RPC 通信和处理权限请求,是集成多种 AI 代理的核心。

/**
 * @license
 * Copyright 2025 AionUi (aionui.com)
 * SPDX-License-Identifier: Apache-2.0
 */
import type { AcpBackend, AcpIncomingMessage, AcpMessage, AcpNotification, AcpPermissionRequest, AcpRequest, AcpResponse, AcpSessionUpdate } from '@/types/acpTypes';
import { ACP_METHODS, JSONRPC_VERSION } from '@/types/acpTypes';
import type { ChildProcess, SpawnOptions } from 'child_process';
import { spawn } from 'child_process';
import { promises as fs } from 'fs';
import path from 'path';

interface PendingRequest<T = unknown> {
  resolve: (value: T) => void;
  reject: (error: Error) => void;
  timeoutId?: NodeJS.Timeout;
  method: string;
  isPaused: boolean;
  startTime: number;
  timeoutDuration: number;
}

/**
 * 创建用于生成 ACP CLI 进程的配置。
 */
export function createGenericSpawnConfig(cliPath: string, workingDir: string, acpArgs?: string[], customEnv?: Record<string, string>) {
  const isWindows = process.platform === 'win32';
  const env = { ...process.env, ...customEnv };
  const effectiveAcpArgs = acpArgs && acpArgs.length > 0 ? acpArgs : ['--experimental-acp'];

  let spawnCommand: string;
  let spawnArgs: string[];

  if (cliPath.startsWith('npx ')) {
    // 处理 npm 包命令
    const parts = cliPath.split(' ');
    spawnCommand = isWindows ? 'npx.cmd' : 'npx';
    spawnArgs = [...parts.slice(1), ...effectiveAcpArgs];
  } else {
    // 处理直接路径或简单命令
    spawnCommand = cliPath;
    spawnArgs = effectiveAcpArgs;
  }

  const options: SpawnOptions = {
    cwd: workingDir,
    env,
    stdio: ['pipe', 'pipe', 'pipe'], // 标准输入、输出、错误流
  };

  return { spawnCommand, spawnArgs, options };
}

/**
 * ACP 连接管理类。
 */
export class AcpConnection {
  private child: ChildProcess | null = null;
  private nextId = 1;
  private pendingRequests = new Map<number | string, PendingRequest>();
  // ... 其他私有属性和方法

  /**
   * 启动 ACP 会话。
   */
  async startSession(cliPath: string, workingDir: string, acpArgs?: string[], customEnv?: Record<string, string>): Promise<void> {
    const { spawnCommand, spawnArgs, options } = createGenericSpawnConfig(cliPath, workingDir, acpArgs, customEnv);
    
    return new Promise((resolve, reject) => {
      this.child = spawn(spawnCommand, spawnArgs, options);

      this.child.stdout?.on('data', (data) => {
        this.handleStdout(data.toString());
      });

      this.child.stderr?.on('data', (data) => {
        console.error(`[ACP STDERR] ${data.toString()}`);
      });

      this.child.on('error', (error) => {
        reject(new Error(`Failed to spawn ACP process: ${error.message}`));
      });

      this.child.on('close', (code) => {
        const message = `ACP process exited with code ${code}`;
        this.rejectAllPendingRequests(new Error(message));
      });

      // 发送初始化请求以验证连接
      this.sendRequest('initialize', {
        protocolVersion: '0.1.0',
        capabilities: { tools: [] },
      })
        .then(resolve)
        .catch(reject);
    });
  }

  /**
   * 发送 JSON-RPC 请求。
   */
  private sendRequest(method: string, params?: unknown, timeoutMs = 30000): Promise<unknown> {
    return new Promise((resolve, reject) => {
      const id = this.nextId++;
      const request: AcpRequest = {
        jsonrpc: JSONRPC_VERSION,
        id,
        method,
        params,
      };

      const timeoutId = setTimeout(() => {
        this.pendingRequests.delete(id);
        reject(new Error(`Request ${method} timed out after ${timeoutMs}ms`));
      }, timeoutMs);

      this.pendingRequests.set(id, {
        resolve,
        reject,
        timeoutId,
        method,
        isPaused: false,
        startTime: Date.now(),
        timeoutDuration: timeoutMs,
      });

      if (this.child && this.child.stdin && !this.child.stdin.destroyed) {
        this.child.stdin.write(JSON.stringify(request) + '\n');
      } else {
        reject(new Error('Process stdin is not available'));
      }
    });
  }

  /**
   * 处理来自子进程标准输出的数据(解析 JSON-RPC 消息)。
   */
  private handleStdout(data: string): void {
    try {
      const message = JSON.parse(data) as AcpIncomingMessage;
      if ('id' in message && message.id !== undefined) {
        // 处理响应
        this.handleResponse(message as AcpResponse);
      } else if ('method' in message) {
        // 处理通知或请求(如权限请求)
        this.handleNotification(message as AcpNotification);
      }
    } catch (error) {
      console.warn('[ACP] Failed to parse stdout:', data, error);
    }
  }

  // ... 处理响应、通知、权限请求等其他方法
}

XfkHivgPackSeBYb8h6edGRK+FQI9gdrv8FBRI2tlQs=
更多精彩内容 请关注我的个人公众号 公众号(办公AI智能小助手)
对网络安全、黑客技术感兴趣的朋友可以关注我的安全公众号(网络安全技术点滴分享)

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐