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.
- claude_sessions/__init__.py +0 -0
- claude_sessions/__main__.py +19 -0
- claude_sessions/accounts.py +158 -0
- claude_sessions/agents.py +594 -0
- claude_sessions/brief.py +125 -0
- claude_sessions/ccsettings.py +168 -0
- claude_sessions/checkpoints.py +150 -0
- claude_sessions/claude_md.py +1074 -0
- claude_sessions/cli.py +19 -0
- claude_sessions/clientstate.py +234 -0
- claude_sessions/concise_hook.py +39 -0
- claude_sessions/config.py +627 -0
- claude_sessions/connections.py +1012 -0
- claude_sessions/context_inject.py +166 -0
- claude_sessions/conventions.py +127 -0
- claude_sessions/ctxaudit.py +246 -0
- claude_sessions/denygen.py +133 -0
- claude_sessions/diffview.py +192 -0
- claude_sessions/diskgc.py +178 -0
- claude_sessions/failover.py +578 -0
- claude_sessions/guard_hook.py +53 -0
- claude_sessions/gui.py +698 -0
- claude_sessions/gui_api.py +2451 -0
- claude_sessions/gui_html.py +73 -0
- claude_sessions/gui_qt.py +85 -0
- claude_sessions/health.py +219 -0
- claude_sessions/hooks.py +719 -0
- claude_sessions/jsonstore.py +75 -0
- claude_sessions/lessons.py +297 -0
- claude_sessions/logbash_hook.py +67 -0
- claude_sessions/main.py +728 -0
- claude_sessions/mcp.py +310 -0
- claude_sessions/memhub.py +177 -0
- claude_sessions/memory.py +993 -0
- claude_sessions/memrules.py +110 -0
- claude_sessions/minimalcode_hook.py +39 -0
- claude_sessions/omniroute.py +527 -0
- claude_sessions/outputstyles.py +167 -0
- claude_sessions/paths.py +112 -0
- claude_sessions/plan_execute.py +657 -0
- claude_sessions/plugins.py +258 -0
- claude_sessions/proc.py +205 -0
- claude_sessions/recall.py +364 -0
- claude_sessions/recall_hook.py +73 -0
- claude_sessions/render.py +285 -0
- claude_sessions/repos.py +277 -0
- claude_sessions/review.py +239 -0
- claude_sessions/search.py +100 -0
- claude_sessions/session_menu.py +675 -0
- claude_sessions/sessions.py +460 -0
- claude_sessions/skills.py +481 -0
- claude_sessions/skills_templates/changelog/SKILL.md +26 -0
- claude_sessions/skills_templates/code-explainer/SKILL.md +26 -0
- claude_sessions/skills_templates/commit-message/SKILL.md +25 -0
- claude_sessions/skills_templates/pr-description/SKILL.md +40 -0
- claude_sessions/skills_templates/refactor-planner/SKILL.md +27 -0
- claude_sessions/skills_templates/security-review/SKILL.md +31 -0
- claude_sessions/skills_templates/test-writer/SKILL.md +29 -0
- claude_sessions/skills_templates/token-economy/SKILL.md +29 -0
- claude_sessions/skillscan.py +232 -0
- claude_sessions/stats.py +645 -0
- claude_sessions/statusline.py +508 -0
- claude_sessions/statusline_cli.py +32 -0
- claude_sessions/store.py +43 -0
- claude_sessions/system_prompt.py +159 -0
- claude_sessions/term.py +198 -0
- claude_sessions/testfilter_filter.py +47 -0
- claude_sessions/testfilter_hook.py +56 -0
- claude_sessions/themes.py +807 -0
- claude_sessions/transcript.py +236 -0
- claude_sessions/transcripts.py +87 -0
- claude_sessions/ui.py +1302 -0
- claude_sessions/usage.py +433 -0
- claude_sessions/web/app.css +1622 -0
- claude_sessions/web/app.js +3999 -0
- claude_sessions/web/index.html +219 -0
- claude_sessions/web/instruments.js +495 -0
- claude_sessions/web/motion.js +702 -0
- claude_sessions/web/stage.js +1165 -0
- claude_sessions/web/vendor/LICENSE-anime.txt +9 -0
- claude_sessions/web/vendor/LICENSE-three.txt +21 -0
- claude_sessions/web/vendor/anime.esm.min.js +7 -0
- claude_sessions/web/vendor/postprocessing/EffectComposer.js +365 -0
- claude_sessions/web/vendor/postprocessing/MaskPass.js +195 -0
- claude_sessions/web/vendor/postprocessing/OutputPass.js +148 -0
- claude_sessions/web/vendor/postprocessing/Pass.js +191 -0
- claude_sessions/web/vendor/postprocessing/RenderPass.js +193 -0
- claude_sessions/web/vendor/postprocessing/ShaderPass.js +135 -0
- claude_sessions/web/vendor/postprocessing/UnrealBloomPass.js +524 -0
- claude_sessions/web/vendor/shaders/CopyShader.js +52 -0
- claude_sessions/web/vendor/shaders/LuminosityHighPassShader.js +68 -0
- claude_sessions/web/vendor/shaders/OutputShader.js +103 -0
- claude_sessions/web/vendor/three.core.min.js +6 -0
- claude_sessions/web/vendor/three.module.min.js +6 -0
- claude_sessions/worklog.py +133 -0
- claude_sessions/worklog_hook.py +84 -0
- claude_sessions/workspace.py +576 -0
- claude_sessions/worktrees.py +298 -0
- claudectl-1.6.0.dist-info/METADATA +924 -0
- claudectl-1.6.0.dist-info/RECORD +104 -0
- claudectl-1.6.0.dist-info/WHEEL +5 -0
- claudectl-1.6.0.dist-info/entry_points.txt +2 -0
- claudectl-1.6.0.dist-info/licenses/LICENSE +21 -0
- 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)
|