claudectl 1.6.0__py3-none-any.whl

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 (104) hide show
  1. claude_sessions/__init__.py +0 -0
  2. claude_sessions/__main__.py +19 -0
  3. claude_sessions/accounts.py +158 -0
  4. claude_sessions/agents.py +594 -0
  5. claude_sessions/brief.py +125 -0
  6. claude_sessions/ccsettings.py +168 -0
  7. claude_sessions/checkpoints.py +150 -0
  8. claude_sessions/claude_md.py +1074 -0
  9. claude_sessions/cli.py +19 -0
  10. claude_sessions/clientstate.py +234 -0
  11. claude_sessions/concise_hook.py +39 -0
  12. claude_sessions/config.py +627 -0
  13. claude_sessions/connections.py +1012 -0
  14. claude_sessions/context_inject.py +166 -0
  15. claude_sessions/conventions.py +127 -0
  16. claude_sessions/ctxaudit.py +246 -0
  17. claude_sessions/denygen.py +133 -0
  18. claude_sessions/diffview.py +192 -0
  19. claude_sessions/diskgc.py +178 -0
  20. claude_sessions/failover.py +578 -0
  21. claude_sessions/guard_hook.py +53 -0
  22. claude_sessions/gui.py +698 -0
  23. claude_sessions/gui_api.py +2451 -0
  24. claude_sessions/gui_html.py +73 -0
  25. claude_sessions/gui_qt.py +85 -0
  26. claude_sessions/health.py +219 -0
  27. claude_sessions/hooks.py +719 -0
  28. claude_sessions/jsonstore.py +75 -0
  29. claude_sessions/lessons.py +297 -0
  30. claude_sessions/logbash_hook.py +67 -0
  31. claude_sessions/main.py +728 -0
  32. claude_sessions/mcp.py +310 -0
  33. claude_sessions/memhub.py +177 -0
  34. claude_sessions/memory.py +993 -0
  35. claude_sessions/memrules.py +110 -0
  36. claude_sessions/minimalcode_hook.py +39 -0
  37. claude_sessions/omniroute.py +527 -0
  38. claude_sessions/outputstyles.py +167 -0
  39. claude_sessions/paths.py +112 -0
  40. claude_sessions/plan_execute.py +657 -0
  41. claude_sessions/plugins.py +258 -0
  42. claude_sessions/proc.py +205 -0
  43. claude_sessions/recall.py +364 -0
  44. claude_sessions/recall_hook.py +73 -0
  45. claude_sessions/render.py +285 -0
  46. claude_sessions/repos.py +277 -0
  47. claude_sessions/review.py +239 -0
  48. claude_sessions/search.py +100 -0
  49. claude_sessions/session_menu.py +675 -0
  50. claude_sessions/sessions.py +460 -0
  51. claude_sessions/skills.py +481 -0
  52. claude_sessions/skills_templates/changelog/SKILL.md +26 -0
  53. claude_sessions/skills_templates/code-explainer/SKILL.md +26 -0
  54. claude_sessions/skills_templates/commit-message/SKILL.md +25 -0
  55. claude_sessions/skills_templates/pr-description/SKILL.md +40 -0
  56. claude_sessions/skills_templates/refactor-planner/SKILL.md +27 -0
  57. claude_sessions/skills_templates/security-review/SKILL.md +31 -0
  58. claude_sessions/skills_templates/test-writer/SKILL.md +29 -0
  59. claude_sessions/skills_templates/token-economy/SKILL.md +29 -0
  60. claude_sessions/skillscan.py +232 -0
  61. claude_sessions/stats.py +645 -0
  62. claude_sessions/statusline.py +508 -0
  63. claude_sessions/statusline_cli.py +32 -0
  64. claude_sessions/store.py +43 -0
  65. claude_sessions/system_prompt.py +159 -0
  66. claude_sessions/term.py +198 -0
  67. claude_sessions/testfilter_filter.py +47 -0
  68. claude_sessions/testfilter_hook.py +56 -0
  69. claude_sessions/themes.py +807 -0
  70. claude_sessions/transcript.py +236 -0
  71. claude_sessions/transcripts.py +87 -0
  72. claude_sessions/ui.py +1302 -0
  73. claude_sessions/usage.py +433 -0
  74. claude_sessions/web/app.css +1622 -0
  75. claude_sessions/web/app.js +3999 -0
  76. claude_sessions/web/index.html +219 -0
  77. claude_sessions/web/instruments.js +495 -0
  78. claude_sessions/web/motion.js +702 -0
  79. claude_sessions/web/stage.js +1165 -0
  80. claude_sessions/web/vendor/LICENSE-anime.txt +9 -0
  81. claude_sessions/web/vendor/LICENSE-three.txt +21 -0
  82. claude_sessions/web/vendor/anime.esm.min.js +7 -0
  83. claude_sessions/web/vendor/postprocessing/EffectComposer.js +365 -0
  84. claude_sessions/web/vendor/postprocessing/MaskPass.js +195 -0
  85. claude_sessions/web/vendor/postprocessing/OutputPass.js +148 -0
  86. claude_sessions/web/vendor/postprocessing/Pass.js +191 -0
  87. claude_sessions/web/vendor/postprocessing/RenderPass.js +193 -0
  88. claude_sessions/web/vendor/postprocessing/ShaderPass.js +135 -0
  89. claude_sessions/web/vendor/postprocessing/UnrealBloomPass.js +524 -0
  90. claude_sessions/web/vendor/shaders/CopyShader.js +52 -0
  91. claude_sessions/web/vendor/shaders/LuminosityHighPassShader.js +68 -0
  92. claude_sessions/web/vendor/shaders/OutputShader.js +103 -0
  93. claude_sessions/web/vendor/three.core.min.js +6 -0
  94. claude_sessions/web/vendor/three.module.min.js +6 -0
  95. claude_sessions/worklog.py +133 -0
  96. claude_sessions/worklog_hook.py +84 -0
  97. claude_sessions/workspace.py +576 -0
  98. claude_sessions/worktrees.py +298 -0
  99. claudectl-1.6.0.dist-info/METADATA +924 -0
  100. claudectl-1.6.0.dist-info/RECORD +104 -0
  101. claudectl-1.6.0.dist-info/WHEEL +5 -0
  102. claudectl-1.6.0.dist-info/entry_points.txt +2 -0
  103. claudectl-1.6.0.dist-info/licenses/LICENSE +21 -0
  104. claudectl-1.6.0.dist-info/top_level.txt +1 -0
File without changes
@@ -0,0 +1,19 @@
1
+ """`python -m claude_sessions [...]` — same dispatch as claude-sessions.py.
2
+
3
+ Exists so the detached background memory worker (memory.spawn_background_worker)
4
+ can re-invoke claudectl without depending on the repo-root launcher script.
5
+
6
+ The statusline is dispatched HERE, before `main` is imported: `main` pulls the
7
+ whole TUI stack plus `usage`, which drags in urllib/ssl/http.client for an OAuth
8
+ poll the statusline must never make — 48ms of import on a path that runs on
9
+ every conversation turn.
10
+ """
11
+ import sys
12
+
13
+ if len(sys.argv) >= 2 and sys.argv[1] == 'statusline':
14
+ from .statusline import main
15
+ raise SystemExit(main(sys.argv[2:]))
16
+
17
+ from .main import run
18
+
19
+ run()
@@ -0,0 +1,158 @@
1
+ """Multi-account manager — make running two (or more) Claude accounts easy.
2
+
3
+ Claude Code picks the account from its config dir (CLAUDE_CONFIG_DIR). Each
4
+ account = its own dir with its own login. claudectl stores named accounts, lets
5
+ you switch the active one, and — the point — **launch a second account in a new
6
+ terminal with one key** so both run at the same time.
7
+ """
8
+
9
+ import os
10
+ import subprocess
11
+
12
+ from . import config as _c
13
+ from .ui import menu, text_input, flash, confirm, _cls, pause
14
+ from . import render
15
+
16
+
17
+ def _default_dir():
18
+ return os.path.join(_c._USERPROFILE, '.claude')
19
+
20
+
21
+ def _accounts(s):
22
+ """[(name, dir, is_active)] — always includes the built-in default account."""
23
+ active = s.get('claude_config_dir', '') or ''
24
+ out = [('default', '', not active)] # '' dir = ~/.claude
25
+ for a in s.get('accounts', []):
26
+ if isinstance(a, dict) and a.get('dir'):
27
+ out.append((a.get('name', a['dir']), a['dir'],
28
+ os.path.expanduser(a['dir']) == os.path.expanduser(active)))
29
+ return out
30
+
31
+
32
+ def _resolved(d):
33
+ return os.path.expanduser(os.path.expandvars(d)) if d else _default_dir()
34
+
35
+
36
+ def accounts_menu():
37
+ from .config import load_settings, save_settings
38
+ while True:
39
+ s = load_settings()
40
+ accts = _accounts(s)
41
+ items = []
42
+ for name, d, active in accts:
43
+ dot = f"{_c.C_OK}●{_c.C_RESET}" if active else '○'
44
+ loc = d or '~/.claude'
45
+ tag = f" {_c.C_OK}(active){_c.C_RESET}" if active else ''
46
+ items.append((f"{dot} {name} {_c.C_DIM}{render.trunc(loc, 40)}{_c.C_RESET}{tag}",
47
+ f'acct:{name}'))
48
+ items += [(f"{'─' * _c.W}", None),
49
+ ('+ Add account', '__add__'),
50
+ ('Back', 'back')]
51
+ sel = menu(items, "CLAUDE ACCOUNTS")
52
+ if not sel or sel == 'back':
53
+ return
54
+ if sel == '__add__':
55
+ _add_account(s)
56
+ elif sel.startswith('acct:'):
57
+ _account_actions(s, sel[5:])
58
+
59
+
60
+ def _add_account(s):
61
+ from .config import save_settings
62
+ name = text_input("Account name (e.g. work, personal):")
63
+ if not name:
64
+ return
65
+ default_dir = os.path.join(_c._USERPROFILE, f'.claude-{name}')
66
+ d = text_input("Config dir for this account:", default=default_dir)
67
+ if not d:
68
+ return
69
+ rd = _resolved(d)
70
+ try:
71
+ os.makedirs(rd, exist_ok=True)
72
+ except Exception as e:
73
+ flash(f"Could not create dir: {e}", ok=False, secs=2)
74
+ return
75
+ accts = [a for a in s.get('accounts', []) if a.get('name') != name]
76
+ accts.append({'name': name, 'dir': d})
77
+ s['accounts'] = accts
78
+ save_settings(s)
79
+ if confirm(f"Log in to '{name}' now? (opens claude for /login)"):
80
+ _login(d)
81
+ else:
82
+ flash(f"Account '{name}' added — log in later from its row", secs=1.6)
83
+
84
+
85
+ def _account_actions(s, name):
86
+ from .config import save_settings
87
+ d = '' if name == 'default' else next(
88
+ (a['dir'] for a in s.get('accounts', []) if a.get('name') == name), '')
89
+ acts = [('Switch active account (this claudectl)', 'switch'),
90
+ ('Open in NEW terminal (run in parallel)', 'parallel'),
91
+ ('Log in / re-login here', 'login')]
92
+ if name != 'default':
93
+ acts.append(('Rename', 'rename'))
94
+ acts.append(('Remove from list', 'remove'))
95
+ acts.append(('Cancel', 'cancel'))
96
+ act = menu(acts, f"ACCOUNT / {name}")
97
+ if act == 'switch':
98
+ s['claude_config_dir'] = d
99
+ save_settings(s)
100
+ flash(f"Active account → {name}. Restart claudectl to fully apply.", secs=2)
101
+ elif act == 'parallel':
102
+ _open_terminal(d, name)
103
+ elif act == 'login':
104
+ _login(d)
105
+ elif act == 'rename':
106
+ new = text_input("New account name:", default=name)
107
+ if new and new != name:
108
+ if any(a.get('name') == new for a in s.get('accounts', [])) or new == 'default':
109
+ flash(f"Name '{new}' already in use", ok=False, secs=1.8)
110
+ else:
111
+ for a in s.get('accounts', []):
112
+ if a.get('name') == name:
113
+ a['name'] = new
114
+ save_settings(s)
115
+ flash(f"Renamed '{name}' → '{new}' (config dir unchanged)", secs=1.8)
116
+ elif act == 'remove':
117
+ s['accounts'] = [a for a in s.get('accounts', []) if a.get('name') != name]
118
+ if os.path.expanduser(s.get('claude_config_dir', '')) == os.path.expanduser(d):
119
+ s['claude_config_dir'] = ''
120
+ save_settings(s)
121
+ flash(f"Removed '{name}' (its config dir on disk is untouched)", secs=1.8)
122
+
123
+
124
+ def _env_for(d):
125
+ env = os.environ.copy()
126
+ env['CLAUDE_CONFIG_DIR'] = _resolved(d)
127
+ env.pop('ANTHROPIC_API_KEY', None) # a set key would shadow the account login
128
+ return env
129
+
130
+
131
+ def _login(d):
132
+ exe = _c.get_claude_exe()
133
+ if not exe:
134
+ flash("claude.exe not found", ok=False, secs=1.6)
135
+ return
136
+ _cls()
137
+ print(f"\n Opening claude under {_resolved(d)}")
138
+ print(f" Use /login to sign in, then exit to return.\n")
139
+ try:
140
+ subprocess.call([exe], env=_env_for(d))
141
+ except Exception as e:
142
+ print(f"\n Failed: {e}")
143
+ pause("\n Press Enter…")
144
+
145
+
146
+ def _open_terminal(d, name):
147
+ """Launch claude for this account in a NEW terminal window so it runs
148
+ alongside the current one (the easy 'two accounts at once')."""
149
+ exe = _c.get_claude_exe()
150
+ if not exe:
151
+ flash("claude.exe not found", ok=False, secs=1.6)
152
+ return
153
+ from . import proc
154
+ _p, err = proc.spawn_terminal([exe], env=_env_for(d), title=f'claude [{name}]')
155
+ if err:
156
+ flash(f"Could not open terminal: {err}", ok=False, secs=2)
157
+ else:
158
+ flash(f"Opened a new terminal running claude as '{name}'", secs=1.8)