Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,149 @@ jobs:

- run: npm run build

installer:
# The only place a real scheduled job is ever registered: disposable
# runners. Installs the packed checkout through the real installer, links
# a local bare repository with every root redirected into the runner's
# temp directory, then installs, runs and removes the scheduler entry.
name: installer · ${{ matrix.os }}
runs-on: ${{ matrix.os }}
needs: check
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
env:
OPENCODE_SYNC_NO_GH: "1"
OPENCODE_SYNC_NO_STAGGER: "1"
OPENCODE_SYNC_LANG: en
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Configure git identity
shell: bash
run: |
git config --global user.name "CI"
git config --global user.email "ci@example.com"
git config --global init.defaultBranch main

- name: Build and pack the checkout
shell: bash
run: |
npm ci
npm run build
npm pack
pkg="$(pwd)/$(ls opencode-github-sync-*.tgz)"
if [ "$RUNNER_OS" = "Windows" ]; then pkg=$(cygpath -w "$pkg"); fi
echo "PKG=$pkg" >> "$GITHUB_ENV"

- name: Isolated roots and a local bare repository
shell: bash
run: |
base="$RUNNER_TEMP/ogs"
mkdir -p "$base/home" "$base/config"
git init -q --bare -b main "$base/remote.git"
printf '{ "model": "ci" }\n' > "$base/config/opencode.jsonc"
{
echo "SYNC_CONFIG_ROOT=$base/config"
echo "SYNC_DATA_ROOT=$base/data"
echo "SYNC_STATE_ROOT=$base/state"
echo "SYNC_AGENTS_ROOT=$base/agents/skills"
echo "SYNC_CACHE_ROOT=$base/cache"
echo "SYNC_HOME=$base/home"
echo "OGS_REMOTE=$base/remote.git"
echo "OGS_LOG=$base/config/logs/opencode-sync-autosync.log"
} >> "$GITHUB_ENV"

- name: Run install.sh non-interactively
if: runner.os != 'Windows'
shell: bash
run: sh install.sh --mode link --repo "$OGS_REMOTE" --interval 5 --lang en --package "$PKG" --yes </dev/null

- name: install.ps1 parses and plans under Windows PowerShell 5.1
if: runner.os == 'Windows'
shell: powershell
run: ./install.ps1 -DryRun -Mode auto -Repo o/r

- name: Run install.ps1 non-interactively
if: runner.os == 'Windows'
shell: pwsh
run: ./install.ps1 -Mode link -Repo $env:OGS_REMOTE -Interval 5 -Lang en -Package $env:PKG -Yes

- name: The first sync landed in the bare repository
shell: bash
run: git --git-dir="$OGS_REMOTE" log --oneline -1 | grep -q sync

- name: autosync enable registers the scheduler entry
shell: bash
run: |
opencode-sync autosync enable
opencode-sync autosync status
case "$RUNNER_OS" in
Windows) schtasks //Query //TN opencode-github-sync ;;
macOS) test -f "$HOME/Library/LaunchAgents/com.opencode-github-sync.autosync.plist"
launchctl list | grep -q com.opencode-github-sync.autosync ;;
Linux) systemctl --user is-enabled opencode-github-sync.timer 2>/dev/null \
|| crontab -l | grep -q "opencode-github-sync autosync" ;;
esac

- name: The scheduler actually starts the job
shell: bash
run: |
if [ "$RUNNER_OS" = "Windows" ]; then schtasks //Run //TN opencode-github-sync; fi
if [ "$RUNNER_OS" = "Linux" ]; then
if systemctl --user is-enabled opencode-github-sync.timer >/dev/null 2>&1; then
systemctl --user start opencode-github-sync.service || true
else
echo "cron fallback: not waiting for the next minute boundary"; exit 0
fi
fi
for _ in $(seq 1 45); do
if grep -q "scheduled:" "$OGS_LOG" 2>/dev/null; then cat "$OGS_LOG"; exit 0; fi
sleep 2
done
echo "the scheduled job did not write to $OGS_LOG"
cat "$OGS_LOG" 2>/dev/null || true
cat "$SYNC_CONFIG_ROOT/logs/autosync-launchd.log" 2>/dev/null || true
if [ "$RUNNER_OS" = "Windows" ]; then
# An InteractiveToken task needs a signed-in desktop session, which
# a hosted runner may not provide. Report what Task Scheduler saw.
powershell -NoProfile -Command "Get-ScheduledTaskInfo -TaskName opencode-github-sync | Format-List"
echo "::warning::Task Scheduler did not start the job in this runner session"
exit 0
fi
exit 1

- name: autosync run
shell: bash
run: |
opencode-sync autosync run --trigger ci
grep "ci:" "$OGS_LOG"

- name: autosync uninstall removes the scheduler entry
shell: bash
run: |
opencode-sync autosync uninstall
case "$RUNNER_OS" in
Windows) ! schtasks //Query //TN opencode-github-sync >/dev/null 2>&1 ;;
macOS) test ! -f "$HOME/Library/LaunchAgents/com.opencode-github-sync.autosync.plist"
! launchctl list | grep -q com.opencode-github-sync.autosync ;;
Linux) ! systemctl --user is-enabled opencode-github-sync.timer >/dev/null 2>&1
! crontab -l 2>/dev/null | grep -q "opencode-github-sync autosync" ;;
esac

shellcheck:
name: shellcheck install.sh
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: shellcheck -s sh install.sh

package:
name: package contents
runs-on: ubuntu-latest
Expand All @@ -68,3 +211,6 @@ jobs:
test -f dist/plugin/index.js
test -f dist/cli/index.js
test -f dist/core/index.js
# Installers and README images are served from GitHub, not npm.
files=$(npm pack --dry-run --json | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s)[0].files.map(f=>f.path).join("\n")))')
! echo "$files" | grep -E '^(install\.(sh|ps1)|docs/)'
78 changes: 78 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,84 @@

[简体中文](./CHANGELOG.zh-CN.md)

## 3.2.0

### Added
- **One-line installers.** `install.ps1` (Windows PowerShell 5.1 and
PowerShell 7) and `install.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), run
`npm install -g opencode-github-sync@latest`, and open the menu. `--mode`,
`--repo`, `--interval`, `--lang`, `--proxy` and `--yes` run the whole setup
non-interactively. Under `curl | sh` questions are read from the terminal,
and on Windows the menu runs as its own process so it gets the keyboard.
- **Interactive menu.** `opencode-sync` with no arguments (or `opencode-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`/`LANG` or the
system locale, and switchable with `--lang` or 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 through `conhost --headless` or 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-Device` trailer naming the machine.
- `link` accepts any git URL or local path in addition to `owner/name`.
- A forced pull, and the first pull onto a machine that already has files, copy
the files they replace into `opencode-sync-recovery` under 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=off` and English messages, so non-ASCII file
names count toward the large-change guard and a lost push race is
recognised on a localized git.

### Contributors
- @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)

## 3.1.1

### Fixed
Expand Down
32 changes: 32 additions & 0 deletions CHANGELOG.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,38 @@

[English](./CHANGELOG.md)

## 3.2.0

### 新增
- **一行命令安装。** `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` 和英文提示运行:中文文件名会正确计入大改动保护,非英文系统上也能识别推送被抢先的情况。

### Contributors
- @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)

## 3.1.1

### 修复
Expand Down
Loading
Loading