project-sandbox-tui 0.1.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 (89) hide show
  1. project_sandbox_tui-0.1.0/.gitignore +13 -0
  2. project_sandbox_tui-0.1.0/LICENSE +21 -0
  3. project_sandbox_tui-0.1.0/PKG-INFO +96 -0
  4. project_sandbox_tui-0.1.0/README.md +68 -0
  5. project_sandbox_tui-0.1.0/docs/architecture.md +124 -0
  6. project_sandbox_tui-0.1.0/docs/cleanup.md +43 -0
  7. project_sandbox_tui-0.1.0/docs/command-workflow.md +133 -0
  8. project_sandbox_tui-0.1.0/docs/configuration.md +265 -0
  9. project_sandbox_tui-0.1.0/docs/development.md +150 -0
  10. project_sandbox_tui-0.1.0/docs/images/command-workflow/01-repository-command.svg +137 -0
  11. project_sandbox_tui-0.1.0/docs/images/command-workflow/02-repository-task.svg +132 -0
  12. project_sandbox_tui-0.1.0/docs/images/command-workflow/03-workspace-tasks.svg +134 -0
  13. project_sandbox_tui-0.1.0/docs/images/command-workflow/04-failure-output.svg +130 -0
  14. project_sandbox_tui-0.1.0/docs/merging.md +31 -0
  15. project_sandbox_tui-0.1.0/docs/models.md +29 -0
  16. project_sandbox_tui-0.1.0/docs/sessions.md +101 -0
  17. project_sandbox_tui-0.1.0/docs/usage.md +422 -0
  18. project_sandbox_tui-0.1.0/pyproject.toml +99 -0
  19. project_sandbox_tui-0.1.0/scripts/make-release.sh +173 -0
  20. project_sandbox_tui-0.1.0/scripts/test.sh +8 -0
  21. project_sandbox_tui-0.1.0/scripts/update-pins.py +305 -0
  22. project_sandbox_tui-0.1.0/src/project_sandbox_tui/__init__.py +8 -0
  23. project_sandbox_tui-0.1.0/src/project_sandbox_tui/__main__.py +3 -0
  24. project_sandbox_tui-0.1.0/src/project_sandbox_tui/app.py +2534 -0
  25. project_sandbox_tui-0.1.0/src/project_sandbox_tui/cleanup.py +459 -0
  26. project_sandbox_tui-0.1.0/src/project_sandbox_tui/cli.py +343 -0
  27. project_sandbox_tui-0.1.0/src/project_sandbox_tui/commands.py +288 -0
  28. project_sandbox_tui-0.1.0/src/project_sandbox_tui/config.py +812 -0
  29. project_sandbox_tui-0.1.0/src/project_sandbox_tui/doctor.py +209 -0
  30. project_sandbox_tui-0.1.0/src/project_sandbox_tui/history.py +103 -0
  31. project_sandbox_tui-0.1.0/src/project_sandbox_tui/initialization.py +57 -0
  32. project_sandbox_tui-0.1.0/src/project_sandbox_tui/jobs.py +713 -0
  33. project_sandbox_tui-0.1.0/src/project_sandbox_tui/launchers.py +193 -0
  34. project_sandbox_tui-0.1.0/src/project_sandbox_tui/models.py +406 -0
  35. project_sandbox_tui-0.1.0/src/project_sandbox_tui/notify.py +56 -0
  36. project_sandbox_tui-0.1.0/src/project_sandbox_tui/octopus.py +292 -0
  37. project_sandbox_tui-0.1.0/src/project_sandbox_tui/opgraph.py +607 -0
  38. project_sandbox_tui-0.1.0/src/project_sandbox_tui/processes.py +16 -0
  39. project_sandbox_tui-0.1.0/src/project_sandbox_tui/review.py +95 -0
  40. project_sandbox_tui-0.1.0/src/project_sandbox_tui/sandbox.py +317 -0
  41. project_sandbox_tui-0.1.0/src/project_sandbox_tui/screens/__init__.py +0 -0
  42. project_sandbox_tui-0.1.0/src/project_sandbox_tui/screens/command.py +156 -0
  43. project_sandbox_tui-0.1.0/src/project_sandbox_tui/screens/dialogs.py +563 -0
  44. project_sandbox_tui-0.1.0/src/project_sandbox_tui/screens/history.py +111 -0
  45. project_sandbox_tui-0.1.0/src/project_sandbox_tui/screens/launch.py +529 -0
  46. project_sandbox_tui-0.1.0/src/project_sandbox_tui/screens/log.py +204 -0
  47. project_sandbox_tui-0.1.0/src/project_sandbox_tui/screens/oplog.py +281 -0
  48. project_sandbox_tui-0.1.0/src/project_sandbox_tui/screens/review.py +200 -0
  49. project_sandbox_tui-0.1.0/src/project_sandbox_tui/screens/tasks.py +208 -0
  50. project_sandbox_tui-0.1.0/src/project_sandbox_tui/settings.py +230 -0
  51. project_sandbox_tui-0.1.0/src/project_sandbox_tui/skills/sbx-tui-config/SKILL.md +125 -0
  52. project_sandbox_tui-0.1.0/src/project_sandbox_tui/tasks.py +383 -0
  53. project_sandbox_tui-0.1.0/src/project_sandbox_tui/terminal.py +71 -0
  54. project_sandbox_tui-0.1.0/src/project_sandbox_tui/vcs.py +708 -0
  55. project_sandbox_tui-0.1.0/src/project_sandbox_tui/widgets.py +311 -0
  56. project_sandbox_tui-0.1.0/tests/__init__.py +0 -0
  57. project_sandbox_tui-0.1.0/tests/conftest.py +231 -0
  58. project_sandbox_tui-0.1.0/tests/fake_interactive_agent.py +43 -0
  59. project_sandbox_tui-0.1.0/tests/fake_project_sandbox.py +101 -0
  60. project_sandbox_tui-0.1.0/tests/test_app.py +2605 -0
  61. project_sandbox_tui-0.1.0/tests/test_cleanup.py +560 -0
  62. project_sandbox_tui-0.1.0/tests/test_cli.py +272 -0
  63. project_sandbox_tui-0.1.0/tests/test_command_line.py +555 -0
  64. project_sandbox_tui-0.1.0/tests/test_commands.py +245 -0
  65. project_sandbox_tui-0.1.0/tests/test_config.py +382 -0
  66. project_sandbox_tui-0.1.0/tests/test_container_smoke.py +209 -0
  67. project_sandbox_tui-0.1.0/tests/test_dialogs.py +328 -0
  68. project_sandbox_tui-0.1.0/tests/test_doctor.py +125 -0
  69. project_sandbox_tui-0.1.0/tests/test_history.py +159 -0
  70. project_sandbox_tui-0.1.0/tests/test_jobs.py +626 -0
  71. project_sandbox_tui-0.1.0/tests/test_keys.py +89 -0
  72. project_sandbox_tui-0.1.0/tests/test_launchers.py +225 -0
  73. project_sandbox_tui-0.1.0/tests/test_log.py +158 -0
  74. project_sandbox_tui-0.1.0/tests/test_maintenance_scripts.py +356 -0
  75. project_sandbox_tui-0.1.0/tests/test_models.py +600 -0
  76. project_sandbox_tui-0.1.0/tests/test_notify.py +96 -0
  77. project_sandbox_tui-0.1.0/tests/test_octopus.py +174 -0
  78. project_sandbox_tui-0.1.0/tests/test_opgraph.py +382 -0
  79. project_sandbox_tui-0.1.0/tests/test_packaging.py +76 -0
  80. project_sandbox_tui-0.1.0/tests/test_processes.py +103 -0
  81. project_sandbox_tui-0.1.0/tests/test_pty_session.py +267 -0
  82. project_sandbox_tui-0.1.0/tests/test_review.py +59 -0
  83. project_sandbox_tui-0.1.0/tests/test_sandbox.py +299 -0
  84. project_sandbox_tui-0.1.0/tests/test_settings.py +231 -0
  85. project_sandbox_tui-0.1.0/tests/test_tasks.py +240 -0
  86. project_sandbox_tui-0.1.0/tests/test_terminal.py +51 -0
  87. project_sandbox_tui-0.1.0/tests/test_vcs.py +786 -0
  88. project_sandbox_tui-0.1.0/tests/test_widgets.py +78 -0
  89. project_sandbox_tui-0.1.0/uv.lock +275 -0
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .mypy_cache/
10
+ .project-sandbox/
11
+ .release-status/
12
+ # project-sandbox — do not commit agent secrets
13
+ .devcontainer/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Peter Krusche
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.5
2
+ Name: project-sandbox-tui
3
+ Version: 0.1.0
4
+ Summary: Minimal vim/ranger-style TUI for running project-sandbox sessions per branch
5
+ Project-URL: Homepage, https://github.com/pkrusche/project-sandbox-tui
6
+ Project-URL: Documentation, https://github.com/pkrusche/project-sandbox-tui/tree/main/docs
7
+ Project-URL: Issues, https://github.com/pkrusche/project-sandbox-tui/issues
8
+ Project-URL: Source, https://github.com/pkrusche/project-sandbox-tui
9
+ Author: Peter Krusche
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: coding-agent,jj,jujutsu,sandbox,textual,tui
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console :: Curses
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Software Development :: Version Control
24
+ Requires-Python: >=3.12
25
+ Requires-Dist: textual<9,>=6.0
26
+ Requires-Dist: tomlkit<0.16,>=0.13
27
+ Description-Content-Type: text/markdown
28
+
29
+ # project-sandbox-tui
30
+
31
+ A [vim](https://www.vim.org/)/[ranger](https://ranger.fm/)-style terminal UI for
32
+ running coding agents in [project-sandbox](https://github.com/pkrusche/project-sandbox)
33
+ containers, with one sandbox per [jj bookmark](https://docs.jj-vcs.dev/latest/bookmarks/).
34
+
35
+ > 🤖 Written largely by AI, reviewed by a human. Use at your own risk.
36
+
37
+ - Run agents interactively or in the background with completion notifications.
38
+ - Merge or rebase tasks from separate jj workspaces with agent assistance.
39
+ - Follow each bookmark's prompts and merges in a history graph (`L` for every run); delete and undo with `dd` / `u`.
40
+ - Run shell or configured commands in the foreground (`:!make`, `:test`), once in the repository background (`:&test`), or in each selected workspace (`:*test`); see the [command comparison](docs/usage.md#choosing-how-to-run-a-shell-command). Map any form to leader keys.
41
+ - Let project-sandbox handle isolation, credentials and workspace teardown.
42
+
43
+ ```text
44
+ sbx-tui ~/workspace/myproject 5 bookmarks · 1 running
45
+ bookmark vs main reviewed head subject status
46
+ @ working copy ● ↑1 qzmvtlno (no description set)
47
+ ▶─╮ merge/parser-tui ↑6 Δ9 0/9 wnrtlxqz Merge feature/parser, spike… merging
48
+ ✓ │ feature/parser ↑3 Δ5 5/5 sutnpmvo Add streaming parser completed
49
+ │ ✗ spike/tui ↑2 ↓1 Δ4 1/4 zsuskuln Try a ranger-style layout failed (1)
50
+ │ │ ○ fix/flaky-tests ↑1 Δ2 0/2 rlvkpnrz Stabilise integration tests
51
+ ○─┴─╯ main ● = kxyrwpsn Initial import
52
+ ```
53
+
54
+ ## Requirements
55
+
56
+ - Linux or macOS (WSL on Windows)
57
+ - Python ≥ 3.12 and [uv](https://docs.astral.sh/uv/)
58
+ - [jj](https://github.com/jj-vcs/jj) ≥ 0.46 on `PATH`; for an existing Git repository, run `jj git init --colocate`
59
+ - [project-sandbox](https://github.com/pkrusche/project-sandbox) ≥ 0.2.2 and Apple Containers, Docker or Podman
60
+
61
+ ## Getting started
62
+
63
+ ```sh
64
+ uv tool install project-sandbox
65
+ uv tool install git+https://github.com/pkrusche/project-sandbox-tui
66
+ # From a checkout: uv tool install . (or uv sync && uv run sbx-tui)
67
+
68
+ cd ~/workspace/myproject
69
+ sbx-tui --init # detect the project type, write config and install a local setup skill
70
+ sbx-tui doctor # check dependencies, runtime and configuration
71
+ sbx-tui --dry-run # preview project-sandbox calls
72
+ sbx-tui # launch
73
+ ```
74
+
75
+ Press `?` for full help.
76
+
77
+ `--init` detects a root `Dockerfile`, Python/uv, Rust/Cargo or Node/npm project
78
+ (in that order). It installs a gitignored `sbx-tui-config` agent skill to help
79
+ tailor the configuration. Existing config and skill files are preserved; see
80
+ [initialization](docs/configuration.md#initialization) for detection and skill paths.
81
+
82
+ ## Documentation
83
+
84
+ - [Usage and keys](docs/usage.md)
85
+ - [Worked example: foreground and background commands](docs/command-workflow.md)
86
+ - [Sessions, terminals and notifications](docs/sessions.md)
87
+ - [Merging and rebasing](docs/merging.md)
88
+ - [Workspace cleanup](docs/cleanup.md)
89
+ - [Configuration](docs/configuration.md)
90
+ - [Model suggestions](docs/models.md)
91
+ - [Architecture](docs/architecture.md)
92
+ - [Development](docs/development.md)
93
+
94
+ ## License
95
+
96
+ [MIT](LICENSE)
@@ -0,0 +1,68 @@
1
+ # project-sandbox-tui
2
+
3
+ A [vim](https://www.vim.org/)/[ranger](https://ranger.fm/)-style terminal UI for
4
+ running coding agents in [project-sandbox](https://github.com/pkrusche/project-sandbox)
5
+ containers, with one sandbox per [jj bookmark](https://docs.jj-vcs.dev/latest/bookmarks/).
6
+
7
+ > 🤖 Written largely by AI, reviewed by a human. Use at your own risk.
8
+
9
+ - Run agents interactively or in the background with completion notifications.
10
+ - Merge or rebase tasks from separate jj workspaces with agent assistance.
11
+ - Follow each bookmark's prompts and merges in a history graph (`L` for every run); delete and undo with `dd` / `u`.
12
+ - Run shell or configured commands in the foreground (`:!make`, `:test`), once in the repository background (`:&test`), or in each selected workspace (`:*test`); see the [command comparison](docs/usage.md#choosing-how-to-run-a-shell-command). Map any form to leader keys.
13
+ - Let project-sandbox handle isolation, credentials and workspace teardown.
14
+
15
+ ```text
16
+ sbx-tui ~/workspace/myproject 5 bookmarks · 1 running
17
+ bookmark vs main reviewed head subject status
18
+ @ working copy ● ↑1 qzmvtlno (no description set)
19
+ ▶─╮ merge/parser-tui ↑6 Δ9 0/9 wnrtlxqz Merge feature/parser, spike… merging
20
+ ✓ │ feature/parser ↑3 Δ5 5/5 sutnpmvo Add streaming parser completed
21
+ │ ✗ spike/tui ↑2 ↓1 Δ4 1/4 zsuskuln Try a ranger-style layout failed (1)
22
+ │ │ ○ fix/flaky-tests ↑1 Δ2 0/2 rlvkpnrz Stabilise integration tests
23
+ ○─┴─╯ main ● = kxyrwpsn Initial import
24
+ ```
25
+
26
+ ## Requirements
27
+
28
+ - Linux or macOS (WSL on Windows)
29
+ - Python ≥ 3.12 and [uv](https://docs.astral.sh/uv/)
30
+ - [jj](https://github.com/jj-vcs/jj) ≥ 0.46 on `PATH`; for an existing Git repository, run `jj git init --colocate`
31
+ - [project-sandbox](https://github.com/pkrusche/project-sandbox) ≥ 0.2.2 and Apple Containers, Docker or Podman
32
+
33
+ ## Getting started
34
+
35
+ ```sh
36
+ uv tool install project-sandbox
37
+ uv tool install git+https://github.com/pkrusche/project-sandbox-tui
38
+ # From a checkout: uv tool install . (or uv sync && uv run sbx-tui)
39
+
40
+ cd ~/workspace/myproject
41
+ sbx-tui --init # detect the project type, write config and install a local setup skill
42
+ sbx-tui doctor # check dependencies, runtime and configuration
43
+ sbx-tui --dry-run # preview project-sandbox calls
44
+ sbx-tui # launch
45
+ ```
46
+
47
+ Press `?` for full help.
48
+
49
+ `--init` detects a root `Dockerfile`, Python/uv, Rust/Cargo or Node/npm project
50
+ (in that order). It installs a gitignored `sbx-tui-config` agent skill to help
51
+ tailor the configuration. Existing config and skill files are preserved; see
52
+ [initialization](docs/configuration.md#initialization) for detection and skill paths.
53
+
54
+ ## Documentation
55
+
56
+ - [Usage and keys](docs/usage.md)
57
+ - [Worked example: foreground and background commands](docs/command-workflow.md)
58
+ - [Sessions, terminals and notifications](docs/sessions.md)
59
+ - [Merging and rebasing](docs/merging.md)
60
+ - [Workspace cleanup](docs/cleanup.md)
61
+ - [Configuration](docs/configuration.md)
62
+ - [Model suggestions](docs/models.md)
63
+ - [Architecture](docs/architecture.md)
64
+ - [Development](docs/development.md)
65
+
66
+ ## License
67
+
68
+ [MIT](LICENSE)
@@ -0,0 +1,124 @@
1
+ # Architecture
2
+
3
+ The Textual app calls `jj` and `project-sandbox` and reads session records from
4
+ disk. Project-sandbox manages containers, credentials and runtimes. Repositories
5
+ require a `.jj/` directory.
6
+
7
+ ## Modules (`src/project_sandbox_tui/`)
8
+
9
+ | Module | Role |
10
+ |---|---|
11
+ | `cli.py`, `settings.py`, `doctor.py` | Entry point, config commands, dependency checks |
12
+ | `config.py` | Typed TOML loading, validation, trust, state paths and launch defaults |
13
+ | `vcs.py` | Bookmark discovery, creation, changed files, working-copy moves and fast-forwarding |
14
+ | `sandbox.py` | `LaunchSpec`, argv construction and session matching |
15
+ | `jobs.py` | Detached jobs, persistence, polling, adoption and stopping |
16
+ | `tasks.py` | `:&` / `:*` host commands: detached, output and exit status captured |
17
+ | `launchers.py`, `terminal.py` | External terminals and terminal restoration |
18
+ | `octopus.py`, `cleanup.py` | Host-side integration and workspace cleanup |
19
+ | `models.py`, `history.py`, `notify.py` | Suggestions, prompt and command history, notifications |
20
+ | `commands.py` | `:` command parsing (`:!`, `:&`, `:*`, `[actions]`), completion and leader-key mappings |
21
+ | `review.py` | Diff review: files marked seen per bookmark |
22
+ | `opgraph.py` | Bookmark history graph, op log, undo stack and jj-style graph layout |
23
+ | `app.py`, `widgets.py`, `screens/` | Bookmark list, forms, dialogs, logs, op history and prompt search |
24
+
25
+ ## Launches and job lifecycle
26
+
27
+ `sandbox.py` maps [configuration](configuration.md) to `--flag=value` arguments,
28
+ preventing leading `-` characters from becoming options. Bookmark launches add
29
+ `--branch`; background launches add `--prompt-text`, `--log`, `--json-summary`
30
+ and optional `--timeout`.
31
+
32
+ 1. **Guard:** reject busy workspaces. Project-sandbox records in
33
+ `$XDG_STATE_HOME/project-sandbox/sessions/` lack branch names; match
34
+ `workspace_path` using its slug rule
35
+ (`/` → `-`, plus sha256[:6]).
36
+ 2. **Spawn:** detach with `start_new_session=True`, capture output and write a
37
+ job record under a repository lock.
38
+ 3. **Poll:** every `refresh_seconds`, finalise dead processes using the summary's
39
+ exit code, then the child's, else `orphaned`. A summary never closes
40
+ a live process.
41
+ 4. **Notify:** claim `notified` atomically so only one instance announces completion.
42
+ 5. **Restart:** adopt live processes; finalise dead ones.
43
+
44
+ Detached helpers (notifications, editors, terminal launchers and container stop
45
+ commands) are reaped by daemon waiters. Forgotten or replaced job handles are
46
+ handed to the same waiters so their children are collected even without another
47
+ refresh. Waiters do not stop live processes or delay quitting the TUI.
48
+
49
+ Inline sessions use temporary job records to mark workspaces busy across
50
+ instances. Job records keep only each bookmark's latest run; `opgraph.json`
51
+ keeps every run as a node of the [bookmark history](usage.md#bookmark-history),
52
+ finalised with the bookmark's commit on the first refresh that lists bookmarks
53
+ after the run ended. See [sessions](sessions.md) for terminal behavior and
54
+ [state storage](configuration.md#state-directory) for paths and permissions.
55
+
56
+ ## Concurrency and locking
57
+
58
+ Several TUI instances may run on one repository, and each does its jj, file and
59
+ process work in worker threads. These locks order what they do:
60
+
61
+ ```text
62
+ this TUI only _history_lock (asyncio) held for a whole action that
63
+ │ changes the repository: n dd u
64
+ │ ctrl+r f @ e, merge or rebase
65
+ │ preparation, W removal
66
+ ▼
67
+ every TUI on the opgraph.json.lock bookmark history and undo stack
68
+ repository │
69
+ (flock on files ▼
70
+ in the state jobs.lock job records, and the launch
71
+ directory) │ guard: busy checks and spawns
72
+ │ happen under it
73
+ ▼
74
+ tasks.lock background task records
75
+ ```
76
+
77
+ A lock is only taken while holding the ones above it, never the reverse, so
78
+ actions cannot deadlock. The diff review, prompt history and command history
79
+ each have their own `<file>.lock`, taken alone.
80
+
81
+ Which actions take which locks. *history* is `_history_lock`, *graph*
82
+ `opgraph.json.lock`; `A → B` takes B while holding A, `A, B` one after the other:
83
+
84
+ | Action | Locks | Done under them |
85
+ |---|---|---|
86
+ | Refresh (every `ui.refresh_seconds`) | graph, jobs, tasks | Adopt new bookmarks and record finished runs; poll jobs and claim their notifications; the same for tasks |
87
+ | `r` / `i`, `:NAME` actions | jobs, graph | Recheck busy, spawn and write the job record; add the run node. A new bookmark typed into the form is recorded first, under graph |
88
+ | `r` / `i` with bookmarks selected | history → (jobs, graph); then as `r` / `i` | Check the result bookmark is free, merge or rebase with jj, record the result bookmark |
89
+ | `n` | history → graph | Create with jj, then record the creation and its undo entry |
90
+ | `dd` | history → graph → jobs | Busy check, delete, undo entry |
91
+ | `u` / `ctrl+r` | history → graph → jobs | Busy check, delete or restore, move the undo position |
92
+ | `f` | history → graph | Fast-forward main, undo entry |
93
+ | `@` | history → jobs | Busy check of the working copy (and of the target's workspace before starting a change there), move it |
94
+ | `e` | history | Describe, only if the target is still the commit that was edited |
95
+ | `:*` | jobs, tasks | Create a missing workspace unless a session is starting; start the task |
96
+ | `:&` | jobs, tasks | Start one task in the repository under `@`; warn if a session is running there |
97
+ | `o` | jobs | Create a missing workspace unless a session is starting |
98
+ | `x` | tasks, jobs, graph | Signal tasks; signal or forget the run; record a forgotten run's outcome |
99
+ | `W` | tasks; removal: history → graph → jobs → tasks | List without the launch guard; recheck each chosen workspace right before deleting it |
100
+ | `D` in the cleanup list | jobs → tasks | Recheck, then force-delete |
101
+ | `sbx-tui --cleanup-workspaces` | tasks; jobs → tasks | As `W`, without the bookmark history |
102
+
103
+ - Checks that guard an action are repeated under its lock with records and
104
+ session files read again. The UI's cached state only decides what to offer.
105
+ - Inline sessions hold `jobs.lock` only while starting and finishing, not
106
+ while the agent runs.
107
+ - Readers take no lock. Files are replaced atomically, and a reload swaps the
108
+ in-memory records in one assignment, so readers see the old state or the new.
109
+ - Locks are `flock`s on separately opened files, so they also order the threads
110
+ of one TUI. They are not re-entrant: code holding one uses unlocked helpers
111
+ such as `JobManager._busy_reason`.
112
+ - Workers that change the repository are not `exclusive`. A second action waits
113
+ for `_history_lock` instead of cancelling the first after jj has already run.
114
+ - `last_used.json` and the trusted project config are replaced atomically
115
+ without a lock; the last write wins.
116
+
117
+ ## Limitations
118
+
119
+ - Inline sessions, diffs and shell commands suspend the event loop;
120
+ notifications arrive on return.
121
+ - External launches, stale records and PID reuse after crashes may need
122
+ [manual recovery](sessions.md#interactive-sessions).
123
+ - Working-copy dirty markers, prompt files, remote bookmarks and switching
124
+ repositories are not implemented.
@@ -0,0 +1,43 @@
1
+ # Workspace cleanup
2
+
3
+ ```sh
4
+ sbx-tui --cleanup-workspaces --dry-run # preview
5
+ sbx-tui --cleanup-workspaces # remove eligible workspaces and merged bookmarks
6
+ ```
7
+
8
+ Press `W` to review candidates: eligible rows are preselected; others show why
9
+ they are kept. `j` `k` move, `space` toggles a row, `a` selects all/none,
10
+ `enter` lists what goes and asks for `y`, and `esc` or `q` cancels. The CLI removes all eligible candidates, reporting removals and
11
+ reasons for keeping others.
12
+
13
+ Candidates come from `jj workspace list`, regardless of name or location. The
14
+ current workspace is excluded. Normal cleanup keeps `default` and workspaces
15
+ with running sessions or [`:*` tasks](usage.md#running-in-each-workspace),
16
+ uncommitted changes or files too large to snapshot; workspaces in use cannot
17
+ be force-removed either.
18
+ Directories containing another registered workspace are protected from both
19
+ normal and force removal; remove the nested workspace first and scan again.
20
+ Running sessions also protect enclosing and nested workspace directories.
21
+
22
+ ## Merged work
23
+
24
+ The [main bookmark](configuration.md#settings-reference) defines merged work:
25
+
26
+ - Workspaces with only empty revisions outside `::main` can be removed, deleting
27
+ ignored files or unregistering missing directories. Their bookmarks are also
28
+ deleted if the bookmarks themselves are fully merged, except main. A
29
+ workspace and its namesake bookmark can point at different revisions.
30
+ - Fully merged bookmarks without workspaces appear as separate rows.
31
+
32
+ Unmerged workspaces with ignored files or missing directories are kept. Removal
33
+ checks each workspace again just before deleting it, then runs
34
+ `jj workspace forget` and deletes eligible bookmarks;
35
+ tracked remote bookmarks are deleted on the next `jj git push`. Job history and the
36
+ [bookmark history](usage.md#bookmark-history) stay. In the TUI, `u` restores the
37
+ deleted bookmarks; removed workspaces cannot be undone.
38
+
39
+ ## Force delete
40
+
41
+ Highlight a kept workspace, press `D` and confirm to unregister it and delete
42
+ its directory, including uncommitted, untracked and ignored files. Bookmarks
43
+ stay. Running sessions and `default` cannot be force-deleted.
@@ -0,0 +1,133 @@
1
+ # Workflow: run tests while keeping sbx-tui open
2
+
3
+ This example follows a Python project from a quick foreground check to a
4
+ repository background task, then tests two bookmarks in parallel. It uses
5
+ `feature/parser` and `fix/cache` as example bookmarks; substitute your own.
6
+ The illustrations use the actual TUI renderer with staged project data and
7
+ test results. They show the workflow, not results from this repository's tests.
8
+
9
+ ## 1. Define one test command
10
+
11
+ From your project directory, configure a reusable command:
12
+
13
+ ```sh
14
+ sbx-tui config set --project commands.test 'uv run pytest -q'
15
+ sbx-tui
16
+ ```
17
+
18
+ This adds `test = "uv run pytest -q"` under `[commands]` in the project
19
+ configuration. The project must already have pytest available through uv.
20
+ These commands run on the host; they do not launch a sandbox or an agent.
21
+
22
+ You can run the same configured command three ways:
23
+
24
+ | Enter in sbx-tui | Where the tests start | What you can do meanwhile |
25
+ |---|---|---|
26
+ | `:test` | Once in the repository root | Use the command's terminal; return when it exits |
27
+ | `:&test` | Once in the repository root, ignoring marks | Keep navigating sbx-tui |
28
+ | `:*test` | Once in each marked workspace, or the highlighted workspace if none are marked | Keep navigating sbx-tui |
29
+
30
+ The literal equivalents are `:!uv run pytest -q`, `:&!uv run pytest -q`,
31
+ and `:*!uv run pytest -q`. The prefix changes execution; the configured script
32
+ stays the same. See the [full comparison](usage.md#choosing-how-to-run-a-shell-command)
33
+ for environment variables and task persistence.
34
+
35
+ ## 2. Do a quick foreground check
36
+
37
+ Press `:`, type `test`, and press `Enter`. The command takes over the terminal:
38
+
39
+ ```text
40
+ ── sbx-tui: :test → uv run pytest -q ──
41
+
42
+ .......................................... [100%]
43
+ 42 passed in 3.12s
44
+
45
+ ── exited with 0; press Enter to return to sbx-tui ──
46
+ ```
47
+
48
+ Press `Enter` to return. If the tests take longer than expected, `Ctrl+C`
49
+ interrupts them; press `Enter` at the exit prompt to return to the TUI.
50
+ An exit status of `-2` or `130` means the command was interrupted. This form
51
+ does not create a background task or a captured log in the task list.
52
+
53
+ ## 3. Run once in the repository background
54
+
55
+ Use `j` / `k` to move to `feature/parser` and press `Space` to mark it.
56
+ Move to `fix/cache` and mark it too. `Space` marks a row and moves down;
57
+ move back to `feature/parser` to leave it highlighted.
58
+
59
+ The marks are deliberately left in place for the next command:
60
+
61
+ ![Bookmark list with feature/parser highlighted and both feature/parser and fix/cache marked.](images/command-workflow/01-repository-command.svg)
62
+
63
+ *Two rows are marked, but `:&test` will ignore them.*
64
+
65
+ Enter `:&test` to launch **one task in the repository root**, then press `T`
66
+ to open the task list:
67
+
68
+ ![Task list showing one running :&test task under the working copy @.](images/command-workflow/02-repository-task.svg)
69
+
70
+ *The task belongs to `@`, even though `feature/parser` was highlighted.*
71
+
72
+ The TUI remains usable. In the task list, select the task and press `Enter`
73
+ or `l` to follow its captured output; `q` returns to the task list, and another
74
+ `q` returns to the bookmark list. Let this run finish before the next step.
75
+ Its result remains available in `T`.
76
+
77
+ `:&test` does not create or enter the highlighted bookmark's workspace.
78
+ Use the `&` prefix as shown: a trailing shell `&` (`:!uv run pytest -q &`)
79
+ does not create a tracked sbx-tui task.
80
+
81
+ ## 4. Run in both marked workspaces
82
+
83
+ Back on the bookmark list, keep `feature/parser` and `fix/cache` marked and
84
+ enter `:*test`. This starts two background tasks, each in its own workspace.
85
+ sbx-tui creates a missing workspace before launching its task. For Python
86
+ projects, uv prepares that workspace's environment as needed.
87
+
88
+ Press `T` again. In this example, the parser tests pass and the cache tests fail:
89
+
90
+ ![Task list with :*test completed for feature/parser, failed with exit 1 for fix/cache, and the earlier completed :&test under @.](images/command-workflow/03-workspace-tasks.svg)
91
+
92
+ *Three records: the earlier repository run, plus one run for each marked bookmark.*
93
+
94
+ The separate results matter: a passing repository run says nothing about
95
+ changes that exist only in another bookmark's workspace. With no rows marked,
96
+ `:*test` runs only in the highlighted workspace. `Esc` on the bookmark list
97
+ clears marks when you want that single-workspace behavior.
98
+
99
+ ## 5. Inspect the failure
100
+
101
+ In `T`, use `j` / `k` to select the failed `fix/cache` task, then press `Enter`:
102
+
103
+ ![Captured test output for fix/cache showing an expired cache entry assertion failure and 1 failed, 41 passed.](images/command-workflow/04-failure-output.svg)
104
+
105
+ *The log belongs to the selected task; the title identifies the bookmark and exit code.*
106
+
107
+ Use `j` / `k` to scroll, `gg` / `G` for the top or bottom, and `q` to go back.
108
+ For a running task, the log follows new output automatically.
109
+
110
+ To rerun just the cache tests after making a fix, return to the bookmark list,
111
+ press `Esc` to clear marks, highlight `fix/cache`, and enter `:*test` again.
112
+ Each invocation creates a new task record, so you can compare it with the
113
+ earlier failure in `T`.
114
+
115
+ ## 6. Cancel or leave a task running
116
+
117
+ To stop a background task, open `T`, select its running row, and press `x`.
118
+ That targets just that task. If it does not stop, further presses escalate
119
+ from SIGINT to SIGTERM and SIGKILL. On a finished row, `x` forgets the task
120
+ and removes its captured output instead.
121
+
122
+ On the bookmark list, `x` acts on the target bookmarks and can affect several
123
+ running tasks or sandbox runs. Use the task list when you want to cancel only
124
+ one command.
125
+
126
+ Both `:&` and `:*` tasks keep running if you quit sbx-tui. Confirm the quit
127
+ prompt when tasks are still running; reopening sbx-tui in the same repository
128
+ lets you inspect their status and logs. Background commands capture output
129
+ and have no interactive input, so use the foreground form for commands that
130
+ need terminal interaction.
131
+
132
+ For the complete controls, see [Tasks](usage.md#tasks) and the
133
+ [log viewer](usage.md#log-viewer).