@lucasschirm/claude-session-sync 0.1.0 → 0.2.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.
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "name": "claude-session-sync",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Sync Claude Code session data to S3-compatible storage.",
5
- "hooks": "hooks/hooks.json"
5
+ "author": {
6
+ "name": "lucasschirm"
7
+ }
6
8
  }
package/README.md ADDED
@@ -0,0 +1,476 @@
1
+ # @lucasschirm/claude-session-sync
2
+
3
+ A [Claude Code](https://code.claude.com/) plugin that synchronizes your session
4
+ data — transcripts, workspace configuration, and session telemetry — to
5
+ S3-compatible storage via the [`@lucasschirm/sal-sync`](../../sync) engine.
6
+
7
+ ## What it does
8
+
9
+ Every Claude Code session goes through a lifecycle: start, tool calls, context
10
+ compaction, subagent spawns, and stop. This plugin hooks into those lifecycle
11
+ events to capture and upload session artifacts so they can be analyzed later by
12
+ the [Agentic Sessions Dashboard](../../../) (or any consumer of the sync
13
+ engine's output).
14
+
15
+ ### Hooks
16
+
17
+ The plugin registers hooks for the following Claude Code lifecycle events
18
+ (defined in [`hooks/hooks.json`](hooks/hooks.json)):
19
+
20
+ | Event | What happens |
21
+ | -------------- | ----------------------------------------------------------------------------------------------------- |
22
+ | `SessionStart` | Records the session, spawns a detached transcript watcher that uploads incremental transcript deltas. |
23
+ | `PreCompact` | Flushes pending transcript deltas before context compaction. |
24
+ | `PostCompact` | Resumes capture after compaction. |
25
+ | `Stop` | Flushes a final delta when the turn ends. |
26
+ | `StopFailure` | Flushes a final delta when the turn ends due to an API error. |
27
+ | `SubagentStop` | Flushes subagent transcript deltas when a subagent finishes. |
28
+ | `SessionEnd` | Performs the final sync: flushes remaining deltas, uploads the session manifest, and shuts down. |
29
+
30
+ All hooks except `SessionEnd` run asynchronously (`"async": true`) so they
31
+ never block Claude. `SessionEnd` has a 60-second timeout to ensure the final
32
+ manifest is uploaded before the process exits.
33
+
34
+ ### What gets captured
35
+
36
+ The sync engine captures only the artifacts in its versioned
37
+ [allowlist](../../sync/src/allowlist.ts):
38
+
39
+ - **Session & subagent transcript JSONL files** — captured raw by default,
40
+ subject to size limits. Opt out with `SAL_CAPTURE_TRANSCRIPTS=false`.
41
+ - **Workspace configuration** — `CLAUDE.md`, `.mcp.json`,
42
+ `.claude/settings.json`, `.claude/settings.local.json`,
43
+ `.claude/agents/**`, `.claude/skills/**`, `.claude/rules/**`.
44
+ - **Global configuration** — `~/.claude/settings.json`, `~/.claude/CLAUDE.md`,
45
+ `~/.claude/agents/**`, `~/.claude.json`.
46
+ - **Session telemetry** — `sessionId`, `projectId`, `harness`,
47
+ `harnessVersion`, `model`, `startedAt`, `endedAt`, `durationMs`,
48
+ `endReason`.
49
+
50
+ Configuration artifacts are sanitized before upload: known sensitive fields
51
+ (`env`, `password`, `secret`, `token`, `apiKey`, `authorization`, etc.) are
52
+ redacted, and bearer tokens, credential-bearing URLs, and private-key blocks
53
+ are stripped. See the full [privacy policy](../../sync/POLICY.md).
54
+
55
+ ## Installation
56
+
57
+ The plugin is distributed through the `session-analyzer` marketplace, which is
58
+ hosted in this repository at
59
+ [`.claude-plugin/marketplace.json`](../../../.claude-plugin/marketplace.json).
60
+ Installation is a two-step process: first add the marketplace, then install
61
+ the plugin.
62
+
63
+ ### Step 1 — Add the marketplace
64
+
65
+ You can add the marketplace from GitHub, a git URL, or a local clone.
66
+
67
+ #### From GitHub (recommended)
68
+
69
+ Use the `owner/repo` shorthand:
70
+
71
+ ```bash
72
+ claude plugin marketplace add lucasschirm/session-analyzer
73
+ ```
74
+
75
+ Pin to a specific branch or tag with `@ref`:
76
+
77
+ ```bash
78
+ claude plugin marketplace add lucasschirm/session-analyzer@feature/claude-session-sync
79
+ ```
80
+
81
+ #### From a git URL
82
+
83
+ For non-GitHub hosts or explicit HTTPS cloning:
84
+
85
+ ```bash
86
+ claude plugin marketplace add https://github.com/lucasschirm/session-analyzer.git
87
+ ```
88
+
89
+ #### From a local clone (for development)
90
+
91
+ If you have this repo cloned locally:
92
+
93
+ ```bash
94
+ claude plugin marketplace add ./path/to/session-analyzer
95
+ ```
96
+
97
+ #### Marketplace scope
98
+
99
+ The `--scope` flag controls who sees the marketplace declaration:
100
+
101
+ | Scope | Setting file | Shared with team? |
102
+ | --------- | ------------------------------ | ----------------- |
103
+ | `user` | `~/.claude/settings.json` | No (personal) |
104
+ | `project` | `.claude/settings.json` | Yes (committed) |
105
+ | `local` | `.claude/settings.local.json` | No (gitignored) |
106
+
107
+ ```bash
108
+ # Share the marketplace with your team
109
+ claude plugin marketplace add lucasschirm/session-analyzer --scope project
110
+ ```
111
+
112
+ ### Step 2 — Install the plugin
113
+
114
+ Once the marketplace is added, install the plugin:
115
+
116
+ ```bash
117
+ claude plugin install claude-session-sync@session-analyzer
118
+ ```
119
+
120
+ Or from inside an interactive Claude Code session:
121
+
122
+ ```
123
+ /plugin install claude-session-sync@session-analyzer
124
+ ```
125
+
126
+ The install command opens a details view where you select an installation
127
+ scope (user, project, or local — same semantics as above). After installing,
128
+ run `/reload-plugins` if prompted.
129
+
130
+ ### From source (local development)
131
+
132
+ For local development, build the plugin bundle first, then add the marketplace
133
+ from your local clone:
134
+
135
+ ```bash
136
+ # Build the plugin bundle (esbuild single-file executables)
137
+ pnpm --filter @lucasschirm/claude-session-sync build
138
+
139
+ # Add the local marketplace and install
140
+ claude plugin marketplace add .
141
+ claude plugin install claude-session-sync@session-analyzer
142
+ ```
143
+
144
+ The build produces self-contained executables in `bin/` (no `node_modules`
145
+ required at runtime):
146
+
147
+ ```
148
+ bin/session-start # SessionStart hook entry point
149
+ bin/session-end # SessionEnd hook entry point
150
+ bin/hook # PreCompact/PostCompact/Stop/StopFailure/SubagentStop
151
+ bin/transcript-watcher # Detached watcher process spawned by session-start
152
+ bin/claude-sync # Standalone CLI for manual sync/list/download
153
+ ```
154
+
155
+ ## Standalone CLI
156
+
157
+ In addition to the Claude Code hooks, this package ships a standalone CLI
158
+ binary (`claude-sync`) that lets you manually upload, list, and download
159
+ sessions from S3 storage. It's useful for backfilling historical sessions,
160
+ inspecting what's been synced, or restoring sessions to a new machine.
161
+
162
+ ### Installation
163
+
164
+ The CLI is included in the same npm package. You can run it via `npx` without
165
+ installing anything:
166
+
167
+ ```bash
168
+ npx @lucasschirm/claude-session-sync sync
169
+ ```
170
+
171
+ Or install it globally for a shorter `claude-sync` command:
172
+
173
+ ```bash
174
+ npm install -g @lucasschirm/claude-session-sync
175
+ claude-sync sync
176
+ ```
177
+
178
+ ### Commands
179
+
180
+ #### `sync`
181
+
182
+ Upload all local Claude Code sessions for the current project to S3.
183
+
184
+ ```bash
185
+ claude-sync sync
186
+ ```
187
+
188
+ From the project directory, the CLI:
189
+
190
+ 1. Finds the corresponding Claude Code project folder in `~/.claude/projects/`.
191
+ 2. Lists all local `.jsonl` transcript files.
192
+ 3. For each session, checks if it already exists in S3 — if so, skips it.
193
+ 4. The first session uploaded captures workspace + global + session config.
194
+ Subsequent sessions capture only session-scoped transcripts (config is
195
+ uploaded once).
196
+ 5. Prints a per-session summary and a total.
197
+
198
+ #### `list`
199
+
200
+ List all sessions uploaded for the current project in S3.
201
+
202
+ ```bash
203
+ claude-sync list
204
+ ```
205
+
206
+ Outputs a human-readable table:
207
+
208
+ ```
209
+ SESSION ID FILES SIZE LAST MODIFIED
210
+ -------------------------------------------------------------------------
211
+ d1acf718-cd8d-4c1d-84fd-b074d231995b 43 21.1 MB 2026-08-18 19:36
212
+ test-summary-001 8 119.6 KB 2026-08-18 19:35
213
+
214
+ 2 session(s), 51 files, 21.2 MB total
215
+ ```
216
+
217
+ #### `download`
218
+
219
+ Download session files from S3 to a local directory.
220
+
221
+ ```bash
222
+ # Download a specific session
223
+ claude-sync download --session-id=<session-id> --output=<dir>
224
+
225
+ # Download all sessions for the project
226
+ claude-sync download all --output=<dir>
227
+ ```
228
+
229
+ Files are restored to `<output>/<projectId>/<sessionId>/<scope>/<relativePath>`.
230
+
231
+ ### Configuration
232
+
233
+ The CLI reads configuration from environment variables, falling back to
234
+ `.claude/settings.local.json` `env` key for any variables not set in the
235
+ process environment. This means you can configure it once in
236
+ `.claude/settings.local.json` and the CLI will pick it up automatically.
237
+
238
+ See the [Configuration](#configuration) section below for the full list of
239
+ required and optional variables.
240
+
241
+ If required variables are missing, the CLI prints an error with example
242
+ `export` commands and a `.claude/settings.local.json` template:
243
+
244
+ ```
245
+ Error: required configuration is missing or incomplete.
246
+
247
+ The following environment variables must be set:
248
+ SAL_PROJECT_ID — Unique identifier for the project.
249
+ ...
250
+
251
+ Set them via environment variables before running the CLI:
252
+
253
+ export SAL_PROJECT_ID=session-analyzer
254
+ export SAL_STORAGE_TYPE=s3
255
+ ...
256
+ npx @lucasschirm/claude-session-sync sync
257
+
258
+ Or add them to .claude/settings.local.json:
259
+
260
+ {
261
+ "env": {
262
+ "SAL_PROJECT_ID": "session-analyzer",
263
+ ...
264
+ }
265
+ }
266
+ ```
267
+
268
+ ### Updating
269
+
270
+ To pull the latest version from the marketplace:
271
+
272
+ ```bash
273
+ claude plugin marketplace update session-analyzer
274
+ claude plugin update claude-session-sync@session-analyzer
275
+ ```
276
+
277
+ Or inside an interactive session:
278
+
279
+ ```
280
+ /plugin marketplace update session-analyzer
281
+ /plugin update claude-session-sync@session-analyzer
282
+ ```
283
+
284
+ ### Uninstalling
285
+
286
+ ```bash
287
+ claude plugin uninstall claude-session-sync@session-analyzer
288
+ claude plugin marketplace remove session-analyzer
289
+ ```
290
+
291
+ ## Configuration
292
+
293
+ The sync engine is configured entirely through environment variables. Set them
294
+ in one of the Claude Code settings files (see below), or export them in your
295
+ shell before launching `claude`.
296
+
297
+ ### Required
298
+
299
+ | Variable | Description |
300
+ | -------------------- | ----------------------------------------------------- |
301
+ | `SAL_PROJECT_ID` | Unique identifier for the project (e.g. `my-app`). |
302
+ | `SAL_STORAGE_TYPE` | Storage backend. Currently only `s3` is supported. |
303
+ | `SAL_STORAGE_BUCKET` | S3 bucket name. |
304
+ | `SAL_STORAGE_REGION` | AWS region (e.g. `us-east-1`). |
305
+ | `SAL_STORAGE_ACCESS_KEY_ID` | AWS access key ID. |
306
+ | `SAL_STORAGE_SECRET_ACCESS_KEY` | AWS secret access key. |
307
+
308
+ ### Optional
309
+
310
+ | Variable | Default | Description |
311
+ | ------------------------------ | ----------- | ------------------------------------------------------------------ |
312
+ | `SAL_STORAGE_ENDPOINT` | _(none)_ | Custom S3-compatible endpoint (e.g. `http://localhost:4566` for LocalStack). |
313
+ | `SAL_STORAGE_SESSION_TOKEN` | _(none)_ | Temporary AWS session token (for STS credentials). |
314
+ | `SAL_SYNC_DISABLED` | `false` | Set to `true` to fully disable synchronization. |
315
+ | `SAL_CAPTURE_TRANSCRIPTS` | `true` | Set to `false` to skip transcript capture (config still syncs). |
316
+ | `SAL_SYNC_TIMEOUT` | `30000` | Per-upload timeout in milliseconds. |
317
+ | `SAL_SYNC_RETRIES` | `3` | Number of retry attempts on upload failure. |
318
+ | `SAL_SESSION_END_BUDGET_MS` | `120000` | Time budget for the final SessionEnd sync in milliseconds. |
319
+ | `SAL_HOOK_UPLOAD_TIMEOUT` | `10000` | Per-hook upload timeout in milliseconds. |
320
+ | `SAL_MAX_FILE_BYTES` | `10485760` | Max size per uploaded file (10 MB). |
321
+ | `SAL_MAX_TOTAL_BYTES` | `104857600` | Max total bytes per sync run (100 MB). |
322
+ | `SAL_MAX_FILES` | `1000` | Max files per sync run. |
323
+ | `SAL_MAX_TRANSCRIPT_BYTES` | `52428800` | Max transcript file size (50 MB). |
324
+ | `SAL_MAX_JSON_DEPTH` | `128` | Max JSON nesting depth for sanitization. |
325
+ | `SAL_MAX_JSONL_LINE_BYTES` | `1048576` | Max bytes per JSONL line (1 MB). |
326
+
327
+ ### Configuring via Claude Code settings
328
+
329
+ Claude Code reads environment variables from the `env` key in its settings
330
+ files. There are three relevant scopes:
331
+
332
+ | File | Scope | Commit to git? |
333
+ | ----------------------------- | ------------------------------ | -------------- |
334
+ | `~/.claude/settings.json` | You, in every project | No |
335
+ | `.claude/settings.json` | Everyone in the project | Yes |
336
+ | `.claude/settings.local.json` | You, in this project only | No (gitignored)|
337
+
338
+ #### Option A: User-wide (recommended for personal use)
339
+
340
+ Edit `~/.claude/settings.json`:
341
+
342
+ ```json
343
+ {
344
+ "env": {
345
+ "SAL_PROJECT_ID": "my-app",
346
+ "SAL_STORAGE_TYPE": "s3",
347
+ "SAL_STORAGE_BUCKET": "my-session-bucket",
348
+ "SAL_STORAGE_REGION": "us-east-1",
349
+ "SAL_STORAGE_ACCESS_KEY_ID": "AKIAIOSFODNN7EXAMPLE",
350
+ "SAL_STORAGE_SECRET_ACCESS_KEY": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
351
+ },
352
+ "enabledPlugins": {
353
+ "claude-session-sync@session-analyzer": true
354
+ }
355
+ }
356
+ ```
357
+
358
+ #### Option B: Project-local (per-project credentials)
359
+
360
+ Create `.claude/settings.local.json` in your project root (this file is
361
+ gitignored by default, so credentials stay local):
362
+
363
+ ```json
364
+ {
365
+ "env": {
366
+ "SAL_PROJECT_ID": "my-app",
367
+ "SAL_STORAGE_TYPE": "s3",
368
+ "SAL_STORAGE_BUCKET": "my-session-bucket",
369
+ "SAL_STORAGE_REGION": "us-east-1",
370
+ "SAL_STORAGE_ACCESS_KEY_ID": "AKIAIOSFODNN7EXAMPLE",
371
+ "SAL_STORAGE_SECRET_ACCESS_KEY": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
372
+ "SAL_STORAGE_ENDPOINT": "http://localhost:4566"
373
+ }
374
+ }
375
+ ```
376
+
377
+ > **Note:** `pluginConfigs` values are only read from user settings
378
+ > (`~/.claude/settings.json`), `--settings`, and managed settings — not from
379
+ > project or local settings files. However, the `env` key **is** read from all
380
+ > settings scopes, so environment variables in `.claude/settings.local.json`
381
+ > work correctly for plugin configuration.
382
+
383
+ #### Option C: Shell environment
384
+
385
+ Export the variables before launching Claude Code:
386
+
387
+ ```bash
388
+ export SAL_PROJECT_ID="my-app"
389
+ export SAL_STORAGE_TYPE="s3"
390
+ export SAL_STORAGE_BUCKET="my-session-bucket"
391
+ export SAL_STORAGE_REGION="us-east-1"
392
+ export SAL_STORAGE_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
393
+ export SAL_STORAGE_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
394
+ claude
395
+ ```
396
+
397
+ ### Using LocalStack for local development
398
+
399
+ For local testing without a real AWS account, run
400
+ [LocalStack](https://localstack.dev/) and point the plugin at it:
401
+
402
+ ```json
403
+ {
404
+ "env": {
405
+ "SAL_PROJECT_ID": "local-dev",
406
+ "SAL_STORAGE_TYPE": "s3",
407
+ "SAL_STORAGE_BUCKET": "sal-sessions",
408
+ "SAL_STORAGE_REGION": "us-east-1",
409
+ "SAL_STORAGE_ENDPOINT": "http://localhost:4566",
410
+ "SAL_STORAGE_ACCESS_KEY_ID": "test",
411
+ "SAL_STORAGE_SECRET_ACCESS_KEY": "test"
412
+ }
413
+ }
414
+ ```
415
+
416
+ ## Opting out
417
+
418
+ | Goal | Setting |
419
+ | ----------------------------- | -------------------------------- |
420
+ | Disable transcript capture | `SAL_CAPTURE_TRANSCRIPTS=false` |
421
+ | Fully disable synchronization | `SAL_SYNC_DISABLED=true` |
422
+
423
+ When fully disabled, the engine performs no filesystem discovery beyond the
424
+ minimum required to determine that synchronization is disabled, and
425
+ `SessionStart` does not spawn the watcher.
426
+
427
+ ## Development
428
+
429
+ ```bash
430
+ # Build the plugin
431
+ pnpm --filter @lucasschirm/claude-session-sync build
432
+
433
+ # Run tests
434
+ pnpm --filter @lucasschirm/claude-session-sync test
435
+
436
+ # Typecheck
437
+ pnpm --filter @lucasschirm/claude-session-sync typecheck
438
+
439
+ # Lint
440
+ pnpm --filter @lucasschirm/claude-session-sync lint
441
+ ```
442
+
443
+ ### Package structure
444
+
445
+ ```
446
+ packages/plugins/claude-session-sync/
447
+ ├── .claude-plugin/
448
+ │ └── plugin.json # Plugin manifest
449
+ ├── hooks/
450
+ │ └── hooks.json # Lifecycle hook definitions
451
+ ├── src/
452
+ │ ├── claude.ts # Hook input parsing + Claude session mapping
453
+ │ ├── hook.ts # Generic hook entry (PreCompact/PostCompact/Stop/...)
454
+ │ ├── session-start.ts # SessionStart entry point
455
+ │ ├── session-end.ts # SessionEnd entry point
456
+ │ ├── transcript-watcher.ts # Detached watcher spawner
457
+ │ ├── cli.ts # Standalone CLI entry point (claude-sync)
458
+ │ ├── cli/ # CLI command modules
459
+ │ │ ├── env.ts # Environment resolution (process.env + settings.local.json)
460
+ │ │ ├── config.ts # Config validation with example error messages
461
+ │ │ ├── project.ts # Claude project folder resolution
462
+ │ │ ├── sync-command.ts # "sync" command
463
+ │ │ ├── list-command.ts # "list" command
464
+ │ │ └── download-command.ts # "download" command
465
+ │ └── index.ts # Public API barrel
466
+ ├── bin/ # Built executables (esbuild single-file bundles)
467
+ ├── build.mjs # esbuild bundling script
468
+ ├── tests/
469
+ │ ├── unit/ # Unit tests
470
+ │ └── e2e/ # End-to-end plugin lifecycle tests
471
+ └── package.json
472
+ ```
473
+
474
+ ## License
475
+
476
+ ISC