kctl-claude 0.2.0__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 (41) hide show
  1. kctl_claude-0.2.0/.gitignore +33 -0
  2. kctl_claude-0.2.0/PKG-INFO +17 -0
  3. kctl_claude-0.2.0/README.md +88 -0
  4. kctl_claude-0.2.0/pyproject.toml +45 -0
  5. kctl_claude-0.2.0/skills/claude-admin/SKILL.md +164 -0
  6. kctl_claude-0.2.0/src/kctl_claude/__init__.py +3 -0
  7. kctl_claude-0.2.0/src/kctl_claude/__main__.py +5 -0
  8. kctl_claude-0.2.0/src/kctl_claude/cli.py +137 -0
  9. kctl_claude-0.2.0/src/kctl_claude/commands/__init__.py +0 -0
  10. kctl_claude-0.2.0/src/kctl_claude/commands/api_cmd.py +97 -0
  11. kctl_claude-0.2.0/src/kctl_claude/commands/backup_cmd.py +53 -0
  12. kctl_claude-0.2.0/src/kctl_claude/commands/config_cmd.py +436 -0
  13. kctl_claude-0.2.0/src/kctl_claude/commands/doctor_cmd.py +207 -0
  14. kctl_claude-0.2.0/src/kctl_claude/commands/env_cmd.py +83 -0
  15. kctl_claude-0.2.0/src/kctl_claude/commands/setup_cmd.py +170 -0
  16. kctl_claude-0.2.0/src/kctl_claude/commands/skill_cmd.py +76 -0
  17. kctl_claude-0.2.0/src/kctl_claude/commands/status_cmd.py +244 -0
  18. kctl_claude-0.2.0/src/kctl_claude/commands/sync_cmd.py +83 -0
  19. kctl_claude-0.2.0/src/kctl_claude/commands/verify_cmd.py +111 -0
  20. kctl_claude-0.2.0/src/kctl_claude/core/__init__.py +0 -0
  21. kctl_claude-0.2.0/src/kctl_claude/core/callbacks.py +55 -0
  22. kctl_claude-0.2.0/src/kctl_claude/core/checks.py +249 -0
  23. kctl_claude-0.2.0/src/kctl_claude/core/config.py +21 -0
  24. kctl_claude-0.2.0/src/kctl_claude/core/exceptions.py +17 -0
  25. kctl_claude-0.2.0/src/kctl_claude/core/output.py +39 -0
  26. kctl_claude-0.2.0/src/kctl_claude/core/paths.py +47 -0
  27. kctl_claude-0.2.0/src/kctl_claude/core/plugins.py +7 -0
  28. kctl_claude-0.2.0/src/kctl_claude/core/runner.py +48 -0
  29. kctl_claude-0.2.0/tests/__init__.py +0 -0
  30. kctl_claude-0.2.0/tests/conftest.py +61 -0
  31. kctl_claude-0.2.0/tests/test_api_cmd.py +113 -0
  32. kctl_claude-0.2.0/tests/test_callbacks.py +62 -0
  33. kctl_claude-0.2.0/tests/test_checks.py +207 -0
  34. kctl_claude-0.2.0/tests/test_config.py +69 -0
  35. kctl_claude-0.2.0/tests/test_config_cmd.py +80 -0
  36. kctl_claude-0.2.0/tests/test_doctor_cmd.py +131 -0
  37. kctl_claude-0.2.0/tests/test_output.py +41 -0
  38. kctl_claude-0.2.0/tests/test_paths.py +61 -0
  39. kctl_claude-0.2.0/tests/test_runner.py +53 -0
  40. kctl_claude-0.2.0/tests/test_smoke.py +79 -0
  41. kctl_claude-0.2.0/tests/test_status_cmd.py +85 -0
@@ -0,0 +1,33 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ *.egg
6
+ dist/
7
+ build/
8
+ .eggs/
9
+
10
+ # Virtual environments
11
+ .venv/
12
+ venv/
13
+
14
+ # IDE
15
+ .idea/
16
+ .vscode/
17
+ *.swp
18
+ *.swo
19
+
20
+ # Testing
21
+ .pytest_cache/
22
+ .coverage
23
+ htmlcov/
24
+ .mypy_cache/
25
+ .ruff_cache/
26
+
27
+ # OS
28
+ .DS_Store
29
+ Thumbs.db
30
+
31
+ # Environment
32
+ .env
33
+ .env.local
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: kctl-claude
3
+ Version: 0.2.0
4
+ Summary: Kodemeio Claude Code CLI — manage local and remote Claude Code environments
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: httpx>=0.28.0
7
+ Requires-Dist: kctl-lib>=0.4.0
8
+ Requires-Dist: pydantic>=2.10.0
9
+ Requires-Dist: pyyaml>=6.0.2
10
+ Requires-Dist: rich>=13.9.0
11
+ Requires-Dist: typer>=0.15.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: mypy>=1.14.0; extra == 'dev'
14
+ Requires-Dist: pytest-httpx>=0.35.0; extra == 'dev'
15
+ Requires-Dist: pytest>=8.3.0; extra == 'dev'
16
+ Requires-Dist: ruff>=0.9.0; extra == 'dev'
17
+ Requires-Dist: types-pyyaml>=6.0.0; extra == 'dev'
@@ -0,0 +1,88 @@
1
+ # kctl-claude
2
+
3
+ Kodemeio Claude Code CLI — manage local and remote Claude Code environments.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ uv tool install .
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ kctl-claude config init
15
+ kctl-claude status
16
+ kctl-claude doctor
17
+ kctl-claude env list
18
+ ```
19
+
20
+ ## Command Groups
21
+
22
+ | Group | Description |
23
+ |-------|-------------|
24
+ | `config` | Manage CLI configuration and profiles |
25
+ | `api` | SDK REST API operations |
26
+ | `backup` | Backup and restore Claude Code runtime |
27
+ | `doctor` | Diagnostics and health checks |
28
+ | `env` | Environment file management |
29
+ | `setup` | Setup Claude Code on local, VPS, or Docker |
30
+ | `status` | Status dashboard and health checks |
31
+ | `sync` | Sync config between local `~/.claude` and repo |
32
+ | `verify` | Verify Claude Code config completeness |
33
+
34
+ Top-level commands: `completions`, `update`
35
+
36
+ ## Configuration
37
+
38
+ Config lives in `~/.config/kodemeio/config.yaml` under the `claude` service key.
39
+
40
+ ```bash
41
+ # Initialize a profile
42
+ kctl-claude config init
43
+
44
+ # Add a named profile
45
+ kctl-claude config add work --url https://api.anthropic.com --api-key $ANTHROPIC_API_KEY
46
+
47
+ # Switch active profile
48
+ kctl-claude config use work
49
+
50
+ # Show current profile (secrets masked)
51
+ kctl-claude config show
52
+ ```
53
+
54
+ ## Global Options
55
+
56
+ | Option | Short | Description |
57
+ |--------|-------|-------------|
58
+ | `--json` | | Output as JSON |
59
+ | `--quiet` | `-q` | Suppress info messages |
60
+ | `--verbose` | `-v` | Show debug output |
61
+ | `--version` | `-V` | Show version and exit |
62
+
63
+ ## Shell Completions
64
+
65
+ ```bash
66
+ # Generate completions (prints to stdout)
67
+ kctl-claude completions zsh
68
+ kctl-claude completions bash
69
+ kctl-claude completions fish
70
+
71
+ # Install completions automatically
72
+ kctl-claude completions zsh --install
73
+ kctl-claude completions bash --install
74
+ ```
75
+
76
+ ## Self-Update
77
+
78
+ ```bash
79
+ kctl-claude update
80
+ ```
81
+
82
+ ## Development
83
+
84
+ ```bash
85
+ uv run pytest tests/ -v
86
+ uv run ruff check src/
87
+ uv run mypy src/
88
+ ```
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "kctl-claude"
7
+ version = "0.2.0"
8
+ description = "Kodemeio Claude Code CLI — manage local and remote Claude Code environments"
9
+ requires-python = ">=3.12"
10
+ dependencies = [
11
+ "kctl-lib>=0.4.0",
12
+ "typer>=0.15.0",
13
+ "rich>=13.9.0",
14
+ "pydantic>=2.10.0",
15
+ "pyyaml>=6.0.2",
16
+ "httpx>=0.28.0",
17
+ ]
18
+
19
+ [project.optional-dependencies]
20
+ dev = [
21
+ "pytest>=8.3.0",
22
+ "pytest-httpx>=0.35.0",
23
+ "ruff>=0.9.0",
24
+ "mypy>=1.14.0",
25
+ "types-PyYAML>=6.0.0",
26
+ ]
27
+
28
+ [project.scripts]
29
+ kctl-claude = "kctl_claude.cli:_run"
30
+
31
+ [tool.uv.sources]
32
+ kctl-lib = { workspace = true }
33
+
34
+ [project.entry-points."kctl_claude.plugins"]
35
+
36
+ [tool.hatch.build.targets.wheel]
37
+ packages = ["src/kctl_claude"]
38
+
39
+ [tool.ruff]
40
+ target-version = "py312"
41
+ line-length = 120
42
+
43
+ [tool.mypy]
44
+ python_version = "3.12"
45
+ strict = true
@@ -0,0 +1,164 @@
1
+ ---
2
+ name: claude-admin
3
+ description: >
4
+ Claude Code environment management via kctl-claude CLI (12 groups, ~33 commands).
5
+ MUST use for ANY kctl-claude operation.
6
+ Triggers on: "api", "backup", "check", "compare", "completions", "config", "current", "deploy", "diff", "docker", "doctor", "env", "generate", "health", "init", "kctl-claude", "local", "profile", "profiles", "pull", "push", "remove", "restore", "secrets", "setup", "skill", "status", "sync", "task", "test", "update", "updates", "validate", "verify".
7
+ Auto-generated: 2026-04-05
8
+ registry_hash: 7578bca2463c
9
+ ---
10
+
11
+ # claude-admin — kctl-claude CLI Reference
12
+
13
+ > Auto-generated from `kctl-claude` command registry. Do not edit manually.
14
+ > To regenerate: `kctl-claude skill generate`
15
+ > To add custom content: edit `SKILL.extra.md` in the same directory.
16
+
17
+ ## Overview
18
+
19
+ **CLI:** `kctl-claude`
20
+ **Command groups:** 12
21
+ **Total commands:** ~33
22
+ **Install:** `cd cli && uv tool install --editable .`
23
+
24
+ ## Global Options
25
+
26
+ | Flag | Description |
27
+ |------|-------------|
28
+ | `--json` | JSON output |
29
+ | `--quiet`, `-q` | Suppress info messages |
30
+ | `--format`, `-f` | Output format: pretty/json/csv/yaml |
31
+ | `--no-header` | Omit CSV header row |
32
+ | `--profile`, `-p` | Config profile name |
33
+ | `--version`, `-V` | Show version |
34
+
35
+ ## Command Reference
36
+
37
+ ### `kctl-claude api`
38
+
39
+ SDK REST API operations.
40
+
41
+ | Command | Description |
42
+ |---------|-------------|
43
+ | `api health` | Check SDK API health. |
44
+ | `api task <prompt> [--workspace] [--bare]` | Send a task to Claude Code via SDK API. |
45
+
46
+ ### `kctl-claude backup`
47
+
48
+ Backup and restore Claude Code runtime.
49
+
50
+ | Command | Description |
51
+ |---------|-------------|
52
+ | `backup create` | Backup container runtime volume. |
53
+ | `backup restore <backup_dir>` | Restore runtime from backup. |
54
+
55
+ ### `kctl-claude completions`
56
+
57
+ Generate or install shell completions.
58
+
59
+ Usage: `kctl-claude completions [--shell] [--install]`
60
+
61
+ ### `kctl-claude config`
62
+
63
+ Manage CLI configuration and profiles.
64
+
65
+ | Command | Description |
66
+ |---------|-------------|
67
+ | `config add <name> [--config_dir] [--backup_dir] [--set_default]` | Add or update a profile's Claude configuration. |
68
+ | `config current` | Show the active profile and its Claude configuration. |
69
+ | `config init [--config_dir] [--backup_dir] [--name]` | Initialize CLI configuration (interactive if no flags given). |
70
+ | `config profiles` | List all profiles. |
71
+ | `config remove <name> [--force] [--service_only]` | Remove a profile or just its Claude config. |
72
+ | `config set <key> <value> [--profile_arg]` | Set a configuration value for the current service. |
73
+ | `config show` | Show full configuration. |
74
+ | `config test` | Verify configuration is valid. |
75
+ | `config use <name>` | Switch the default profile. |
76
+ | `config validate` | Validate the current configuration. |
77
+
78
+ **Examples:**
79
+ ```bash
80
+ kctl-claude config set config_dir /path/to/dir
81
+ kctl-claude config set backup_dir /path/to/backups
82
+ kctl-claude config set default_profile work
83
+ ```
84
+
85
+ ### `kctl-claude doctor`
86
+
87
+ Diagnostics and health checks.
88
+
89
+ ### `kctl-claude env`
90
+
91
+ Environment file management.
92
+
93
+ | Command | Description |
94
+ |---------|-------------|
95
+ | `env check` | Validate .env file. |
96
+ | `env deploy` | Deploy .env to Hetzner server. |
97
+ | `env generate` | Interactive .env generator. |
98
+
99
+ ### `kctl-claude setup`
100
+
101
+ Setup Claude Code on local, VPS, or Docker.
102
+
103
+ | Command | Description |
104
+ |---------|-------------|
105
+ | `setup compare` | Compare setup modes side-by-side. |
106
+ | `setup docker` | Show Docker deployment commands. |
107
+ | `setup init` | Run session initialization (git, SSH, tmux, kctl tools). |
108
+ | `setup local` | Setup local development environment (laptop). |
109
+ | `setup vps [--check] [--skip_repos]` | Setup VPS/bare-metal server (requires sudo). |
110
+
111
+ ### `kctl-claude skill`
112
+
113
+ Claude Code skill management.
114
+
115
+ | Command | Description |
116
+ |---------|-------------|
117
+ | `skill generate [--output] [--install] [--check]` | Auto-generate SKILL.md from CLI command registry. |
118
+
119
+ **Examples:**
120
+ ```bash
121
+ kctl-claude skill generate
122
+ kctl-claude skill generate --install
123
+ kctl-claude skill generate --check
124
+ ```
125
+
126
+ ### `kctl-claude status`
127
+
128
+ Status dashboard and health checks.
129
+
130
+ | Command | Description |
131
+ |---------|-------------|
132
+ | `status health` | Quick health check (exit code 0=ok, 1=issues). |
133
+ | `status updates` | Check for Claude Code updates and show changelog link. |
134
+
135
+ ### `kctl-claude sync`
136
+
137
+ Sync config between local ~/.claude and repo.
138
+
139
+ | Command | Description |
140
+ |---------|-------------|
141
+ | `sync diff` | Preview what push would change (dry-run). |
142
+ | `sync pull` | Sync repo config -> local (update local environment). |
143
+ | `sync push` | Sync local config -> repo (for Docker deployment). |
144
+ | `sync secrets` | Deploy kctl credentials to Hetzner. |
145
+
146
+ ### `kctl-claude update`
147
+
148
+ Check for updates and upgrade kctl-claude.
149
+
150
+ ### `kctl-claude verify`
151
+
152
+ Verify Claude Code config completeness.
153
+
154
+ ## Configuration
155
+
156
+ Shared config: `~/.config/kodemeio/config.yaml`
157
+
158
+ ```bash
159
+ kctl-claude config init # Interactive setup
160
+ kctl-claude config show # Show current config
161
+ kctl-claude config profiles # List profiles
162
+ kctl-claude config current # Show active profile
163
+ kctl-claude config validate # Verify config
164
+ ```
@@ -0,0 +1,3 @@
1
+ """kctl-claude — Kodemeio Claude Code CLI."""
2
+
3
+ __version__ = "0.2.0"
@@ -0,0 +1,5 @@
1
+ """Allow running as: python -m kctl_claude."""
2
+
3
+ from kctl_claude.cli import _run
4
+
5
+ _run()
@@ -0,0 +1,137 @@
1
+ """Main CLI entry point for kctl-claude."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ import sys
7
+ from typing import Annotated
8
+
9
+ import typer
10
+
11
+ from kctl_claude import __version__
12
+ from kctl_claude.commands.api_cmd import app as api_app
13
+ from kctl_claude.commands.backup_cmd import app as backup_app
14
+ from kctl_claude.commands.config_cmd import app as config_app
15
+ from kctl_claude.commands.doctor_cmd import app as doctor_app
16
+ from kctl_claude.commands.env_cmd import app as env_app
17
+ from kctl_claude.commands.setup_cmd import app as setup_app
18
+ from kctl_claude.commands.status_cmd import app as status_app
19
+ from kctl_claude.commands.sync_cmd import app as sync_app
20
+ from kctl_claude.commands.verify_cmd import app as verify_app
21
+ from kctl_claude.core.callbacks import AppContext
22
+ from kctl_claude.core.plugins import ENTRY_POINT_GROUP, discover_and_load_plugins
23
+ from kctl_claude.commands.skill_cmd import app as skill_app
24
+ from kctl_lib.self_update import notify_if_outdated
25
+
26
+ app = typer.Typer(
27
+ name="kctl-claude",
28
+ help="Kodemeio Claude Code CLI — manage local and remote Claude Code environments.",
29
+ invoke_without_command=True,
30
+ rich_markup_mode="rich",
31
+ pretty_exceptions_enable=False,
32
+ )
33
+
34
+ # Register command groups
35
+ app.add_typer(config_app, name="config")
36
+ app.add_typer(status_app, name="status")
37
+ app.add_typer(sync_app, name="sync")
38
+ app.add_typer(verify_app, name="verify")
39
+ app.add_typer(api_app, name="api")
40
+ app.add_typer(env_app, name="env")
41
+ app.add_typer(backup_app, name="backup")
42
+ app.add_typer(setup_app, name="setup")
43
+ app.add_typer(doctor_app, name="doctor")
44
+ app.add_typer(skill_app, name="skill", hidden=True)
45
+
46
+ # Discover and load plugins
47
+ discover_and_load_plugins(app, ENTRY_POINT_GROUP)
48
+
49
+
50
+ @app.callback()
51
+ def main(
52
+ ctx: typer.Context,
53
+ json_output: Annotated[bool, typer.Option("--json", help="JSON output")] = False,
54
+ quiet: Annotated[bool, typer.Option("--quiet", "-q", help="Suppress info messages")] = False,
55
+ verbose: Annotated[bool, typer.Option("--verbose", "-v", help="Show debug output")] = False,
56
+ version: Annotated[bool, typer.Option("--version", "-V", help="Show version")] = False,
57
+ ) -> None:
58
+ """Global options applied to all commands."""
59
+ if version:
60
+ typer.echo(f"kctl-claude {__version__}")
61
+ raise typer.Exit()
62
+
63
+ ctx.ensure_object(dict)
64
+ ctx.obj = AppContext(json_mode=json_output, quiet=quiet, verbose=verbose)
65
+ notify_if_outdated(ctx.obj.output, "kctl-claude", __version__)
66
+
67
+ # Record command in history (non-blocking, best-effort)
68
+ try:
69
+ from kctl_lib.history import HistoryStore
70
+
71
+ store = HistoryStore("kctl-claude")
72
+ store.ensure_schema(
73
+ [
74
+ """CREATE TABLE IF NOT EXISTS commands (
75
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
76
+ command TEXT NOT NULL,
77
+ timestamp TEXT NOT NULL
78
+ )"""
79
+ ]
80
+ )
81
+ store.record(table="commands", command=" ".join(sys.argv[1:]))
82
+ except Exception:
83
+ pass # History recording is best-effort
84
+
85
+ # If no subcommand, show help
86
+ if ctx.invoked_subcommand is None and not version:
87
+ typer.echo(ctx.get_help())
88
+ raise typer.Exit()
89
+
90
+
91
+ @app.command()
92
+ def completions(
93
+ shell: Annotated[str, typer.Argument(help="Shell type: zsh, bash, fish")] = "zsh",
94
+ install: Annotated[bool, typer.Option("--install", help="Install completions")] = False,
95
+ ) -> None:
96
+ """Generate or install shell completions."""
97
+ from kctl_lib.completions import get_completion_script, install_completions
98
+
99
+ if install:
100
+ path = install_completions("kctl-claude", shell)
101
+ if path:
102
+ typer.echo(f"Completions installed to {path}")
103
+ else:
104
+ typer.echo(f"Could not install completions for {shell}", err=True)
105
+ raise typer.Exit(code=1)
106
+ else:
107
+ script = get_completion_script("kctl-claude", shell)
108
+ typer.echo(script)
109
+
110
+
111
+ @app.command("update")
112
+ def update_cmd(ctx: typer.Context) -> None:
113
+ """Check for updates and upgrade kctl-claude."""
114
+ actx: AppContext = ctx.obj
115
+ out = actx.output
116
+
117
+ from kctl_lib.self_update import check_update
118
+ from kctl_lib.self_update import update as do_update
119
+
120
+ latest = check_update("kctl-claude", __version__)
121
+ if latest:
122
+ out.info(f"Updating to {latest}...")
123
+ do_update("kctl-claude")
124
+ out.success(f"Updated to {latest}")
125
+ else:
126
+ out.success("Already up to date")
127
+
128
+
129
+ def _run() -> None:
130
+ """Entry point with error handling."""
131
+ try:
132
+ app()
133
+ except KeyboardInterrupt:
134
+ sys.exit(130)
135
+ except Exception as e:
136
+ typer.echo(f"Error: {e}", err=True)
137
+ sys.exit(1)
File without changes
@@ -0,0 +1,97 @@
1
+ """kctl-claude api — interact with the SDK REST API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Annotated
6
+
7
+ import httpx
8
+ import typer
9
+
10
+ from kctl_claude.core.callbacks import AppContext
11
+
12
+ app = typer.Typer(help="SDK REST API operations.")
13
+
14
+ DEFAULT_URL = "http://localhost:3100"
15
+
16
+
17
+ def _base_url() -> str:
18
+ import os
19
+
20
+ return os.environ.get("SDK_BASE_URL", DEFAULT_URL)
21
+
22
+
23
+ def _api_key() -> str:
24
+ import os
25
+
26
+ return os.environ.get("SDK_API_KEY", "")
27
+
28
+
29
+ @app.command()
30
+ def health(ctx: typer.Context) -> None:
31
+ """Check SDK API health."""
32
+ actx: AppContext = ctx.obj
33
+ out = actx.output
34
+ try:
35
+ resp = httpx.get(f"{_base_url()}/health", timeout=5)
36
+ try:
37
+ data = resp.json()
38
+ except Exception:
39
+ data = {}
40
+ if actx.json_mode:
41
+ out.raw_json(data)
42
+ else:
43
+ out.success(f"Status: {data.get('status', 'unknown')}")
44
+ out.success(f"Tasks: {data.get('inFlight', 0)}/{data.get('maxConcurrent', 3)} in flight")
45
+ except (httpx.ConnectError, httpx.TimeoutException) as e:
46
+ out.error(f"SDK API unreachable: {e}")
47
+ raise typer.Exit(code=1) from None
48
+
49
+
50
+ @app.command()
51
+ def task(
52
+ ctx: typer.Context,
53
+ prompt: Annotated[str, typer.Argument(help="Task prompt for Claude Code")],
54
+ workspace: Annotated[str, typer.Option("--workspace", "-w", help="Workspace name")] = "",
55
+ bare: Annotated[bool, typer.Option("--bare", help="Skip auto-discovery")] = False,
56
+ ) -> None:
57
+ """Send a task to Claude Code via SDK API."""
58
+ actx: AppContext = ctx.obj
59
+ out = actx.output
60
+
61
+ # Validate workspace path to prevent traversal attacks
62
+ if workspace and (".." in workspace or workspace.startswith("/")):
63
+ out.error("Invalid workspace path")
64
+ raise typer.Exit(code=1)
65
+
66
+ headers = {"Content-Type": "application/json"}
67
+ key = _api_key()
68
+ if key:
69
+ headers["Authorization"] = f"Bearer {key}"
70
+
71
+ body: dict = {"prompt": prompt}
72
+ if workspace:
73
+ body["workspace"] = workspace
74
+ if bare:
75
+ body["bare"] = True
76
+
77
+ try:
78
+ resp = httpx.post(f"{_base_url()}/task", json=body, headers=headers, timeout=310)
79
+ try:
80
+ data = resp.json()
81
+ except Exception:
82
+ data = {}
83
+ if actx.json_mode:
84
+ out.raw_json(data)
85
+ else:
86
+ if resp.status_code == 200:
87
+ out.success("Task completed")
88
+ out.text(data.get("result", str(data)))
89
+ else:
90
+ out.error(f"Error {resp.status_code}: {data.get('error', str(data))}")
91
+ raise typer.Exit(code=1)
92
+ except httpx.TimeoutException:
93
+ out.error("Task timed out (310s)")
94
+ raise typer.Exit(code=1) from None
95
+ except httpx.ConnectError as e:
96
+ out.error(f"SDK API unreachable: {e}")
97
+ raise typer.Exit(code=1) from None
@@ -0,0 +1,53 @@
1
+ """kctl-claude backup — runtime volume backup/restore."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ from typing import Annotated
7
+
8
+ import typer
9
+ from kctl_lib.exceptions import CommandError
10
+
11
+ from kctl_claude.core.callbacks import AppContext
12
+ from kctl_claude.core.runner import run_script
13
+
14
+ app = typer.Typer(help="Backup and restore Claude Code runtime.")
15
+
16
+
17
+ @app.command("create")
18
+ def create_backup(ctx: typer.Context) -> None:
19
+ """Backup container runtime volume."""
20
+ actx: AppContext = ctx.obj
21
+ out = actx.output
22
+ repo = actx.repo_dir
23
+ if not repo:
24
+ out.error("Repo not found.")
25
+ raise typer.Exit(code=1)
26
+ script = repo / "scripts" / "backup-runtime.sh"
27
+ try:
28
+ run_script(script, capture=False)
29
+ out.success("Backup created")
30
+ except CommandError as e:
31
+ out.error(f"Backup failed (exit {e.returncode}): {e.stderr}")
32
+ raise typer.Exit(code=1) from None
33
+
34
+
35
+ @app.command()
36
+ def restore(
37
+ ctx: typer.Context,
38
+ backup_dir: Annotated[str, typer.Argument(help="Path to backup directory")],
39
+ ) -> None:
40
+ """Restore runtime from backup."""
41
+ actx: AppContext = ctx.obj
42
+ out = actx.output
43
+ out.info(f"Restoring from {backup_dir}...")
44
+ result = subprocess.run(
45
+ ["docker", "cp", f"{backup_dir}/.", "kodemeio-claude:/home/dev/.claude/"],
46
+ capture_output=True,
47
+ text=True,
48
+ )
49
+ if result.returncode == 0:
50
+ out.success("Restore complete")
51
+ else:
52
+ out.error(f"Restore failed (exit {result.returncode}): {result.stderr.strip()}")
53
+ raise typer.Exit(code=1)