multi-claude-cli 0.12.0__tar.gz → 0.12.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.
Files changed (67) hide show
  1. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/PKG-INFO +1 -1
  2. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/__init__.py +1 -1
  3. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/refresh.py +15 -2
  4. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude_cli.egg-info/PKG-INFO +1 -1
  5. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_refresh.py +33 -0
  6. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/LICENSE +0 -0
  7. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/README.md +0 -0
  8. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/pyproject.toml +0 -0
  9. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/setup.cfg +0 -0
  10. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/__main__.py +0 -0
  11. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/accounts.py +0 -0
  12. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/actions.py +0 -0
  13. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/artifacts.py +0 -0
  14. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/bundle.py +0 -0
  15. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/cli.py +0 -0
  16. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/completion.py +0 -0
  17. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/config.py +0 -0
  18. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/doctor.py +0 -0
  19. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/env.py +0 -0
  20. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/fsutil.py +0 -0
  21. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/history.py +0 -0
  22. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/hook.py +0 -0
  23. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/identity.py +0 -0
  24. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/launcher.py +0 -0
  25. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/lock.py +0 -0
  26. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/migrate.py +0 -0
  27. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/plan.py +0 -0
  28. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/platform.py +0 -0
  29. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/restore.py +0 -0
  30. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/routes.py +0 -0
  31. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/sessions.py +0 -0
  32. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/shared.py +0 -0
  33. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/shellpath.py +0 -0
  34. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/statusline.py +0 -0
  35. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude/usage.py +0 -0
  36. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude_cli.egg-info/SOURCES.txt +0 -0
  37. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude_cli.egg-info/dependency_links.txt +0 -0
  38. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude_cli.egg-info/entry_points.txt +0 -0
  39. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/src/multi_claude_cli.egg-info/top_level.txt +0 -0
  40. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_accounts.py +0 -0
  41. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_artifacts.py +0 -0
  42. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_bundle.py +0 -0
  43. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_completion.py +0 -0
  44. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_config.py +0 -0
  45. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_doctor.py +0 -0
  46. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_everyday.py +0 -0
  47. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_fsutil.py +0 -0
  48. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_handoff.py +0 -0
  49. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_help.py +0 -0
  50. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_history.py +0 -0
  51. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_identity.py +0 -0
  52. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_install.py +0 -0
  53. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_mcp.py +0 -0
  54. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_migrate.py +0 -0
  55. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_onboarding.py +0 -0
  56. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_packaging.py +0 -0
  57. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_plan.py +0 -0
  58. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_platform.py +0 -0
  59. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_rename.py +0 -0
  60. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_routes.py +0 -0
  61. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_sessions.py +0 -0
  62. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_set.py +0 -0
  63. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_shared_exclude.py +0 -0
  64. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_statusline.py +0 -0
  65. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_usability.py +0 -0
  66. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.1}/tests/test_usage.py +0 -0
  67. {multi_claude_cli-0.12.0 → multi_claude_cli-0.12.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.12.0
3
+ Version: 0.12.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
@@ -1,3 +1,3 @@
1
1
  """multi-claude:管理多个 Claude Code 账号的配置目录、启动命令、代理与启动参数。"""
2
2
 
3
- __version__ = "0.12.0"
3
+ __version__ = "0.12.1"
@@ -230,13 +230,25 @@ class _Lock(object):
230
230
  """自己建的一把锁,以及用来确认“这个路径上还是我建的那个目录”的指纹。
231
231
 
232
232
  指纹是设备号、inode 和自己最后一次设置的修改时间(proper-lockfile 也靠修改时间判断锁有没有被别人动过)。
233
- 只比 inode 不够:别人删掉重建时文件系统可能复用同一个编号。
233
+ 只比 inode 不够:别人删掉重建时文件系统可能复用同一个编号。修改时间也不能交给内核取:
234
+ ext4 上几毫秒内删掉重建的目录,inode 和内核记的修改时间多半与原来相同(实测 2000 次里 1993 次),会把别人的锁认成自己的。
235
+ 所以建锁和保活时都用 _set_mtime_now 写入此刻的精细时间。
236
+ 不能改成往锁目录里放标记文件:proper-lockfile 用 rmdir 清理过期锁,目录非空就删不掉,锁会一直卡住。
234
237
  """
235
238
 
236
239
  def __init__(self, path: str) -> None:
237
240
  self.path = path
241
+ try:
242
+ self._set_mtime_now()
243
+ except OSError:
244
+ # 设不了时间就用内核写的时间记指纹:锁照样能用,只是分辨不出同一时钟节拍里的删掉重建。
245
+ pass
238
246
  self._stamp = self._read()
239
247
 
248
+ def _set_mtime_now(self) -> None:
249
+ now_ns = time.time_ns()
250
+ os.utime(self.path, ns=(now_ns, now_ns))
251
+
240
252
  def _read(self) -> Optional[Tuple[int, int, int]]:
241
253
  try:
242
254
  status = os.lstat(self.path)
@@ -252,7 +264,8 @@ class _Lock(object):
252
264
  if not self.is_mine():
253
265
  return
254
266
  try:
255
- os.utime(self.path, None)
267
+ # 不能用 os.utime(path, None):那样又由内核取时间,保活一次后指纹就退回分辨不出重建的状态。
268
+ self._set_mtime_now()
256
269
  except OSError:
257
270
  return
258
271
  fresh = self._read()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multi-claude-cli
3
- Version: 0.12.0
3
+ Version: 0.12.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
@@ -7,13 +7,16 @@
7
7
 
8
8
  import json
9
9
  import os
10
+ import shutil
10
11
  import signal
11
12
  import stat
12
13
  import subprocess
13
14
  import sys
15
+ import tempfile
14
16
  import threading
15
17
  import time
16
18
  import types
19
+ import unittest
17
20
  from http.server import BaseHTTPRequestHandler
18
21
  from unittest import mock
19
22
 
@@ -482,6 +485,36 @@ class RenewTest(RefreshTestCase):
482
485
  self.assertNotIn("add-generic-password", call)
483
486
 
484
487
 
488
+ class LockStampTest(unittest.TestCase):
489
+ """锁指纹里的修改时间必须是本工具写入的精细时间,不能交给内核取。
490
+
491
+ ext4 上几毫秒内删掉重建的锁目录,inode 和内核记的修改时间都与原来相同;只有自己写入的时间能分辨出来。
492
+ 这里不依赖 inode 复用,在任何文件系统上确定性地检查“建锁和保活都写入了 time_ns 的值”。
493
+ """
494
+
495
+ # 低位非零:内核按时钟节拍记的时间不会恰好是这个值
496
+ STAMP_NS = 1_700_000_000_123_456_789
497
+
498
+ def setUp(self):
499
+ self.tmp = tempfile.mkdtemp(prefix="mcl-lock-")
500
+ self.addCleanup(shutil.rmtree, self.tmp, True)
501
+ self.path = os.path.join(self.tmp, refresh.LOCK_NAME)
502
+ os.mkdir(self.path)
503
+
504
+ def test_creating_and_touching_write_the_fine_clock(self):
505
+ # refresh.time 就是标准库 time:桩只包住这两次调用,不影响进程里的其它代码
506
+ with mock.patch.object(refresh.time, "time_ns", return_value=self.STAMP_NS):
507
+ lock = refresh._Lock(self.path)
508
+ self.assertEqual(os.lstat(self.path).st_mtime_ns, self.STAMP_NS)
509
+ self.assertTrue(lock.is_mine())
510
+
511
+ later = self.STAMP_NS + 5_000_000_000
512
+ with mock.patch.object(refresh.time, "time_ns", return_value=later):
513
+ lock.touch()
514
+ self.assertEqual(os.lstat(self.path).st_mtime_ns, later)
515
+ self.assertTrue(lock.is_mine())
516
+
517
+
485
518
  class KeychainWriteTest(CliTestCase):
486
519
  """macOS 分支:凭据在钥匙串里,写回走 `security -i`;用假 security,任何平台都能跑。"""
487
520