diff --git a/electron/lib/session_sync_enhanced.js b/electron/lib/session_sync_enhanced.js deleted file mode 100644 index bc30765..0000000 --- a/electron/lib/session_sync_enhanced.js +++ /dev/null @@ -1,187 +0,0 @@ -// session_sync_enhanced.js —— 增强版会话同步:确保本地所有会话一个不剩地同步到新 Codex 账号。 -// 在 provider_sync.js 的基础上增强,确保: -// 1. 扫描所有 sessions/ 和 archived_sessions/ 下的会话 -// 2. 强制插入到当前账号的数据库(UPSERT 逻辑) -// 3. 重建 session_index.jsonl -// 4. 更新全局项目索引 -// 5. 提供详细的同步报告 - -const fs = require("fs"); -const path = require("path"); -const os = require("os"); -const providerSync = require("./provider_sync"); - -const CODEX_HOME = path.join(os.homedir(), ".codex"); -const SESSION_INDEX = path.join(CODEX_HOME, "session_index.jsonl"); - -// 完整同步方案:确保所有本地会话都进入新账号 -async function syncAllSessionsToNewAccount(opts = {}) { - const log = opts.log || (() => {}); - const onProgress = opts.onProgress || (() => {}); - - log("=== 开始全量会话同步 ==="); - log("目标:将本地所有会话同步到当前 Codex 账号"); - - // 1. 检查 Codex 是否在运行 - if (providerSync.codexRunning()) { - throw new Error("检测到 Codex 正在运行,请先完全退出 Codex 再同步。"); - } - - // 2. 获取当前 provider(从 config.toml) - const targetProvider = providerSync.targetProvider(); - log(`当前账号 provider:${targetProvider}`); - - // 3. 执行核心同步(provider_sync 已经做了 UPSERT) - log("开始同步本地会话到数据库..."); - const result = await providerSync.syncProviderToCurrent(log, onProgress); - - // 4. 重建 session_index.jsonl(确保索引完整) - log("重建会话索引..."); - const indexResult = rebuildSessionIndex(log); - - // 5. 生成同步报告 - const report = { - success: true, - provider: result.target, - totalSessions: result.totalConversations, - syncedToDatabase: result.insertedRows, - updatedProvider: result.provRows, - updatedUserEvent: result.ueRows, - indexRebuilt: indexResult.total, - projects: result.projectRows, - backup: result.backup, - details: { - changedFiles: result.changedFiles, - userEventSessions: result.userEvents, - archivedSessions: indexResult.archived, - activeSessions: indexResult.active, - } - }; - - log("\n=== 同步完成 ==="); - log(`✅ 总会话数:${report.totalSessions}`); - log(`✅ 新增到数据库:${report.syncedToDatabase} 条`); - log(`✅ 更新 provider:${report.updatedProvider} 条`); - log(`✅ 重建索引:${report.indexRebuilt} 条`); - log(`✅ 项目分组:${report.projects} 个`); - log(`✅ 备份位置:${report.backup}`); - log("\n现在可以启动 Codex,所有会话应该都能看到了。"); - - return report; -} - -// 重建 session_index.jsonl:扫描所有会话文件,生成完整索引 -function rebuildSessionIndex(log = () => {}) { - const sessions = require("./sessions"); - - // 列出所有会话(包括 archived) - const allConversations = sessions.listConversations({ - includeSubagent: true, - includeArchived: true, - }); - - log(`扫描到 ${allConversations.length} 个会话文件`); - - // 备份旧索引 - if (fs.existsSync(SESSION_INDEX)) { - const backup = SESSION_INDEX + ".bak-" + Date.now(); - fs.copyFileSync(SESSION_INDEX, backup); - log(`已备份旧索引:${path.basename(backup)}`); - } - - // 重建索引:每个会话一行 JSON - const lines = []; - let active = 0; - let archived = 0; - - for (const conv of allConversations) { - if (!conv.id) continue; - - const indexEntry = { - id: conv.id, - thread_name: conv.title || "(无标题对话)", - project_path: conv.cwd || null, - created_at: conv.ts || new Date().toISOString(), - source: conv.source || "vscode", - archived: conv.archived || false, - }; - - lines.push(JSON.stringify(indexEntry)); - - if (conv.archived) archived++; - else active++; - } - - // 写入新索引 - fs.writeFileSync(SESSION_INDEX, lines.join("\n") + "\n", "utf-8"); - log(`已重建索引:活跃 ${active} 条,归档 ${archived} 条`); - - return { total: lines.length, active, archived }; -} - -// 诊断:检查会话同步状态 -function diagnoseSessionSync(log = () => {}) { - const sessions = require("./sessions"); - - log("=== 会话同步诊断 ==="); - - // 1. 统计本地会话文件 - const allConversations = sessions.listConversations({ - includeSubagent: true, - includeArchived: true, - }); - const activeSessions = allConversations.filter(c => !c.archived); - const archivedSessions = allConversations.filter(c => c.archived); - - log(`本地会话文件:${allConversations.length} 个`); - log(` - 活跃:${activeSessions.length} 个`); - log(` - 归档:${archivedSessions.length} 个`); - - // 2. 检查数据库 - const dbFiles = fs.readdirSync(CODEX_HOME).filter(f => /^state_.*\.sqlite$/.test(f)); - log(`数据库文件:${dbFiles.length} 个`); - for (const db of dbFiles) { - log(` - ${db}`); - } - - // 3. 检查索引文件 - const indexExists = fs.existsSync(SESSION_INDEX); - log(`session_index.jsonl:${indexExists ? "存在" : "不存在"}`); - - if (indexExists) { - const indexContent = fs.readFileSync(SESSION_INDEX, "utf-8"); - const indexLines = indexContent.split("\n").filter(l => l.trim()); - log(` - 索引条目:${indexLines.length} 条`); - } - - // 4. 检查 provider - const currentProvider = providerSync.targetProvider(); - log(`当前 provider:${currentProvider}`); - - // 5. 检查会话文件中的 provider 分布 - const providerStats = {}; - for (const conv of allConversations) { - const p = conv.provider || "(未设置)"; - providerStats[p] = (providerStats[p] || 0) + 1; - } - log("会话文件 provider 分布:"); - for (const [p, count] of Object.entries(providerStats)) { - log(` - ${p}: ${count} 个`); - } - - return { - totalSessions: allConversations.length, - activeSessions: activeSessions.length, - archivedSessions: archivedSessions.length, - databases: dbFiles.length, - indexExists, - currentProvider, - providerStats, - }; -} - -module.exports = { - syncAllSessionsToNewAccount, - rebuildSessionIndex, - diagnoseSessionSync, -}; diff --git a/package-lock.json b/package-lock.json index b0a212f..fa498d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "codex-market", - "version": "1.5.14", + "version": "1.5.15", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codex-market", - "version": "1.5.14", + "version": "1.5.15", "license": "MIT", "dependencies": { "electron-updater": "^6.8.9", diff --git a/package.json b/package.json index 5ccfc86..ac13502 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "codex-market", - "version": "1.5.14", + "version": "1.5.15", "description": "TD工具箱 / 技能商店 / 备份工具 —— 跨平台桌面客户端", "author": "Codex Market", "license": "MIT", "main": "electron/bootstrap.js", - "hotContentVersion": "1.5.14", + "hotContentVersion": "1.5.15", "scripts": { "start": "electron .", "dist": "electron-builder", diff --git a/scripts/publish-full.js b/scripts/publish-full.js new file mode 100644 index 0000000..e261d54 --- /dev/null +++ b/scripts/publish-full.js @@ -0,0 +1,86 @@ +#!/usr/bin/env node +// publish-full.js —— 发布完整安装包到自建 gitea。 +// 把 dist/ 下的 dmg/zip/blockmap/latest-*.yml 上传到: +// - latest release(electron-updater 自动更新目标,文件名固定,覆盖上传) +// - v release(版本存档,供手动下载) +"use strict"; +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +const { execFileSync } = require("child_process"); + +const ROOT = path.join(__dirname, ".."); +const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, "package.json"), "utf8")); +const version = pkg.version; + +let token = process.env.GIT_TOKEN || ""; +if (!token) { + try { token = fs.readFileSync(path.join(os.homedir(), ".tokendun-git-token"), "utf8").trim(); } catch {} +} +if (!token) { console.error("缺少 gitea token:设置 GIT_TOKEN 或写入 ~/.tokendun-git-token"); process.exit(1); } + +const OWNER = "mhgmh", REPO = "codex-market-app"; +const API = `https://up.tokendun.cc/api/v1/repos/${OWNER}/${REPO}`; +const DIST = path.join(ROOT, "dist"); + +// 要上传的产物:dmg/zip/blockmap + latest-mac.yml +const files = fs.readdirSync(DIST).filter((f) => + /\.(dmg|zip)(\.blockmap)?$/.test(f) || /^latest.*\.yml$/.test(f) +); +if (!files.length) { console.error("dist/ 下没有可上传的产物"); process.exit(1); } +console.log(`待上传 ${files.length} 个文件:\n ` + files.join("\n ")); + +function api(method, urlPath, body) { + const args = ["-s", "-X", method, "-H", `Authorization: token ${token}`, `${API}${urlPath}`]; + if (body !== undefined) { args.push("-H", "Content-Type: application/json", "-d", JSON.stringify(body)); } + return execFileSync("curl", args, { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 }); +} +function apiJson(method, urlPath, body) { + try { return JSON.parse(api(method, urlPath, body)); } catch { return null; } +} + +// 确保 release 存在(按 tag 取,缺则建) +function ensureRelease(tag, name, prerelease) { + let rel = apiJson("GET", `/releases/tags/${encodeURIComponent(tag)}`); + if (!rel || !rel.id) { + console.log(`创建 release ${tag} …`); + rel = apiJson("POST", `/releases`, { + tag_name: tag, target_commitish: "main", name, body: `TD工具箱 ${version} 完整安装包`, + draft: false, prerelease: !!prerelease, + }); + } + if (!rel || !rel.id) { console.error(`无法创建/获取 release ${tag}`); process.exit(1); } + return rel; +} + +function uploadTo(rel, tag) { + // 删同名旧 asset,再上传 + const existing = new Map((rel.assets || []).map((a) => [a.name, a.id])); + for (const name of files) { + if (existing.has(name)) { + api("DELETE", `/releases/${rel.id}/assets/${existing.get(name)}`); + console.log(` [${tag}] 删除旧 asset: ${name}`); + } + } + for (const name of files) { + const file = path.join(DIST, name); + const code = execFileSync("curl", ["-s", "-o", "/dev/null", "-w", "%{http_code}", + "-X", "POST", "-H", `Authorization: token ${token}`, + "-F", `attachment=@${file};type=application/octet-stream`, + `${API}/releases/${rel.id}/assets?name=${encodeURIComponent(name)}`], + { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 }); + console.log(` [${tag}] 上传 ${name} → HTTP ${code}`); + if (code !== "201") { console.error(` 上传 ${name} 失败`); process.exit(1); } + } +} + +// 1) latest release —— electron-updater 自动更新目标 +const latest = ensureRelease("latest", "自动更新通道", true); +uploadTo(latest, "latest"); + +// 2) v release —— 版本存档 +const verRel = ensureRelease(`v${version}`, `TD工具箱 ${version}`, false); +uploadTo(verRel, `v${version}`); + +console.log(`\n✅ 完整包 v${version} 已发布到 latest 与 v${version} 两个 release。`); +console.log(` 自动更新地址:https://update.tokendun.cc/${OWNER}/${REPO}/releases/download/latest`); diff --git a/test-sync.js b/test-sync.js deleted file mode 100644 index ee171b3..0000000 --- a/test-sync.js +++ /dev/null @@ -1,46 +0,0 @@ -// test-sync.js —— 测试增强后的会话同步功能 -const providerSync = require("./electron/lib/provider_sync"); - -console.log("=== 会话同步测试 ===\n"); - -// 1. 检查 Codex 是否在运行 -console.log("1. 检查 Codex 运行状态..."); -const isRunning = providerSync.codexRunning(); -if (isRunning) { - console.error("❌ Codex 正在运行,请先完全退出 Codex(Cmd+Q 或 Quit)再测试"); - process.exit(1); -} -console.log("✅ Codex 未运行,可以同步\n"); - -// 2. 检查当前 provider -console.log("2. 检查当前 provider..."); -const targetProvider = providerSync.targetProvider(); -console.log(`✅ 当前 provider:${targetProvider}\n`); - -// 3. 执行同步 -console.log("3. 开始同步会话..."); -const log = (msg) => console.log(` ${msg}`); -const onProgress = (p) => { - if (p.phase) console.log(` [进度] ${p.phase} ${p.done || ""}/${p.total || ""}`); -}; - -providerSync.syncProviderToCurrent(log, onProgress) - .then((result) => { - console.log("\n=== 同步完成 ==="); - console.log(`✅ 目标 provider: ${result.target}`); - console.log(`✅ 总会话数: ${result.totalConversations}`); - console.log(`✅ 修改文件: ${result.changedFiles}`); - console.log(`✅ 用户消息会话: ${result.userEvents}`); - console.log(`✅ 更新 provider: ${result.provRows} 行`); - console.log(`✅ 更新 has_user_event: ${result.ueRows} 行`); - console.log(`✅ 新增线程: ${result.insertedRows} 行`); - console.log(`✅ 项目分组: ${result.projectRows} 个`); - console.log(`✅ 索引重建: ${result.indexRebuilt} 条`); - console.log(`✅ 备份位置: ${result.backup}`); - console.log("\n现在可以打开 Codex,所有会话应该都能在侧边栏看到了 🎉"); - }) - .catch((err) => { - console.error("\n❌ 同步失败:", err.message || err); - console.error(err.stack); - process.exit(1); - });