opencode-multidevice-sync 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE +15 -0
- package/README.md +111 -0
- package/README.ru.md +57 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +460 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/render.d.ts +45 -0
- package/dist/cli/render.d.ts.map +1 -0
- package/dist/cli/render.js +167 -0
- package/dist/cli/render.js.map +1 -0
- package/dist/cli/theme.d.ts +45 -0
- package/dist/cli/theme.d.ts.map +1 -0
- package/dist/cli/theme.js +88 -0
- package/dist/cli/theme.js.map +1 -0
- package/dist/core/backup.d.ts +13 -0
- package/dist/core/backup.d.ts.map +1 -0
- package/dist/core/backup.js +102 -0
- package/dist/core/backup.js.map +1 -0
- package/dist/core/fsx.d.ts +81 -0
- package/dist/core/fsx.d.ts.map +1 -0
- package/dist/core/fsx.js +353 -0
- package/dist/core/fsx.js.map +1 -0
- package/dist/core/git.d.ts +62 -0
- package/dist/core/git.d.ts.map +1 -0
- package/dist/core/git.js +162 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/guard.d.ts +5 -0
- package/dist/core/guard.d.ts.map +1 -0
- package/dist/core/guard.js +97 -0
- package/dist/core/guard.js.map +1 -0
- package/dist/core/health.d.ts +18 -0
- package/dist/core/health.d.ts.map +1 -0
- package/dist/core/health.js +88 -0
- package/dist/core/health.js.map +1 -0
- package/dist/core/host.d.ts +20 -0
- package/dist/core/host.d.ts.map +1 -0
- package/dist/core/host.js +50 -0
- package/dist/core/host.js.map +1 -0
- package/dist/core/index.d.ts +18 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +18 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/jsonc.d.ts +20 -0
- package/dist/core/jsonc.d.ts.map +1 -0
- package/dist/core/jsonc.js +115 -0
- package/dist/core/jsonc.js.map +1 -0
- package/dist/core/lock.d.ts +14 -0
- package/dist/core/lock.d.ts.map +1 -0
- package/dist/core/lock.js +111 -0
- package/dist/core/lock.js.map +1 -0
- package/dist/core/overrides.d.ts +43 -0
- package/dist/core/overrides.d.ts.map +1 -0
- package/dist/core/overrides.js +169 -0
- package/dist/core/overrides.js.map +1 -0
- package/dist/core/paths.d.ts +36 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +55 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/preview.d.ts +3 -0
- package/dist/core/preview.d.ts.map +1 -0
- package/dist/core/preview.js +56 -0
- package/dist/core/preview.js.map +1 -0
- package/dist/core/repo.d.ts +57 -0
- package/dist/core/repo.d.ts.map +1 -0
- package/dist/core/repo.js +196 -0
- package/dist/core/repo.js.map +1 -0
- package/dist/core/reporter.d.ts +50 -0
- package/dist/core/reporter.d.ts.map +1 -0
- package/dist/core/reporter.js +63 -0
- package/dist/core/reporter.js.map +1 -0
- package/dist/core/sessions.d.ts +83 -0
- package/dist/core/sessions.d.ts.map +1 -0
- package/dist/core/sessions.js +508 -0
- package/dist/core/sessions.js.map +1 -0
- package/dist/core/settings.d.ts +68 -0
- package/dist/core/settings.d.ts.map +1 -0
- package/dist/core/settings.js +126 -0
- package/dist/core/settings.js.map +1 -0
- package/dist/core/setup.d.ts +48 -0
- package/dist/core/setup.d.ts.map +1 -0
- package/dist/core/setup.js +186 -0
- package/dist/core/setup.js.map +1 -0
- package/dist/core/sqlite.d.ts +37 -0
- package/dist/core/sqlite.d.ts.map +1 -0
- package/dist/core/sqlite.js +108 -0
- package/dist/core/sqlite.js.map +1 -0
- package/dist/core/stage.d.ts +44 -0
- package/dist/core/stage.d.ts.map +1 -0
- package/dist/core/stage.js +304 -0
- package/dist/core/stage.js.map +1 -0
- package/dist/core/sync.d.ts +52 -0
- package/dist/core/sync.d.ts.map +1 -0
- package/dist/core/sync.js +423 -0
- package/dist/core/sync.js.map +1 -0
- package/dist/plugin/index.d.ts +29 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +88 -0
- package/dist/plugin/index.js.map +1 -0
- package/docs/MIGRATION.md +29 -0
- package/docs/ROADMAP.md +29 -0
- package/docs/SAFETY.md +31 -0
- package/examples/home-laptop.jsonc +31 -0
- package/examples/opencode-sync.overrides.jsonc +4 -0
- package/examples/work-laptop.jsonc +26 -0
- package/package.json +81 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { git, isGitRepo, parseNameStatus, parsePorcelainPaths, toGitError, } from "./git.js";
|
|
4
|
+
import { assertPrivateRemote, assertSafeTrackedFiles, createConfigBackup } from "./guard.js";
|
|
5
|
+
import { commitMessage, hostAlias } from "./host.js";
|
|
6
|
+
import { applyOverrides, configMatchesRepo, findConfigFile } from "./overrides.js";
|
|
7
|
+
import { getDatabasePath, getRoots } from "./paths.js";
|
|
8
|
+
import { previewSync } from "./preview.js";
|
|
9
|
+
import { LOCAL_RUNTIME_PATHS, assertPushLanded, assertRemoteReachable, ensureRepo, excludePathspecs, isLocalRuntimePath, unstageLocalRuntimePaths, writeRepoMetadata, } from "./repo.js";
|
|
10
|
+
import { emptySummary, silentReporter, summarize, totalChanges, } from "./reporter.js";
|
|
11
|
+
import { acknowledgeExport, exportSessions, importSessions } from "./sessions.js";
|
|
12
|
+
import { loadSettings, repoBranch, repoUrl } from "./settings.js";
|
|
13
|
+
import { sqliteAvailable } from "./sqlite.js";
|
|
14
|
+
import { createStageContext, normalizeConfigForRepo, prunePluginCache, stageIn, stageOut, } from "./stage.js";
|
|
15
|
+
class SyncError extends Error {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "SyncError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function prepare(options) {
|
|
22
|
+
const roots = options.roots ?? getRoots();
|
|
23
|
+
const settings = options.settings ?? loadSettings(roots.config);
|
|
24
|
+
const remote = repoUrl(settings);
|
|
25
|
+
if (!remote) {
|
|
26
|
+
throw new SyncError("No sync repository configured.\n" +
|
|
27
|
+
"Run `omds init` to create one, or `omds link <owner/repo>` to use an existing one.");
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
root: roots.config,
|
|
31
|
+
branch: repoBranch(settings),
|
|
32
|
+
remote,
|
|
33
|
+
settings,
|
|
34
|
+
roots,
|
|
35
|
+
reporter: options.reporter ?? silentReporter,
|
|
36
|
+
force: options.force ?? false,
|
|
37
|
+
dryRun: options.dryRun ?? false,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function toChangedFiles(result, excludeRuntime) {
|
|
41
|
+
return parseNameStatus(result.ok ? result.stdout : "")
|
|
42
|
+
.filter((entry) => !excludeRuntime || !isLocalRuntimePath(entry.path))
|
|
43
|
+
.map((entry) => ({ kind: entry.kind, path: entry.path }));
|
|
44
|
+
}
|
|
45
|
+
// ── Push ────────────────────────────────────────────────────────────────────
|
|
46
|
+
export async function push(options = {}) {
|
|
47
|
+
if (options.dryRun)
|
|
48
|
+
return previewSync("push", options, pushImpl);
|
|
49
|
+
return pushImpl(options);
|
|
50
|
+
}
|
|
51
|
+
async function pushImpl(options) {
|
|
52
|
+
const ctx = prepare(options);
|
|
53
|
+
const { root, branch, reporter } = ctx;
|
|
54
|
+
if (!fs.existsSync(root))
|
|
55
|
+
throw new SyncError(`Config directory not found: ${root}`);
|
|
56
|
+
ensureRepo({ root, remote: ctx.remote, branch });
|
|
57
|
+
assertRemoteReachable(root, branch, "Push", true);
|
|
58
|
+
assertPrivateRemote(ctx.remote, ctx.settings);
|
|
59
|
+
const fetched = git(["fetch", "origin", branch], { cwd: root });
|
|
60
|
+
if (fetched.ok && !ctx.force && countCommits(root, `HEAD..origin/${branch}`) > 0)
|
|
61
|
+
throw new SyncError("Remote changed: pull first before exporting local sessions");
|
|
62
|
+
writeRepoMetadata(root);
|
|
63
|
+
try {
|
|
64
|
+
const result = await runPush(ctx);
|
|
65
|
+
if (!ctx.dryRun && ctx.settings.sessions.enabled)
|
|
66
|
+
await acknowledgeExport(getDatabasePath(ctx.roots), root, ctx.settings.sessions.pathMappings);
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
// Staging rewrites the on-disk config into its shareable form. Whatever
|
|
71
|
+
// happens next — success, conflict, network failure — this machine must be
|
|
72
|
+
// left with its own effective configuration, not the stripped one.
|
|
73
|
+
applyOverrides(ctx.roots.config);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function runPush(ctx) {
|
|
77
|
+
const { root, branch, reporter } = ctx;
|
|
78
|
+
reporter.step("Staging local files");
|
|
79
|
+
stageIn(createStageContext(root, ctx.settings, reporter, ctx.force, ctx.roots));
|
|
80
|
+
const sessions = await maybeExportSessions(ctx);
|
|
81
|
+
git(["add", "-A"], { cwd: root });
|
|
82
|
+
unstageLocalRuntimePaths(root);
|
|
83
|
+
assertSafeTrackedFiles(root, ctx.settings);
|
|
84
|
+
const staged = git(["diff", "--cached", "--name-status"], { cwd: root });
|
|
85
|
+
const files = toChangedFiles(staged, true);
|
|
86
|
+
const summary = summarize(files);
|
|
87
|
+
if (files.length === 0) {
|
|
88
|
+
// No file changes, but a previous push may have failed after committing.
|
|
89
|
+
git(["fetch", "origin", branch], { cwd: root });
|
|
90
|
+
const ahead = countCommits(root, `origin/${branch}..HEAD`);
|
|
91
|
+
if (ahead > 0) {
|
|
92
|
+
if (ctx.dryRun) {
|
|
93
|
+
return done("push", false, summary, files, `${ahead} local commit(s) waiting to be pushed`, sessions);
|
|
94
|
+
}
|
|
95
|
+
reporter.step(`Pushing ${ahead} pending commit(s)`);
|
|
96
|
+
pushBranch(ctx, true);
|
|
97
|
+
return done("push", true, summary, files, `Pushed ${ahead} pending commit(s)`, sessions);
|
|
98
|
+
}
|
|
99
|
+
return done("push", false, summary, files, "Already up to date", sessions);
|
|
100
|
+
}
|
|
101
|
+
reporter.changes(summary, files);
|
|
102
|
+
if (ctx.dryRun) {
|
|
103
|
+
return done("push", true, summary, files, `${totalChanges(summary)} change(s) would be pushed`, sessions);
|
|
104
|
+
}
|
|
105
|
+
const alias = hostAlias(ctx.settings.machineAlias);
|
|
106
|
+
const commit = git(["commit", "-m", commitMessage("sync", alias)], { cwd: root });
|
|
107
|
+
if (!commit.ok)
|
|
108
|
+
throw toGitError("Commit", commit);
|
|
109
|
+
pushBranch(ctx, false);
|
|
110
|
+
return done("push", true, summary, files, `Pushed ${totalChanges(summary)} change(s)`, sessions);
|
|
111
|
+
}
|
|
112
|
+
function pushBranch(ctx, alreadyCommitted) {
|
|
113
|
+
const { root, branch, force, reporter } = ctx;
|
|
114
|
+
const hasRemoteBranch = git(["rev-parse", `origin/${branch}`], { cwd: root }).ok;
|
|
115
|
+
if (!hasRemoteBranch) {
|
|
116
|
+
const result = git(["push", "-u", "origin", branch], { cwd: root });
|
|
117
|
+
if (!result.ok)
|
|
118
|
+
throw toGitError("Push", result);
|
|
119
|
+
assertPushLanded(root, branch, "Push");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (force) {
|
|
123
|
+
// Refresh the tracking ref first. `--force-with-lease` compares against the
|
|
124
|
+
// local idea of the remote, so a stale ref makes it refuse the very
|
|
125
|
+
// overwrite the user explicitly asked for.
|
|
126
|
+
if (git(["fetch", "origin", branch], { cwd: root }).ok) {
|
|
127
|
+
const behind = countCommits(root, `HEAD..origin/${branch}`);
|
|
128
|
+
if (behind > 0) {
|
|
129
|
+
reporter.warn(`Overwriting ${behind} remote commit(s) that this machine does not have`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const result = git(["push", "--force-with-lease", "origin", branch], { cwd: root });
|
|
133
|
+
if (!result.ok)
|
|
134
|
+
throw toGitError("Force push", result);
|
|
135
|
+
assertPushLanded(root, branch, "Force push");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const rebase = git(["pull", "--rebase", "origin", branch], { cwd: root });
|
|
139
|
+
if (!rebase.ok) {
|
|
140
|
+
git(["rebase", "--abort"], { cwd: root });
|
|
141
|
+
throw new SyncError("The remote has changes that conflict with this machine.\n" +
|
|
142
|
+
"Run `omds pull` to take the remote version, or `omds push --force` to overwrite it.");
|
|
143
|
+
}
|
|
144
|
+
const result = git(["push", "origin", branch], { cwd: root });
|
|
145
|
+
if (!result.ok)
|
|
146
|
+
throw toGitError("Push", result);
|
|
147
|
+
assertPushLanded(root, branch, "Push");
|
|
148
|
+
void alreadyCommitted;
|
|
149
|
+
}
|
|
150
|
+
// ── Pull ────────────────────────────────────────────────────────────────────
|
|
151
|
+
export async function pull(options = {}) {
|
|
152
|
+
if (options.dryRun)
|
|
153
|
+
return previewSync("pull", options, pullImpl);
|
|
154
|
+
return pullImpl(options);
|
|
155
|
+
}
|
|
156
|
+
async function pullImpl(options) {
|
|
157
|
+
const ctx = prepare(options);
|
|
158
|
+
assertPrivateRemote(ctx.remote, ctx.settings);
|
|
159
|
+
try {
|
|
160
|
+
return await pullInternal(options);
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
applyOverrides(ctx.roots.config);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async function pullInternal(options) {
|
|
167
|
+
const ctx = prepare(options);
|
|
168
|
+
const { root, branch, reporter, force } = ctx;
|
|
169
|
+
fs.mkdirSync(root, { recursive: true });
|
|
170
|
+
createConfigBackup(root, ctx.roots.data);
|
|
171
|
+
if (!isGitRepo(root)) {
|
|
172
|
+
const state = ensureRepo({ root, remote: ctx.remote, branch });
|
|
173
|
+
if (state === "fresh") {
|
|
174
|
+
return done("pull", false, emptySummary(), [], "The sync repository is empty — push from another machine first.");
|
|
175
|
+
}
|
|
176
|
+
stageOut(createStageContext(root, ctx.settings, reporter, force, ctx.roots));
|
|
177
|
+
const sessions = await maybeImportSessions(ctx);
|
|
178
|
+
const count = git(["ls-files"], { cwd: root }).stdout.split("\n").filter(Boolean).length;
|
|
179
|
+
finishPull(ctx);
|
|
180
|
+
return done("pull", true, emptySummary(), [], `First pull complete — ${count} file(s)`, sessions, true);
|
|
181
|
+
}
|
|
182
|
+
ensureRepo({ root, remote: ctx.remote, branch });
|
|
183
|
+
reporter.step("Fetching from GitHub");
|
|
184
|
+
const fetched = git(["fetch", "origin", branch], { cwd: root });
|
|
185
|
+
if (!fetched.ok)
|
|
186
|
+
throw toGitError("Fetch", fetched);
|
|
187
|
+
const ahead = countCommits(root, `origin/${branch}..HEAD`);
|
|
188
|
+
if (!force && ahead > 0) {
|
|
189
|
+
throw new SyncError(`This machine has ${ahead} commit(s) that were never pushed.\n` +
|
|
190
|
+
"Run `omds push` first, or `omds pull --force` to discard them.");
|
|
191
|
+
}
|
|
192
|
+
const behind = countCommits(root, `HEAD..origin/${branch}`);
|
|
193
|
+
if (!force && behind === 0) {
|
|
194
|
+
// Nothing incoming, so the worktree does not need to be touched at all.
|
|
195
|
+
// Skipping the stash cycle here is what keeps a no-op pull fast on Windows.
|
|
196
|
+
const sessions = await maybeImportSessions(ctx);
|
|
197
|
+
return done("pull", Boolean(sessions?.imported), emptySummary(), [], "Already up to date", sessions, Boolean(sessions?.imported));
|
|
198
|
+
}
|
|
199
|
+
const stageCtx = createStageContext(root, ctx.settings, reporter, force, ctx.roots);
|
|
200
|
+
// The config file on disk carries this machine's overrides, so it always
|
|
201
|
+
// differs from HEAD. Rewinding it to the committed form first means the
|
|
202
|
+
// worktree is clean and the pull never has to stash-and-merge the one file
|
|
203
|
+
// that would conflict on every single run.
|
|
204
|
+
normalizeConfigForRepo(stageCtx);
|
|
205
|
+
const stashed = prepareWorktree(ctx);
|
|
206
|
+
const nameStatus = git(["diff", "--name-status", "HEAD", `origin/${branch}`], { cwd: root });
|
|
207
|
+
const files = toChangedFiles(nameStatus, true);
|
|
208
|
+
const summary = summarize(files);
|
|
209
|
+
if (ctx.dryRun) {
|
|
210
|
+
if (stashed)
|
|
211
|
+
restoreStash(ctx);
|
|
212
|
+
return done("pull", files.length > 0, summary, files, `${totalChanges(summary)} change(s) would be pulled`);
|
|
213
|
+
}
|
|
214
|
+
const reset = git(["reset", "--hard", `origin/${branch}`], { cwd: root });
|
|
215
|
+
if (!reset.ok) {
|
|
216
|
+
if (stashed)
|
|
217
|
+
restoreStash(ctx);
|
|
218
|
+
throw toGitError("Reset", reset);
|
|
219
|
+
}
|
|
220
|
+
if (stashed)
|
|
221
|
+
restoreStash(ctx);
|
|
222
|
+
stageOut(createStageContext(root, ctx.settings, reporter, force, ctx.roots));
|
|
223
|
+
const sessions = await maybeImportSessions(ctx);
|
|
224
|
+
finishPull(ctx);
|
|
225
|
+
reporter.changes(summary, files);
|
|
226
|
+
return done("pull", files.length > 0, summary, files, files.length > 0 ? `Pulled ${totalChanges(summary)} change(s)` : "Already up to date", sessions, true);
|
|
227
|
+
}
|
|
228
|
+
/** Re-apply machine-local settings and clean up caches after a pull. */
|
|
229
|
+
function finishPull(ctx) {
|
|
230
|
+
if (applyOverrides(ctx.roots.config)) {
|
|
231
|
+
ctx.reporter.detail("Re-applied per-machine overrides");
|
|
232
|
+
}
|
|
233
|
+
const pruned = prunePluginCache(ctx.roots.config, ctx.roots.cache);
|
|
234
|
+
if (pruned > 0)
|
|
235
|
+
ctx.reporter.detail(`Removed ${pruned} stale plugin package(s) from the cache`);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get the worktree into a state where a hard reset is safe.
|
|
239
|
+
*
|
|
240
|
+
* Without `force` local edits are stashed and restored afterwards. With
|
|
241
|
+
* `force` they are discarded — but the runtime paths are still protected,
|
|
242
|
+
* because `git clean` would otherwise happily delete the user's database.
|
|
243
|
+
*/
|
|
244
|
+
function prepareWorktree(ctx) {
|
|
245
|
+
const { root, force } = ctx;
|
|
246
|
+
git(["add", "-A"], { cwd: root });
|
|
247
|
+
unstageLocalRuntimePaths(root);
|
|
248
|
+
const hasStaged = git(["diff", "--cached", "--stat"], { cwd: root }).stdout.length > 0;
|
|
249
|
+
const untracked = git(["ls-files", "--others", "--exclude-standard", "--", ".", ...excludePathspecs()], { cwd: root });
|
|
250
|
+
const hasUntracked = untracked.ok && untracked.stdout.length > 0;
|
|
251
|
+
if (!hasStaged && !hasUntracked)
|
|
252
|
+
return false;
|
|
253
|
+
git(["reset", "HEAD"], { cwd: root });
|
|
254
|
+
if (force) {
|
|
255
|
+
git(["checkout", "--", "."], { cwd: root });
|
|
256
|
+
// `-e` takes gitignore-style patterns and *keeps* the matches. Using
|
|
257
|
+
// `:(exclude)` pathspecs here would invert the meaning and delete
|
|
258
|
+
// everything except the runtime paths.
|
|
259
|
+
git(["clean", "-fd", ...LOCAL_RUNTIME_PATHS.flatMap((p) => ["-e", p])], { cwd: root });
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
git(["add", "-A"], { cwd: root });
|
|
263
|
+
unstageLocalRuntimePaths(root);
|
|
264
|
+
const stash = git([
|
|
265
|
+
"stash",
|
|
266
|
+
"push",
|
|
267
|
+
"--include-untracked",
|
|
268
|
+
"-m",
|
|
269
|
+
"omds auto-stash",
|
|
270
|
+
"--",
|
|
271
|
+
".",
|
|
272
|
+
...excludePathspecs(),
|
|
273
|
+
], { cwd: root });
|
|
274
|
+
if (!stash.ok)
|
|
275
|
+
throw toGitError("Stash", stash);
|
|
276
|
+
return !/No local changes to save/i.test(stash.stdout);
|
|
277
|
+
}
|
|
278
|
+
function restoreStash(ctx) {
|
|
279
|
+
const { root } = ctx;
|
|
280
|
+
const pop = git(["stash", "pop"], { cwd: root });
|
|
281
|
+
if (pop.ok)
|
|
282
|
+
return;
|
|
283
|
+
// A conflicting pop leaves the stash in place and the worktree half-merged.
|
|
284
|
+
// A half-merged opencode.jsonc stops OpenCode from starting, so reset to a
|
|
285
|
+
// known-good tree and tell the user exactly where their work still is.
|
|
286
|
+
git(["reset", "--hard", "HEAD"], { cwd: root });
|
|
287
|
+
throw new SyncError("Local changes conflicted with the incoming version.\n" +
|
|
288
|
+
"The worktree was rolled back to a clean state and your changes are still saved in the stash:\n" +
|
|
289
|
+
` cd "${root}"\n` +
|
|
290
|
+
" git stash list\n" +
|
|
291
|
+
" git stash show -p stash@{0} # inspect\n" +
|
|
292
|
+
" git stash apply --index stash@{0} # restore manually\n" +
|
|
293
|
+
" git stash drop stash@{0} # discard\n" +
|
|
294
|
+
"Or re-run with --force to take the remote version.");
|
|
295
|
+
}
|
|
296
|
+
// ── Sessions ────────────────────────────────────────────────────────────────
|
|
297
|
+
async function maybeExportSessions(ctx) {
|
|
298
|
+
if (!ctx.settings.sessions.enabled)
|
|
299
|
+
return undefined;
|
|
300
|
+
if (!(await sqliteAvailable())) {
|
|
301
|
+
ctx.reporter.warn("Session sync is enabled but no SQLite driver is available — skipping.");
|
|
302
|
+
return undefined;
|
|
303
|
+
}
|
|
304
|
+
const database = getDatabasePath(ctx.roots);
|
|
305
|
+
ctx.reporter.step("Exporting sessions");
|
|
306
|
+
const result = await exportSessions(database, ctx.root, ctx.settings.sessions, ctx.reporter);
|
|
307
|
+
if (result.conflicts?.length)
|
|
308
|
+
throw new SyncError(`Session export conflicts: ${result.conflicts.join(", ")}. No push was performed.`);
|
|
309
|
+
for (const skipped of result.skippedTooLarge) {
|
|
310
|
+
ctx.reporter.warn(`Skipped "${skipped.title || skipped.id}" — ${formatBytes(skipped.bytes)} exceeds maxSessionBytes`);
|
|
311
|
+
}
|
|
312
|
+
if (result.written.length > 0) {
|
|
313
|
+
ctx.reporter.detail(`Exported ${result.written.length} session(s)`);
|
|
314
|
+
}
|
|
315
|
+
return { exported: result.written.length, skipped: result.skippedTooLarge.length };
|
|
316
|
+
}
|
|
317
|
+
async function maybeImportSessions(ctx) {
|
|
318
|
+
if (!ctx.settings.sessions.enabled)
|
|
319
|
+
return undefined;
|
|
320
|
+
if (!fs.existsSync(path.join(ctx.root, "_sessions")))
|
|
321
|
+
return undefined;
|
|
322
|
+
if (!(await sqliteAvailable())) {
|
|
323
|
+
ctx.reporter.warn("Session shards were pulled but no SQLite driver is available to import them.");
|
|
324
|
+
return undefined;
|
|
325
|
+
}
|
|
326
|
+
ctx.reporter.step("Importing sessions");
|
|
327
|
+
const result = await importSessions(getDatabasePath(ctx.roots), ctx.root, ctx.reporter, ctx.settings.sessions);
|
|
328
|
+
for (const failure of result.failed) {
|
|
329
|
+
ctx.reporter.warn(`Could not import ${failure.file}: ${failure.reason}`);
|
|
330
|
+
}
|
|
331
|
+
if (result.failed.length || result.conflicts.length)
|
|
332
|
+
throw new SyncError(`Session import stopped: ${result.failed.map((f) => f.reason).join("; ")} ${result.conflicts.map((c) => c.sessionId + ": " + c.reason).join("; ")}`);
|
|
333
|
+
if (result.imported > 0)
|
|
334
|
+
ctx.reporter.detail(`Imported ${result.imported} session(s)`);
|
|
335
|
+
return {
|
|
336
|
+
imported: result.imported,
|
|
337
|
+
skipped: result.skippedOlder,
|
|
338
|
+
conflicts: result.conflicts.length,
|
|
339
|
+
backup: result.backup,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
export function status(options = {}) {
|
|
343
|
+
const roots = options.roots ?? getRoots();
|
|
344
|
+
const settings = options.settings ?? loadSettings(roots.config);
|
|
345
|
+
const branch = repoBranch(settings);
|
|
346
|
+
const remote = repoUrl(settings);
|
|
347
|
+
const root = roots.config;
|
|
348
|
+
const base = {
|
|
349
|
+
configured: Boolean(remote),
|
|
350
|
+
remote,
|
|
351
|
+
branch,
|
|
352
|
+
initialized: false,
|
|
353
|
+
ahead: 0,
|
|
354
|
+
behind: 0,
|
|
355
|
+
dirty: 0,
|
|
356
|
+
sessionsEnabled: settings.sessions.enabled,
|
|
357
|
+
sessionShards: countShards(root),
|
|
358
|
+
overridesActive: fs.existsSync(path.join(root, "opencode-sync.overrides.jsonc")),
|
|
359
|
+
machineAlias: hostAlias(settings.machineAlias),
|
|
360
|
+
};
|
|
361
|
+
if (!remote || !isGitRepo(root))
|
|
362
|
+
return base;
|
|
363
|
+
base.initialized = true;
|
|
364
|
+
const fetched = git(["fetch", "origin", branch], { cwd: root });
|
|
365
|
+
if (!fetched.ok)
|
|
366
|
+
throw toGitError("Status fetch", fetched);
|
|
367
|
+
base.ahead = countCommits(root, `origin/${branch}..HEAD`);
|
|
368
|
+
base.behind = countCommits(root, `HEAD..origin/${branch}`);
|
|
369
|
+
const dirty = git(["status", "--porcelain", "--", ".", ...excludePathspecs()], { cwd: root });
|
|
370
|
+
base.dirty = dirty.ok ? countRealChanges(root, dirty.stdout) : 0;
|
|
371
|
+
const log = git(["log", "-1", "--format=%s%n%cI"], { cwd: root });
|
|
372
|
+
if (log.ok) {
|
|
373
|
+
const [subject, date] = log.stdout.split("\n");
|
|
374
|
+
base.lastCommit = subject;
|
|
375
|
+
base.lastCommitDate = date;
|
|
376
|
+
}
|
|
377
|
+
return base;
|
|
378
|
+
}
|
|
379
|
+
function countShards(root) {
|
|
380
|
+
const dir = path.join(root, "_sessions");
|
|
381
|
+
if (!fs.existsSync(dir))
|
|
382
|
+
return 0;
|
|
383
|
+
return fs.readdirSync(dir).filter((f) => f.endsWith(".json.gz")).length;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Count genuinely uncommitted files.
|
|
387
|
+
*
|
|
388
|
+
* With overrides in use the config file always differs from the committed
|
|
389
|
+
* version, so a raw `git status` would report one modified file forever. That
|
|
390
|
+
* entry is dropped when the only difference is the overrides themselves.
|
|
391
|
+
*/
|
|
392
|
+
function countRealChanges(root, porcelain) {
|
|
393
|
+
const paths = parsePorcelainPaths(porcelain);
|
|
394
|
+
if (paths.length === 0)
|
|
395
|
+
return 0;
|
|
396
|
+
const configFile = findConfigFile(root);
|
|
397
|
+
if (!configFile)
|
|
398
|
+
return paths.length;
|
|
399
|
+
const relative = path.relative(root, configFile).split(path.sep).join("/");
|
|
400
|
+
const head = git(["show", `HEAD:${relative}`], { cwd: root });
|
|
401
|
+
if (!configMatchesRepo(root, head.ok ? head.stdout : undefined))
|
|
402
|
+
return paths.length;
|
|
403
|
+
return paths.filter((file) => file !== relative).length;
|
|
404
|
+
}
|
|
405
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
406
|
+
function countCommits(root, range) {
|
|
407
|
+
const result = git(["rev-list", "--count", range], { cwd: root });
|
|
408
|
+
return result.ok ? Number(result.stdout) || 0 : 0;
|
|
409
|
+
}
|
|
410
|
+
function done(action, changed, summary, files, message, sessions, restartRequired = false) {
|
|
411
|
+
return { action, changed, summary, files, message, sessions, restartRequired };
|
|
412
|
+
}
|
|
413
|
+
export function formatBytes(bytes) {
|
|
414
|
+
if (bytes < 1024)
|
|
415
|
+
return `${bytes} B`;
|
|
416
|
+
if (bytes < 1024 * 1024)
|
|
417
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
418
|
+
if (bytes < 1024 * 1024 * 1024)
|
|
419
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
420
|
+
return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
|
421
|
+
}
|
|
422
|
+
export { SyncError };
|
|
423
|
+
//# sourceMappingURL=sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/core/sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export default function OpencodeMultideviceSync(ctx: {
|
|
2
|
+
client?: any;
|
|
3
|
+
directory?: string;
|
|
4
|
+
}): Promise<{
|
|
5
|
+
event?: undefined;
|
|
6
|
+
tool?: undefined;
|
|
7
|
+
} | {
|
|
8
|
+
event: ({ event }: {
|
|
9
|
+
event: {
|
|
10
|
+
type: string;
|
|
11
|
+
};
|
|
12
|
+
}) => Promise<void>;
|
|
13
|
+
tool: {
|
|
14
|
+
opencode_sync: {
|
|
15
|
+
description: string;
|
|
16
|
+
args: {
|
|
17
|
+
action: import("zod").ZodEnum<{
|
|
18
|
+
push: "push";
|
|
19
|
+
status: "status";
|
|
20
|
+
pull: "pull";
|
|
21
|
+
}>;
|
|
22
|
+
};
|
|
23
|
+
execute(args: {
|
|
24
|
+
action: "push" | "status" | "pull";
|
|
25
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
}>;
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugin/index.ts"],"names":[],"mappings":"AAqBA,wBAA8B,uBAAuB,CAAC,GAAG,EAAE;IAAE,MAAM,CAAC,EAAE,GAAG,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE;;;;uBAkClE;QAAE,KAAK,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;;;;;;;;;;;;;;;;GAgCvD"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { tool } from "@opencode-ai/plugin";
|
|
3
|
+
import { withSyncLock } from "../core/lock.js";
|
|
4
|
+
import { getRoots } from "../core/paths.js";
|
|
5
|
+
import { CollectingReporter } from "../core/reporter.js";
|
|
6
|
+
import { loadSettings, repoUrl, settingsPath } from "../core/settings.js";
|
|
7
|
+
import { pull, push, status } from "../core/sync.js";
|
|
8
|
+
const startups = new Map();
|
|
9
|
+
const timers = new Map();
|
|
10
|
+
async function notify(client, message, failed = false) {
|
|
11
|
+
try {
|
|
12
|
+
await client?.app?.log?.({
|
|
13
|
+
body: { service: "opencode-multidevice-sync", level: failed ? "error" : "info", message },
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
catch { }
|
|
17
|
+
try {
|
|
18
|
+
await client?.tui?.showToast?.({
|
|
19
|
+
body: { title: "OpenCode Multidevice Sync", message, variant: failed ? "error" : "success" },
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
catch { }
|
|
23
|
+
}
|
|
24
|
+
export default async function OpencodeMultideviceSync(ctx) {
|
|
25
|
+
const roots = getRoots();
|
|
26
|
+
const client = ctx.client;
|
|
27
|
+
if (!fs.existsSync(settingsPath(roots.config)) || !repoUrl(loadSettings(roots.config)))
|
|
28
|
+
return {};
|
|
29
|
+
async function perform(action, announce) {
|
|
30
|
+
try {
|
|
31
|
+
const reporter = new CollectingReporter();
|
|
32
|
+
const result = await withSyncLock(roots.config, { waitMs: 30000 }, () => action === "push" ? push({ roots, reporter }) : pull({ roots, reporter }));
|
|
33
|
+
const message = `${action}: ${result.message}` +
|
|
34
|
+
(result.restartRequired ? ". Restart OpenCode to reload imported data/config." : "");
|
|
35
|
+
if (announce || result.changed)
|
|
36
|
+
await notify(client, message);
|
|
37
|
+
else
|
|
38
|
+
try {
|
|
39
|
+
await client?.app?.log?.({
|
|
40
|
+
body: { service: "opencode-multidevice-sync", level: "info", message },
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch { }
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
await notify(client, `${action} failed: ${error.message}`, true);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
let startup = startups.get(roots.config);
|
|
52
|
+
if (!startup) {
|
|
53
|
+
startup = loadSettings(roots.config).autoPullOnStartup
|
|
54
|
+
? perform("pull", false)
|
|
55
|
+
: Promise.resolve(true);
|
|
56
|
+
startups.set(roots.config, startup);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
event: async ({ event }) => {
|
|
60
|
+
if (event.type !== "session.idle" || !loadSettings(roots.config).autoPushOnIdle)
|
|
61
|
+
return;
|
|
62
|
+
const previous = timers.get(roots.config);
|
|
63
|
+
if (previous)
|
|
64
|
+
clearTimeout(previous);
|
|
65
|
+
timers.set(roots.config, setTimeout(async () => {
|
|
66
|
+
timers.delete(roots.config);
|
|
67
|
+
if (await startup)
|
|
68
|
+
await perform("push", true);
|
|
69
|
+
else
|
|
70
|
+
await notify(client, "Auto-push paused: initial pull failed. Resolve the error and restart OpenCode.", true);
|
|
71
|
+
}, 1000));
|
|
72
|
+
},
|
|
73
|
+
tool: {
|
|
74
|
+
opencode_sync: tool({
|
|
75
|
+
description: "Sync this OpenCode installation with its configured private repository. Report status, push, or pull. Never resolves conflicts with force.",
|
|
76
|
+
args: { action: tool.schema.enum(["status", "push", "pull"]) },
|
|
77
|
+
async execute(args) {
|
|
78
|
+
if (args.action === "status")
|
|
79
|
+
return JSON.stringify(status({ roots }), null, 2);
|
|
80
|
+
return (await perform(args.action, true))
|
|
81
|
+
? "Sync completed; see sync status and notifications."
|
|
82
|
+
: "Sync failed. Review the error notification/log; data was not force-overwritten.";
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/plugin/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Migrating from opencode-github-sync 3.0.1
|
|
2
|
+
|
|
3
|
+
Do not migrate both devices blindly. This is an early preview. Test with isolated roots and disposable databases first.
|
|
4
|
+
|
|
5
|
+
1. Finish work on the source computer and verify its last successful upstream push. Close OpenCode on both computers.
|
|
6
|
+
2. Preserve a consistent SQLite backup, your configuration, and the sync repository history outside the synchronized directory. Never copy only a live DB while ignoring its WAL.
|
|
7
|
+
3. Keep the existing PRIVATE sync repository; do not create a public replacement for your personal data.
|
|
8
|
+
4. Install the same reviewed multidevice-sync tarball on both machines. The new CLI is `omds`; it does not take over `opencode-sync`.
|
|
9
|
+
5. Keep `opencode-sync.jsonc` and `opencode-sync.overrides.jsonc`. Set `includeCredentials:false`, `includeState:false`, and initially both automatic flags to false.
|
|
10
|
+
6. Remove the upstream package from the OpenCode plugin list. Move any `opencode-github-sync-safe.js` wrapper outside the plugins directory. Do not delete backups or credential files.
|
|
11
|
+
7. Add the new installed plugin's absolute file URL. Do not put machine-specific paths into shared configuration without a local override.
|
|
12
|
+
8. Run `omds doctor`, then `omds pull --dry-run`. Review all failures and conflicts. The first fork sync establishes content baselines only when local and remote history agree.
|
|
13
|
+
9. Run `omds pull` and `omds push --dry-run`. Compare session/message/part counts and inspect at least one full conversation. A reserialization diff is expected once; repeated unchanged exports should not change shard bytes.
|
|
14
|
+
10. If a legacy shard differs and no common baseline exists, the fork refuses to choose automatically. Preserve both copies and reconcile on disposable databases. Do not use force as an improvised data-repair tool.
|
|
15
|
+
11. Repeat the checks on the other computer before enabling automatic operation. Keep one active device and wait for successful push before switching.
|
|
16
|
+
|
|
17
|
+
## Isolation
|
|
18
|
+
|
|
19
|
+
The library supports explicit `roots`. CLI isolation variables are `SYNC_CONFIG_ROOT`, `SYNC_DATA_ROOT`, `SYNC_STATE_ROOT`, `SYNC_AGENTS_ROOT`, and `SYNC_CACHE_ROOT`. Use a LOCAL disposable Git remote for automated tests and keep secrets out of fixtures.
|
|
20
|
+
|
|
21
|
+
## Paths
|
|
22
|
+
|
|
23
|
+
In `sessions.pathMappings`, `from` is the canonical shared path and `to` is this machine's local path. Import converts from-to; export converts back. Keep mappings one-to-one. The synthetic `global` project must already exist locally and is never overwritten from a shard.
|
|
24
|
+
|
|
25
|
+
## Rolling back the application
|
|
26
|
+
|
|
27
|
+
Close OpenCode, remove only the new plugin entry, and restore the prior configuration from backup. Keep the known-good patched upstream package until both devices pass acceptance tests. Database restoration is separate: `omds restore BACKUP --offline` refuses journals and preserves a safety backup. Never delete WAL/SHM files to bypass that refusal.
|
|
28
|
+
|
|
29
|
+
The source repo is public; personal configuration, sessions and backups are not part of this migration guide or release artifacts.
|
package/docs/ROADMAP.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
## Included in the 0.1.0 preview
|
|
4
|
+
|
|
5
|
+
- Source-level fixes for upstream reports #2, #3 and #4.
|
|
6
|
+
- Deterministic shards and content-based conflict detection.
|
|
7
|
+
- Whole-batch import transaction and integrity validation.
|
|
8
|
+
- Verified backups, guarded offline restore, doctor and isolated previews.
|
|
9
|
+
- Canonical/local path mapping and credential-file opt-out defaults.
|
|
10
|
+
- Synthetic fixtures, CLI smoke tests and cross-platform CI.
|
|
11
|
+
|
|
12
|
+
## Required before recommending broad production use
|
|
13
|
+
|
|
14
|
+
- Install the packaged fork on both real Windows devices and complete a round-trip Desktop acceptance test.
|
|
15
|
+
- Independent review of session schema coverage and Git conflict/recovery paths.
|
|
16
|
+
- Broader OpenCode version compatibility matrix and explicit schema adapters.
|
|
17
|
+
- More crash-injection tests, disk-full tests, large-history soak tests and plugin lifecycle tests.
|
|
18
|
+
- Reproducible npm registry publishing with maintainer authorization/provenance.
|
|
19
|
+
|
|
20
|
+
## Planned, not implemented or promised by 0.1.0
|
|
21
|
+
|
|
22
|
+
- Distributed machine ownership lease with expiry and safe takeover.
|
|
23
|
+
- A conflict viewer and explicit reconciliation commands.
|
|
24
|
+
- Fine-grained config-only / sessions-only sync selection.
|
|
25
|
+
- Session deletion tombstones and deliberate remote retention policies.
|
|
26
|
+
- Full workspace/path portability beyond top-level mapped fields.
|
|
27
|
+
- Optional encryption and stronger secret scanning.
|
|
28
|
+
|
|
29
|
+
No automatic upgrades of a user's working installation are part of CI or package installation.
|