chore: bump to 1.5.15, publish full package + hot update
- 版本号 1.5.14 → 1.5.15 - 新增 scripts/publish-full.js:一键上传完整包到 latest 与 v<version> release - 清理未引用的 session_sync_enhanced.js 与临时 test-sync.js Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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,
|
|
||||||
};
|
|
||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "codex-market",
|
"name": "codex-market",
|
||||||
"version": "1.5.14",
|
"version": "1.5.15",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "codex-market",
|
"name": "codex-market",
|
||||||
"version": "1.5.14",
|
"version": "1.5.15",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"electron-updater": "^6.8.9",
|
"electron-updater": "^6.8.9",
|
||||||
|
|||||||
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "codex-market",
|
"name": "codex-market",
|
||||||
"version": "1.5.14",
|
"version": "1.5.15",
|
||||||
"description": "TD工具箱 / 技能商店 / 备份工具 —— 跨平台桌面客户端",
|
"description": "TD工具箱 / 技能商店 / 备份工具 —— 跨平台桌面客户端",
|
||||||
"author": "Codex Market",
|
"author": "Codex Market",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "electron/bootstrap.js",
|
"main": "electron/bootstrap.js",
|
||||||
"hotContentVersion": "1.5.14",
|
"hotContentVersion": "1.5.15",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron .",
|
"start": "electron .",
|
||||||
"dist": "electron-builder",
|
"dist": "electron-builder",
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// publish-full.js —— 发布完整安装包到自建 gitea。
|
||||||
|
// 把 dist/ 下的 dmg/zip/blockmap/latest-*.yml 上传到:
|
||||||
|
// - latest release(electron-updater 自动更新目标,文件名固定,覆盖上传)
|
||||||
|
// - v<version> 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<version> 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`);
|
||||||
@@ -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);
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user