claude-util 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.
Files changed (32) hide show
  1. claude_util-0.1.1/PKG-INFO +20 -0
  2. claude_util-0.1.1/README.md +0 -0
  3. claude_util-0.1.1/pyproject.toml +46 -0
  4. claude_util-0.1.1/src/claude_cli/__init__.py +3 -0
  5. claude_util-0.1.1/src/claude_cli/__main__.py +5 -0
  6. claude_util-0.1.1/src/claude_cli/commands/__init__.py +0 -0
  7. claude_util-0.1.1/src/claude_cli/commands/account.py +280 -0
  8. claude_util-0.1.1/src/claude_cli/commands/config.py +28 -0
  9. claude_util-0.1.1/src/claude_cli/commands/history.py +32 -0
  10. claude_util-0.1.1/src/claude_cli/commands/init.py +136 -0
  11. claude_util-0.1.1/src/claude_cli/commands/run.py +78 -0
  12. claude_util-0.1.1/src/claude_cli/commands/status.py +44 -0
  13. claude_util-0.1.1/src/claude_cli/commands/usage.py +80 -0
  14. claude_util-0.1.1/src/claude_cli/commands/use.py +41 -0
  15. claude_util-0.1.1/src/claude_cli/core/__init__.py +0 -0
  16. claude_util-0.1.1/src/claude_cli/core/account.py +184 -0
  17. claude_util-0.1.1/src/claude_cli/core/auth.py +76 -0
  18. claude_util-0.1.1/src/claude_cli/core/config.py +43 -0
  19. claude_util-0.1.1/src/claude_cli/core/history.py +63 -0
  20. claude_util-0.1.1/src/claude_cli/core/usage.py +138 -0
  21. claude_util-0.1.1/src/claude_cli/main.py +78 -0
  22. claude_util-0.1.1/src/claude_cli/models/__init__.py +0 -0
  23. claude_util-0.1.1/src/claude_cli/models/config.py +23 -0
  24. claude_util-0.1.1/src/claude_cli/models/usage.py +51 -0
  25. claude_util-0.1.1/src/claude_cli/ui/__init__.py +21 -0
  26. claude_util-0.1.1/src/claude_cli/ui/console.py +46 -0
  27. claude_util-0.1.1/src/claude_cli/ui/prompts.py +120 -0
  28. claude_util-0.1.1/src/claude_cli/ui/tables.py +212 -0
  29. claude_util-0.1.1/src/claude_cli/utils/__init__.py +0 -0
  30. claude_util-0.1.1/src/claude_cli/utils/completers.py +19 -0
  31. claude_util-0.1.1/src/claude_cli/utils/formatters.py +17 -0
  32. claude_util-0.1.1/src/claude_cli/utils/validators.py +27 -0
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.3
2
+ Name: claude-util
3
+ Version: 0.1.1
4
+ Summary: CLI tool for managing multiple Claude Code subscription accounts
5
+ License: GPL-3.0-or-later
6
+ Requires-Python: >=3.10
7
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Requires-Dist: pydantic (>=2.6.0,<3)
14
+ Requires-Dist: pyyaml (>=6.0.1,<7)
15
+ Requires-Dist: questionary (>=2.0.1,<3)
16
+ Requires-Dist: rich (>=13.7.0,<14)
17
+ Requires-Dist: typer (>=0.12.0)
18
+ Description-Content-Type: text/markdown
19
+
20
+
File without changes
@@ -0,0 +1,46 @@
1
+ [tool.poetry]
2
+ name = "claude-util"
3
+ version = "0.1.1"
4
+ description = "CLI tool for managing multiple Claude Code subscription accounts"
5
+ readme = "README.md"
6
+ license = "GPL-3.0-or-later"
7
+ packages = [{include = "claude_cli", from = "src"}]
8
+
9
+ [tool.poetry.dependencies]
10
+ python = ">=3.10"
11
+ typer = ">=0.12.0"
12
+ rich = ">=13.7.0,<14"
13
+ questionary = ">=2.0.1,<3"
14
+ pydantic = ">=2.6.0,<3"
15
+ pyyaml = ">=6.0.1,<7"
16
+
17
+ [tool.poetry.group.dev.dependencies]
18
+ pytest = ">=8.0.0,<9"
19
+ pytest-cov = ">=4.1.0,<5"
20
+ mypy = ">=1.8.0,<2"
21
+ ruff = ">=0.2.0"
22
+ pre-commit = ">=3.6.0,<4"
23
+ types-pyyaml = ">=6.0.12,<7"
24
+
25
+ [tool.poetry.scripts]
26
+ claude-cli = "claude_cli.main:app"
27
+ cc = "claude_cli.main:app"
28
+
29
+ [build-system]
30
+ requires = ["poetry-core>=2.0.0"]
31
+ build-backend = "poetry.core.masonry.api"
32
+
33
+ [tool.ruff]
34
+ line-length = 100
35
+ target-version = "py310"
36
+
37
+ [tool.ruff.lint]
38
+ select = ["E", "F", "I", "N", "W", "UP"]
39
+
40
+ [tool.mypy]
41
+ python_version = "3.10"
42
+ strict = true
43
+ warn_return_any = true
44
+
45
+ [tool.pytest.ini_options]
46
+ testpaths = ["tests"]
@@ -0,0 +1,3 @@
1
+ """CLI tool for managing multiple Claude Code subscription accounts."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ """Allow running as: python -m claude_cli."""
2
+
3
+ from claude_cli.main import app
4
+
5
+ app()
File without changes
@@ -0,0 +1,280 @@
1
+ """claude-cli account — Account management sub-commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional
6
+
7
+ import typer
8
+
9
+ from claude_cli.core.account import (
10
+ add_account,
11
+ get_account_dir,
12
+ get_default_account,
13
+ list_accounts,
14
+ remove_account,
15
+ rename_account,
16
+ set_default_account,
17
+ setup_symlinks,
18
+ ensure_shared_dir,
19
+ )
20
+ from claude_cli.core.auth import check_auth_status, trigger_login
21
+ from claude_cli.core.config import ACCOUNTS_DIR, SHARED_DIR, load_config
22
+ from claude_cli.ui import console, error, info, print_detail, print_header, success
23
+ from claude_cli.ui.prompts import confirm, select_account, select_from_list, text_input
24
+ from claude_cli.ui.tables import print_accounts_table
25
+ from claude_cli.utils.completers import complete_account_name, complete_tier
26
+ from claude_cli.utils.formatters import format_date
27
+ from claude_cli.utils.validators import validate_account_name, validate_label
28
+
29
+ app = typer.Typer(no_args_is_help=True)
30
+
31
+ TIER_CHOICES = ["pro", "max", "team", "enterprise"]
32
+
33
+
34
+ @app.command("list")
35
+ def list_command(
36
+ output: Optional[str] = typer.Option(None, "--output", "-O", help="Output format: table, json"),
37
+ ) -> None:
38
+ """List all configured accounts."""
39
+ config = load_config()
40
+ if not config.accounts:
41
+ info("No accounts configured. Run 'claude-cli init' to get started.")
42
+ raise typer.Exit(0)
43
+
44
+ if output == "json":
45
+ import json
46
+
47
+ data = {
48
+ name: {
49
+ "label": acc.label,
50
+ "tier": acc.tier,
51
+ "created_at": acc.created_at.isoformat(),
52
+ "is_default": name == config.default,
53
+ "auth_status": check_auth_status(name),
54
+ }
55
+ for name, acc in config.accounts.items()
56
+ }
57
+ console.print_json(json.dumps(data))
58
+ return
59
+
60
+ auth_statuses = {name: check_auth_status(name) for name in config.accounts}
61
+ print_accounts_table(config, auth_statuses)
62
+
63
+
64
+ @app.command("add")
65
+ def add_command(
66
+ name: Optional[str] = typer.Argument(None, help="Account name slug"),
67
+ label: Optional[str] = typer.Option(None, "--label", "-l", help="Display label"),
68
+ tier: Optional[str] = typer.Option(
69
+ None, "--tier", "-t", help="Subscription tier", autocompletion=complete_tier
70
+ ),
71
+ ) -> None:
72
+ """Add a new account profile."""
73
+ accounts = list_accounts()
74
+
75
+ if not name:
76
+ while True:
77
+ name = text_input("Account name (slug):")
78
+ if name is None:
79
+ raise typer.Exit(130)
80
+ name = name.strip().lower()
81
+ if not validate_account_name(name):
82
+ error("Invalid name. Use lowercase letters, digits, hyphens. Cannot be 'shared'.")
83
+ continue
84
+ if name in accounts:
85
+ error(f"Account '{name}' already exists.")
86
+ continue
87
+ break
88
+ else:
89
+ if not validate_account_name(name):
90
+ error("Invalid account name. Use lowercase letters, digits, hyphens.")
91
+ raise typer.Exit(2)
92
+ if name in accounts:
93
+ error(f"Account '{name}' already exists.")
94
+ raise typer.Exit(2)
95
+
96
+ if not label:
97
+ label = text_input("Display label:")
98
+ if label is None:
99
+ raise typer.Exit(130)
100
+ if not validate_label(label):
101
+ error("Label cannot be empty.")
102
+ raise typer.Exit(2)
103
+
104
+ if not tier:
105
+ tier = select_from_list("Subscription tier:", TIER_CHOICES)
106
+ if tier is None:
107
+ raise typer.Exit(130)
108
+
109
+ if tier not in TIER_CHOICES:
110
+ error(f"Invalid tier '{tier}'. Choose from: {', '.join(TIER_CHOICES)}")
111
+ raise typer.Exit(2)
112
+
113
+ add_account(name, label, tier)
114
+
115
+ console.print("\n Opening browser for Claude login...")
116
+ if trigger_login(name):
117
+ success("Authenticated successfully")
118
+ else:
119
+ error("Login failed or claude binary not found on PATH")
120
+ info(f"You can login later with: claude-cli account login {name}")
121
+
122
+ success(f'Account "{name}" created')
123
+
124
+
125
+ @app.command("remove")
126
+ def remove_command(
127
+ name: Optional[str] = typer.Argument(
128
+ None, help="Account name to remove", autocompletion=complete_account_name
129
+ ),
130
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
131
+ ) -> None:
132
+ """Remove an account profile (credentials only, shared files untouched)."""
133
+ accounts = list_accounts()
134
+ if not accounts:
135
+ info("No accounts configured.")
136
+ raise typer.Exit(0)
137
+
138
+ if not name:
139
+ name = select_account("Select account to remove:")
140
+ if name is None:
141
+ raise typer.Exit(130)
142
+
143
+ if name not in accounts:
144
+ error(f"Account '{name}' not found.")
145
+ raise typer.Exit(4)
146
+
147
+ if not yes:
148
+ console.print(f'\n [warning]\u26a0 This will remove account "{name}" and its credentials.[/warning]')
149
+ console.print(" Shared settings, rules, and memory are NOT affected.\n")
150
+ proceed = confirm("Continue?", default=False)
151
+ if not proceed:
152
+ info("Cancelled.")
153
+ raise typer.Exit(0)
154
+
155
+ remove_account(name)
156
+ success(f'Account "{name}" removed')
157
+
158
+
159
+ @app.command("rename")
160
+ def rename_command(
161
+ old_name: Optional[str] = typer.Argument(
162
+ None, help="Current account name", autocompletion=complete_account_name
163
+ ),
164
+ new_name: Optional[str] = typer.Argument(None, help="New account name"),
165
+ ) -> None:
166
+ """Rename an account slug."""
167
+ accounts = list_accounts()
168
+ if not accounts:
169
+ info("No accounts configured.")
170
+ raise typer.Exit(0)
171
+
172
+ if not old_name:
173
+ old_name = select_account("Select account to rename:")
174
+ if old_name is None:
175
+ raise typer.Exit(130)
176
+
177
+ if old_name not in accounts:
178
+ error(f"Account '{old_name}' not found.")
179
+ raise typer.Exit(4)
180
+
181
+ if not new_name:
182
+ new_name = text_input("New account name (slug):")
183
+ if new_name is None:
184
+ raise typer.Exit(130)
185
+ new_name = new_name.strip().lower()
186
+
187
+ if not validate_account_name(new_name):
188
+ error("Invalid new name. Use lowercase letters, digits, hyphens.")
189
+ raise typer.Exit(2)
190
+ if new_name in accounts:
191
+ error(f"Account '{new_name}' already exists.")
192
+ raise typer.Exit(2)
193
+
194
+ rename_account(old_name, new_name)
195
+ success(f"Account renamed: {old_name} \u2192 {new_name}")
196
+
197
+
198
+ @app.command("login")
199
+ def login_command(
200
+ name: Optional[str] = typer.Argument(
201
+ None, help="Account name (default: active account)", autocompletion=complete_account_name
202
+ ),
203
+ ) -> None:
204
+ """Re-authenticate (refresh OAuth) for an account."""
205
+ accounts = list_accounts()
206
+
207
+ if not name:
208
+ # Try default first, otherwise interactive select
209
+ name = get_default_account()
210
+ if not name:
211
+ if not accounts:
212
+ error("No accounts configured. Run 'claude-cli init' first.")
213
+ raise typer.Exit(2)
214
+ name = select_account("Select account to login:")
215
+ if name is None:
216
+ raise typer.Exit(130)
217
+
218
+ if name not in accounts:
219
+ error(f"Account '{name}' not found.")
220
+ raise typer.Exit(4)
221
+
222
+ console.print(f"\n Opening browser for Claude login (account: {name})...")
223
+ if trigger_login(name):
224
+ success("Authenticated successfully")
225
+ else:
226
+ error("Login failed or claude binary not found on PATH")
227
+ raise typer.Exit(3)
228
+
229
+
230
+ @app.command("current")
231
+ def current_command() -> None:
232
+ """Show the active account configuration."""
233
+ config = load_config()
234
+ default = config.default
235
+
236
+ if not default or default not in config.accounts:
237
+ info("No active account. Run 'claude-cli use <name>' to set one.")
238
+ raise typer.Exit(0)
239
+
240
+ account = config.accounts[default]
241
+ auth = check_auth_status(default)
242
+ if auth == "valid":
243
+ status = "\u2713 logged in"
244
+ elif auth == "expired":
245
+ status = "\u2717 expired"
246
+ else:
247
+ status = "\u2717 none"
248
+
249
+ print_header("Current Account")
250
+ print_detail("Name", default)
251
+ print_detail("Label", account.label)
252
+ print_detail("Tier", account.tier)
253
+ print_detail("Status", status)
254
+ print_detail("Config Dir", str(get_account_dir(default)))
255
+ print_detail("Shared Dir", str(SHARED_DIR))
256
+ print_detail("Created", format_date(account.created_at))
257
+
258
+
259
+ @app.command("repair")
260
+ def repair_command(
261
+ name: Optional[str] = typer.Argument(
262
+ None, help="Account name (default: all accounts)", autocompletion=complete_account_name
263
+ ),
264
+ ) -> None:
265
+ """Repair symlinks for account(s) — re-links shared items like commands, skills, etc."""
266
+ accounts = list_accounts()
267
+ if not accounts:
268
+ info("No accounts configured.")
269
+ raise typer.Exit(0)
270
+
271
+ ensure_shared_dir()
272
+
273
+ targets = [name] if name else accounts
274
+ for acct in targets:
275
+ if acct not in accounts:
276
+ error(f"Account '{acct}' not found.")
277
+ raise typer.Exit(4)
278
+ account_dir = get_account_dir(acct)
279
+ setup_symlinks(account_dir)
280
+ success(f"Repaired symlinks for '{acct}'")
@@ -0,0 +1,28 @@
1
+ """claude-cli config — Global CLI configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+
7
+ from claude_cli.core.config import ACCOUNTS_DIR, CONFIG_FILE, SHARED_DIR, load_config
8
+ from claude_cli.ui import console, print_detail, print_header
9
+
10
+ app = typer.Typer(no_args_is_help=True)
11
+
12
+
13
+ @app.command("show")
14
+ def show_command() -> None:
15
+ """Show global configuration details."""
16
+ config = load_config()
17
+ print_header("Global Configuration")
18
+ print_detail("Config File", str(CONFIG_FILE))
19
+ print_detail("Shared Dir", str(SHARED_DIR))
20
+ print_detail("Accounts Dir", str(ACCOUNTS_DIR))
21
+ print_detail("Default Account", config.default or "none")
22
+ print_detail("Total Accounts", str(len(config.accounts)))
23
+
24
+
25
+ @app.command("path")
26
+ def path_command() -> None:
27
+ """Print the config file path (useful for scripting)."""
28
+ console.print(str(CONFIG_FILE))
@@ -0,0 +1,32 @@
1
+ """claude-cli history — View command history."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional
6
+
7
+ import typer
8
+
9
+ from claude_cli.core.history import clear_history, get_history
10
+ from claude_cli.ui import info, success
11
+ from claude_cli.ui.tables import print_history_table
12
+
13
+ app = typer.Typer(invoke_without_command=True)
14
+
15
+
16
+ @app.callback(invoke_without_command=True)
17
+ def history_command(
18
+ limit: int = typer.Option(20, "--limit", "-n", help="Number of entries to show"),
19
+ clear: bool = typer.Option(False, "--clear", help="Clear all history"),
20
+ ) -> None:
21
+ """View CLI command history."""
22
+ if clear:
23
+ clear_history()
24
+ success("History cleared.")
25
+ return
26
+
27
+ entries = get_history(limit=limit)
28
+ if not entries:
29
+ info("No command history yet.")
30
+ raise typer.Exit(0)
31
+
32
+ print_history_table(entries)
@@ -0,0 +1,136 @@
1
+ """claude-cli init — Interactive setup wizard."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+ from pathlib import Path
7
+
8
+ import typer
9
+
10
+ from claude_cli.core.account import (
11
+ add_account,
12
+ ensure_shared_dir,
13
+ get_account_dir,
14
+ migrate_existing_claude_dir,
15
+ set_default_account,
16
+ )
17
+ from claude_cli.core.auth import trigger_login
18
+ from claude_cli.core.config import config_exists
19
+ from claude_cli.ui import console, error, info, success
20
+ from claude_cli.ui.prompts import confirm, select_from_list, text_input
21
+ from claude_cli.utils.validators import validate_account_name, validate_label
22
+
23
+ TIER_CHOICES = ["pro", "max", "team", "enterprise"]
24
+
25
+
26
+ def init_command() -> None:
27
+ """Interactive setup wizard — create your first (or next) account."""
28
+ is_first_run = not config_exists()
29
+
30
+ if is_first_run:
31
+ console.print("\n[bold]Welcome to Claude CLI![/bold]")
32
+ console.print(" Let's set up your first account.\n")
33
+ else:
34
+ console.print("\n[bold]Adding a new Claude account profile...[/bold]\n")
35
+
36
+ # Migration offer on first run
37
+ if is_first_run:
38
+ claude_dir = Path.home() / ".claude"
39
+ if claude_dir.exists():
40
+ console.print(f" Existing Claude config found at [highlight]{claude_dir}[/highlight]\n")
41
+ migrate = confirm("Migrate existing config to claude-cli?", default=True)
42
+ if migrate is None:
43
+ raise typer.Exit(130)
44
+ if migrate:
45
+ creds_path = migrate_existing_claude_dir()
46
+ success("Shared files moved to ~/.claude-cli/shared/")
47
+ success("Backup saved to ~/.claude.bak/")
48
+ console.print()
49
+
50
+ # Prompt for account name for existing credentials
51
+ name = _prompt_account_name()
52
+ label = _prompt_label()
53
+ tier = _prompt_tier()
54
+
55
+ account_dir = add_account(name, label, tier)
56
+
57
+ # Move existing credentials into the account
58
+ if creds_path and creds_path.exists():
59
+ shutil.move(str(creds_path), str(account_dir / ".credentials.json"))
60
+ success(f'Account "{name}" created from existing config')
61
+ else:
62
+ info("No existing credentials found. Logging in...")
63
+ if not trigger_login(name):
64
+ error("Login failed or claude binary not found")
65
+
66
+ set_default_account(name)
67
+ success("Set as default")
68
+
69
+ # Offer to add another
70
+ console.print()
71
+ another = confirm("Add another account now?", default=False)
72
+ if another:
73
+ _add_new_account()
74
+ return
75
+
76
+ # Normal flow: add a new account
77
+ ensure_shared_dir()
78
+ _add_new_account()
79
+
80
+ # On first run, auto-set as default
81
+ if is_first_run:
82
+ return
83
+
84
+ set_as_default = confirm("Set as default account?", default=True)
85
+ if set_as_default is None:
86
+ raise typer.Exit(130)
87
+
88
+
89
+ def _add_new_account() -> None:
90
+ """Prompt for account details and create it."""
91
+ name = _prompt_account_name()
92
+ label = _prompt_label()
93
+ tier = _prompt_tier()
94
+
95
+ add_account(name, label, tier)
96
+
97
+ console.print("\n Opening browser for Claude login...")
98
+ if trigger_login(name):
99
+ success("Authenticated successfully")
100
+ else:
101
+ error("Login failed or claude binary not found on PATH")
102
+ info("You can login later with: claude-cli account login " + name)
103
+
104
+ set_default_account(name)
105
+ success(f'Account "{name}" created and set as default')
106
+
107
+
108
+ def _prompt_account_name() -> str:
109
+ """Prompt for a valid account name slug."""
110
+ while True:
111
+ name = text_input("Account name (slug):")
112
+ if name is None:
113
+ raise typer.Exit(130)
114
+ name = name.strip().lower()
115
+ if validate_account_name(name):
116
+ return name
117
+ error("Invalid name. Use lowercase letters, digits, hyphens. Cannot be 'shared'.")
118
+
119
+
120
+ def _prompt_label() -> str:
121
+ """Prompt for an account display label."""
122
+ while True:
123
+ label = text_input("Display label:")
124
+ if label is None:
125
+ raise typer.Exit(130)
126
+ if validate_label(label):
127
+ return label.strip()
128
+ error("Label cannot be empty.")
129
+
130
+
131
+ def _prompt_tier() -> str:
132
+ """Prompt for subscription tier."""
133
+ tier = select_from_list("Subscription tier:", TIER_CHOICES)
134
+ if tier is None:
135
+ raise typer.Exit(130)
136
+ return tier
@@ -0,0 +1,78 @@
1
+ """claude-cli run — Launch Claude Code with account selection."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import shutil
7
+ from typing import Optional
8
+
9
+ import typer
10
+
11
+ from claude_cli.core.account import get_account_dir, get_default_account, list_accounts
12
+ from claude_cli.ui import error, info
13
+ from claude_cli.ui.prompts import select_account
14
+ from claude_cli.utils.completers import complete_account_name
15
+
16
+
17
+ def _launch(name: str, args: list[str]) -> None:
18
+ """Exec into claude with the given account's config dir."""
19
+ accounts = list_accounts()
20
+ if name not in accounts:
21
+ error(f"Account '{name}' not found.")
22
+ raise typer.Exit(4)
23
+
24
+ claude_bin = shutil.which("claude")
25
+ if not claude_bin:
26
+ error("'claude' binary not found on PATH.")
27
+ info("Install Claude Code first: https://docs.anthropic.com/en/docs/claude-code")
28
+ raise typer.Exit(1)
29
+
30
+ account_dir = get_account_dir(name)
31
+ env = os.environ.copy()
32
+ env["CLAUDE_CONFIG_DIR"] = str(account_dir)
33
+ os.execvpe(claude_bin, [claude_bin] + args, env)
34
+
35
+
36
+ def quick_run_command(
37
+ args: Optional[list[str]] = typer.Argument(None, help="Arguments passed through to claude"),
38
+ ) -> None:
39
+ """Launch Claude Code with the current default account (no prompt)."""
40
+ accounts = list_accounts()
41
+ if not accounts:
42
+ error("No accounts configured. Run 'claude-cli init' first.")
43
+ raise typer.Exit(2)
44
+
45
+ name = get_default_account()
46
+ if not name:
47
+ error("No default account set. Run 'claude-cli use <name>' or 'claude-cli run'.")
48
+ raise typer.Exit(2)
49
+
50
+ _launch(name, list(args) if args else [])
51
+
52
+
53
+ def run_command(
54
+ ctx: typer.Context,
55
+ account: Optional[str] = typer.Option(
56
+ None, "--account", "-a",
57
+ help="Account to use (skip interactive prompt)",
58
+ autocompletion=complete_account_name,
59
+ ),
60
+ args: Optional[list[str]] = typer.Argument(None, help="Arguments passed through to claude"),
61
+ ) -> None:
62
+ """Launch Claude Code — interactively select account (or use -a to specify)."""
63
+ if ctx.invoked_subcommand is not None:
64
+ return
65
+
66
+ accounts = list_accounts()
67
+ if not accounts:
68
+ error("No accounts configured. Run 'claude-cli init' first.")
69
+ raise typer.Exit(2)
70
+
71
+ if account:
72
+ name = account
73
+ else:
74
+ name = select_account("Select account:", default=get_default_account())
75
+ if name is None:
76
+ raise typer.Exit(130)
77
+
78
+ _launch(name, list(args) if args else [])
@@ -0,0 +1,44 @@
1
+ """claude-cli status — Dashboard overview of all accounts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+
7
+ from claude_cli.core.auth import check_auth_status
8
+ from claude_cli.core.config import SHARED_DIR, load_config
9
+ from claude_cli.ui import console, info
10
+ from claude_cli.ui.tables import print_status_table
11
+
12
+
13
+ def status_command() -> None:
14
+ """Quick overview of all accounts — a dashboard view."""
15
+ config = load_config()
16
+
17
+ if not config.accounts:
18
+ info("No accounts configured. Run 'claude-cli init' to get started.")
19
+ raise typer.Exit(0)
20
+
21
+ auth_statuses = {name: check_auth_status(name) for name in config.accounts}
22
+ print_status_table(config, auth_statuses)
23
+
24
+ # Summary line
25
+ active = config.default or "none"
26
+ total = len(config.accounts)
27
+ console.print(f" Active: [bold]{active}[/bold] | {total} account(s) configured")
28
+
29
+ # Shared files check
30
+ shared_items = {
31
+ "settings": SHARED_DIR / "settings.json",
32
+ "rules": SHARED_DIR / "rules",
33
+ "agents": SHARED_DIR / "agents",
34
+ "MCP": SHARED_DIR / ".claude.json",
35
+ }
36
+ checks = []
37
+ for label, path in shared_items.items():
38
+ marker = "\u2713" if path.exists() else "\u2717"
39
+ checks.append(f"{label} {marker}")
40
+ console.print(f" Shared: {' '.join(checks)}", style="dim")
41
+
42
+ console.print()
43
+ console.print(' Run "claude-cli use <name>" to switch.', style="dim")
44
+ console.print(' Run "claude-cli account login <name>" to re-authenticate.', style="dim")