tasktree-manager 2.4.0__tar.gz → 2.5.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 (59) hide show
  1. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/CHANGELOG.md +6 -0
  2. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/PKG-INFO +1 -1
  3. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/docs/user-guide.md +1 -1
  4. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/pyproject.toml +1 -1
  5. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tasktree_manager/_version.py +2 -2
  6. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tasktree_manager/app.py +7 -2
  7. tasktree_manager-2.5.0/tasktree_manager/commands.py +69 -0
  8. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tests/test_app.py +1 -0
  9. tasktree_manager-2.5.0/tests/test_commands.py +55 -0
  10. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/.claude/skills/textual-tui/SKILL.md +0 -0
  11. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/.claude/skills/textual-tui/assets/README.md +0 -0
  12. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/.claude/skills/textual-tui/assets/dashboard_app.py +0 -0
  13. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/.claude/skills/textual-tui/assets/data_viewer.py +0 -0
  14. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/.claude/skills/textual-tui/assets/todo_app.py +0 -0
  15. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/.claude/skills/textual-tui/assets/worker_demo.py +0 -0
  16. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/.claude/skills/textual-tui/references/layouts.md +0 -0
  17. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/.claude/skills/textual-tui/references/official-guides-index.md +0 -0
  18. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/.claude/skills/textual-tui/references/styling.md +0 -0
  19. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/.claude/skills/textual-tui/references/widgets.md +0 -0
  20. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/.github/templates/CHANGELOG.md.j2 +0 -0
  21. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/.github/workflows/ci.yml +0 -0
  22. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/.github/workflows/release.yml +0 -0
  23. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/.gitignore +0 -0
  24. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/CONTRIBUTING.md +0 -0
  25. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/LICENSE +0 -0
  26. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/OPENSPEC.md +0 -0
  27. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/README.md +0 -0
  28. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/ROADMAP.md +0 -0
  29. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/docs/claude-code-integration-spec.md +0 -0
  30. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/docs/configuration.md +0 -0
  31. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/docs/installation.md +0 -0
  32. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/docs/troubleshooting.md +0 -0
  33. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/mise.toml +0 -0
  34. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/scripts/bump_version.py +0 -0
  35. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tasktree_manager/__init__.py +0 -0
  36. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tasktree_manager/services/__init__.py +0 -0
  37. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tasktree_manager/services/claude_hooks.py +0 -0
  38. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tasktree_manager/services/config.py +0 -0
  39. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tasktree_manager/services/git_ops.py +0 -0
  40. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tasktree_manager/services/models.py +0 -0
  41. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tasktree_manager/services/task_manager.py +0 -0
  42. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tasktree_manager/widgets/__init__.py +0 -0
  43. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tasktree_manager/widgets/create_modal.py +0 -0
  44. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tasktree_manager/widgets/messages_panel.py +0 -0
  45. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tasktree_manager/widgets/setup_modal.py +0 -0
  46. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tasktree_manager/widgets/status_panel.py +0 -0
  47. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tasktree_manager/widgets/task_list.py +0 -0
  48. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tasktree_manager/widgets/worktree_list.py +0 -0
  49. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tests/__init__.py +0 -0
  50. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tests/conftest.py +0 -0
  51. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tests/test_config.py +0 -0
  52. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tests/test_git_ops.py +0 -0
  53. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tests/test_integration_git.py +0 -0
  54. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tests/test_memory.py +0 -0
  55. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tests/test_messages_panel.py +0 -0
  56. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tests/test_modals.py +0 -0
  57. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tests/test_setup_modal.py +0 -0
  58. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tests/test_status_panel.py +0 -0
  59. {tasktree_manager-2.4.0 → tasktree_manager-2.5.0}/tests/test_task_manager.py +0 -0
@@ -15,6 +15,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
15
  - Git hooks support
16
16
  - Custom task metadata
17
17
 
18
+ ## [2.5.0] - 2026-06-10
19
+
20
+ ### Added
21
+ - Searchable app actions in the command palette (#7)
22
+
23
+
18
24
  ## [2.4.0] - 2026-06-10
19
25
 
20
26
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tasktree-manager
3
- Version: 2.4.0
3
+ Version: 2.5.0
4
4
  Summary: TUI application for managing worktree-based tasks
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.10
@@ -364,7 +364,7 @@ Useful when you're spinning up several similar tasks that share the same set of
364
364
 
365
365
  | Key | Action | Description |
366
366
  |---------|-------------------------|----------------------------------------------|
367
- | `Ctrl+P`| Open Command Palette | Switch themes and run commands |
367
+ | `Ctrl+P`| Open Command Palette | Search and run any action (hotkeys shown), switch themes |
368
368
  | `o` | Open folder | Open current folder in new terminal tab |
369
369
  | `m` | Toggle messages | Show/hide the activity messages panel |
370
370
  | `?` | Show help | Display help modal with keybindings |
@@ -70,7 +70,7 @@ version-file = "tasktree_manager/_version.py"
70
70
  # Semantic Release Configuration
71
71
  [tool.semantic_release]
72
72
  version_toml = ["pyproject.toml:tool.semantic_release.version"]
73
- version = "2.4.0"
73
+ version = "2.5.0"
74
74
  tag_format = "v{version}"
75
75
  branch = "main"
76
76
  commit_message = "chore(release): {version} [skip ci]"
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2.4.0'
22
- __version_tuple__ = version_tuple = (2, 4, 0)
21
+ __version__ = version = '2.5.0'
22
+ __version_tuple__ = version_tuple = (2, 5, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -10,6 +10,7 @@ from textual.binding import Binding
10
10
  from textual.containers import Horizontal, Vertical
11
11
  from textual.widgets import Footer, Header, Static
12
12
 
13
+ from .commands import TaskTreeCommands
13
14
  from .services.claude_hooks import ensure_claude_hooks, has_claude_session
14
15
  from .services.config import Config
15
16
  from .services.git_ops import GitOps
@@ -35,6 +36,10 @@ class TaskTreeApp(App):
35
36
 
36
37
  TITLE = "tasktree-manager"
37
38
 
39
+ # System commands plus all keybound tasktree actions (searchable by
40
+ # name, displayed with their configured hotkey)
41
+ COMMANDS = App.COMMANDS | {TaskTreeCommands}
42
+
38
43
  CSS = """
39
44
  /* Main layout */
40
45
  Screen {
@@ -226,7 +231,7 @@ class TaskTreeApp(App):
226
231
  Binding("c", "open_claude_resume", "Claude", show=False),
227
232
  Binding("p", "push_all", "Push", show=False),
228
233
  Binding("r", "refresh", "Refresh"),
229
- Binding("m", "toggle_messages", "Messages", show=False),
234
+ Binding("m", "toggle_messages", "Messages"),
230
235
  Binding("q", "quit", "Quit"),
231
236
  Binding("?", "help", "Help"),
232
237
  Binding("enter", "open_shell", "Shell", show=False),
@@ -287,7 +292,7 @@ class TaskTreeApp(App):
287
292
  Binding(kb.get("open_claude_resume", "c"), "open_claude_resume", "Claude", show=False),
288
293
  Binding(kb.get("push_all", "p"), "push_all", "Push", show=False),
289
294
  Binding(kb.get("refresh", "r"), "refresh", "Refresh"),
290
- Binding(kb.get("toggle_messages", "m"), "toggle_messages", "Messages", show=False),
295
+ Binding(kb.get("toggle_messages", "m"), "toggle_messages", "Messages"),
291
296
  Binding(kb.get("quit", "q"), "quit", "Quit"),
292
297
  Binding(kb.get("help", "?"), "help", "Help"),
293
298
  Binding(kb.get("open_shell", "enter"), "open_shell", "Shell", show=False),
@@ -0,0 +1,69 @@
1
+ """Command palette provider for tasktree-manager actions."""
2
+
3
+ from functools import partial
4
+
5
+ from textual.command import DiscoveryHit, Hit, Hits, Provider
6
+
7
+ # (action, title, help) for every keybound app action. The key for each
8
+ # action is looked up from the app's config at search time so customized
9
+ # keybindings display correctly.
10
+ APP_COMMANDS: list[tuple[str, str, str]] = [
11
+ ("new_task", "New Task", "Create a new task with worktrees"),
12
+ ("clone_task", "Clone Task", "Create a new task with the same repos as the current one"),
13
+ ("add_repo", "Add Repo", "Add repositories to the current task"),
14
+ ("delete_task", "Delete Task", "Delete the current task (with safety checks)"),
15
+ ("delete_worktree", "Delete Worktree", "Delete the selected worktree from the task"),
16
+ ("open_lazygit", "Open Lazygit", "Open lazygit in the selected worktree"),
17
+ ("open_editor", "Open Editor", "Open your editor in the focused task/worktree folder"),
18
+ ("open_folder", "Open Folder", "Open the focused folder in a new terminal tab"),
19
+ ("open_shell", "Open Shell", "Open a shell in the selected worktree"),
20
+ ("open_claude_resume", "Open Claude", "Open Claude Code for the task (resume if possible)"),
21
+ (
22
+ "open_claude_gui_code",
23
+ "Open Claude Desktop",
24
+ "Open the Claude desktop app on the Code page",
25
+ ),
26
+ ("push_all", "Push All", "Push all worktrees in the current task"),
27
+ ("pull_all", "Pull All", "Pull all worktrees in the current task"),
28
+ ("refresh", "Refresh", "Refresh git status for all tasks"),
29
+ ("toggle_messages", "Toggle Messages", "Show or hide the activity messages panel"),
30
+ ("toggle_grouping", "Toggle Grouping", "Group worktrees by dirty/clean status"),
31
+ ("cycle_sort", "Cycle Sort", "Cycle through task sort modes"),
32
+ ("help", "Help", "Show the keybinding reference"),
33
+ ("quit", "Quit", "Exit tasktree-manager"),
34
+ ]
35
+
36
+
37
+ class TaskTreeCommands(Provider):
38
+ """Expose all keybound tasktree actions in the command palette."""
39
+
40
+ def _entries(self):
41
+ """Yield (display title with hotkey, action, help) tuples."""
42
+ config = getattr(self.app, "config", None)
43
+ keybindings = config.keybindings if config else {}
44
+ for action, title, help_text in APP_COMMANDS:
45
+ key = keybindings.get(action, "")
46
+ display = f"{title} ({key})" if key else title
47
+ yield display, action, help_text
48
+
49
+ async def discover(self) -> Hits:
50
+ """List all commands when the palette opens with an empty query."""
51
+ for display, action, help_text in self._entries():
52
+ yield DiscoveryHit(
53
+ display,
54
+ partial(self.app.run_action, action),
55
+ help=help_text,
56
+ )
57
+
58
+ async def search(self, query: str) -> Hits:
59
+ """Fuzzy-match commands against the query."""
60
+ matcher = self.matcher(query)
61
+ for display, action, help_text in self._entries():
62
+ score = matcher.match(display)
63
+ if score > 0:
64
+ yield Hit(
65
+ score,
66
+ matcher.highlight(display),
67
+ partial(self.app.run_action, action),
68
+ help=help_text,
69
+ )
@@ -602,6 +602,7 @@ class TestContextSensitiveFooter:
602
602
  assert "app.open_shell" not in shown
603
603
  # Globals always visible
604
604
  assert "refresh" in shown
605
+ assert "toggle_messages" in shown
605
606
  assert "help" in shown
606
607
  assert "quit" in shown
607
608
 
@@ -0,0 +1,55 @@
1
+ """Tests for the command palette provider."""
2
+
3
+ from tasktree_manager.app import TaskTreeApp
4
+ from tasktree_manager.commands import APP_COMMANDS, TaskTreeCommands
5
+
6
+
7
+ class TestTaskTreeCommands:
8
+ """Tests for the TaskTreeCommands palette provider."""
9
+
10
+ async def test_app_registers_provider(self, app):
11
+ """The app exposes the provider alongside the system commands."""
12
+ assert TaskTreeCommands in TaskTreeApp.COMMANDS
13
+
14
+ async def test_discover_lists_all_commands(self, app):
15
+ """Opening the palette with an empty query lists every action."""
16
+ async with app.run_test() as pilot:
17
+ await pilot.pause()
18
+
19
+ provider = TaskTreeCommands(app.screen)
20
+ hits = [hit async for hit in provider.discover()]
21
+
22
+ assert len(hits) == len(APP_COMMANDS)
23
+
24
+ async def test_search_finds_action_with_hotkey(self, app):
25
+ """Searching shows the matching command with its hotkey."""
26
+ async with app.run_test() as pilot:
27
+ await pilot.pause()
28
+
29
+ provider = TaskTreeCommands(app.screen)
30
+ hits = [hit async for hit in provider.search("lazygit")]
31
+
32
+ assert hits
33
+ assert any("(g)" in str(hit.match_display) for hit in hits)
34
+
35
+ async def test_search_shows_customized_key(self, app):
36
+ """A remapped keybinding shows the configured key, not the default."""
37
+ app.config.keybindings["push_all"] = "x"
38
+
39
+ async with app.run_test() as pilot:
40
+ await pilot.pause()
41
+
42
+ provider = TaskTreeCommands(app.screen)
43
+ hits = [hit async for hit in provider.search("push all")]
44
+
45
+ assert any("(x)" in str(hit.match_display) for hit in hits)
46
+
47
+ async def test_search_no_match_returns_nothing(self, app):
48
+ """A nonsense query yields no hits."""
49
+ async with app.run_test() as pilot:
50
+ await pilot.pause()
51
+
52
+ provider = TaskTreeCommands(app.screen)
53
+ hits = [hit async for hit in provider.search("zzz-no-such-command")]
54
+
55
+ assert hits == []