multi-claude-cli 0.10.0__tar.gz → 0.11.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.
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/PKG-INFO +10 -7
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/README.md +9 -6
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/__init__.py +1 -1
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/cli.py +90 -18
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude_cli.egg-info/PKG-INFO +10 -7
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_sessions.py +84 -21
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/LICENSE +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/pyproject.toml +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/setup.cfg +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/__main__.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/accounts.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/actions.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/bundle.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/completion.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/config.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/doctor.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/env.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/fsutil.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/history.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/hook.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/identity.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/launcher.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/lock.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/migrate.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/plan.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/platform.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/refresh.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/restore.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/routes.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/sessions.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/shared.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/shellpath.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/statusline.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude/usage.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude_cli.egg-info/SOURCES.txt +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude_cli.egg-info/dependency_links.txt +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude_cli.egg-info/entry_points.txt +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude_cli.egg-info/top_level.txt +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_accounts.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_bundle.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_completion.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_config.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_doctor.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_everyday.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_fsutil.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_handoff.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_help.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_history.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_identity.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_install.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_mcp.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_migrate.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_onboarding.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_packaging.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_plan.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_platform.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_refresh.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_rename.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_routes.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_set.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_shared_exclude.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_statusline.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_usability.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_usage.py +0 -0
- {multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/tests/test_vscode.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multi-claude-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.1
|
|
4
4
|
Summary: Run several Claude Code accounts side by side: separate CLAUDE_CONFIG_DIR directories, launchers, proxies, environment variables and arguments.
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/jakoes-wu/multi-claude
|
|
@@ -333,17 +333,20 @@ old keychain token expired - inherit no -
|
|
|
333
333
|
|
|
334
334
|
### Finding a session
|
|
335
335
|
|
|
336
|
-
Forgot which account or which directory a session was started in? `sessions` lists the sessions of every account and every project directory, newest first
|
|
336
|
+
Forgot which account or which directory a session was started in? `sessions` lists the sessions of every account and every project directory, newest first; `--cmd` adds the command that resumes each one:
|
|
337
337
|
|
|
338
338
|
```sh
|
|
339
339
|
multi-claude sessions
|
|
340
|
-
# ACCOUNT UPDATED
|
|
341
|
-
# work 2026-10-11
|
|
342
|
-
# cd /path/to/project && claude-work --resume 3c588340-1219-48df-a231-509866789487
|
|
340
|
+
# TITLE ACCOUNT CREATED UPDATED SESSION ID
|
|
341
|
+
# Fix the importer work 2026-10-11 16:05 2026-10-11 17:42 3c588340-1219-48df-a231-509866789487
|
|
343
342
|
multi-claude sessions work # one account
|
|
344
343
|
multi-claude sessions --grep importer # title, directory or session id contains the text
|
|
344
|
+
multi-claude sessions --grep importer --cmd
|
|
345
|
+
# TITLE ACCOUNT CREATED UPDATED CMD
|
|
346
|
+
# Fix the importer work 2026-10-11 16:05 2026-10-11 17:42 cd ~/project && claude-work --resume 3c588340-1219-48df-a231-509866789487
|
|
345
347
|
```
|
|
346
348
|
|
|
349
|
+
- `--cmd` puts the resume command in a `CMD` column when the whole table fits the width of the terminal; otherwise each command goes on its own line below its session. The `SESSION ID` column is left out, since each command ends with the session id. The home directory is written as `~`. When the output is piped, the width counts as 80 columns unless `COLUMNS` is set.
|
|
347
350
|
- It shows the 30 most recent sessions; `--limit N` changes that and `--limit 0` shows all. `--json` prints every match with the full fields (`directory`, `created`, `updated`, `title_source`, `file`, `resume`). `conversations` is another name for the same command.
|
|
348
351
|
- The title is the session's name in Claude Code; a session without one shows the beginning of its first prompt, and `-` if it has no prompt at all. Times are local. `UPDATED` is the time of the last message, or the file's modification time when the session has no timestamps.
|
|
349
352
|
- A `~/.claude` that has not been turned into an account is listed as `default`.
|
|
@@ -473,7 +476,7 @@ There is no command that switches which account plain `claude` uses. Besides `~/
|
|
|
473
476
|
|
|
474
477
|
### Which account or directory was that session in?
|
|
475
478
|
|
|
476
|
-
`multi-claude sessions --grep TEXT` searches the titles, directories and ids of the sessions of all accounts
|
|
479
|
+
`multi-claude sessions --grep TEXT` searches the titles, directories and ids of the sessions of all accounts; add `--cmd` to print the command that resumes each match. See [Finding a session](#finding-a-session).
|
|
477
480
|
|
|
478
481
|
### How do I continue a conversation with another account?
|
|
479
482
|
|
|
@@ -522,7 +525,7 @@ It never copies, exports, uploads or backs up a login. One command touches one:
|
|
|
522
525
|
| `multi-claude path NAME` | Print the account's directory. |
|
|
523
526
|
| `multi-claude code NAME [PATH] [-- ARGS ...]` | Open a separate VS Code instance with the account's environment (experimental). |
|
|
524
527
|
| `multi-claude handoff TARGET [--from NAME] [--session ID] [--force]` | Copy a session to another account and print the command that resumes it there. |
|
|
525
|
-
| `multi-claude sessions [NAME] [--grep TEXT] [--limit N] [--json]` | List the sessions of every account and directory, newest first
|
|
528
|
+
| `multi-claude sessions [NAME] [--grep TEXT] [--limit N] [--cmd] [--json]` | List the sessions of every account and directory, newest first; `--cmd` adds the command that resumes each. Also `conversations`. |
|
|
526
529
|
| `multi-claude statusline install\|uninstall FILE` | Wrap the statusLine command in a settings file so that `usage` gets fresh numbers, or restore it. |
|
|
527
530
|
| `multi-claude completion bash\|zsh\|fish` | Print a shell completion script. |
|
|
528
531
|
|
|
@@ -306,17 +306,20 @@ old keychain token expired - inherit no -
|
|
|
306
306
|
|
|
307
307
|
### Finding a session
|
|
308
308
|
|
|
309
|
-
Forgot which account or which directory a session was started in? `sessions` lists the sessions of every account and every project directory, newest first
|
|
309
|
+
Forgot which account or which directory a session was started in? `sessions` lists the sessions of every account and every project directory, newest first; `--cmd` adds the command that resumes each one:
|
|
310
310
|
|
|
311
311
|
```sh
|
|
312
312
|
multi-claude sessions
|
|
313
|
-
# ACCOUNT UPDATED
|
|
314
|
-
# work 2026-10-11
|
|
315
|
-
# cd /path/to/project && claude-work --resume 3c588340-1219-48df-a231-509866789487
|
|
313
|
+
# TITLE ACCOUNT CREATED UPDATED SESSION ID
|
|
314
|
+
# Fix the importer work 2026-10-11 16:05 2026-10-11 17:42 3c588340-1219-48df-a231-509866789487
|
|
316
315
|
multi-claude sessions work # one account
|
|
317
316
|
multi-claude sessions --grep importer # title, directory or session id contains the text
|
|
317
|
+
multi-claude sessions --grep importer --cmd
|
|
318
|
+
# TITLE ACCOUNT CREATED UPDATED CMD
|
|
319
|
+
# Fix the importer work 2026-10-11 16:05 2026-10-11 17:42 cd ~/project && claude-work --resume 3c588340-1219-48df-a231-509866789487
|
|
318
320
|
```
|
|
319
321
|
|
|
322
|
+
- `--cmd` puts the resume command in a `CMD` column when the whole table fits the width of the terminal; otherwise each command goes on its own line below its session. The `SESSION ID` column is left out, since each command ends with the session id. The home directory is written as `~`. When the output is piped, the width counts as 80 columns unless `COLUMNS` is set.
|
|
320
323
|
- It shows the 30 most recent sessions; `--limit N` changes that and `--limit 0` shows all. `--json` prints every match with the full fields (`directory`, `created`, `updated`, `title_source`, `file`, `resume`). `conversations` is another name for the same command.
|
|
321
324
|
- The title is the session's name in Claude Code; a session without one shows the beginning of its first prompt, and `-` if it has no prompt at all. Times are local. `UPDATED` is the time of the last message, or the file's modification time when the session has no timestamps.
|
|
322
325
|
- A `~/.claude` that has not been turned into an account is listed as `default`.
|
|
@@ -446,7 +449,7 @@ There is no command that switches which account plain `claude` uses. Besides `~/
|
|
|
446
449
|
|
|
447
450
|
### Which account or directory was that session in?
|
|
448
451
|
|
|
449
|
-
`multi-claude sessions --grep TEXT` searches the titles, directories and ids of the sessions of all accounts
|
|
452
|
+
`multi-claude sessions --grep TEXT` searches the titles, directories and ids of the sessions of all accounts; add `--cmd` to print the command that resumes each match. See [Finding a session](#finding-a-session).
|
|
450
453
|
|
|
451
454
|
### How do I continue a conversation with another account?
|
|
452
455
|
|
|
@@ -495,7 +498,7 @@ It never copies, exports, uploads or backs up a login. One command touches one:
|
|
|
495
498
|
| `multi-claude path NAME` | Print the account's directory. |
|
|
496
499
|
| `multi-claude code NAME [PATH] [-- ARGS ...]` | Open a separate VS Code instance with the account's environment (experimental). |
|
|
497
500
|
| `multi-claude handoff TARGET [--from NAME] [--session ID] [--force]` | Copy a session to another account and print the command that resumes it there. |
|
|
498
|
-
| `multi-claude sessions [NAME] [--grep TEXT] [--limit N] [--json]` | List the sessions of every account and directory, newest first
|
|
501
|
+
| `multi-claude sessions [NAME] [--grep TEXT] [--limit N] [--cmd] [--json]` | List the sessions of every account and directory, newest first; `--cmd` adds the command that resumes each. Also `conversations`. |
|
|
499
502
|
| `multi-claude statusline install\|uninstall FILE` | Wrap the statusLine command in a settings file so that `usage` gets fresh numbers, or restore it. |
|
|
500
503
|
| `multi-claude completion bash\|zsh\|fish` | Print a shell completion script. |
|
|
501
504
|
|
|
@@ -15,6 +15,7 @@ import shutil
|
|
|
15
15
|
import stat
|
|
16
16
|
import subprocess
|
|
17
17
|
import sys
|
|
18
|
+
import unicodedata
|
|
18
19
|
from datetime import datetime, timedelta, timezone
|
|
19
20
|
from typing import List, NamedTuple, Optional, Tuple
|
|
20
21
|
|
|
@@ -279,15 +280,19 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
279
280
|
_add_dry_run(p_handoff)
|
|
280
281
|
|
|
281
282
|
p_sessions = sub.add_parser("sessions", description="list the Claude sessions of every account and every directory, "
|
|
282
|
-
"newest first
|
|
283
|
+
"newest first; --cmd adds the command that resumes each "
|
|
283
284
|
"(read-only; also available as `conversations`): "
|
|
284
|
-
"sessions [NAME] [--grep TEXT] [--limit N] [--json]")
|
|
285
|
+
"sessions [NAME] [--grep TEXT] [--limit N] [--cmd] [--json]")
|
|
285
286
|
p_sessions.add_argument("name", nargs="?", metavar="NAME",
|
|
286
287
|
help="only this account (`default` is the unregistered ~/.claude)")
|
|
287
288
|
p_sessions.add_argument("--grep", metavar="TEXT",
|
|
288
289
|
help="only sessions whose title, directory or id contains TEXT (case-insensitive)")
|
|
289
290
|
p_sessions.add_argument("--limit", type=int, metavar="N",
|
|
290
291
|
help="show at most N sessions, 0 for all (default: 30, or all with --json)")
|
|
292
|
+
p_sessions.add_argument("--cmd", action="store_true",
|
|
293
|
+
help="also print the command that resumes each session (a CMD column when the "
|
|
294
|
+
"terminal is wide enough, otherwise a line below each session); the SESSION ID "
|
|
295
|
+
"column is then left out, since the command contains the id")
|
|
291
296
|
p_sessions.add_argument("--json", action="store_true", help="print machine-readable JSON")
|
|
292
297
|
return parser
|
|
293
298
|
|
|
@@ -427,7 +432,7 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
427
432
|
return cmd_usage(args.name, args.json)
|
|
428
433
|
if args.command == "sessions":
|
|
429
434
|
# 只读:不加写锁,迁移未完成时也放行(与 list、usage 相同)。
|
|
430
|
-
return cmd_sessions(args.name, args.grep, args.limit, args.json)
|
|
435
|
+
return cmd_sessions(args.name, args.grep, args.limit, args.json, args.cmd)
|
|
431
436
|
if args.command == "which":
|
|
432
437
|
return cmd_which(args.path)
|
|
433
438
|
if args.command == "statusline":
|
|
@@ -1767,8 +1772,11 @@ def _session_sources(config: Config, exists: bool) -> List[_SessionSource]:
|
|
|
1767
1772
|
return sources
|
|
1768
1773
|
|
|
1769
1774
|
|
|
1770
|
-
def cmd_sessions(name: Optional[str], grep: Optional[str], limit: Optional[int], as_json: bool
|
|
1771
|
-
|
|
1775
|
+
def cmd_sessions(name: Optional[str], grep: Optional[str], limit: Optional[int], as_json: bool,
|
|
1776
|
+
show_cmd: bool = False) -> int:
|
|
1777
|
+
"""只读列出各账号、各目录的会话,最近更新的在前(方案 feature-session-list、feature-sessions-cmd-column)。
|
|
1778
|
+
|
|
1779
|
+
show_cmd 只影响文本输出:为真时才打印续聊命令。JSON 输出始终带 resume 字段,不受它影响。
|
|
1772
1780
|
|
|
1773
1781
|
会把会话标题或首条提问的开头打印出来(这是对话内容的片段),不保存、不联网、不读凭据。
|
|
1774
1782
|
同一会话 ID 在多个账号里各有一份时(handoff 复制过)各列一行,不合并。
|
|
@@ -1821,31 +1829,95 @@ def cmd_sessions(name: Optional[str], grep: Optional[str], limit: Optional[int],
|
|
|
1821
1829
|
if not entries:
|
|
1822
1830
|
print("no sessions found")
|
|
1823
1831
|
return accounts.EXIT_OK
|
|
1824
|
-
# 不用 _print_table
|
|
1825
|
-
|
|
1832
|
+
# 不用 _print_table:标题在第一列且可能是中文,要按终端显示宽度对齐;带 --cmd 时还要在两种排版之间选。
|
|
1833
|
+
header = ["TITLE", "ACCOUNT", "CREATED", "UPDATED", "SESSION ID"]
|
|
1834
|
+
rows = []
|
|
1835
|
+
commands = []
|
|
1826
1836
|
for source, session, full_title in entries:
|
|
1827
|
-
|
|
1837
|
+
rows.append([sessions.clean_title(full_title, _TITLE_TEXT_CHARS) or "-", source.name,
|
|
1838
|
+
_local_minute(session.created), _local_minute(session.updated), session.session_id])
|
|
1839
|
+
resume = _resume_command(source, session, tilde=True)
|
|
1828
1840
|
if session.directory is None:
|
|
1829
1841
|
resume += " (start directory unknown; project {})".format(
|
|
1830
1842
|
sessions.clean_title(session.project, len(session.project)))
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1843
|
+
commands.append(resume)
|
|
1844
|
+
if not show_cmd:
|
|
1845
|
+
for line in _aligned([header] + rows):
|
|
1846
|
+
print(line)
|
|
1847
|
+
else:
|
|
1848
|
+
# 续聊命令里总有 --resume <会话ID>(目录未知时也有),带 --cmd 时两种排版都去掉 SESSION ID 列,
|
|
1849
|
+
# 免得同一串 ID 在一条会话里出现两次;不带 --cmd 时它是唯一能看到 ID 的地方,必须保留。
|
|
1850
|
+
header = header[:-1]
|
|
1851
|
+
rows = [row[:-1] for row in rows]
|
|
1852
|
+
# 续聊命令很长:整张表(表头与数据行,不含末尾的截断提示)放得下终端宽度才并成 CMD 列,
|
|
1853
|
+
# 否则命令在窄终端里会从中间折行,比单独一行更难读、也更难复制。按整张表决定,不逐行切换。
|
|
1854
|
+
wide = _aligned([header + ["CMD"]] + [row + [command] for row, command in zip(rows, commands)])
|
|
1855
|
+
if max(_display_width(line) for line in wide) <= shutil.get_terminal_size((80, 24)).columns:
|
|
1856
|
+
for line in wide:
|
|
1857
|
+
print(line)
|
|
1858
|
+
else:
|
|
1859
|
+
narrow = _aligned([header] + rows)
|
|
1860
|
+
print(narrow[0])
|
|
1861
|
+
for line, command in zip(narrow[1:], commands):
|
|
1862
|
+
print(line)
|
|
1863
|
+
print(" " + command)
|
|
1838
1864
|
if total > len(entries):
|
|
1839
1865
|
print("showing {} of {} sessions; use --limit 0 for all, or --grep TEXT to narrow".format(len(entries), total))
|
|
1840
1866
|
return accounts.EXIT_OK
|
|
1841
1867
|
|
|
1842
1868
|
|
|
1843
|
-
def _resume_command(source: _SessionSource, session: sessions.SessionInfo) -> str:
|
|
1844
|
-
"""续聊命令。Claude 按当前目录找会话,所以先 cd 到会话启动时的目录;目录未知时只给后半段。
|
|
1869
|
+
def _resume_command(source: _SessionSource, session: sessions.SessionInfo, tilde: bool = False) -> str:
|
|
1870
|
+
"""续聊命令。Claude 按当前目录找会话,所以先 cd 到会话启动时的目录;目录未知时只给后半段。
|
|
1871
|
+
|
|
1872
|
+
tilde 为真时把主目录写成 ~,只用于给人看的文本输出;JSON 的 resume 字段保持绝对路径。
|
|
1873
|
+
"""
|
|
1845
1874
|
resume = "{} --resume {}".format(source.launcher, session.session_id)
|
|
1846
1875
|
if session.directory is None:
|
|
1847
1876
|
return resume
|
|
1848
|
-
|
|
1877
|
+
target = _tilde_path(session.directory) if tilde else shlex.quote(session.directory)
|
|
1878
|
+
return "cd {} && {}".format(target, resume)
|
|
1879
|
+
|
|
1880
|
+
|
|
1881
|
+
def _tilde_path(directory: str) -> str:
|
|
1882
|
+
"""把主目录之下的目录写成 ~/…,可直接拼在 cd 后面;不在主目录之下时与 shlex.quote 相同。
|
|
1883
|
+
|
|
1884
|
+
按字符串前缀比较,不解析软链:会话里记的是当时的目录原文。
|
|
1885
|
+
"""
|
|
1886
|
+
home = os.path.expanduser("~")
|
|
1887
|
+
if not os.path.isabs(home) or home == os.sep:
|
|
1888
|
+
return shlex.quote(directory)
|
|
1889
|
+
if directory == home:
|
|
1890
|
+
return "~"
|
|
1891
|
+
if not directory.startswith(home + os.sep):
|
|
1892
|
+
return shlex.quote(directory)
|
|
1893
|
+
rest = directory[len(home) + 1:]
|
|
1894
|
+
# 波浪号必须留在引号外面,放进引号里 shell 不会把它展开成主目录
|
|
1895
|
+
return "~/" + shlex.quote(rest) if rest else "~"
|
|
1896
|
+
|
|
1897
|
+
|
|
1898
|
+
def _display_width(text: str) -> int:
|
|
1899
|
+
"""文字在终端里占的格数:中日韩等宽字符占 2 格,组合字符和零宽连接符不占格。
|
|
1900
|
+
|
|
1901
|
+
表情符号的组合序列在不同终端里宽度不一,这里可能差一格。
|
|
1902
|
+
"""
|
|
1903
|
+
width = 0
|
|
1904
|
+
for char in text:
|
|
1905
|
+
# U+200C、U+200D 是 clean_title 特意保留的零宽字符
|
|
1906
|
+
if char in "\u200c\u200d" or unicodedata.category(char) in ("Mn", "Me"):
|
|
1907
|
+
continue
|
|
1908
|
+
width += 2 if unicodedata.east_asian_width(char) in ("W", "F") else 1
|
|
1909
|
+
return width
|
|
1910
|
+
|
|
1911
|
+
|
|
1912
|
+
def _aligned(rows: List[List[str]]) -> List[str]:
|
|
1913
|
+
"""按显示宽度对齐各列,返回每行的文字;列间两个空格,最后一列不补空格。"""
|
|
1914
|
+
widths = [max(_display_width(row[index]) for row in rows) for index in range(len(rows[0]))]
|
|
1915
|
+
lines = []
|
|
1916
|
+
for row in rows:
|
|
1917
|
+
# str.ljust 按字符数补空格,中文标题会少补;这里按显示宽度补
|
|
1918
|
+
cells = [cell + " " * (width - _display_width(cell)) for cell, width in zip(row, widths)]
|
|
1919
|
+
lines.append(" ".join(cells).rstrip())
|
|
1920
|
+
return lines
|
|
1849
1921
|
|
|
1850
1922
|
|
|
1851
1923
|
def _local_minute(moment: Optional[datetime]) -> str:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multi-claude-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.1
|
|
4
4
|
Summary: Run several Claude Code accounts side by side: separate CLAUDE_CONFIG_DIR directories, launchers, proxies, environment variables and arguments.
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/jakoes-wu/multi-claude
|
|
@@ -333,17 +333,20 @@ old keychain token expired - inherit no -
|
|
|
333
333
|
|
|
334
334
|
### Finding a session
|
|
335
335
|
|
|
336
|
-
Forgot which account or which directory a session was started in? `sessions` lists the sessions of every account and every project directory, newest first
|
|
336
|
+
Forgot which account or which directory a session was started in? `sessions` lists the sessions of every account and every project directory, newest first; `--cmd` adds the command that resumes each one:
|
|
337
337
|
|
|
338
338
|
```sh
|
|
339
339
|
multi-claude sessions
|
|
340
|
-
# ACCOUNT UPDATED
|
|
341
|
-
# work 2026-10-11
|
|
342
|
-
# cd /path/to/project && claude-work --resume 3c588340-1219-48df-a231-509866789487
|
|
340
|
+
# TITLE ACCOUNT CREATED UPDATED SESSION ID
|
|
341
|
+
# Fix the importer work 2026-10-11 16:05 2026-10-11 17:42 3c588340-1219-48df-a231-509866789487
|
|
343
342
|
multi-claude sessions work # one account
|
|
344
343
|
multi-claude sessions --grep importer # title, directory or session id contains the text
|
|
344
|
+
multi-claude sessions --grep importer --cmd
|
|
345
|
+
# TITLE ACCOUNT CREATED UPDATED CMD
|
|
346
|
+
# Fix the importer work 2026-10-11 16:05 2026-10-11 17:42 cd ~/project && claude-work --resume 3c588340-1219-48df-a231-509866789487
|
|
345
347
|
```
|
|
346
348
|
|
|
349
|
+
- `--cmd` puts the resume command in a `CMD` column when the whole table fits the width of the terminal; otherwise each command goes on its own line below its session. The `SESSION ID` column is left out, since each command ends with the session id. The home directory is written as `~`. When the output is piped, the width counts as 80 columns unless `COLUMNS` is set.
|
|
347
350
|
- It shows the 30 most recent sessions; `--limit N` changes that and `--limit 0` shows all. `--json` prints every match with the full fields (`directory`, `created`, `updated`, `title_source`, `file`, `resume`). `conversations` is another name for the same command.
|
|
348
351
|
- The title is the session's name in Claude Code; a session without one shows the beginning of its first prompt, and `-` if it has no prompt at all. Times are local. `UPDATED` is the time of the last message, or the file's modification time when the session has no timestamps.
|
|
349
352
|
- A `~/.claude` that has not been turned into an account is listed as `default`.
|
|
@@ -473,7 +476,7 @@ There is no command that switches which account plain `claude` uses. Besides `~/
|
|
|
473
476
|
|
|
474
477
|
### Which account or directory was that session in?
|
|
475
478
|
|
|
476
|
-
`multi-claude sessions --grep TEXT` searches the titles, directories and ids of the sessions of all accounts
|
|
479
|
+
`multi-claude sessions --grep TEXT` searches the titles, directories and ids of the sessions of all accounts; add `--cmd` to print the command that resumes each match. See [Finding a session](#finding-a-session).
|
|
477
480
|
|
|
478
481
|
### How do I continue a conversation with another account?
|
|
479
482
|
|
|
@@ -522,7 +525,7 @@ It never copies, exports, uploads or backs up a login. One command touches one:
|
|
|
522
525
|
| `multi-claude path NAME` | Print the account's directory. |
|
|
523
526
|
| `multi-claude code NAME [PATH] [-- ARGS ...]` | Open a separate VS Code instance with the account's environment (experimental). |
|
|
524
527
|
| `multi-claude handoff TARGET [--from NAME] [--session ID] [--force]` | Copy a session to another account and print the command that resumes it there. |
|
|
525
|
-
| `multi-claude sessions [NAME] [--grep TEXT] [--limit N] [--json]` | List the sessions of every account and directory, newest first
|
|
528
|
+
| `multi-claude sessions [NAME] [--grep TEXT] [--limit N] [--cmd] [--json]` | List the sessions of every account and directory, newest first; `--cmd` adds the command that resumes each. Also `conversations`. |
|
|
526
529
|
| `multi-claude statusline install\|uninstall FILE` | Wrap the statusLine command in a settings file so that `usage` gets fresh numbers, or restore it. |
|
|
527
530
|
| `multi-claude completion bash\|zsh\|fish` | Print a shell completion script. |
|
|
528
531
|
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
|
+
import shlex
|
|
9
|
+
import shutil
|
|
8
10
|
import sys
|
|
9
11
|
import unittest
|
|
10
12
|
|
|
@@ -83,15 +85,69 @@ class ListingTest(SessionsTestCase):
|
|
|
83
85
|
self.make(self.dir_b, SID3, [user("q", cwd="/w/third", ts="2026-10-02T00:00:00.000Z"), ai("Middle session")],
|
|
84
86
|
project="-w-third")
|
|
85
87
|
out = self.ok("sessions", env={"TZ": "Asia/Shanghai"}).out.splitlines()
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
self.
|
|
88
|
+
# 按最后更新时间从新到旧;时间按本地时区(UTC+8)显示。不带 --cmd 时每条会话一行,不打印续聊命令
|
|
89
|
+
self.assertEqual(out, [
|
|
90
|
+
"TITLE ACCOUNT CREATED UPDATED SESSION ID",
|
|
91
|
+
"Newest session a 2026-10-03 00:30 2026-10-03 09:00 " + SID2,
|
|
92
|
+
"Middle session b 2026-10-02 08:00 2026-10-02 08:00 " + SID3,
|
|
93
|
+
"Older session a 2026-10-01 10:00 2026-10-01 11:30 " + SID1])
|
|
94
|
+
|
|
95
|
+
def test_cmd_column_when_the_terminal_is_wide_enough(self):
|
|
96
|
+
self.make(self.dir_a, SID1, [user("q"), ai("One")])
|
|
97
|
+
self.make(self.dir_b, SID2, [user("q", cwd="/w/other", ts="2026-10-01T01:00:00.000Z"), ai("Two")],
|
|
98
|
+
project="-w-other")
|
|
99
|
+
env = {"TZ": "Asia/Shanghai", "COLUMNS": "300"}
|
|
100
|
+
wide = self.ok("sessions", "--cmd", env=env).out.splitlines()
|
|
101
|
+
self.assertEqual(wide, [
|
|
102
|
+
"TITLE ACCOUNT CREATED UPDATED CMD",
|
|
103
|
+
"One a 2026-10-01 10:00 2026-10-01 10:00 cd /w/proj && claude-a --resume " + SID1,
|
|
104
|
+
"Two b 2026-10-01 09:00 2026-10-01 09:00 cd /w/other && claude-b --resume " + SID2])
|
|
105
|
+
# 会话 ID 已在命令末尾,带 --cmd 时不再单列一次
|
|
106
|
+
# 放不下时整张表改成命令单独一行;恰好放得下仍算放得下。标题与目录都是 ASCII,len 就是显示宽度
|
|
107
|
+
widest = max(len(line) for line in wide)
|
|
108
|
+
self.assertEqual(self.ok("sessions", "--cmd", env=dict(env, COLUMNS=str(widest))).out.splitlines(), wide)
|
|
109
|
+
narrow = [
|
|
110
|
+
"TITLE ACCOUNT CREATED UPDATED",
|
|
111
|
+
"One a 2026-10-01 10:00 2026-10-01 10:00",
|
|
112
|
+
" cd /w/proj && claude-a --resume " + SID1,
|
|
113
|
+
"Two b 2026-10-01 09:00 2026-10-01 09:00",
|
|
114
|
+
" cd /w/other && claude-b --resume " + SID2]
|
|
115
|
+
self.assertEqual(self.ok("sessions", "--cmd", env=dict(env, COLUMNS=str(widest - 1))).out.splitlines(), narrow)
|
|
116
|
+
self.assertEqual(self.ok("sessions", "--cmd", env=dict(env, COLUMNS="40")).out.splitlines(), narrow)
|
|
117
|
+
|
|
118
|
+
def test_wide_titles_keep_the_columns_aligned(self):
|
|
119
|
+
# 中文一个字占两格:按字符数补空格的话,中文标题后面的列会往右错
|
|
120
|
+
self.make(self.dir_a, SID1, [user("q"), custom("修复解析")])
|
|
121
|
+
self.make(self.dir_a, SID2, [user("q", ts="2026-10-01T01:00:00.000Z"), custom("abcdefgh")])
|
|
122
|
+
tail = " a 2026-10-01 {}:00 2026-10-01 {}:00 {}"
|
|
123
|
+
env = {"TZ": "Asia/Shanghai"}
|
|
124
|
+
self.assertEqual(self.ok("sessions", env=env).out.splitlines(),
|
|
125
|
+
["TITLE ACCOUNT CREATED UPDATED SESSION ID",
|
|
126
|
+
"修复解析" + tail.format("10", "10", SID1), "abcdefgh" + tail.format("09", "09", SID2)])
|
|
127
|
+
self.make(self.dir_a, SID3, [user("q", ts="2026-10-01T00:00:00.000Z"), custom("修复解析器")])
|
|
128
|
+
self.assertEqual(self.ok("sessions", env=env).out.splitlines(),
|
|
129
|
+
["TITLE ACCOUNT CREATED UPDATED SESSION ID",
|
|
130
|
+
"修复解析 " + tail.format("10", "10", SID1), "abcdefgh " + tail.format("09", "09", SID2),
|
|
131
|
+
"修复解析器" + tail.format("08", "08", SID3)])
|
|
132
|
+
# 零宽连接符与组合字符不占格:e 加组合重音符是 1 格,这个标题共 8 格,和 abcdefgh 一样宽
|
|
133
|
+
self.make(self.dir_a, SID3, [user("q", ts="2026-10-01T00:00:00.000Z"), custom("abcde\u0301f\u200dgh")])
|
|
134
|
+
self.assertEqual(self.ok("sessions", env=env).out.splitlines()[1:],
|
|
135
|
+
["修复解析" + tail.format("10", "10", SID1), "abcdefgh" + tail.format("09", "09", SID2),
|
|
136
|
+
"abcde\u0301f\u200dgh" + tail.format("08", "08", SID3)])
|
|
137
|
+
|
|
138
|
+
def test_cmd_writes_the_home_directory_as_a_tilde(self):
|
|
139
|
+
cases = ((os.path.join(self.home, "sub"), "cd ~/sub"), (self.home, "cd ~"), (self.home + "/", "cd ~"),
|
|
140
|
+
(os.path.join(self.home, "my proj"), "cd ~/'my proj'"), ("/w/proj", "cd /w/proj"),
|
|
141
|
+
(self.home + "2", "cd " + shlex.quote(self.home + "2")))
|
|
142
|
+
for cwd, expected in cases:
|
|
143
|
+
self.make(self.dir_a, SID1, [user("q", cwd=cwd), ai("T")], project=sessions.encode(cwd))
|
|
144
|
+
line = " {} && claude-a --resume {}".format(expected, SID1)
|
|
145
|
+
self.assertEqual(self.ok("sessions", "--cmd").out.splitlines()[2], line, cwd)
|
|
146
|
+
# JSON 给脚本用,保持绝对路径;--cmd 对它没有影响
|
|
147
|
+
data = self.listing()
|
|
148
|
+
self.assertTrue(data["sessions"][0]["resume"].startswith("cd " + shlex.quote(cwd) + " && "), cwd)
|
|
149
|
+
self.assertEqual(self.listing("--cmd"), data)
|
|
150
|
+
shutil.rmtree(os.path.join(self.dir_a, "projects"))
|
|
95
151
|
|
|
96
152
|
def test_t16_json_structure(self):
|
|
97
153
|
path = self.make(self.dir_a, SID1, [user("q"), assistant("2026-10-01T03:30:00.000Z"), custom("Named")])
|
|
@@ -147,7 +203,7 @@ class TitleTest(SessionsTestCase):
|
|
|
147
203
|
self.make(self.dir_a, SID1, [{"type": "mode", "mode": "x"}, {"type": "system", "cwd": "/w/proj",
|
|
148
204
|
"timestamp": "2026-10-01T02:00:00.000Z"}])
|
|
149
205
|
self.assertEqual((self.one()["title"], self.one()["title_source"]), ("", "none"))
|
|
150
|
-
self.assertTrue(self.ok("sessions").out.splitlines()[1].
|
|
206
|
+
self.assertTrue(self.ok("sessions").out.splitlines()[1].startswith("- a "))
|
|
151
207
|
|
|
152
208
|
def test_t3_first_plain_prompt(self):
|
|
153
209
|
self.make(self.dir_a, SID1, [
|
|
@@ -170,8 +226,9 @@ class TitleTest(SessionsTestCase):
|
|
|
170
226
|
out = self.ok("sessions").out
|
|
171
227
|
self.assertNotIn("\x1b", out)
|
|
172
228
|
self.assertNotIn("\x07", out)
|
|
173
|
-
self.assertEqual(len(out.splitlines()),
|
|
174
|
-
|
|
229
|
+
self.assertEqual(len(out.splitlines()), 2)
|
|
230
|
+
# 标题在第一列;清洗后的标题里没有连续空格,按列间的两个空格切开即可
|
|
231
|
+
shown = out.splitlines()[1].split(" a ", 1)[0]
|
|
175
232
|
self.assertEqual(len(shown), 61)
|
|
176
233
|
self.assertTrue(shown.endswith("…"))
|
|
177
234
|
|
|
@@ -193,7 +250,7 @@ class TitleTest(SessionsTestCase):
|
|
|
193
250
|
self.make(self.dir_a, SID1, [user("q", cwd=cwd), custom(title)], project=sessions.encode(cwd))
|
|
194
251
|
row = self.one()
|
|
195
252
|
self.assertEqual((row["title"], row["directory"]), (title, cwd))
|
|
196
|
-
self.assertIn("cd '{}' && claude-a --resume {}".format(cwd, SID1), self.ok("sessions").out)
|
|
253
|
+
self.assertIn("cd '{}' && claude-a --resume {}".format(cwd, SID1), self.ok("sessions", "--cmd").out)
|
|
197
254
|
|
|
198
255
|
def test_t6_title_only_near_the_start(self):
|
|
199
256
|
# 标题行在首条提问之前、又被挤出尾部窗口:靠头部扫描取到
|
|
@@ -231,7 +288,8 @@ class FieldFallbackTest(SessionsTestCase):
|
|
|
231
288
|
def test_t5c_cwd_with_a_newline_is_not_used(self):
|
|
232
289
|
self.make(self.dir_a, SID1, [user("q", cwd="/w/pr\noj"), assistant("2026-10-01T05:00:00.000Z"), ai("T")])
|
|
233
290
|
self.assertIsNone(self.one()["directory"])
|
|
234
|
-
self.assertEqual(len(self.ok("sessions").out.splitlines()),
|
|
291
|
+
self.assertEqual(len(self.ok("sessions").out.splitlines()), 2)
|
|
292
|
+
self.assertEqual(len(self.ok("sessions", "--cmd").out.splitlines()), 3)
|
|
235
293
|
|
|
236
294
|
def test_t7_missing_fields_bad_lines_and_half_line(self):
|
|
237
295
|
self.make(self.dir_a, SID1, [b"not json\n", b"[1, 2]\n", {"type": "mode", "mode": "x"}, ai("Only a title")],
|
|
@@ -242,9 +300,13 @@ class FieldFallbackTest(SessionsTestCase):
|
|
|
242
300
|
self.assertEqual(row["created"], None)
|
|
243
301
|
self.assertEqual(row["updated"], "2026-09-21T14:13:20Z")
|
|
244
302
|
self.assertEqual(row["resume"], "claude-a --resume " + SID1)
|
|
245
|
-
|
|
246
|
-
self.
|
|
303
|
+
unknown = "claude-a --resume {} (start directory unknown; project -w-proj)".format(SID1)
|
|
304
|
+
out = self.ok("sessions", "--cmd", env={"COLUMNS": "40"}).out.splitlines()
|
|
305
|
+
self.assertEqual(out[2], " " + unknown)
|
|
247
306
|
self.assertIn(" - ", out[1])
|
|
307
|
+
self.assertTrue(self.ok("sessions", "--cmd", env={"COLUMNS": "300"}).out.splitlines()[1].endswith(" " + unknown))
|
|
308
|
+
# 不带 --cmd 时没有命令,这段说明也不出现
|
|
309
|
+
self.assertNotIn("start directory unknown", self.ok("sessions").out)
|
|
248
310
|
|
|
249
311
|
def test_t7b_deeply_nested_line_is_skipped(self):
|
|
250
312
|
# 嵌套太深的行让 json 抛 RecursionError(不是 ValueError);它只是一条坏行,别的字段照常取
|
|
@@ -260,9 +322,9 @@ class FieldFallbackTest(SessionsTestCase):
|
|
|
260
322
|
for zone, sid in (("America/New_York", SID1), ("Asia/Shanghai", SID2), ("UTC", None)):
|
|
261
323
|
result = self.ok("sessions", env={"TZ": zone})
|
|
262
324
|
self.assertNotIn("Traceback", result.err)
|
|
263
|
-
self.assertEqual(len(result.out.splitlines()),
|
|
325
|
+
self.assertEqual(len(result.out.splitlines()), 3, result)
|
|
264
326
|
# 换算不了的时间显示 -(这两条会话的创建与更新时间是同一个值);UTC 下两条都能显示
|
|
265
|
-
dashed = [line.split()[
|
|
327
|
+
dashed = [line.split()[4] for line in result.out.splitlines() if line.split()[2:4] == ["-", "-"]]
|
|
266
328
|
self.assertEqual(dashed, [sid] if sid else [], result)
|
|
267
329
|
self.assertEqual(len(self.listing()["sessions"]), 2)
|
|
268
330
|
|
|
@@ -375,9 +437,10 @@ class FilterTest(SessionsTestCase):
|
|
|
375
437
|
data = self.listing("--limit", "1")
|
|
376
438
|
self.assertEqual(([row["session_id"] for row in data["sessions"]], data["total"]), ([SID2], 3))
|
|
377
439
|
out = self.ok("sessions", "--limit", "1").out.splitlines()
|
|
378
|
-
self.assertEqual(len(out),
|
|
440
|
+
self.assertEqual(len(out), 3)
|
|
379
441
|
self.assertEqual(out[-1], "showing 1 of 3 sessions; use --limit 0 for all, or --grep TEXT to narrow")
|
|
380
|
-
self.assertEqual(len(self.ok("sessions", "--limit", "0").out.splitlines()),
|
|
442
|
+
self.assertEqual(len(self.ok("sessions", "--limit", "0").out.splitlines()), 4)
|
|
443
|
+
self.assertEqual(len(self.ok("sessions", "--limit", "0", "--cmd").out.splitlines()), 7)
|
|
381
444
|
result = self.run_cli("sessions", "--limit", "-1")
|
|
382
445
|
self.assertEqual(result.code, 2)
|
|
383
446
|
self.assertIn("--limit must be 0 or more", result.err)
|
|
@@ -387,7 +450,7 @@ class FilterTest(SessionsTestCase):
|
|
|
387
450
|
for index in range(28):
|
|
388
451
|
self.make(self.dir_b, "44444444-aaaa-bbbb-cccc-0000000000{:02d}".format(index), [user("q")])
|
|
389
452
|
out = self.ok("sessions").out.splitlines()
|
|
390
|
-
self.assertEqual(len(out), 1 + 30
|
|
453
|
+
self.assertEqual(len(out), 1 + 30 + 1)
|
|
391
454
|
self.assertEqual(out[-1], "showing 30 of 31 sessions; use --limit 0 for all, or --grep TEXT to narrow")
|
|
392
455
|
self.assertEqual(len(self.listing()["sessions"]), 31)
|
|
393
456
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude_cli.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude_cli.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{multi_claude_cli-0.10.0 → multi_claude_cli-0.11.1}/src/multi_claude_cli.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|