7.5 KiB
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 版移植)
- 扫描:
~/.codex/plugins/cache/<source>/<plugin>/每个是插件;~/.codex/skills/<skill>/每个是技能。跳过.开头目录。size 递归求和。 - 打包排除:拷贝时跳过隐私文件
auth.json、config.toml、history.jsonl、*.sqlite*、*.log、*.bak、.DS_Store、installation_id。 - 打包产物:staging 内保持相对结构;额外生成
.gitignore、restore.sh、README.md、enabled-plugins.toml(记录选中插件在本机 config.toml 里的启用状态)。 - 上传:用 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 })。 - 发布市场:上传成功后,对选中的插件项(技能无 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:[...]}。 - 克隆/安装:isomorphic-git clone(私有仓库 onAuth 用 token)到临时目录 → 扫描 → 按
entry.rel精确匹配 → 拷进~/.codex/<rel>→ 若是插件则在config.toml追加[plugins."name@source"]\nenabled = true(已存在则不重复;改动前备份 config.toml)。 - 仓库地址解析:接受
owner/repo、https://github.com/owner/repo(.git)。 - 密钥存储:
safeStorage.encryptString加密后写入app.getPath("userData")/secrets.json;读时解密。name 取github/registry。 - 市场 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.*;不得在前端出现市场地址或密钥。 - 空状态、加载中、错误提示都要有像样的样式。