@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/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @askalf/claude-sync
2
2
 
3
+ > _claude-sync — own your sessions — move Claude Code sessions across machines. Part of **[Own Your Stack](https://github.com/askalf)** — own your AI infrastructure instead of renting it by the token._
4
+
3
5
  > Sync Claude Code sessions across machines. Pack a 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 the canonical project key.
4
6
 
5
7
  ```bash
@@ -19,8 +21,8 @@ Claude Code stores sessions locally at `~/.claude/projects/<encoded-cwd>/<sessio
19
21
 
20
22
  `claude-sync` solves both:
21
23
 
22
- - **Canonical project key** = `git:<remote-url>`. Same logical repo → same key, regardless of where it's checked out. (Falls back to `name:<basename>` when no git remote.)
23
- - **Single-writer model.** You explicitly `push` from machine A, then `pull` on machine B. No background daemon racing files. The transport is a directory you nominate (Dropbox / iCloud / Syncthing / a USB stick); each machine writes its own file under its own machine name, so two machines pushing the same session don't collide.
24
+ - **Canonical project key** = `git:<host>/<owner>/<repo>`, derived from the git remote. Same logical repo → same key, regardless of where it's checked out or which protocol you cloned with — SSH and HTTPS remotes normalize to the same key. (Falls back to `name:<basename>` when there's no git remote.)
25
+ - **You choose the cadence.** `push` from machine A and `pull` on machine B by hand, or run `claude-sync watch` to push-on-change and pull-on-interval automatically. The transport is either a local directory you nominate (Dropbox / iCloud / Syncthing / a USB stick) **or a directory on a remote host over SSH** (a dev box). Each machine writes its own file under its own machine name, so two machines pushing the same session don't collide.
24
26
 
25
27
  ## Use it
26
28
 
@@ -57,12 +59,80 @@ claude --resume sess-abc123
57
59
 
58
60
  That's the whole loop.
59
61
 
62
+ ### Or let it run: watch mode
63
+
64
+ Tired of remembering to `push` and `pull`? Run a daemon per project:
65
+
66
+ ```bash
67
+ cd ~/code/myapp
68
+ claude-sync watch
69
+ # claude-sync watch — desktop
70
+ # project: git:github.com/you/myapp
71
+ # cwd: /Users/you/code/myapp
72
+ # syncDir: /Users/you/Dropbox/claude-sync
73
+ # every 10s · push most-recent changed session · pull all projects
74
+ # Ctrl-C to stop.
75
+ #
76
+ # ↑ pushed sess-abc123 (142 lines)
77
+ # Imported sess-def456 from laptop → …/sess-def456.jsonl
78
+ ```
79
+
80
+ Every interval (default 10s, `--interval <seconds>`) it pushes the active
81
+ session whenever it has grown and pulls anything newer from your other
82
+ machines. It watches the **most-recent** session by default; pass a session-id
83
+ to pin one, or `--all` to push every changed session in the project. `--once`
84
+ runs a single cycle and exits — handy from `cron`. Ctrl-C stops cleanly.
85
+
86
+ It's still poll-based, not real-time co-editing: don't run the *same* session
87
+ live on two machines at once (see [Concurrency](#concurrency)). Watch is for
88
+ "pick up where the other machine left off without thinking about it."
89
+
90
+ ### Sync over SSH (no Dropbox needed)
91
+
92
+ If you run Claude Code on a **dev server** and on a **laptop**, point the
93
+ transport straight at a directory on the server — no third-party sync folder in
94
+ the middle:
95
+
96
+ ```bash
97
+ # On the laptop: the syncDir lives on the remote box, reached over SSH.
98
+ claude-sync init --ssh me@devbox:/home/me/claude-sync --name laptop
99
+ claude-sync doctor # live connectivity + remote-dir check
100
+
101
+ # On the server: a plain filesystem transport pointed at the same dir.
102
+ claude-sync init /home/me/claude-sync --name devbox
103
+ ```
104
+
105
+ Now `push` / `pull` / `watch` on the laptop shuttle sessions to and from the
106
+ server with no manual `scp`. Options: `--port <n>`, `--identity <keyfile>`.
107
+ It uses your system `ssh`, honors your `~/.ssh/config`, runs in `BatchMode`
108
+ (so it fails fast instead of prompting), and multiplexes connections on
109
+ macOS/Linux so a `pull`'s round-trips share one SSH session.
110
+
111
+ > The remote layout is identical to the filesystem transport, so the server's
112
+ > `fs` transport and the laptop's `ssh` transport — both pointed at
113
+ > `/home/me/claude-sync` — interoperate. The remote host needs a POSIX shell
114
+ > with `find`/`tar` (any Linux box).
115
+
116
+ ### Starting on a fresh clone
117
+
118
+ `pull` can only deliver a session to a directory it knows about. On a machine
119
+ that has never had a session for this project yet, register the directory once:
120
+
121
+ ```bash
122
+ cd ~/code/myapp
123
+ claude-sync register # binds this cwd to the project key — no session needed
124
+ claude-sync pull # now resolves
125
+ ```
126
+
127
+ (`watch` registers automatically on start, and `import … --cwd <path>` registers
128
+ the target, so you usually only need `register` for a manual `pull`-first flow.)
129
+
60
130
  ## How path resolution works
61
131
 
62
132
  When you `push` from a project with a git remote, claude-sync:
63
133
 
64
134
  1. Reads `git remote get-url origin` from your cwd.
65
- 2. Builds a project key: `git:https://github.com/you/myapp.git`.
135
+ 2. Canonicalizes it into a project key: `git:github.com/you/myapp` (SSH and HTTPS remotes land on the same key).
66
136
  3. Registers the local cwd under that key in `~/.claude-sync/projects.json`.
67
137
  4. Writes the `.ccsync` into `<syncDir>/<encoded-key>/<session>-<machine>.ccsync`.
68
138
 
@@ -71,36 +141,37 @@ When you `pull`, the receiving machine:
71
141
  1. Scans `<syncDir>/` for project subdirs.
72
142
  2. For each, looks up the encoded key in its own `projects.json`.
73
143
  3. If a local cwd is registered for that key, installs the session there.
74
- 4. If not — claude-sync hasn't seen this project on this machine yet — it skips with a warning. You either `push` once from the right cwd to register it, or run `claude-sync import <file> --cwd <path>` explicitly.
144
+ 4. If not — claude-sync hasn't seen this project on this machine yet — it skips with a warning. Run `claude-sync register` in the cwd once (binds the key, no session needed), or `claude-sync import <file> --cwd <path>` to install a specific file (which also registers the directory).
75
145
 
76
- In practice: clone the repo on the new machine, `cd` in, run `claude-sync push` once with no real session (it'll error if there's no session — that's fine), then run `claude-sync pull` to fetch. The first push registers the cwd.
77
-
78
- A cleaner way: just run any `claude` command in the cwd once, then `claude-sync export` (which registers without pushing).
146
+ In practice: clone the repo on the new machine, `cd` in, `claude-sync register`, then `claude-sync pull`. Or just start `claude-sync watch`, which registers on launch.
79
147
 
80
148
  ## Concurrency
81
149
 
82
- There's no daemon and no automatic background sync. You decide when to push and when to pull. If you forget and edit the same session on both machines:
150
+ `watch` automates *when* to push and pull, but it's still poll-based, not a locking protocol. If two machines edit the **same** session concurrently:
83
151
 
84
152
  - Two `.ccsync` files end up in `<syncDir>/<project>/`, named with each machine's name.
85
153
  - The receiving `pull` skips files older or shorter than the local copy.
86
154
  - Newer/longer files install with `--overwrite`. The losing machine's edits become a `<session-id>-copy.jsonl` (you can manually inspect + reconcile).
87
155
 
88
- Future versions may add a watch-mode + lock-file protocol for "real-time" sync; v0.0.1 is deliberate-action only.
156
+ So: use `watch` to hand a project *back and forth* between machines hands-free; don't drive the same live session from two machines at once. A lock-file protocol for true real-time co-editing is a possible future addition.
89
157
 
90
158
  ## CLI reference
91
159
 
92
160
  ```text
93
161
  claude-sync init <syncDir> [--name <machine>]
162
+ claude-sync init --ssh <[user@]host:/path> [--port <n>] [--identity <keyfile>] [--name <machine>]
94
163
  claude-sync list
164
+ claude-sync register [cwd]
95
165
  claude-sync export [session-id] [-o <file>]
96
166
  claude-sync import <file> [--cwd <path>] [--overwrite]
97
167
  claude-sync push [session-id]
98
168
  claude-sync pull
169
+ claude-sync watch [session-id] [--interval <seconds>] [--all] [--once]
99
170
  claude-sync doctor
100
171
  claude-sync --help / --version
101
172
  ```
102
173
 
103
- `doctor` prints config, machine name, registered projects, and warns if any registered cwd no longer exists on disk.
174
+ `doctor` prints config, machine name, registered projects, and transport peers — and warns if any registered cwd no longer exists on disk, or if every file in the transport carries this machine's own name (which makes `pull` a silent no-op — give each machine a distinct `--name`).
104
175
 
105
176
  ## File format
106
177
 
@@ -110,7 +181,7 @@ claude-sync --help / --version
110
181
  {
111
182
  "_schemaVersion": 1,
112
183
  "sessionId": "...",
113
- "projectKey": "git:https://github.com/you/myapp.git",
184
+ "projectKey": "git:github.com/you/myapp",
114
185
  "originalCwd": "C:\\Users\\you\\code\\myapp",
115
186
  "machineName": "desktop",
116
187
  "exportedAt": 1715380000000,
@@ -119,13 +190,12 @@ claude-sync --help / --version
119
190
  }
120
191
  ```
121
192
 
122
- Schema-versioned. Version 1 is the only thing v0.0.1 understands; importers refuse unknown versions explicitly rather than silently dropping fields.
193
+ Schema-versioned. Version 1 is the only thing the current release understands; importers refuse unknown versions explicitly rather than silently dropping fields.
123
194
 
124
195
  ## What it isn't
125
196
 
126
- - **Not a daemon.** Push and pull are explicit user actions. Run a watcher yourself if you want background sync.
127
- - **Not a relay service.** v0.0.1 ships only the filesystem transport point it at any synced folder. SSH / S3 / gist / WebSocket transports would be straightforward additions; not in v0.0.1.
128
- - **Not real-time.** If both machines are CC-active simultaneously, the second to `pull` overwrites their in-progress session unless they noticed and stopped first. Use one machine at a time.
197
+ - **Not real-time co-editing.** `watch` polls on an interval; it isn't a locking protocol. If both machines drive the *same* session simultaneously, the second to sync overwrites the other's in-progress copy (the loser is kept as `<id>-copy.jsonl`). Use it to hand a project between machines, not to co-edit one session live.
198
+ - **Not a relay service.** Two transports ship a local synced folder and a remote directory over SSH. There's no hosted relay; S3 / gist / WebSocket transports would be straightforward additions on the `Transport` interface, but aren't shipped yet.
129
199
  - **Not a CC re-implementation.** It just shuffles JSONL. CC's session schema can change without breaking claude-sync — we never parse the events.
130
200
 
131
201
  ## Library API
@@ -134,13 +204,21 @@ For embedders (custom transports, watch daemons, etc.):
134
204
 
135
205
  ```ts
136
206
  import {
137
- listSessions, readSession, writeSession,
207
+ listSessions, listSessionStats, countSessionLines, readSession, writeSession,
138
208
  buildCcsync, parseCcsync, readCcsyncFile, writeCcsyncFile,
139
- projectKey, registerProject, lookupCwd,
140
- pushToTransport, listTransport,
209
+ projectKey, normalizeGitRemote, registerProject, registerProjectKey, lookupCwd,
210
+ // Transports: the abstraction + both backends.
211
+ createTransport, resolveTransportConfig, FsTransport, SshTransport,
212
+ type Transport, type TransportConfig,
141
213
  } from '@askalf/claude-sync';
142
214
  ```
143
215
 
216
+ Everything is built on these exports — `listSessionStats` for cheap change
217
+ detection, the `Transport` interface (`push` / `listEntries` / …) for the sync,
218
+ `registerProject` for first-contact. A custom transport is one class
219
+ implementing `Transport`; `SshTransport` takes an injectable `SshExecutor`, so
220
+ you can wrap it or swap the wire entirely.
221
+
144
222
  See `src/index.ts` for the full surface. Zero runtime dependencies.
145
223
 
146
224
  ## Environment
@@ -153,27 +231,16 @@ See `src/index.ts` for the full surface. Zero runtime dependencies.
153
231
 
154
232
  MIT — see [LICENSE](LICENSE).
155
233
 
156
- ## Also by askalf
234
+ ## Own Your Stack
157
235
 
158
- | Project | What it does |
159
- |---------|-------------|
160
- | [arnie](https://github.com/askalf/arnie) | Portable IT troubleshooting companion. Networking, AD, Windows Update, package managers, log triage, hardware checks. |
161
- | [brio](https://github.com/askalf/brio) | Capability layer for AI workloads — semantic cache, cost tiering, policy. Sits in front of any Anthropic-compat endpoint. |
162
- | [browser-bridge](https://github.com/askalf/browser-bridge) | Stealth headless Chromium in a container. CDP on 9222 — Playwright/Puppeteer/MCP-compatible. |
163
- | [claude-bridge](https://github.com/askalf/claude-bridge) | Bridge Claude Code sessions to Discord. |
164
- | [dario](https://github.com/askalf/dario) | Local LLM router. Use your Claude Max/Pro subscription as an API. |
165
- | [deepdive](https://github.com/askalf/deepdive) | Local research agent. Plan → search → fetch → extract → synthesize. Cited answers. |
166
- | [git-providers](https://github.com/askalf/git-providers) | Unified GitHub + GitLab + Bitbucket Cloud REST clients behind one GitProvider interface. Plus a 44-entry api-key-provider taxonomy. |
167
- | [hands](https://github.com/askalf/hands) | Cross-platform computer-use agent. Mouse, keyboard, screen. |
168
- | [install-kit](https://github.com/askalf/install-kit) | curl-pipe-bash template for self-hosted Docker apps. |
169
- | [pgflex](https://github.com/askalf/pgflex) | One Postgres API. Two modes (real PG ↔ PGlite WASM). |
170
- | [redisflex](https://github.com/askalf/redisflex) | One Redis API. Two modes (ioredis ↔ in-process). |
236
+ Part of **[Own Your Stack](https://github.com/askalf)** — open tools for owning your AI infrastructure instead of renting it by the token. One subscription. Your box. Your terms.
171
237
 
238
+ - **[dario](https://github.com/askalf/dario)** — own your routing
239
+ - **[deepdive](https://github.com/askalf/deepdive)** — own your research
240
+ - **[hands](https://github.com/askalf/hands)** — own your computer-use
241
+ - **[agent](https://github.com/askalf/agent)** — own your fleet
242
+ - **[browser-bridge](https://github.com/askalf/browser-bridge)** — own your browser
243
+ - **claude-sync** — own your sessions _(you are here)_
172
244
 
173
245
  ---
174
-
175
- ## Built by Sprayberry Labs
176
-
177
- This is one of the open-source building blocks from **[Sprayberry Labs](https://sprayberrylabs.com)** — an independent studio (Atlanta, GA) that ships bespoke software and **fixed-price code & security audits**, delivered with the AI workforce these tools are part of.
178
-
179
- **Got a codebase that needs an expert read?** → **[Scan a repo — free mini-audit](https://sprayberrylabs.com)**, or see the **$1,500 fixed-price Audit** and build Sprints. · [sprayberrylabs.com](https://sprayberrylabs.com) · hello@sprayberrylabs.com
246
+ Part of **[Own Your Stack](https://github.com/askalf)** — own your AI infrastructure instead of renting it. Built by Thomas Sprayberry.
package/dist/cli.d.ts CHANGED
@@ -5,10 +5,12 @@
5
5
  * Subcommands:
6
6
  * init <syncDir> [--name <machine>] scaffold ~/.claude-sync/config.json
7
7
  * list list local CC sessions for the cwd
8
+ * register [cwd] register a cwd's project key (no session needed)
8
9
  * export [session-id] [-o <file>] pack a session into a .ccsync file
9
10
  * import <file> [--cwd <path>] install a .ccsync file locally
10
11
  * push [session-id] export + ship to configured syncDir
11
12
  * pull import any newer .ccsync files
13
+ * watch [session-id] [--interval <s>] daemon: auto-push changes + auto-pull peers
12
14
  * doctor diagnose config + registry health
13
15
  *
14
16
  * Common flags: --help, --version. No external arg-parser dep — the
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;GAeG"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG"}