AIOS 的目录结构
想快速理解一个项目,看目录比读 README 更直接。这是 AIOS 顶层的样子,以及为什么这么切。
想快速理解一个项目,看目录结构比读 README 更直接。README 告诉你它想成为什么,目录告诉你它实际上是什么。AIOS 顶层就这么几个目录,但每一层的边界划得相当明确。
顶层
把 AIOS 仓库 clone 下来,顶层会看到这些目录(node_modules、dist 之类的运行态产物不算):
- server/ —— 后端,分主服务和应用服务两块
- gui/ —— 前端 Vue 3 单页应用
- apps/ —— 应用的 APP.md 文档(不放实现代码)
- language/ —— i18n 烘焙源,中英文文案在这里
- skills/ —— Agent skill 系统
- scripts/ —— 启动 / 构建 / 修复脚本
- database/ —— SQLite 数据,运行态,默认不入 git
- files/ —— 用户上传和生成的文件
- install-*.sh / install-*.ps1 —— 一键安装入口
顶层目录少,是有意为之。新手 clone 下来,30 秒就能拼出整个项目的轮廓。
server/ —— 后端是两个进程
AIOS 的后端不是一个进程,是两个,跑在不同端口:
- server/main/ —— 主服务,端口 9502。HTTP / WebSocket 入口,系统级能力(Chat、Task、Auth、LLM、Settings 等)
- server/apps/ —— 应用服务,端口 9503。每个用户态应用挂在这里
为什么要拆?因为系统级能力和用户态应用的重启代价不一样。改一个应用,只需要重启 apps 进程;系统内核相对稳定,不需要每次都跟着重启。
server/main —— 内核
主服务内部进一步分层:
- api/ —— 路由入口,只做 path/method 分发
- service/ —— 业务编排(auth, chat, task, prompt, settings, runtime)
- ai/ —— Agent 执行循环、工具调用
- llm/ —— Provider 抽象、输入归一、输出解析、流式处理
- repository/ —— SQLite 数据访问
这是经典的 api → service → repository 三段式,但多了一个 ai/ 和 llm/。前者负责「AI 怎么用工具」,后者负责「怎么和模型说话」。这两块在 chat 和 task 里都会用到,所以单独拆出来。
server/apps —— 用户态应用进程
应用服务的入口 server/apps/index.js 只有 30 行,核心逻辑就是:启动时从 registry 里 import 每个应用,运行时按 URL 前缀找对应应用,把请求转给它。整个 dispatcher 不参与业务。
每个应用是一个模块,默认导出一个对象,包含这五个字段:
- name —— 应用名
- match(path) —— 判断这条 URL 是不是属于自己
- initDb() —— 第一次启动时建表(可选)
- initRuntime() —— 启动后台循环,比如订阅箱、炒币机(可选)
- handleApi(req, res, path) —— 处理请求
模块内部按主服务的同样三段式组织:api/ + service/ + repository/。所以读完一个应用,基本能读懂全部应用。
apps/ —— 应用的「自报家门」
这个目录容易让人误解。它不放代码,只放每个应用的 APP.md 文档。每份 APP.md 都会写清楚四件事:应用名、一句话描述、后端在哪、用了哪张表。
这份文档不是写给人读的,真正的读者是 AI 自己。AIOS 在组装系统提示词时,会扫描 apps/*/APP.md,把「应用目录」完整地喂给 LLM。AI 因此一眼就知道:这个 OS 上有哪些应用、它们后端在哪、用了哪张表。
把元信息和实现分开,既让 AI 的认知接口足够薄,也让 README、代码、文档三者不会互相错位。
gui/ —— 前端
前端是一个 Vite + Vue 3 单页应用,源码全部在 gui/src/ 下:
- views/AppShell.vue —— 整个 OS 的壳,根据路由动态加载应用组件
- apps/ —— 每个应用的前端实现,目录名和 server/apps/ 一一对应
- apps.js —— 应用注册表(id / 名称 / 图标 / 颜色 / 是否有内部侧栏 / 懒加载 import)
- components/ —— 全站共用组件(QuickChat、AppsPopup、ToastHost、ReloadDialog 等)
- stores/ —— 全站状态(auth、appContext、settings 等)
- system/ —— 系统级工具(WebSocket、locale 等)
前端没有引入 Pinia 之外的状态管理,也没有运行时 i18n 库。多语言通过构建时烘焙完成,见下一节。
language/ —— 烘焙源
AIOS 不在运行时做 i18n,而是构建时把 __T_XXX__ token 一次性替换成目标语言。源码可以直接写自然语言,也可以写 token。语言资源放在 language/<locale>/ 下:
- language/zh/server/*.json —— 后端文案
- language/zh/gui/*.json —— 前端文案
- language/zh/system/*.md —— 系统永远告诉 AI 的事,比如应用开发约定
language/zh/system/app-creation-guide.md 是 AIOS 给 AI 的「内部宪法」:新建应用必须把后端放在哪、前端放在哪、APP.md 写哪些字段、改完代码怎么 reload。这份文档在每次对话开始时都会被注入 system prompt。
i18n 烘焙最初是个权宜之计,后来发现它真正的价值是给 AI 减负:AI 写新应用时,不必同步维护翻译 JSON、不必关心 t() 调用——这是会复利的认知税。
skills/ —— Agent 技能
类似 Claude Skills 的设计:每个 skill 是一个独立目录,内含 SKILL.md 描述能力和触发条件,具体实现可以是脚本或子工作流。当前内置 skill 不多,主要作为可扩展点保留。
scripts/ —— 工具链
scripts/ 下放启动、构建、修复脚本。其中两个值得展开:
- start.mjs —— 启动入口,会做语言烘焙。它有意在源码仓里做防烘焙保护,只在用户安装目录的运行副本里做语言替换,确保源码树不被 token 替换后的具体语言污染
- fix-node-pty.js —— postinstall 阶段修 node-pty 的本机编译失败,跨 macOS / Linux / Windows 自动适配
database/ 和 files/ —— 运行态
这两个目录是用户真实数据的归宿:
- database/aios.db —— 整个 OS 的状态,一个 SQLite 文件装下对话、设置、笔记、记账、应用数据
- files/ —— 用户上传的附件、AI 生成的文件,按用途归类成子目录
都不入 git。备份 OS = 拷走这两个目录;重置 OS = 删掉这两个目录。
三个端口
运行时同时起多个进程,端口固定为约定值:
- 9502 —— 主服务
- 9503 —— 应用服务
- 5173 —— Vite dev server(只在 npm run dev 时存在)
生产模式下没有 5173,主服务直接 serve gui/dist/。
为什么这样切
把目录划法和 AIOS 想做的事对照看,几条逻辑就清楚了。
一、AI 是一等公民。apps/ 只放 APP.md、language/zh/system/ 强制注入到提示词、ai/ 和 llm/ 单独拆出来——都是为了让 AI 拥有比传统用户更高优先级的认知通道。
二、应用是一等公民。server/apps/ 和 gui/src/apps/ 一一对应,每个应用自带 api+service+repository+前端组件,registry 注册即可上线。新增或删除应用不会污染主服务。
三、运行态和源码态严格分离。database / files / node_modules / gui/dist / .aios 都不入 git。源码树永远是「AI 写应用时该读到的样子」,不被语言烘焙、数据库内容、用户上传污染。
四、约定多于配置。后端三段式、前端 apps.js 注册、APP.md 自报家门、reload 三档(restartServer / restartApps / build)——这些都是约定,不是机制。AI 学会一套就能复用到所有应用。
目录结构本身就是一份契约。它告诉所有合作者——人类的、AI 的——这个项目的边界在哪,什么属于谁。
AIOS 是一个开源项目,完整源码在 GitHub: