Repository navigation
Releases: lazyalex616/opencode-github-sync
Release list
v3.2.0 — one-line install, bilingual menu, OS-scheduled autosync
English
Added
- One-line installers.
install.ps1(Windows PowerShell 5.1 and PowerShell 7) andinstall.sh(macOS, Linux) check for Node.js 18+ and git, offer to install a missing one through winget, Homebrew or the system package manager (they ask first and never install silently), runnpm install -g opencode-github-sync@latest, and open the menu.--mode,--repo,--interval,--lang,--proxyand--yesrun the whole setup non-interactively. Undercurl | shquestions are read from the terminal, and on Windows the menu runs as its own process so it gets the keyboard. - Interactive menu.
opencode-syncwith no arguments (oropencode-sync menu) opens a menu with arrow keys, hotkeys and checkboxes, and falls back to numbered prompts whenever input is not a terminal. Every existing subcommand still works and stays scriptable. First run offers: install the command only, link a repository, or link and make this the primary device with autosync. When the machine and the repository differ, it shows the differences and lets you pick a side; the losing side's files go to a recovery folder. - English and Simplified Chinese for every menu and command string, detected from
OPENCODE_SYNC_LANG,LC_ALL/LC_MESSAGES/LANGor the system locale, and switchable with--langor from the menu. - Autosync driven by the operating system.
opencode-sync autosync install|uninstall|enable|disable|status|run|reset|pause|resume. Task Scheduler on Windows (logon, unlock, remote connect and a repeating timer; hidden throughconhost --headlessor a UTF-16 VBScript launcher), launchd on macOS, a systemd user timer on Linux with a crontab fallback. Each run pushes only when the synced trees have been quiet for a settle period, and otherwise receives only when this machine has nothing of its own in flight. Machines are staggered so they do not collide, failures back off from 30 to 60 minutes without ever giving up, a lost push race is retried rather than counted, and a desktop notification appears only when backing off starts and when it recovers. - What to sync as checkboxes: main config, instruction files, agents, commands, plugins, skills, credentials and sessions. A switched-off category stays local in both directions.
- Large-change guard. A push that adds more than 300 files, more than 10 MB, or any file over 2 MB waits for confirmation (thresholds are adjustable). Autosync never confirms: it records a failure and notifies. The first push into an empty repository carries the whole existing setup, so only the single-file limit applies there. Generated directories (
node_modules,.venv,venv,__pycache__,dist,.cache) are never staged. - Restore a file from history, with the replaced version kept in the recovery folder.
- Device list built from commit history, and renaming this machine.
- Network proxy for git, gh and npm, applied to child processes only.
- Preview of what a sync would change before running it.
- Support bundle export with tokens, passwords and URL credentials removed.
- Update menu: check npm, upgrade, and an optional daily auto-update run by the autosync job.
Changed
- When autosync is on, the plugin no longer pushes on idle (it still pulls at startup), so it never races the scheduled job.
- Sync commits carry an
Opencode-Sync-Devicetrailer naming the machine. linkaccepts any git URL or local path in addition toowner/name.- A forced pull, and the first pull onto a machine that already has files, copy the files they replace into
opencode-sync-recoveryunder the data directory first. This covers new folders, non-ASCII file names, and local skills and extra-path files that the forced pull removes or overwrites.
Fixed
- A push that loses a race with another machine rebases and retries up to three times instead of failing. A protected branch or a declined hook is reported as a failure rather than retried.
- The first push from a machine whose config directory is not yet a repository now writes the remote's skills out before staging, so it can no longer publish every skill it lacks as a deletion.
- Git runs with
core.quotepath=offand English messages, so non-ASCII file names count toward the large-change guard and a lost push race is recognised on a localized git.
简体中文
新增
- 一行命令安装。
install.ps1(Windows PowerShell 5.1 和 PowerShell 7 都能用)和install.sh(macOS、Linux)会检查 Node.js 18+ 和 git,缺了就提示用 winget、Homebrew 或系统包管理器安装(一定先问,绝不偷偷装),然后执行npm install -g opencode-github-sync@latest并打开菜单。带上--mode、--repo、--interval、--lang、--proxy、--yes可以全程无人值守完成设置。用curl | sh时问题从终端读取;在 Windows 上菜单作为独立进程启动,键盘输入不会被管道吃掉。 - 交互菜单。 直接运行
opencode-sync(或opencode-sync menu)会打开菜单,支持方向键、快捷键和勾选框;输入不是终端时自动改用编号提问。原有子命令全部保留,照样可以写进脚本。第一次运行时可以选:只装命令、关联仓库、关联并把本机设为开启自动同步的主力设备。本机和仓库内容不一样时会先列出差异让你选以哪边为准,被替换的一方会先存进恢复目录。 - 英文和简体中文界面,覆盖所有菜单和命令输出。语言按
OPENCODE_SYNC_LANG、LC_ALL/LC_MESSAGES/LANG、系统区域设置依次判断,也可以用--lang或在菜单里切换。 - 由操作系统驱动的自动同步。 新增
opencode-sync autosync install|uninstall|enable|disable|status|run|reset|pause|resume。Windows 用任务计划程序(登录、解锁、远程连接时触发,外加定时重复;通过conhost --headless或 UTF-16 编码的 VBScript 隐藏窗口),macOS 用 launchd,Linux 用 systemd 用户定时器,不可用时退回 crontab。每次运行只在同步目录安静一段时间后才推送;还有文件在变化时,只有本机没有待处理的改动才会接收。多台机器会错开时间避免撞车;连续失败后退避间隔从 30 分钟加到 60 分钟,但永不放弃;推送被别的机器抢先不算失败,会自动重试;桌面通知只在开始退避和恢复正常时各弹一次。 - 同步内容勾选:主配置、指令文件、智能体、自定义命令、插件、技能、登录凭据、会话。关掉的类别双向都只留在本机。
- 大改动保护。 一次推送新增超过 300 个文件、总量超过 10 MB,或有单个文件超过 2 MB 时,要先确认(阈值可调)。自动同步从不自动确认,只记一次失败并发通知。往空仓库的第一次推送本来就要带上全部现有配置,所以只检查单个文件大小。
node_modules、.venv、venv、__pycache__、dist、.cache这类生成目录永远不会提交。 - 从历史版本恢复单个文件,被替换的版本保存在恢复目录里。
- 设备列表,根据提交历史显示每台机器上次同步的时间,并可以给本机改名。
- 网络代理,只作用于 opencode-sync 启动的 git、gh 和 npm。
- 同步前预览会改动哪些文件。
- 导出排障包,自动去掉令牌、密码和 URL 里的账号密码。
- 更新菜单:检查 npm 新版本、立即升级,以及可选的每日自动更新(由自动同步任务执行)。
变更
- 开启自动同步后,插件不再在会话空闲时推送(启动时仍然拉取),避免和定时任务抢着同步。
- 同步提交带上
Opencode-Sync-Device尾注,记录是哪台机器提交的。 link除了用户名/仓库名,也接受任意 git 地址或本地路径。- 强制拉取,以及本机已有文件时的第一次拉取,会先把将被替换的文件复制到数据目录下的
opencode-sync-recovery。新建的整个文件夹、中文等非 ASCII 文件名,以及强制拉取会删除或覆盖的本机技能和额外路径文件,也都包括在内。
修复
- 推送时被别的机器抢先,会自动变基并最多重试三次,而不是直接失败。分支受保护或被钩子拒绝时会如实报错,不会反复重试。
- 配置目录还不是仓库的机器第一次推送时,会先把仓库里的技能写回本机再打包,不会再把本机缺少的技能当成删除推送出去。
- git 改为以
core.quotepath=off和英文提示运行:中文文件名会正确计入大改动保护,非英文系统上也能识别推送被抢先的情况。
Install
npm install -g opencode-github-sync@3.2.0Plugin users: OpenCode picks up the new version on its next start.
Contributors
Thank you to everyone who made this release possible:
- @vitaly74 — OpenCode 2 plugin entrypoint (#6, 3.1.0)
- @aquelejota — deterministic shard export and working session tests (#5), analysis of #2, #3 and #4 (3.1.0)
- @logser13 — reported #2, #3 and #4 with reproductions and tested fixes (3.1.x)
What's Changed
- 3.2.0: installers, bilingual menu, OS-scheduled autosync by @doomsday616 in #7
Full Changelog: v3.1.1...v3.2.0
v3.1.1 — OpenCode 2 support and sync fixes
This release rolls up everything since 3.0.1: the community fixes and OpenCode 2 support from 3.1.0, plus the 3.1.1 sync fixes.
本版本汇总了 3.0.1 之后的全部改动:3.1.0 的社区修复和 OpenCode 2 支持,以及 3.1.1 的同步修复。
English
3.1.1
Fixed
- A push that rebased onto other machines' commits now writes the merged result out to the real directories (
~/.agents/skills, state, data, extra paths). Previously the new files landed only in the repository, a later pull saw nothing incoming and skipped the write-out, and the next push staged the stale local tree and deleted another machine's new skills. A push with nothing local now fast-forwards and writes out the same way. - The commit last written out is recorded in
.git/info/opencode-sync-stage-out. When HEAD has moved past it (for example a push that stopped after the rebase), the next push or pull writes the repository out before doing anything else, so the gap can no longer be published as deletions. - An
extraPathssource that does not exist on this machine no longer deletes the shared repository copy. It is skipped and reported; other machines keep receiving it. - Writing out a directory
extraPathsentry no longer deletes local files that are not in the repository copy, so a tool's runtime files beside its synced config survive a pull.--forcestill makes it an exact copy. File entries continue to replace only that file.
3.1.0
Added
- OpenCode 2 support. One package now loads on both OpenCode 1.x and 2.x. Thanks @vitaly74 (#6). How it works:
- The default export is a single object that satisfies both loaders. OpenCode 2 looks for
{ id, setup }:setup(ctx)registers theopencode_synctool throughctx.tool.transform, subscribes tosession.idlethroughctx.event.subscribefor the optional idle push, and runs the startup pull. OpenCode 1.x (1.3.4 and later) sees the same object'sidandserver()and callsserver()once, so the plugin is registered exactly once. - No dependency on
@opencode/plugin; the V2 context is used structurally. - Config key. OpenCode 1.x reads only
"plugin"and silently drops"plugins". OpenCode 2 reads its native"plugins"and still accepts the legacy"plugin". So"plugin": ["opencode-github-sync"]is the one form that works on both, and it is what the README recommends. A 2.x-only setup may use"plugins", but never list the plugin under both keys: 2.x loads both lists. - Plugin cache pruning reads both keys, so a mixed fleet where some machines are on 1.x and some on 2.x keeps every plugin either version needs.
- OpenCode 1.0–1.3.3 iterate every export and call each as a function, which the new object default export cannot satisfy. Stay on 3.0.1 there.
- The default export is a single object that satisfies both loaders. OpenCode 2 looks for
Fixed
- Session import no longer cascade-deletes sessions.
INSERT OR REPLACEdeletes the conflicting row before inserting, so replacing a shared project row cascaded throughON DELETE CASCADEand removed every session imported before it. It is replaced by a realINSERT ... ON CONFLICT DO UPDATEupsert that never deletes and keeps newer-time_updated-wins per row. Reported with a tested fix by @logser13, cascade analysis by @aquelejota (#2). - The synthetic
globalproject is machine-specific, so it is no longer exported, and importing an older shard that carries it never overwrites the local row. Reported by @logser13, confirmed by @aquelejota (#4). - Session shard export is deterministic: an unchanged session produces a byte-identical shard, so it no longer shows up as a change on every push. Reported by @logser13 (#3), fixed by @aquelejota (#5).
- The session tests now actually run under vitest; previously
node:sqlitefailed to load there and every session test returned early. Found and fixed by @aquelejota (#5). - Plugin cache pruning reads both the
pluginandpluginskeys and understands[spec, options]tuples and{ package }objects, so a plugin listed in either form is no longer pruned from the cache. Follow-up to @vitaly74's #6.
Changed
- The minimum supported OpenCode 1.x is now 1.3.4. OpenCode 1.0–1.3.3 cannot load this release; stay on 3.0.1 there.
简体中文
3.1.1
修复
- 推送时如果先合并了其他机器的提交,现在会把合并后的结果写回本机的真实目录(
~/.agents/skills、state、data、额外路径)。以前这些新文件只进了仓库,之后的拉取发现没有新提交就跳过写回,下一次推送又把本机过时的目录打包上去,结果把别的机器刚装的 skill 删掉了。本机没有改动的推送现在会直接快进,并用同样的方式写回。 - 最近一次写回的提交记录在
.git/info/opencode-sync-stage-out。如果 HEAD 已经越过这个记录(例如推送在合并之后中断),下一次推送或拉取会先把仓库写回本机,再做其他事,这段差异不会再被当成删除推送出去。 extraPaths里的文件在本机不存在时,不再删除仓库里的共享副本,而是跳过并提示,其他机器照常接收。- 写回目录类型的
extraPaths时,不再删除仓库副本里没有的本机文件,同步配置旁边的工具运行文件在拉取后能保留下来。--force仍然会做成完全一致的副本。文件类型的条目仍然只替换那一个文件。
3.1.0
新增
- 支持 OpenCode 2。 同一个包现在在 OpenCode 1.x 和 2.x 上都能加载。感谢 @vitaly74(#6)。原理如下:
- 默认导出是一个同时满足两种加载方式的对象。OpenCode 2 找的是
{ id, setup }:setup(ctx)通过ctx.tool.transform注册opencode_sync工具,通过ctx.event.subscribe监听session.idle以支持可选的空闲推送,并在启动时拉取一次。OpenCode 1.x(1.3.4 及以上)看到的是同一个对象上的id和server(),只调用一次server(),所以插件只注册一次,不会重复。 - 不依赖
@opencode/plugin,V2 的上下文按结构直接使用。 - 配置写法。 OpenCode 1.x 只读
"plugin",遇到"plugins"会直接丢掉且不报错;OpenCode 2 读自己的新写法"plugins",同时也认旧的"plugin"。所以"plugin": ["opencode-github-sync"]是两个版本都能用的唯一写法,README 也推荐这样写。只用 2.x 的话可以写"plugins",但不要两个键里都写这个插件,2.x 会把两个列表都加载一遍。 - 清理插件缓存时同时读取两个键,有的机器在 1.x、有的在 2.x 时,两个版本需要的插件都不会被误删。
- OpenCode 1.0–1.3.3 会把每个导出都当函数调用,新的对象形式默认导出满足不了,这些版本请继续使用 3.0.1。
- 默认导出是一个同时满足两种加载方式的对象。OpenCode 2 找的是
修复
- 导入会话不再连带删除其他会话。
INSERT OR REPLACE会先删掉冲突的行再插入,替换共享的 project 行时会通过ON DELETE CASCADE把之前导入的会话全部删掉。现在改用真正的INSERT ... ON CONFLICT DO UPDATE,从不删除行,并且按行保留time_updated较新的一方。由 @logser13 报告并附上验证过的修法,@aquelejota 补充了连带删除的分析(#2)。 - 合成的
global项目属于每台机器自己,不再导出;导入带有它的旧分片时,也不会覆盖本机的那一行。由 @logser13 报告,@aquelejota 确认(#4)。 - 会话分片导出现在是确定的:没有变化的会话生成的分片逐字节相同,不会在每次推送时都显示为改动。由 @logser13 报告(#3),@aquelejota 修复(#5)。
- 会话测试现在真的会在 vitest 下运行。以前
node:sqlite在那里加载失败,每个会话测试都提前返回。由 @aquelejota 发现并修复(#5)。 - 清理插件缓存时同时读取
plugin和plugins两个键,并识别[spec, options]元组和{ package }对象,无论用哪种写法列出的插件都不会再被误删缓存。这是对 @vitaly74 #6 的补充修改。
变更
- OpenCode 1.x 的最低支持版本变为 1.3.4。OpenCode 1.0–1.3.3 无法加载此版本,请继续使用 3.0.1。
Install
npm install -g opencode-github-sync@3.1.1Plugin users: OpenCode picks up the new version on its next start.
Contributors
Thank you to everyone who made this release possible:
- @vitaly74 — OpenCode 2 plugin entrypoint (#6)
- @aquelejota — deterministic shard export and working session tests (#5), analysis of #2, #3 and #4
- @logser13 — reported #2, #3 and #4 with reproductions and tested fixes
What's Changed
- fix: make session shard export deterministic by @aquelejota in #5
- Add OpenCode 2 plugin entrypoint (dual V1+V2 export) by @vitaly74 in #6
New Contributors
- @aquelejota made their first contribution in #5
- @vitaly74 made their first contribution in #6
Full Changelog: v3.0.1...v3.1.1
v3.1.0
English
Added
- OpenCode 2 support. One package now loads on both OpenCode 1.x and 2.x. Thanks @vitaly74 (#6). How it works:
- The default export is a single object that satisfies both loaders. OpenCode 2 looks for
{ id, setup }:setup(ctx)registers theopencode_synctool throughctx.tool.transform, subscribes tosession.idlethroughctx.event.subscribefor the optional idle push, and runs the startup pull. OpenCode 1.x (1.3.4 and later) sees the same object'sidandserver()and callsserver()once, so the plugin is registered exactly once. - No dependency on
@opencode/plugin; the V2 context is used structurally. - Config key. OpenCode 1.x reads only
"plugin"and silently drops"plugins". OpenCode 2 reads its native"plugins"and still accepts the legacy"plugin". So"plugin": ["opencode-github-sync"]is the one form that works on both, and it is what the README recommends. A 2.x-only setup may use"plugins", but never list the plugin under both keys: 2.x loads both lists. - Plugin cache pruning reads both keys, so a mixed fleet where some machines are on 1.x and some on 2.x keeps every plugin either version needs.
- OpenCode 1.0–1.3.3 iterate every export and call each as a function, which the new object default export cannot satisfy. Stay on 3.0.1 there.
- The default export is a single object that satisfies both loaders. OpenCode 2 looks for
Fixed
- Session import no longer cascade-deletes sessions.
INSERT OR REPLACEdeletes the conflicting row before inserting, so replacing a shared project row cascaded throughON DELETE CASCADEand removed every session imported before it. It is replaced by a realINSERT ... ON CONFLICT DO UPDATEupsert that never deletes and keeps newer-time_updated-wins per row. Reported with a tested fix by @logser13, cascade analysis by @aquelejota (#2). - The synthetic
globalproject is machine-specific, so it is no longer exported, and importing an older shard that carries it never overwrites the local row. Reported by @logser13, confirmed by @aquelejota (#4). - Session shard export is deterministic: an unchanged session produces a byte-identical shard, so it no longer shows up as a change on every push. Reported by @logser13 (#3), fixed by @aquelejota (#5).
- The session tests now actually run under vitest; previously
node:sqlitefailed to load there and every session test returned early. Found and fixed by @aquelejota (#5). - Plugin cache pruning reads both the
pluginandpluginskeys and understands[spec, options]tuples and{ package }objects, so a plugin listed in either form is no longer pruned from the cache. Follow-up to @vitaly74's #6.
Changed
- The minimum supported OpenCode 1.x is now 1.3.4. OpenCode 1.0–1.3.3 cannot load this release; stay on 3.0.1 there.
简体中文
新增
- 支持 OpenCode 2。 同一个包现在在 OpenCode 1.x 和 2.x 上都能加载。感谢 @vitaly74(#6)。原理如下:
- 默认导出是一个同时满足两种加载方式的对象。OpenCode 2 找的是
{ id, setup }:setup(ctx)通过ctx.tool.transform注册opencode_sync工具,通过ctx.event.subscribe监听session.idle以支持可选的空闲推送,并在启动时拉取一次。OpenCode 1.x(1.3.4 及以上)看到的是同一个对象上的id和server(),只调用一次server(),所以插件只注册一次,不会重复。 - 不依赖
@opencode/plugin,V2 的上下文按结构直接使用。 - 配置写法。 OpenCode 1.x 只读
"plugin",遇到"plugins"会直接丢掉且不报错;OpenCode 2 读自己的新写法"plugins",同时也认旧的"plugin"。所以"plugin": ["opencode-github-sync"]是两个版本都能用的唯一写法,README 也推荐这样写。只用 2.x 的话可以写"plugins",但不要两个键里都写这个插件,2.x 会把两个列表都加载一遍。 - 清理插件缓存时同时读取两个键,有的机器在 1.x、有的在 2.x 时,两个版本需要的插件都不会被误删。
- OpenCode 1.0–1.3.3 会把每个导出都当函数调用,新的对象形式默认导出满足不了,这些版本请继续使用 3.0.1。
- 默认导出是一个同时满足两种加载方式的对象。OpenCode 2 找的是
修复
- 导入会话不再连带删除其他会话。
INSERT OR REPLACE会先删掉冲突的行再插入,替换共享的 project 行时会通过ON DELETE CASCADE把之前导入的会话全部删掉。现在改用真正的INSERT ... ON CONFLICT DO UPDATE,从不删除行,并且按行保留time_updated较新的一方。由 @logser13 报告并附上验证过的修法,@aquelejota 补充了连带删除的分析(#2)。 - 合成的
global项目属于每台机器自己,不再导出;导入带有它的旧分片时,也不会覆盖本机的那一行。由 @logser13 报告,@aquelejota 确认(#4)。 - 会话分片导出现在是确定的:没有变化的会话生成的分片逐字节相同,不会在每次推送时都显示为改动。由 @logser13 报告(#3),@aquelejota 修复(#5)。
- 会话测试现在真的会在 vitest 下运行。以前
node:sqlite在那里加载失败,每个会话测试都提前返回。由 @aquelejota 发现并修复(#5)。 - 清理插件缓存时同时读取
plugin和plugins两个键,并识别[spec, options]元组和{ package }对象,无论用哪种写法列出的插件都不会再被误删缓存。这是对 @vitaly74 #6 的补充修改。
变更
- OpenCode 1.x 的最低支持版本变为 1.3.4。OpenCode 1.0–1.3.3 无法加载此版本,请继续使用 3.0.1。
Install
npm install -g opencode-github-sync@3.1.0Plugin users: OpenCode picks up the new version on its next start.
Contributors
Thank you to everyone who made this release possible:
- @vitaly74 — OpenCode 2 plugin entrypoint (#6)
- @aquelejota — deterministic shard export and working session tests (#5), analysis of #2, #3 and #4
- @logser13 — reported #2, #3 and #4 with reproductions and tested fixes
What's Changed
- fix: make session shard export deterministic by @aquelejota in #5
- Add OpenCode 2 plugin entrypoint (dual V1+V2 export) by @vitaly74 in #6
New Contributors
- @aquelejota made their first contribution in #5
- @vitaly74 made their first contribution in #6
Full Changelog: v3.0.1...v3.1.0
v3.0.1
English
Fixed
statusno longer reports the config file as uncommitted work on machines that use per-machine overrides. The file on disk is supposed to differ from the committed version once overrides are in play, so plaingit statusflagged it as modified forever — a warning the user could never clear. The config is now compared with overrides subtracted, so only genuine edits count.git status --porcelainoutput is parsed by pattern rather than at a fixed column offset. Output is trimmed before parsing, which removed the leading column of an unstaged entry and shifted every field after it, silently yielding a truncated filename.
简体中文
修复
- 在使用本机覆盖的机器上,
status不再把配置文件报告为未提交的改动。启用覆盖后,磁盘上的文件本来就应该和提交的版本不同,所以单纯的git status会永远提示已修改,用户无法消除。现在会先减去覆盖再比较,只有真正的编辑才算改动。 - 按模式解析
git status --porcelain的输出,而不是按固定列位置。以前输出在解析前被裁掉了首部空白,未暂存条目的第一列丢失,后面的字段整体错位,得到的文件名被悄悄截断。
Install
npm install -g opencode-github-sync@3.0.1Plugin users: OpenCode picks up the new version on its next start.
Full Changelog: v3.0.0...v3.0.1
v3.0.0 — plugin, overrides, selective session sync
Complete rewrite. The published scripts had fallen four months behind the version in daily use, and the whole-database session sync they shipped stops being viable once opencode.db passes a gigabyte.
npm install -g opencode-github-sync
Highlights
OpenCode plugin. Startup pull, optional push on idle, and an opencode_sync tool. The CLI stays, because a plugin cannot repair the configuration that stops the plugin from loading.
Per-machine overrides. opencode-sync.overrides.jsonc is merged into the config after a pull and subtracted again before a push, using git show HEAD: as the baseline rather than the working copy. An overridden key is invisible to sync in both directions, and the machine keeps its own settings throughout.
Selective session sync. Sessions export individually as gzipped JSON shards, chosen by time window, include list, project directory and per-session size cap. Different sessions are different files, so git merges them unaided; the same session edited twice resolves by time_updated. Import is a transactional upsert.
One-command setup. opencode-sync init creates the private repository and configures the machine through the GitHub CLI.
Correctness
- Pushes are verified by reading the remote head back —
git pushcan exit zero without the remote moving. - Symlinks and Windows junctions are refused rather than followed.
- File and directory replacements are atomic.
- A cross-process lock covers the plugin, the CLI and every OpenCode window.
- Line endings pinned to LF;
text=autoalone rewrote skill shell scripts to CRLF on Windows. - Commit messages use a stable pseudonym, so a corporate asset-tag hostname cannot reach the repository.
- Credentials are opt-in and refused on public repositories.
Breaking changes
Session sync no longer commits the database, and Git LFS is no longer used. The shell and .cmd wrappers are replaced by a real bin entry — install from npm instead of copying scripts.
Full notes in CHANGELOG.md.
v2.1.0 — Tokyo Night UI + Bug Fixes
What's New in v2.0 → v2.1
🎨 Complete UI Redesign — Tokyo Night Theme
The entire CLI has been redesigned with a Tokyo Night color palette using true color RGB and ANSI 256:
- No-box header:
OpenCode Sync — ⚡ Push to GitHub(clean, no border) - Force mode shown inline as
⚠ FORCEin red - Interactive menu via
@inquirer/selectwith○/●circle indicators and cyanrgb(92,156,245)highlight - Braille spinner (⠋⠙⠹...) during sync operations, clears on first real output
- Step-by-step streaming output: Each phase appears as it completes with file-level detail
- Completion banner: Cyan
━━━━━separator + green✨ 同步完成!+ descriptive slate text on the same line
📝 Chinese UI Text
All user-facing text is now in Chinese. Git terminology (Push, Pull, Fetch, Stash, Rebase, Commit, Reset) is kept in English for clarity.
🧭 4-Option Interactive Menu
? 你想同步什么?
❯ ● 仅配置
○ 仅会话
○ 全部
○ 退出
After selection, the full menu is redrawn with the selected item highlighted in cyan ● and others dimmed in gray ○.
🚀 Bootstrap Launcher
opencode-launcher.js sits between wrapper scripts and the sync core:
- Self-update: Fetches remote and updates
scripts/if changed - Dependency bootstrap: Auto-installs
@inquirer/selectwhen needed - Delegation: All arguments pass through transparently
🐛 Bug Fixes (v2.1)
- Fix data loss on pull when already up-to-date —
doPullpreviously calledstageOuteven when no remote changes existed, which ranrmDirRecursiveon local directories and deleted files not yet pushed. NowstageOutis only called when there are actual incoming changes. - Fix banner accuracy — Banner is now printed by the child scripts (sync-config/sync-sessions) instead of dispatch, so "已是最新" shows
upToDateBannerand actual syncs showcompletionBanner. - Fix
--targetlabel propagation — When running "both", each child script gets its own--target=config/--target=sessionsfor accurate banner text. - Add
SYNC_CONFIG_ROOT/SYNC_DATA_ROOTenv overrides to sync-sessions.js — Previously only sync-config.js supported these; now both are consistent. - Add
skipLfs()to sync-sessions.js — Matches sync-config.js behavior. - Fix color inconsistency —
c.gray→c.tn.graythroughout. - Fix English messages in sync-sessions.js —
initRepostep messages changed to Chinese.
🧪 Test Suite (v2.1)
New tests/test-sync-logic.js with 34 integration tests covering:
- Push with changes / no changes
- Pull with incoming / no changes
- BUG1 regression: local new files preserved on pull-no-changes
- stageOut called correctly on pull-with-changes
- Banner target label accuracy (
config/sessions/both) - No double banner from dispatch
🎨 Color Reference
| Element | Color | Code |
|---|---|---|
| Titles, accents, selected item, separator | Blue/cyan | rgb(92,156,245) |
| "同步完成!" text | Green | rgb(0,210,106) |
| Success checkmark | ✅ emoji | — |
| Warnings | Yellow | #e0af68 (ANSI 179) |
| Errors, FORCE tag | Red | #f7768e (ANSI 204) |
| ⚡ icon | Purple | #9d7cd8 (ANSI 140) |
| Borders, dim, unselected | Gray | #565f89 (ANSI 60) |
| Body text | Text | #a9b1d6 (ANSI 146) |
| Banner description | Slate | #94a3b8 (ANSI 103) |
Terminal Output
OpenCode Sync — ⚡ Push to GitHub ⚠ FORCE
? 你想同步什么?
❯ ● 仅配置
○ 仅会话
○ 全部
○ 退出
✅ 正在从 4 个目录暂存文件... 📝~5
~ _state/frecency.json
~ _state/kv.json
+ _agents/new-skill/SKILL.md
✅ 已提交:5 个文件变更
✅ 已 Push 到 GitHub repo(rebase,无冲突)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✨ 同步完成! 配置文件已成功 Push
📦 File Inventory
| File | Purpose |
|---|---|
scripts/ui.js |
Shared UI — Tokyo Night RGB colors, logo, banners, formatStats |
scripts/opencode-sync-core.js |
Menu, streaming runner, dispatch |
scripts/sync-config.js |
Config push/pull with step-by-step output |
scripts/sync-sessions.js |
Session push/pull with SQLite safety |
scripts/opencode-launcher.js |
Bootstrap launcher with self-update |
tests/test-sync-logic.js |
34 integration tests |
Breaking Changes
- Wrapper scripts call
opencode-launcher.jsinstead ofopencode-sync-core.jsdirectly. Update your wrapper files (copy fromwrappers/to~/.local/bin/). @inquirer/selectis a required dependency. The launcher auto-installs it.
Full Changelog
v1.0.0...v2.1.0: v1.0.0...v2.1.0
v1.0.0 — Initial Release
Cross-device OpenCode config & session sync via a private GitHub repository.
What's included
- 3 core scripts —
opencode-sync-core.js,sync-config.js,sync-sessions.js - 8 wrapper commands — 4 for macOS/Linux, 4 for Windows (.cmd)
- Bilingual docs — English + 中文
Features
- Sync 4 directories (config, data, state, agents/skills) into one git repo
- Interactive mode: choose config / sessions / both
- Force push/pull for conflict resolution
- Git LFS for large SQLite databases
- SQLite safe backup via
.backupcommand - Migration auto-repair across different OpenCode versions
- Auto-stash local changes during pull
Getting started
- Clone this repo
- Copy scripts to
~/.config/opencode/scripts/ - Install wrappers to
~/.local/bin/ - Set
SYNC_REMOTE_URLenv var to your private GitHub repo - Run
opencode-pushoropencode-pull