@askalf/claude-sync 0.0.1

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.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Filesystem transport — the only transport in v0.0.1.
3
+ *
4
+ * Layout under `<syncDir>`:
5
+ *
6
+ * <syncDir>/
7
+ * <encoded-project-key>/
8
+ * <sessionId>-<machineName>.ccsync
9
+ *
10
+ * Project key is encoded the same way CC encodes cwds — `[/\\:.\s]+`
11
+ * collapsed to `-` — so `git:https://github.com/owner/repo.git` becomes
12
+ * `git-https---github-com-owner-repo-git`. Reversible enough for human
13
+ * inspection.
14
+ *
15
+ * Multiple machines can push into the same project subdir without
16
+ * collision: each writes a file named with its own machineName, and
17
+ * `pull` picks the newest non-self file per session id.
18
+ */
19
+ import type { CcsyncFile } from './format.js';
20
+ export declare function projectSubdir(syncDir: string, projectKey: string): string;
21
+ /** Write a .ccsync file into the project's transport subdir. The
22
+ * filename includes the machine name so the receiver can tell who
23
+ * pushed it (and so two machines pushing the same session don't
24
+ * collide). */
25
+ export declare function pushToTransport(syncDir: string, file: CcsyncFile): string;
26
+ export interface TransportEntry {
27
+ path: string;
28
+ file: CcsyncFile;
29
+ mtime: number;
30
+ }
31
+ /** List all .ccsync files for a project key in the transport. Sorted
32
+ * by mtime descending. Files from the local machine (matching
33
+ * `selfMachineName`) are omitted — pull is for receiving OTHER
34
+ * machines' work, not echoing your own. */
35
+ export declare function listTransport(syncDir: string, projectKey: string, selfMachineName: string): TransportEntry[];
36
+ /** List all project keys present in the transport. Used by the CLI's
37
+ * `pull` (no args) form to fan out across every known project. */
38
+ export declare function listProjectKeys(syncDir: string): string[];
39
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;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"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Filesystem transport — the only transport in v0.0.1.
3
+ *
4
+ * Layout under `<syncDir>`:
5
+ *
6
+ * <syncDir>/
7
+ * <encoded-project-key>/
8
+ * <sessionId>-<machineName>.ccsync
9
+ *
10
+ * Project key is encoded the same way CC encodes cwds — `[/\\:.\s]+`
11
+ * collapsed to `-` — so `git:https://github.com/owner/repo.git` becomes
12
+ * `git-https---github-com-owner-repo-git`. Reversible enough for human
13
+ * inspection.
14
+ *
15
+ * Multiple machines can push into the same project subdir without
16
+ * collision: each writes a file named with its own machineName, and
17
+ * `pull` picks the newest non-self file per session id.
18
+ */
19
+ import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync, readFileSync, renameSync, } from 'node:fs';
20
+ import { join } from 'node:path';
21
+ import { encodeProjectDir } from './project.js';
22
+ import { parseCcsync, serializeCcsync } from './format.js';
23
+ export function projectSubdir(syncDir, projectKey) {
24
+ return join(syncDir, encodeProjectDir(projectKey));
25
+ }
26
+ /** Write a .ccsync file into the project's transport subdir. The
27
+ * filename includes the machine name so the receiver can tell who
28
+ * pushed it (and so two machines pushing the same session don't
29
+ * collide). */
30
+ export function pushToTransport(syncDir, file) {
31
+ const dir = projectSubdir(syncDir, file.projectKey);
32
+ if (!existsSync(dir))
33
+ mkdirSync(dir, { recursive: true });
34
+ const filename = `${file.sessionId}-${file.machineName}.ccsync`;
35
+ const path = join(dir, filename);
36
+ // Atomic write: tmp + rename. If the sync provider mid-snapshots the
37
+ // dir (Dropbox does this), at least it sees a complete file.
38
+ const tmp = `${path}.tmp`;
39
+ writeFileSync(tmp, serializeCcsync(file), { mode: 0o600 });
40
+ renameSync(tmp, path);
41
+ return path;
42
+ }
43
+ /** List all .ccsync files for a project key in the transport. Sorted
44
+ * by mtime descending. Files from the local machine (matching
45
+ * `selfMachineName`) are omitted — pull is for receiving OTHER
46
+ * machines' work, not echoing your own. */
47
+ export function listTransport(syncDir, projectKey, selfMachineName) {
48
+ const dir = projectSubdir(syncDir, projectKey);
49
+ if (!existsSync(dir))
50
+ return [];
51
+ const entries = readdirSync(dir, { withFileTypes: true });
52
+ const result = [];
53
+ for (const entry of entries) {
54
+ if (!entry.isFile() || !entry.name.endsWith('.ccsync'))
55
+ continue;
56
+ const path = join(dir, entry.name);
57
+ let file;
58
+ try {
59
+ file = parseCcsync(readFileSync(path, 'utf-8'));
60
+ }
61
+ catch {
62
+ // Skip malformed files quietly. The transport directory is shared
63
+ // across machines and might contain in-progress writes from
64
+ // other tools or stale tmp files.
65
+ continue;
66
+ }
67
+ if (file.machineName === selfMachineName)
68
+ continue;
69
+ const mtime = statSync(path).mtimeMs;
70
+ result.push({ path, file, mtime });
71
+ }
72
+ result.sort((a, b) => b.mtime - a.mtime);
73
+ return result;
74
+ }
75
+ /** List all project keys present in the transport. Used by the CLI's
76
+ * `pull` (no args) form to fan out across every known project. */
77
+ export function listProjectKeys(syncDir) {
78
+ if (!existsSync(syncDir))
79
+ return [];
80
+ const entries = readdirSync(syncDir, { withFileTypes: true });
81
+ return entries
82
+ .filter((e) => e.isDirectory())
83
+ .map((e) => e.name)
84
+ // We could try to reverse-decode the encoded key, but the lossy
85
+ // encoding means we can't reliably get the original `git:...`
86
+ // form back. Return the encoded form; callers that need to match
87
+ // by raw key should encode their key first too.
88
+ .sort();
89
+ }
90
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@askalf/claude-sync",
3
+ "version": "0.0.1",
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.",
5
+ "type": "module",
6
+ "bin": {
7
+ "claude-sync": "./dist/cli.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "test": "node --test test/project.test.mjs test/format.test.mjs test/session.test.mjs test/cli.test.mjs",
25
+ "typecheck": "tsc --noEmit",
26
+ "audit": "npm audit --production --audit-level=high",
27
+ "prepublishOnly": "npm run build",
28
+ "start": "node dist/cli.js",
29
+ "dev": "tsx src/cli.ts"
30
+ },
31
+ "keywords": [
32
+ "claude-code",
33
+ "claude",
34
+ "session",
35
+ "sync",
36
+ "developer-tools",
37
+ "cli"
38
+ ],
39
+ "author": "askalf (https://github.com/askalf)",
40
+ "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/askalf/claude-sync.git"
44
+ },
45
+ "homepage": "https://github.com/askalf/claude-sync",
46
+ "bugs": {
47
+ "url": "https://github.com/askalf/claude-sync/issues"
48
+ },
49
+ "engines": {
50
+ "node": ">=20.0.0"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^25.6.0",
54
+ "tsx": "^4.19.0",
55
+ "typescript": "^6.0.3"
56
+ }
57
+ }