claude-session-sync-cli 0.1.0__tar.gz → 0.1.1__tar.gz

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.
@@ -4,8 +4,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
- ### Fixed
8
- - Windows: a running Claude Code CLI (`claude.exe`) was mistaken for the desktop app, blocking `--apply` and `--undo`. Processes are now told apart by install path; unknown ones only trigger a warning.
7
+ ## [0.1.1] - 2026-11-11
8
+
9
+ ### Added
10
+ - `--include-recent`: also sync sessions written in the last 2 minutes instead of skipping them. Such sessions are marked in the output, and `--purge-archived` still never touches them.
9
11
 
10
12
  ## [0.1.0] - 2026-11-11
11
13
 
@@ -15,9 +17,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
15
17
  - Borrow a template from another account when an account has no sessions yet.
16
18
  - `--purge-archived`: permanently delete archived sessions after syncing, moving files to a backup folder first. Respects `--exclude`, `--exclude-file`, and `--project`, and skips sessions whose transcript was written in the last 2 minutes.
17
19
  - `--undo`: revert the most recent write or deletion, including restoring backups.
18
- - Refuse `--apply` and `--undo` while the desktop app is running (macOS, Windows, best effort on Linux) unless `--force` is given.
20
+ - Refuse `--apply` and `--undo` while the desktop app is running unless `--force` is given. On Windows, where the desktop app and the Claude Code CLI are both `claude.exe`, they are told apart by install path and unknown processes only trigger a warning; detection on Linux is best effort.
19
21
  - `--version` and `--help` with examples.
20
22
  - English and Chinese output, chosen from the locale; override with `CLAUDE_SESSION_SYNC_LANG=en|zh`.
21
23
 
22
- [Unreleased]: https://github.com/xushenjie/claude-session-sync/compare/v0.1.0...HEAD
24
+ [Unreleased]: https://github.com/xushenjie/claude-session-sync/compare/v0.1.1...HEAD
25
+ [0.1.1]: https://github.com/xushenjie/claude-session-sync/compare/v0.1.0...v0.1.1
23
26
  [0.1.0]: https://github.com/xushenjie/claude-session-sync/releases/tag/v0.1.0
@@ -40,6 +40,6 @@ The `dev` dependency group in `pyproject.toml` pins the ruff version used by CI.
40
40
  git tag vX.Y.Z && git push origin vX.Y.Z
41
41
  ```
42
42
 
43
- The `Release` workflow checks that the tag matches `__version__` and that the changelog has an entry, builds the package, publishes it to PyPI, and creates a GitHub Release with the changelog section as notes.
43
+ The `Release` workflow checks that the tag matches `__version__` and that the changelog has an entry, builds the package, publishes it to PyPI, and creates a GitHub Release with the changelog section as notes. It runs without manual approval: pushing the tag is the release decision, so double-check the version before tagging — a version published to PyPI can never be reused.
44
44
 
45
- One-time setup before the first release: on PyPI, add a [Trusted Publisher](https://docs.pypi.org/trusted-publishers/adding-a-publisher/) for the `claude-session-sync-cli` project and this repository with workflow `release.yml` and environment `pypi`, and create an environment named `pypi` under the repository's Settings → Environments.
45
+ One-time setup before the first release: on PyPI, add a [Trusted Publisher](https://docs.pypi.org/trusted-publishers/adding-a-publisher/) for the `claude-session-sync-cli` project and this repository with workflow `release.yml` and environment `pypi`, and create an environment named `pypi` under the repository's Settings → Environments, limited to tags matching `v*` (no required reviewers). If more people get write access later, add required reviewers there or a tag ruleset so only maintainers can create `v*` tags.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: claude-session-sync-cli
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Sync local Claude Code CLI sessions into the Claude desktop app sidebar (unofficial).
5
5
  Project-URL: Homepage, https://github.com/xushenjie/claude-session-sync
6
6
  Project-URL: Issues, https://github.com/xushenjie/claude-session-sync/issues
@@ -138,7 +138,7 @@ Output is in English, or in Chinese when your locale (`LC_ALL` / `LC_MESSAGES` /
138
138
  | The session is archived in **any** account (`isArchived` field or `archived-sessions.idx`) | Not added to any account. Use `--per-account-archive` to only consider each account's own archive state |
139
139
  | The session was deleted in the desktop app in any account (`deleted_<session-id>` marker) | Not added to any account |
140
140
  | The session is listed via `--exclude` or `--exclude-file` | Not added |
141
- | The transcript was written within the last 2 minutes (probably still running) | Skipped |
141
+ | The transcript was written within the last 2 minutes (probably still running) | Skipped; pass `--include-recent` to sync it anyway |
142
142
  | Empty sessions (no user message) or transcripts without a working directory | Skipped |
143
143
  | `.jsonl` files in subfolders of a project directory (subagents etc.) | Not scanned |
144
144
 
@@ -167,7 +167,7 @@ claude-session-sync --purge-archived --apply # do it
167
167
  - Nothing is erased outright: files are moved to `~/.claude-desktop-sync/trash-<timestamp>/`, and `--undo` restores everything;
168
168
  - purging always covers **all accounts**, even with `--account`, so no account is left with an index pointing to a removed transcript;
169
169
  - `--exclude`, `--exclude-file`, and `--project` apply to purging too — excluded sessions are never deleted;
170
- - sessions whose `.jsonl` was modified in the last 2 minutes are skipped;
170
+ - sessions whose `.jsonl` was modified in the last 2 minutes are always skipped, even with `--include-recent`;
171
171
  - backups are not cleaned up automatically; once you're happy, delete `~/.claude-desktop-sync/trash-*` yourself.
172
172
 
173
173
  ---
@@ -186,6 +186,7 @@ claude-session-sync --purge-archived --apply # do it
186
186
  | `--project <string>` | Only handle sessions whose working directory contains this string (sync and purge) |
187
187
  | `--exclude <session-id>` | Exclude a session from sync and purge; repeatable |
188
188
  | `--exclude-file <file>` | Read excluded session IDs from a file, one per line |
189
+ | `--include-recent` | Also sync sessions written in the last 2 minutes (possibly still running). They are marked in the output; purging still skips them |
189
190
  | `--per-account-archive` | Only exclude sessions archived in the same account |
190
191
  | `--purge-archived` | Permanently delete archived sessions after syncing (see above) |
191
192
  | `--projects-dir <path>` | CLI sessions directory; defaults to `~/.claude/projects` and honors `CLAUDE_CONFIG_DIR` |
@@ -219,6 +220,23 @@ Undo only touches files the tool itself changed, never the desktop app's existin
219
220
 
220
221
  ---
221
222
 
223
+ ## Upgrade and uninstall
224
+
225
+ Use the package name `claude-session-sync-cli` (not the command name) with your installer:
226
+
227
+ | Installed with | Upgrade | Uninstall |
228
+ |---|---|---|
229
+ | uv | `uv tool upgrade claude-session-sync-cli` | `uv tool uninstall claude-session-sync-cli` |
230
+ | pipx | `pipx upgrade claude-session-sync-cli` | `pipx uninstall claude-session-sync-cli` |
231
+ | single file | re-run the `curl` command from [Install](#install) | delete `cli.py` |
232
+
233
+ Not sure which one you used? `uv tool list` or `pipx list` will show it.
234
+
235
+ - **Upgrading** replaces the old version in place; the command name stays the same. Running `uv tool install` again does not upgrade — use `upgrade`. Journals and backups in `~/.claude-desktop-sync/` are kept, so `--undo` still works across upgrades. Run `claude-session-sync --version` to confirm.
236
+ - **Uninstalling removes only the program.** Sessions it added to the desktop sidebar and everything in `~/.claude-desktop-sync/` stay. To revert those too, quit the desktop app and run `claude-session-sync --undo` repeatedly *before* uninstalling, until it reports nothing left to undo; then delete `~/.claude-desktop-sync/` if you no longer need the backups.
237
+
238
+ ---
239
+
222
240
  ## Known limitations
223
241
 
224
242
  - **The index format is inferred from existing files.** Archived state is recognized from `isArchived`, `archived`, `archivedAt`, `status: "archived"`, and `archived-sessions.idx`; deletions from `deleted_<session-id>` markers. If a desktop update changes the format, `is_archived()` or `load_markers()` need updating.
@@ -109,7 +109,7 @@ Output is in English, or in Chinese when your locale (`LC_ALL` / `LC_MESSAGES` /
109
109
  | The session is archived in **any** account (`isArchived` field or `archived-sessions.idx`) | Not added to any account. Use `--per-account-archive` to only consider each account's own archive state |
110
110
  | The session was deleted in the desktop app in any account (`deleted_<session-id>` marker) | Not added to any account |
111
111
  | The session is listed via `--exclude` or `--exclude-file` | Not added |
112
- | The transcript was written within the last 2 minutes (probably still running) | Skipped |
112
+ | The transcript was written within the last 2 minutes (probably still running) | Skipped; pass `--include-recent` to sync it anyway |
113
113
  | Empty sessions (no user message) or transcripts without a working directory | Skipped |
114
114
  | `.jsonl` files in subfolders of a project directory (subagents etc.) | Not scanned |
115
115
 
@@ -138,7 +138,7 @@ claude-session-sync --purge-archived --apply # do it
138
138
  - Nothing is erased outright: files are moved to `~/.claude-desktop-sync/trash-<timestamp>/`, and `--undo` restores everything;
139
139
  - purging always covers **all accounts**, even with `--account`, so no account is left with an index pointing to a removed transcript;
140
140
  - `--exclude`, `--exclude-file`, and `--project` apply to purging too — excluded sessions are never deleted;
141
- - sessions whose `.jsonl` was modified in the last 2 minutes are skipped;
141
+ - sessions whose `.jsonl` was modified in the last 2 minutes are always skipped, even with `--include-recent`;
142
142
  - backups are not cleaned up automatically; once you're happy, delete `~/.claude-desktop-sync/trash-*` yourself.
143
143
 
144
144
  ---
@@ -157,6 +157,7 @@ claude-session-sync --purge-archived --apply # do it
157
157
  | `--project <string>` | Only handle sessions whose working directory contains this string (sync and purge) |
158
158
  | `--exclude <session-id>` | Exclude a session from sync and purge; repeatable |
159
159
  | `--exclude-file <file>` | Read excluded session IDs from a file, one per line |
160
+ | `--include-recent` | Also sync sessions written in the last 2 minutes (possibly still running). They are marked in the output; purging still skips them |
160
161
  | `--per-account-archive` | Only exclude sessions archived in the same account |
161
162
  | `--purge-archived` | Permanently delete archived sessions after syncing (see above) |
162
163
  | `--projects-dir <path>` | CLI sessions directory; defaults to `~/.claude/projects` and honors `CLAUDE_CONFIG_DIR` |
@@ -190,6 +191,23 @@ Undo only touches files the tool itself changed, never the desktop app's existin
190
191
 
191
192
  ---
192
193
 
194
+ ## Upgrade and uninstall
195
+
196
+ Use the package name `claude-session-sync-cli` (not the command name) with your installer:
197
+
198
+ | Installed with | Upgrade | Uninstall |
199
+ |---|---|---|
200
+ | uv | `uv tool upgrade claude-session-sync-cli` | `uv tool uninstall claude-session-sync-cli` |
201
+ | pipx | `pipx upgrade claude-session-sync-cli` | `pipx uninstall claude-session-sync-cli` |
202
+ | single file | re-run the `curl` command from [Install](#install) | delete `cli.py` |
203
+
204
+ Not sure which one you used? `uv tool list` or `pipx list` will show it.
205
+
206
+ - **Upgrading** replaces the old version in place; the command name stays the same. Running `uv tool install` again does not upgrade — use `upgrade`. Journals and backups in `~/.claude-desktop-sync/` are kept, so `--undo` still works across upgrades. Run `claude-session-sync --version` to confirm.
207
+ - **Uninstalling removes only the program.** Sessions it added to the desktop sidebar and everything in `~/.claude-desktop-sync/` stay. To revert those too, quit the desktop app and run `claude-session-sync --undo` repeatedly *before* uninstalling, until it reports nothing left to undo; then delete `~/.claude-desktop-sync/` if you no longer need the backups.
208
+
209
+ ---
210
+
193
211
  ## Known limitations
194
212
 
195
213
  - **The index format is inferred from existing files.** Archived state is recognized from `isArchived`, `archived`, `archivedAt`, `status: "archived"`, and `archived-sessions.idx`; deletions from `deleted_<session-id>` markers. If a desktop update changes the format, `is_archived()` or `load_markers()` need updating.
@@ -109,7 +109,7 @@ claude-session-sync --undo
109
109
  | 会话在**任意一个账号**里被归档过(`isArchived` 字段或 `archived-sessions.idx`) | 所有账号都不补。加 `--per-account-archive` 改为只看当前账号 |
110
110
  | 会话在任意账号里被桌面端删除过(存在 `deleted_<会话ID>` 标记) | 所有账号都不补 |
111
111
  | 会话在 `--exclude` 或 `--exclude-file` 里 | 不补 |
112
- | 最近 2 分钟内还在写入的会话(可能正在运行) | 跳过 |
112
+ | 最近 2 分钟内还在写入的会话(可能正在运行) | 跳过;加 `--include-recent` 可以照样同步 |
113
113
  | 空会话(没有用户消息)、找不到工作目录的记录 | 跳过 |
114
114
  | 项目目录下子文件夹里的 `.jsonl`(子代理等) | 不扫描 |
115
115
 
@@ -138,7 +138,7 @@ claude-session-sync --purge-archived --apply # 执行
138
138
  - 被删的文件不会直接抹掉,而是移到 `~/.claude-desktop-sync/trash-<时间>/`,`--undo` 可以全部还原;
139
139
  - 不管有没有加 `--account`,清除都会覆盖**全部账号**,避免别的账号留下指向已删文件的索引;
140
140
  - `--exclude`、`--exclude-file` 和 `--project` 对清除同样生效,排除的会话不会被删;
141
- - 最近 2 分钟内还在写入的会话会跳过(按 `.jsonl` 的修改时间判断);
141
+ - 最近 2 分钟内还在写入的会话一定会跳过(按 `.jsonl` 的修改时间判断),加了 `--include-recent` 也一样;
142
142
  - 备份目录不会自动清理,确认没问题后可以手动删除 `~/.claude-desktop-sync/trash-*`。
143
143
 
144
144
  ---
@@ -157,6 +157,7 @@ claude-session-sync --purge-archived --apply # 执行
157
157
  | `--project <字符串>` | 只处理工作目录里包含这个字符串的会话(同步和清除都生效) |
158
158
  | `--exclude <会话ID>` | 排除某个会话,同步和清除都跳过,可以写多次 |
159
159
  | `--exclude-file <文件>` | 从文件读取排除列表,每行一个会话 ID |
160
+ | `--include-recent` | 同步时不跳过最近 2 分钟内仍在写入的会话(可能正在运行),输出里会单独标出;清除时仍然跳过 |
160
161
  | `--per-account-archive` | 只按当前账号的归档状态来排除 |
161
162
  | `--purge-archived` | 同步后彻底删除已归档会话(见上一节) |
162
163
  | `--projects-dir <路径>` | CLI 会话目录,默认 `~/.claude/projects`,也会读取 `CLAUDE_CONFIG_DIR` |
@@ -190,6 +191,23 @@ claude-session-sync --exclude-file ~/no-sync.txt --apply
190
191
 
191
192
  ---
192
193
 
194
+ ## 升级与卸载
195
+
196
+ 用安装时的工具,配合**包名** `claude-session-sync-cli`(不是命令名):
197
+
198
+ | 安装方式 | 升级 | 卸载 |
199
+ |---|---|---|
200
+ | uv | `uv tool upgrade claude-session-sync-cli` | `uv tool uninstall claude-session-sync-cli` |
201
+ | pipx | `pipx upgrade claude-session-sync-cli` | `pipx uninstall claude-session-sync-cli` |
202
+ | 单文件 | 重新运行[安装](#安装)一节里的 `curl` 命令 | 删除 `cli.py` |
203
+
204
+ 不记得用哪种方式装的,运行 `uv tool list` 或 `pipx list` 看一下。
205
+
206
+ - **升级**会直接替换旧版本,命令名不变。再运行一次 `uv tool install` 不会升级,要用 `upgrade`。`~/.claude-desktop-sync/` 里的日志和备份会保留,升级后 `--undo` 依然可以撤销以前的操作。升级完可以用 `claude-session-sync --version` 确认版本。
207
+ - **卸载只删除程序本身。** 已经同步到桌面端侧边栏的会话和 `~/.claude-desktop-sync/` 都会保留。想一并撤销,请在**卸载前**退出桌面端,反复运行 `claude-session-sync --undo`,直到提示没有可撤销的记录;不再需要备份的话,再手动删除 `~/.claude-desktop-sync/`。
208
+
209
+ ---
210
+
193
211
  ## 已知限制
194
212
 
195
213
  - **索引格式是根据现有文件推测的**:归档会识别 `isArchived`、`archived`、`archivedAt`、`status: "archived"` 和 `archived-sessions.idx`;删除会识别 `deleted_<会话ID>` 标记。桌面端更新后如果格式变了,需要修改 `is_archived()` 或 `load_markers()`。
@@ -40,7 +40,7 @@ import time
40
40
  import uuid
41
41
  from pathlib import Path, PurePosixPath, PureWindowsPath
42
42
 
43
- __version__ = "0.1.0"
43
+ __version__ = "0.1.1"
44
44
 
45
45
  HOME = Path.home()
46
46
  JOURNAL_DIR = HOME / ".claude-desktop-sync"
@@ -668,6 +668,15 @@ Notes:
668
668
  "--exclude-file",
669
669
  help=t("file with session IDs to exclude, one per line", "排除列表文件,每行一个会话ID"),
670
670
  )
671
+ ap.add_argument(
672
+ "--include-recent",
673
+ action="store_true",
674
+ help=t(
675
+ "also sync sessions written in the last 2 minutes (possibly still running); "
676
+ "--purge-archived still skips them",
677
+ "同步时不跳过最近 2 分钟内仍在写入的会话(可能正在运行);--purge-archived 仍然会跳过",
678
+ ),
679
+ )
671
680
  ap.add_argument(
672
681
  "--per-account-archive",
673
682
  action="store_true",
@@ -792,7 +801,9 @@ Notes:
792
801
  if args.project:
793
802
  sessions = [s for s in sessions if args.project in s["cwd"]]
794
803
  now = time.time()
795
- running = {s["id"] for s in sessions if now - s["mtime"] < RUNNING_GRACE_SEC}
804
+ recent = {s["id"] for s in sessions if now - s["mtime"] < RUNNING_GRACE_SEC}
805
+ skip_recent = set() if args.include_recent else recent
806
+ included_recent = set()
796
807
 
797
808
  print(
798
809
  t(
@@ -819,7 +830,7 @@ Notes:
819
830
  todo = [
820
831
  s
821
832
  for s in sessions
822
- if s["id"] not in known and s["id"] not in arch and s["id"] not in excluded and s["id"] not in running
833
+ if s["id"] not in known and s["id"] not in arch and s["id"] not in excluded and s["id"] not in skip_recent
823
834
  ]
824
835
  print(
825
836
  t(
@@ -847,7 +858,11 @@ Notes:
847
858
  continue
848
859
  for s in sorted(todo, key=lambda x: -x["lastActivityAt"]):
849
860
  when = time.strftime("%Y-%m-%d %H:%M", time.localtime(s["lastActivityAt"] / 1000))
850
- print(f" + {when} {s['title']} ({s['cwd']})")
861
+ mark = ""
862
+ if s["id"] in recent:
863
+ included_recent.add(s["id"])
864
+ mark = t(" [still being written]", " (最近仍在写入)")
865
+ print(f" + {when} {s['title']} ({s['cwd']}){mark}")
851
866
  if args.apply:
852
867
  new_id, entry = build_entry(template, s)
853
868
  out = d / f"{new_id}.json"
@@ -855,11 +870,23 @@ Notes:
855
870
  json.dump(entry, fh, ensure_ascii=False, indent=2)
856
871
  created.append(out)
857
872
 
858
- if running:
873
+ if skip_recent:
874
+ print(
875
+ t(
876
+ f"\nSkipped {len(skip_recent)} session(s) written in the last 2 minutes (probably still running). "
877
+ "Pass --include-recent to sync them anyway.",
878
+ f"\n跳过 {len(skip_recent)} 个最近 2 分钟内仍在写入的会话(可能正在运行)。"
879
+ "如需一并同步,加 --include-recent。",
880
+ )
881
+ )
882
+ if included_recent:
859
883
  print(
860
884
  t(
861
- f"\nSkipped {len(running)} session(s) written in the last 2 minutes (probably still running).",
862
- f"\n跳过 {len(running)} 个最近 2 分钟内仍在写入的会话(可能正在运行)。",
885
+ f"\n! Included {len(included_recent)} session(s) still being written. Their title and last-activity "
886
+ "time may be out of date, and don't type into the same session from the CLI and the desktop app "
887
+ "at the same time.",
888
+ f"\n! 包含了 {len(included_recent)} 个最近仍在写入的会话。它们的标题和最后活动时间可能不是最新的,"
889
+ "也不要在 CLI 和桌面端同时向同一个会话输入。",
863
890
  )
864
891
  )
865
892
 
@@ -375,3 +375,39 @@ def test_windows_desktop_detection(paths, expected):
375
375
  def test_windows_unknown_process_warns_but_does_not_block(capsys):
376
376
  assert cli.windows_desktop_running(["", r"D:\tools\claude.exe"]) is False
377
377
  assert "may or may not be the desktop app" in capsys.readouterr().err
378
+
379
+
380
+ # ---------------------------------------------------------------- --include-recent
381
+
382
+
383
+ def test_recent_session_skipped_with_hint(env, capsys):
384
+ import time
385
+
386
+ env.add_index("existing")
387
+ env.add_cli_session("busy", mtime=time.time())
388
+ env.run("--apply")
389
+ assert cli_ids(env.index_entries()) == ["existing"]
390
+ assert "--include-recent" in capsys.readouterr().out
391
+
392
+
393
+ def test_include_recent_syncs_and_warns(env, capsys):
394
+ import time
395
+
396
+ env.add_index("existing")
397
+ env.add_cli_session("busy", mtime=time.time())
398
+ env.add_cli_session("old")
399
+ env.run("--apply", "--include-recent")
400
+ assert cli_ids(env.index_entries()) == ["busy", "existing", "old"]
401
+ out = capsys.readouterr().out
402
+ assert out.count("[still being written]") == 1 # 只标注最近写入的那个
403
+ assert "Included 1 session(s) still being written" in out
404
+ assert "Skipped" not in out
405
+
406
+
407
+ def test_include_recent_does_not_affect_purge(env):
408
+ import time
409
+
410
+ arch = env.add_index("busy", archived=True)
411
+ jsonl = env.add_cli_session("busy", mtime=time.time())
412
+ env.run("--purge-archived", "--apply", "--include-recent")
413
+ assert arch.exists() and jsonl.exists()