@askalf/claude-sync 0.0.3 → 0.2.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/dist/ssh.js ADDED
@@ -0,0 +1,216 @@
1
+ /**
2
+ * SSH transport — `.ccsync` files live in a directory on a remote host,
3
+ * reached over SSH. The target audience is "I run Claude Code on a dev
4
+ * server AND on my laptop"; the round-trip validation showed manual `scp`
5
+ * shuttling was the real friction, so this makes the remote box a
6
+ * first-class transport.
7
+ *
8
+ * Mechanics: every operation is one (or a few) `ssh host <command>` calls,
9
+ * where <command> is a small POSIX shell pipeline (mkdir / cat / mv /
10
+ * find). The remote layout is identical to the filesystem transport —
11
+ * `<dir>/<encoded-project-key>/<sessionId>-<machine>.ccsync` — so the two
12
+ * backends are wire-compatible (a server on `fs` and a laptop on `ssh`
13
+ * pointed at the same dir sync cleanly).
14
+ *
15
+ * Quoting: ssh concatenates its command args with spaces and hands the
16
+ * result to the remote login shell, which re-parses it. So every
17
+ * interpolated value is POSIX single-quoted via `shQuote`; file *lists*
18
+ * are never interpolated (we `find` them remotely and read by path).
19
+ *
20
+ * Testability: the actual `ssh` invocation sits behind `SshExecutor`.
21
+ * Tests inject a fake executor (to assert command construction) or a
22
+ * local-shell executor (to run the real pipelines against a temp dir
23
+ * without an sshd). `realSshExecutor` is the production one.
24
+ */
25
+ import { spawnSync } from 'node:child_process';
26
+ import { createHash } from 'node:crypto';
27
+ import { tmpdir, homedir } from 'node:os';
28
+ import { join } from 'node:path';
29
+ import { encodeProjectDir } from './project.js';
30
+ import { parseCcsync, serializeCcsync } from './format.js';
31
+ /** POSIX single-quote `s` for safe interpolation into a remote shell
32
+ * command: wrap in '…' and rewrite each embedded ' as '\'' . */
33
+ export function shQuote(s) {
34
+ return `'${s.split("'").join("'\\''")}'`;
35
+ }
36
+ /** Strip leading and/or trailing `/` with a linear two-pointer scan — NOT
37
+ * `/\/+$/`, which is a polynomial-ReDoS vector on the config-derived path
38
+ * (CodeQL flags it). */
39
+ function trimSlashes(s, leading, trailing) {
40
+ let lo = 0;
41
+ let hi = s.length;
42
+ if (leading)
43
+ while (lo < hi && s[lo] === '/')
44
+ lo++;
45
+ if (trailing)
46
+ while (hi > lo && s[hi - 1] === '/')
47
+ hi--;
48
+ return s.slice(lo, hi);
49
+ }
50
+ /** Join remote (POSIX) path segments with `/`, trimming stray slashes but
51
+ * preserving a leading `/` on the first segment (absolute remote dirs). */
52
+ export function posixJoin(...parts) {
53
+ return parts
54
+ .map((p, i) => (i === 0 ? trimSlashes(p, false, true) : trimSlashes(p, true, true)))
55
+ .filter((p) => p.length > 0)
56
+ .join('/');
57
+ }
58
+ function expandHome(p) {
59
+ return p === '~' || p.startsWith('~/') ? join(homedir(), p.slice(1)) : p;
60
+ }
61
+ /** Build the `ssh` argv (everything before the remote command). Pure +
62
+ * exported so it can be unit-tested without spawning. */
63
+ export function buildSshArgs(cfg, controlPath) {
64
+ const args = [];
65
+ if (cfg.port)
66
+ args.push('-p', String(cfg.port));
67
+ if (cfg.identityFile)
68
+ args.push('-i', expandHome(cfg.identityFile));
69
+ // BatchMode: never block on a password/passphrase prompt — fail fast so
70
+ // `watch` / scripts don't hang. ConnectTimeout bounds an unreachable host.
71
+ args.push('-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10');
72
+ if (controlPath) {
73
+ // Multiplex over one connection so the many small round-trips a single
74
+ // pull makes don't each pay a full TCP+auth handshake.
75
+ args.push('-o', 'ControlMaster=auto', '-o', `ControlPath=${controlPath}`, '-o', 'ControlPersist=60');
76
+ }
77
+ args.push(cfg.user ? `${cfg.user}@${cfg.host}` : cfg.host);
78
+ return args;
79
+ }
80
+ /** ControlMaster socket path for connection reuse — POSIX only (Windows
81
+ * OpenSSH historically lacks Unix-socket multiplexing, so we skip it and
82
+ * pay per-call connection cost there). */
83
+ function controlPathFor(cfg) {
84
+ if (process.platform === 'win32')
85
+ return undefined;
86
+ const id = createHash('sha256')
87
+ .update(`${cfg.user ?? ''}@${cfg.host}:${cfg.port ?? 22}`)
88
+ .digest('hex')
89
+ .slice(0, 12);
90
+ return join(tmpdir(), `cs-ssh-${id}`);
91
+ }
92
+ /** Production executor: shells out to the system `ssh`. */
93
+ export function realSshExecutor(cfg) {
94
+ const baseArgs = buildSshArgs(cfg, controlPathFor(cfg));
95
+ return {
96
+ run(remoteCommand, input) {
97
+ const r = spawnSync('ssh', [...baseArgs, remoteCommand], {
98
+ input,
99
+ encoding: 'utf-8',
100
+ maxBuffer: 64 * 1024 * 1024,
101
+ });
102
+ if (r.error)
103
+ return { status: 1, stdout: '', stderr: r.error.message };
104
+ return { status: r.status ?? 1, stdout: r.stdout ?? '', stderr: r.stderr ?? '' };
105
+ },
106
+ };
107
+ }
108
+ /** Parse `find -printf '%T@\t%f\n'` output into {mtimeMs, name} rows. */
109
+ function parseFindRows(out) {
110
+ const rows = [];
111
+ for (const line of out.split('\n')) {
112
+ if (!line)
113
+ continue;
114
+ const tab = line.indexOf('\t');
115
+ if (tab < 0)
116
+ continue;
117
+ const mt = parseFloat(line.slice(0, tab));
118
+ const name = line.slice(tab + 1);
119
+ if (!name || !Number.isFinite(mt))
120
+ continue;
121
+ rows.push({ mtimeMs: mt * 1000, name });
122
+ }
123
+ return rows;
124
+ }
125
+ export class SshTransport {
126
+ cfg;
127
+ exec;
128
+ constructor(cfg, exec) {
129
+ this.cfg = cfg;
130
+ this.exec = exec ?? realSshExecutor(cfg);
131
+ }
132
+ describe() {
133
+ const who = this.cfg.user ? `${this.cfg.user}@${this.cfg.host}` : this.cfg.host;
134
+ const port = this.cfg.port && this.cfg.port !== 22 ? `:${this.cfg.port}` : '';
135
+ return `ssh ${who}${port} ${this.cfg.dir}`;
136
+ }
137
+ push(file) {
138
+ const enc = encodeProjectDir(file.projectKey);
139
+ const dir = posixJoin(this.cfg.dir, enc);
140
+ const name = `${file.sessionId}-${file.machineName}.ccsync`;
141
+ const path = posixJoin(dir, name);
142
+ const tmp = `${path}.tmp`;
143
+ // mkdir -p, stream content to a tmp file via stdin, atomic rename.
144
+ const cmd = `mkdir -p ${shQuote(dir)} && cat > ${shQuote(tmp)} && mv -f ${shQuote(tmp)} ${shQuote(path)}`;
145
+ const r = this.exec.run(cmd, serializeCcsync(file));
146
+ if (r.status !== 0) {
147
+ throw new Error(`ssh push failed (${this.describe()}): ${r.stderr.trim() || `exit ${r.status}`}`);
148
+ }
149
+ return `${this.describe()} → ${enc}/${name}`;
150
+ }
151
+ listProjectKeys() {
152
+ const cmd = `find ${shQuote(this.cfg.dir)} -mindepth 1 -maxdepth 1 -type d -printf '%f\\n' 2>/dev/null`;
153
+ const r = this.exec.run(cmd);
154
+ return r.stdout.split('\n').map((s) => s.trim()).filter(Boolean).sort();
155
+ }
156
+ listEntries(projectKey, selfMachineName) {
157
+ const enc = encodeProjectDir(projectKey);
158
+ const dir = posixJoin(this.cfg.dir, enc);
159
+ const listCmd = `find ${shQuote(dir)} -mindepth 1 -maxdepth 1 -type f -name '*.ccsync' ` +
160
+ `-printf '%T@\\t%f\\n' 2>/dev/null`;
161
+ const rows = parseFindRows(this.exec.run(listCmd).stdout);
162
+ const entries = [];
163
+ for (const row of rows) {
164
+ const content = this.readFile(posixJoin(dir, row.name));
165
+ if (content == null)
166
+ continue;
167
+ let file;
168
+ try {
169
+ file = parseCcsync(content);
170
+ }
171
+ catch {
172
+ continue; // malformed / mid-write — skip quietly, like the fs transport
173
+ }
174
+ if (file.machineName === selfMachineName)
175
+ continue;
176
+ entries.push({ path: `${this.describe()}/${enc}/${row.name}`, file, mtime: row.mtimeMs });
177
+ }
178
+ entries.sort((a, b) => b.mtime - a.mtime);
179
+ return entries;
180
+ }
181
+ listMachines() {
182
+ const counts = new Map();
183
+ // Files live exactly two levels under the root: <dir>/<key>/<file>.
184
+ const listCmd = `find ${shQuote(this.cfg.dir)} -mindepth 2 -maxdepth 2 -type f -name '*.ccsync' 2>/dev/null`;
185
+ const paths = this.exec.run(listCmd).stdout.split('\n').map((s) => s.trim()).filter(Boolean);
186
+ for (const p of paths) {
187
+ const content = this.readFile(p);
188
+ if (content == null)
189
+ continue;
190
+ try {
191
+ const file = parseCcsync(content);
192
+ counts.set(file.machineName, (counts.get(file.machineName) ?? 0) + 1);
193
+ }
194
+ catch {
195
+ // skip malformed
196
+ }
197
+ }
198
+ return counts;
199
+ }
200
+ health() {
201
+ const cmd = `test -d ${shQuote(this.cfg.dir)} && echo DIR_OK || echo DIR_MISSING`;
202
+ const r = this.exec.run(cmd);
203
+ if (!r.stdout.includes('DIR_OK') && !r.stdout.includes('DIR_MISSING')) {
204
+ return { ok: false, detail: `unreachable: ${r.stderr.trim() || `ssh exit ${r.status}`}` };
205
+ }
206
+ if (r.stdout.includes('DIR_MISSING')) {
207
+ return { ok: true, detail: `${this.describe()} (remote dir absent — created on first push)` };
208
+ }
209
+ return { ok: true, detail: this.describe() };
210
+ }
211
+ readFile(remotePath) {
212
+ const r = this.exec.run(`cat -- ${shQuote(remotePath)} 2>/dev/null`);
213
+ return r.status === 0 ? r.stdout : null;
214
+ }
215
+ }
216
+ //# sourceMappingURL=ssh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh.js","sourceRoot":"","sources":["../src/ssh.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,7 @@
18
18
  * `pull` picks the newest non-self file per session id.
19
19
  */
20
20
  import type { CcsyncFile } from './format.js';
21
+ import type { TransportConfig } from './config.js';
21
22
  export declare function projectSubdir(syncDir: string, projectKey: string): string;
22
23
  /** Write a .ccsync file into the project's transport subdir. The
23
24
  * filename includes the machine name so the receiver can tell who
@@ -34,6 +35,45 @@ export interface TransportEntry {
34
35
  * `selfMachineName`) are omitted — pull is for receiving OTHER
35
36
  * machines' work, not echoing your own. */
36
37
  export declare function listTransport(syncDir: string, projectKey: string, selfMachineName: string): TransportEntry[];
38
+ /** Tally `machineName` → file count across every `.ccsync` in the
39
+ * transport (all projects). Used by `doctor` to catch the
40
+ * duplicate-machineName footgun: `pull` self-filters files whose
41
+ * machineName matches this machine's, so if two machines share a name —
42
+ * e.g. both defaulted to the same hostname — pull silently imports
43
+ * nothing. Malformed files are skipped quietly. */
44
+ export declare function listTransportMachines(syncDir: string): Map<string, number>;
45
+ export interface TransportHealth {
46
+ ok: boolean;
47
+ detail: string;
48
+ }
49
+ export interface Transport {
50
+ /** Write a .ccsync into the transport; return a human-readable location. */
51
+ push(file: CcsyncFile): string;
52
+ /** Newest-first entries for a project key, excluding self-authored files. */
53
+ listEntries(projectKey: string, selfMachineName: string): TransportEntry[];
54
+ /** All project keys (encoded form) present in the transport. */
55
+ listProjectKeys(): string[];
56
+ /** machineName → file count across the whole transport. */
57
+ listMachines(): Map<string, number>;
58
+ /** One-line description for banners / doctor. */
59
+ describe(): string;
60
+ /** Reachability + presence check for `doctor`. */
61
+ health(): TransportHealth;
62
+ }
63
+ /** Filesystem transport — thin object wrapper over the functions above so
64
+ * it satisfies the `Transport` interface. */
65
+ export declare class FsTransport implements Transport {
66
+ private readonly dir;
67
+ constructor(dir: string);
68
+ push(file: CcsyncFile): string;
69
+ listEntries(projectKey: string, selfMachineName: string): TransportEntry[];
70
+ listProjectKeys(): string[];
71
+ listMachines(): Map<string, number>;
72
+ describe(): string;
73
+ health(): TransportHealth;
74
+ }
75
+ /** Build the concrete transport for a resolved config. */
76
+ export declare function createTransport(tc: TransportConfig): Transport;
37
77
  /** List all project keys present in the transport. Used by the CLI's
38
78
  * `pull` (no args) form to fan out across every known project. */
39
79
  export declare function listProjectKeys(syncDir: string): string[];
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED;;;gBAGgB;AAChB,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAWzE;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;4CAG4C;AAC5C,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,MAAM,GACtB,cAAc,EAAE,CA0BlB;AAED;mEACmE;AACnE,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAWzD"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED;;;gBAGgB;AAChB,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAWzE;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;4CAG4C;AAC5C,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,MAAM,GACtB,cAAc,EAAE,CA0BlB;AAED;;;;;oDAKoD;AACpD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAkB1E;AAQD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,4EAA4E;IAC5E,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAAC;IAC/B,6EAA6E;IAC7E,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,cAAc,EAAE,CAAC;IAC3E,gEAAgE;IAChE,eAAe,IAAI,MAAM,EAAE,CAAC;IAC5B,2DAA2D;IAC3D,YAAY,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,iDAAiD;IACjD,QAAQ,IAAI,MAAM,CAAC;IACnB,kDAAkD;IAClD,MAAM,IAAI,eAAe,CAAC;CAC3B;AAED;8CAC8C;AAC9C,qBAAa,WAAY,YAAW,SAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,MAAM;IAExC,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM;IAG9B,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,cAAc,EAAE;IAG1E,eAAe,IAAI,MAAM,EAAE;IAG3B,YAAY,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAGnC,QAAQ,IAAI,MAAM;IAGlB,MAAM,IAAI,eAAe;CAK1B;AAED,0DAA0D;AAC1D,wBAAgB,eAAe,CAAC,EAAE,EAAE,eAAe,GAAG,SAAS,CAG9D;AAED;mEACmE;AACnE,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAWzD"}
package/dist/transport.js CHANGED
@@ -21,6 +21,7 @@ import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync, readFileSy
21
21
  import { join } from 'node:path';
22
22
  import { encodeProjectDir } from './project.js';
23
23
  import { parseCcsync, serializeCcsync } from './format.js';
24
+ import { SshTransport } from './ssh.js';
24
25
  export function projectSubdir(syncDir, projectKey) {
25
26
  return join(syncDir, encodeProjectDir(projectKey));
26
27
  }
@@ -73,6 +74,68 @@ export function listTransport(syncDir, projectKey, selfMachineName) {
73
74
  result.sort((a, b) => b.mtime - a.mtime);
74
75
  return result;
75
76
  }
77
+ /** Tally `machineName` → file count across every `.ccsync` in the
78
+ * transport (all projects). Used by `doctor` to catch the
79
+ * duplicate-machineName footgun: `pull` self-filters files whose
80
+ * machineName matches this machine's, so if two machines share a name —
81
+ * e.g. both defaulted to the same hostname — pull silently imports
82
+ * nothing. Malformed files are skipped quietly. */
83
+ export function listTransportMachines(syncDir) {
84
+ const counts = new Map();
85
+ if (!existsSync(syncDir))
86
+ return counts;
87
+ for (const sub of readdirSync(syncDir, { withFileTypes: true })) {
88
+ if (!sub.isDirectory())
89
+ continue;
90
+ const dir = join(syncDir, sub.name);
91
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
92
+ if (!entry.isFile() || !entry.name.endsWith('.ccsync'))
93
+ continue;
94
+ try {
95
+ const file = parseCcsync(readFileSync(join(dir, entry.name), 'utf-8'));
96
+ counts.set(file.machineName, (counts.get(file.machineName) ?? 0) + 1);
97
+ }
98
+ catch {
99
+ // Shared dir may hold in-progress writes / stale tmp files.
100
+ }
101
+ }
102
+ }
103
+ return counts;
104
+ }
105
+ /** Filesystem transport — thin object wrapper over the functions above so
106
+ * it satisfies the `Transport` interface. */
107
+ export class FsTransport {
108
+ dir;
109
+ constructor(dir) {
110
+ this.dir = dir;
111
+ }
112
+ push(file) {
113
+ return pushToTransport(this.dir, file);
114
+ }
115
+ listEntries(projectKey, selfMachineName) {
116
+ return listTransport(this.dir, projectKey, selfMachineName);
117
+ }
118
+ listProjectKeys() {
119
+ return listProjectKeys(this.dir);
120
+ }
121
+ listMachines() {
122
+ return listTransportMachines(this.dir);
123
+ }
124
+ describe() {
125
+ return this.dir;
126
+ }
127
+ health() {
128
+ return existsSync(this.dir)
129
+ ? { ok: true, detail: this.dir }
130
+ : { ok: false, detail: `directory does not exist: ${this.dir}` };
131
+ }
132
+ }
133
+ /** Build the concrete transport for a resolved config. */
134
+ export function createTransport(tc) {
135
+ if (tc.type === 'ssh')
136
+ return new SshTransport(tc);
137
+ return new FsTransport(tc.dir);
138
+ }
76
139
  /** List all project keys present in the transport. Used by the CLI's
77
140
  * `pull` (no args) form to fan out across every known project. */
78
141
  export function listProjectKeys(syncDir) {
@@ -1 +1 @@
1
- {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@askalf/claude-sync",
3
- "version": "0.0.3",
4
- "description": "Sync Claude Code sessions across machines. Pack a local CC session into a portable .ccsync file, ship it via Dropbox / iCloud / Syncthing / a USB stick, unpack on the other side. Path-hash mismatches solved via git-remote-url as canonical project key.",
3
+ "version": "0.2.0",
4
+ "description": "own your sessions — move Claude Code sessions across machines. Part of Own Your Stack.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "claude-sync": "./dist/cli.js"
@@ -21,7 +21,7 @@
21
21
  ],
22
22
  "scripts": {
23
23
  "build": "tsc",
24
- "test": "node --test test/project.test.mjs test/format.test.mjs test/session.test.mjs test/transport.test.mjs test/cli.test.mjs test/version.test.mjs",
24
+ "test": "node --test test/project.test.mjs test/format.test.mjs test/session.test.mjs test/transport.test.mjs test/config.test.mjs test/ssh.test.mjs test/cli.test.mjs test/watch.test.mjs test/version.test.mjs",
25
25
  "typecheck": "tsc --noEmit",
26
26
  "audit": "npm audit --production --audit-level=high",
27
27
  "prepublishOnly": "npm run build",