Files
2026-07-15 18:01:56 +08:00

7.5 KiB
Raw Permalink Blame History

Codex 插件市场客户端 —— 开发契约(唯一事实来源)

Electron + Node。三名开发者按本文件分工,接口/数据结构/文件归属必须严格遵守,避免联调对不上。

分工与文件归属(互不重叠)

  • 负责人(已完成):package.json、electron/preload.js、本契约、build/ 图标。
  • 开发 A(Node 后端逻辑):electron/main.js + electron/lib/*.js。
  • 开发 B(前端 UI):renderer/index.html、renderer/styles.css、renderer/app.js。
  • 开发 C(打包/跨平台):electron-builder.yml、README.md、scripts/(如需)。

任何人不得修改不属于自己的文件。preload.js 是契约边界,已冻结。

常量(写死在 electron/main.js 或 lib,前端不可见)

MARKET_URL = "https://sc.tokendun.cc"
MARKET_KEY = "2e8e96d10ea0c223cc5cc6439df00a97bb4543ca58a1e5be"

市场地址与密钥绝不能出现在 renderer 前端代码里,只能在主进程。

数据结构

// 本机/仓库里的一个可备份/可还原项
type Item = {
  category: "插件 Plugin" | "技能 Skill",
  source: string,   // 如 openai-primary-runtime;技能为 "skills"
  name: string,
  rel: string,      // 相对 ~/.codex,如 plugins/cache/openai-primary-runtime/presentations
  size: number      // 字节
}

// 市场条目
type Entry = {
  id?: number, name: string, source: string, category: string,
  rel: string, repo: string, branch: string,
  web_url: string | null, size: number,
  description: string | null, published_at?: string
}

前端可调用的 API(preload 已暴露为 window.api)

全部返回 Promise。出错时 reject(前端用 try/catch 显示错误)。

方法 入参 返回 说明
api.scanLocal() — Item[] 扫描本机 ~/.codex 的插件+技能
api.pickDir() — string | null 弹系统选目录框
api.packLocal(rels, destDir) string[], string {path:string} 打包选中项到本地目录
api.upload(rels, repo, token, isPrivate) string[],string,string,boolean {owner:string, published:number} 打包→推 GitHub→自动发布市场
api.marketList() — Entry[] 拉官方市场列表
api.marketInstall(entry) Entry {ok:true} 从市场下载并装进 ~/.codex+启用
api.ghPull(repoInput, token) string,string Item[] clone 指定仓库并列出可还原项
api.ghRestore(rels, enable) string[],boolean {ok:true} 把上次 pull 的选中项还原到 ~/.codex
api.getSecret(name) "github"|"registry" string 取已存密钥(无则空串)
api.setSecret(name, value) string,string {ok:true} 存密钥(safeStorage 加密)
api.openExternal(url) string — 系统浏览器打开链接
api.onLog(cb) (msg:string)=>void — 订阅主进程日志流(安装/上传进度)

IPC 通道名(开发 A 在 main.js 用 ipcMain.handle 实现;开发 B 不直接用,只用 window.api)

scan-local pick-dir pack-local upload market-list market-install gh-pull gh-restore get-secret set-secret open-external 日志:主进程 webContents.send("log", msg) → preload 转成 api.onLog。

关键逻辑要点(开发 A 必读,从现有 Python 版移植)

  1. 扫描:~/.codex/plugins/cache/<source>/<plugin>/ 每个是插件;~/.codex/skills/<skill>/ 每个是技能。跳过 . 开头目录。size 递归求和。
  2. 打包排除:拷贝时跳过隐私文件 auth.json、config.toml、history.jsonl、*.sqlite*、*.log、*.bak、.DS_Store、installation_id。
  3. 打包产物:staging 内保持相对结构;额外生成 .gitignore、restore.sh、README.md、enabled-plugins.toml(记录选中插件在本机 config.toml 里的启用状态)。
  4. 上传:用 isomorphic-git。先 GET https://api.github.com/user(Bearer token)拿 owner,并读响应头 x-oauth-scopes 校验含 repo/public_repo(缺失给明确错误:让用户用 classic token 勾 repo);POST /user/repos 建仓(422=已存在则忽略,403/404 继续尝试 push);git init/add/commit/push(force)。push 认证 onAuth: () => ({ username: token })。
  5. 发布市场:上传成功后,对选中的插件项(技能无 config 登记)构造 Entry:repo=owner/reponame, branch=main, web_url=https://github.com/owner/repo/tree/main/<rel>,POST {MARKET_URL}/api/plugins,头 X-API-Key: MARKET_KEY,体 {plugins:[...]}。
  6. 克隆/安装:isomorphic-git clone(私有仓库 onAuth 用 token)到临时目录 → 扫描 → 按 entry.rel 精确匹配 → 拷进 ~/.codex/<rel> → 若是插件则在 config.toml 追加 [plugins."name@source"]\nenabled = true(已存在则不重复;改动前备份 config.toml)。
  7. 仓库地址解析:接受 owner/repo、https://github.com/owner/repo(.git)。
  8. 密钥存储:safeStorage.encryptString 加密后写入 app.getPath("userData")/secrets.json;读时解密。name 取 github / registry。
  9. 市场 API_KEY:安装(读)不需要 key;仅发布需要,用写死的 MARKET_KEY。

会话记忆(新增模块:electron/lib/memory.js)

自动把 Codex 会话浓缩成交接摘要:监视 ~/.codex/sessions,会话静默 idleMinutes 后摘要 (优先走路由中转可用分组的 LLM,失败退化本地提取)→ 存 userData/memory.db(sql.js) → 写项目根目录 HANDOFF.md + 在 AGENTS.md 加带标记的引导段(<!-- codex-memory:start/end -->)。 配置在 userData/memory.json(enabled / disabledProjects / idleMinutes / backfillDays / handoffSessions)。

前端 API(preload 已暴露):

方法 入参 返回 说明
api.memoryStatus() — {enabled, engine, total, pending, idleMinutes, projects[]} 状态 + 项目列表
api.memoryList(opts) {project?, q?, limit?} 摘要行数组 检索记忆库
api.memorySetEnabled(b) boolean status 总开关
api.memoryToggleProject(cwd, b) string, boolean status 项目级写入开关
api.memoryResummarize(path) rollout 路径 {ok} 手动重摘要一条
api.memoryDelete(id) 会话 id {ok} 删一条摘要
api.memoryOpenHandoff(cwd) 项目路径 — Finder 中显示 HANDOFF.md
api.onMemoryChanged(cb) 回调 取消函数 摘要更新推送

IPC 通道:memory-status memory-list memory-set-enabled memory-toggle-project memory-resummarize memory-delete memory-open-handoff;事件 memory-changed。 router.js 新增导出 llmGroup()(供内部功能取可调用的中转分组)。

UI 设计方向(开发 B)

  • 现代、精致、可直接对外发布的观感;深色为主、支持浅色;圆角卡片、柔和阴影、清晰层级。
  • 主色建议靛蓝→紫渐变(与图标一致 #5B6CFF→#A855F7),强调按钮用该渐变。
  • 两大区域:① 插件市场(卡片列表 + 搜索框 + 分类筛选 + 每卡「安装」按钮 + 安装态/进度);② 备份与还原(子区:从本机导出上传 / 从 GitHub 拉取还原)。可用侧边栏或顶部标签切换。
  • 顶部品牌区(logo + 名称「Codex 插件市场」)。底部或抽屉式「运行日志」。
  • 无外部 CDN(离线可用),字体用系统字体栈。图标可用内联 SVG。
  • 所有后端调用走 window.api.*;不得在前端出现市场地址或密钥。
  • 空状态、加载中、错误提示都要有像样的样式。