Metadata-Version: 2.4
Name: kpane
Version: 0.1.1
Summary: Console TUI for everyday kubectl work: KUBECONFIG profiles, command menu, one-key pod shells in tmux panes
Author: Oleh Mozhaiev
License: MIT
Project-URL: Homepage, https://github.com/x-spy-x/kpane
Project-URL: Repository, https://github.com/x-spy-x/kpane
Project-URL: Issues, https://github.com/x-spy-x/kpane/issues
Project-URL: Changelog, https://github.com/x-spy-x/kpane/blob/main/CHANGELOG.md
Keywords: kubernetes,kubectl,tui,terminal,tmux,devops
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console :: Curses
Classifier: Intended Audience :: System Administrators
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: textual>=0.60
Requires-Dist: click>=8.0
Requires-Dist: PyYAML>=5.4
Provides-Extra: dev
Requires-Dist: pytest>=7.4; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
Requires-Dist: pytest-cov>=4.1; extra == "dev"
Requires-Dist: tomli>=2.0; python_version < "3.11" and extra == "dev"
Dynamic: license-file

# kpane

[![tests](https://github.com/x-spy-x/kpane/actions/workflows/tests.yml/badge.svg)](https://github.com/x-spy-x/kpane/actions/workflows/tests.yml)
[![python](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/)
[![pypi](https://img.shields.io/pypi/v/kpane?logo=pypi&logoColor=white&color=blue)](https://pypi.org/project/kpane/)
[![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)

A console interface for everyday `kubectl` work: switch KUBECONFIG profiles, run
the commands you actually use, and drop into a pod's shell without typing
`kubectl exec -n <ns> -it <pod> -- /bin/bash` ever again.

Unlike a full cluster dashboard, kpane stays a launcher: it hands the actual work
to real tmux panes, so the shell you land in is an ordinary terminal - scrollback,
copy/paste, Ctrl-C and your editor all behave exactly as they do everywhere else.

![kpane](https://raw.githubusercontent.com/x-spy-x/kpane/main/docs/screenshot.svg)

## What it does

1. **KUBECONFIG profiles** - every `*.conf` in `~/.kube` becomes a profile;
   `p` switches between them. `--kubeconfig` is passed explicitly on every call,
   so kpane never touches the environment of your other terminals.
2. **A menu of useful commands** - `c` opens listings, monitoring, diagnostics
   and (confirmed) destructive actions.
3. **Instant pod shells** - `Enter` on a row opens `kubectl exec -it` in a new
   pane. bash is used when the image has it, `sh` otherwise; multi-container pods
   ask which container to enter.
   The table filters interactively on any column - `f` picks the field (name,
   status, ready, restarts, age, node, or all at once), `Ctrl+F` cycles through
   them without leaving the search box, and the active filter is spelled out in
   the status bar so nothing "disappears" unexplained.
4. **Several terminals in one window** - panes are real tmux panes, so scrollback,
   copy/paste, resizing and Ctrl-C behave exactly as they do in your shell.

## Install

```bash
pipx install kpane      # recommended: an isolated environment, on your PATH
pip install kpane       # or straight into the current environment
```

From source, to hack on it:

```bash
git clone https://github.com/x-spy-x/kpane.git
cd kpane

python3 -m venv .venv
.venv/bin/pip install -e .

.venv/bin/kpane --version
```

To get a `kpane` command that works from anywhere, either activate the
environment (`source .venv/bin/activate`) or link the entry point onto your
PATH:

```bash
ln -s "$PWD/.venv/bin/kpane" ~/.local/bin/kpane
```

### Requirements

| | |
|---|---|
| Python | 3.10 or newer |
| `kubectl` | on PATH - kpane never talks to the API server itself |
| `tmux` | 3.1+, for panes; without it kpane still runs, minus pane commands |

## Run

```bash
kpane                     # relaunches itself inside a tmux session
kpane --profile prod -n backend
kpane --kubeconfig-dir ~/clusters
kpane --no-tmux-relaunch  # stay in the current terminal, no panes
```

## Keys

| Key     | Action                                        |
|---------|-----------------------------------------------|
| `Enter` | shell into the selected pod (new pane)        |
| `l`     | follow its logs (new pane)                    |
| `d`     | describe the pod                              |
| `c`     | command menu                                  |
| `p`     | switch KUBECONFIG profile                     |
| `n`     | switch namespace                              |
| `/`     | filter, `Esc` clears (the field choice stays) |
| `f`     | choose what to filter by                      |
| `Ctrl+F`| cycle to the next filter field while typing   |
| `r`     | refresh now (the table also auto-refreshes)   |
| `x`     | delete the pod (asks first)                   |
| `q`     | quit                                          |

## Troubleshooting

### `kubectl not found on PATH - install it and try again`

kpane is a front end for `kubectl`, not a replacement: it shells out for every
piece of data. Install kubectl, or point kpane at a specific binary - a pinned
version, or a wrapper such as `kubecolor`:

```yaml
# ~/.config/kpane/config.yaml
kubectl_binary: /usr/local/bin/kubectl-1.29
```

### `no kubeconfig profiles found in ~/.kube`

kpane treats a file as a profile when it is named `config`/`kubeconfig`, or ends
in `.conf`, `.yaml` or `.yml`, **and** contains kubeconfig sections. Files that
are valid YAML but something else entirely (a Helm values file, say) are skipped
without a word. Point it elsewhere with `--kubeconfig-dir ~/clusters`, or set
`kubeconfig_dir` in the config file.

A file that *is* a kubeconfig but fails to parse is not hidden: it stays in the
profile list, greyed out, with the parse error next to it.

### `tmux not found - pane commands will be unavailable`

Everything else works; only the pane commands (shell, log streaming,
`port-forward`, `watch`) need tmux. Install it, or keep using kpane as a viewer.

### It restarted itself when I launched it

By design. Panes need a tmux window to open in, so kpane relaunches itself inside
a session named `kpane` (`tmux new-session -A`). To stay in the current terminal:

```bash
kpane --no-tmux-relaunch
```

### The status bar shows `Unable to connect to the server`

That is kubectl's own message, passed through untouched. The table keeps the rows
it had - stale data beats an empty screen - and retries on the next refresh.
Check the cluster directly with the same profile:

```bash
kubectl --kubeconfig ~/.kube/staging.conf get pods -n backend
```

Usual causes: a VPN that dropped, an expired token, or an auth plugin that needs
a browser login it cannot start from inside a TUI.

### A pane opened, printed an error, and waited

The pane shows `[kpane] exited with code N - press Enter to close`. Panes stay
open on purpose: a pane that vanished the instant its command failed would take
the explanation with it. The exit code and the message above it are the answer;
Enter closes the pane.

### `OCI runtime exec failed: executable file not found`

The image has no shell. Distroless and `scratch` images ship the application and
nothing else, so there is nothing to attach to - kpane tries bash and falls back
to sh in the same call, and neither exists. Use `l` for logs instead, or attach a
[debug container](https://kubernetes.io/docs/tasks/debug/debug-application/debug-running-pod/):

```bash
kubectl debug -it <pod> --image=busybox --target=<container> -n <ns>
```

### `error: Metrics API not available`

The resource-usage commands (`top pods`, `top nodes`) need `metrics-server` in
the cluster. Many clusters do not run it; the rest of the menu is unaffected.

### The table is empty and I did not expect it

Look at the status bar - an active filter is spelled out there, for example
`filter status: crash`. `Esc` clears it. If the query looks right but matches
nothing, it is probably matching the wrong column: `f` picks the field, `Ctrl+F`
cycles through them.

### Ctrl+F does nothing

Something upstream is eating the key. The usual culprit is a tmux prefix rebound
to `C-f`, in which case tmux swallows it before kpane sees it. Use `f` (the field
picker) instead, or move your prefix back.

### It opened the wrong cluster

kpane starts with, in order: `--profile`, the profile saved from last time, the
`KUBECONFIG` of the terminal it was launched from, then the first valid profile.
The status bar always names the profile, namespace, context and API server in
use. To forget the saved choice:

```bash
rm ~/.cache/kpane/state.json
```

### `kpane: command not found` after installing

The entry point lives in the virtual environment. Either activate it, call it by
path (`.venv/bin/kpane`), or symlink it as shown in [Install](#install). With
pipx, make sure `~/.local/bin` is on your PATH (`pipx ensurepath`).

## Configuration

Optional, `~/.config/kpane/config.yaml`:

```yaml
kubeconfig_dir: ~/.kube     # where profiles live
refresh_interval: 5         # seconds between automatic reloads
pane_size: 50               # share of the window given to a new pane, %
tmux_session: kpane         # session created when started outside tmux
kubectl_binary: kubectl     # a pinned version or a wrapper such as kubecolor
```

The profile and namespace last used are remembered in
`~/.cache/kpane/state.json`, so the next session opens where you left off.

## Development

```bash
.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest                 # the whole suite, no cluster required
.venv/bin/pytest --cov=kpane
```

The project is built test-first. Every external command goes through a single
`CommandRunner`, which tests replace with a stand-in that replays recorded
answers and records the exact argv - so "press Enter on a pod" is verified as
"this precise `kubectl exec` reached tmux", without a cluster in sight.

## Layout

| Module        | Responsibility                                             |
|---------------|------------------------------------------------------------|
| `runner.py`   | the only place external processes are spawned              |
| `profiles.py` | finding and parsing kubeconfig files                       |
| `models.py`   | pods and usage rows, including kubectl's STATUS derivation  |
| `kubectl.py`  | building and running kubectl commands                      |
| `commands.py` | the declarative command menu                               |
| `tmux.py`     | pane control                                               |
| `session.py`  | shells and log streams for a pod                           |
| `app.py`      | the Textual interface                                      |
| `cli.py`      | entry point, profile choice, tmux relaunch                 |

## Built with AI

Most of the code, tests and documentation in this repository were written by
Claude (Anthropic's Claude Code) in a test-first workflow, under human direction
and review.

The division of labour, so you know what you are reading:

- **Decided by the author:** what the tool should do, tmux for the terminals
  rather than an embedded emulator, `kubectl` over subprocess rather than a
  Kubernetes client library, Textual for the interface, and the licence.
- **Written by the model:** the implementation, the 248 tests, this README.
- **Verified against real clusters before release:** profile switching, pod
  shells, log streaming and the command menu were all exercised on live
  clusters, not only against mocks. One bug found that way - a background
  refresh cancelling any dialogue left open - is covered by a regression test.

Every line was reviewed by a human before it was committed. If you find
something that looks machine-written in the bad sense, an issue is welcome.

## License

[MIT](LICENSE) - use it, change it, ship it, sell it. The only requirement is
that the copyright notice travels with the code.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md). The short version: the project is
test-first, so a change comes with the test that failed before it.
