git-juggler 0.1.9__tar.gz → 0.2.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.
- git_juggler-0.2.1/PKG-INFO +102 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/pyproject.toml +1 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/app.py +33 -5
- git_juggler-0.2.1/src/backend/git_juggler/ci.py +135 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/file_diff.py +11 -4
- git_juggler-0.2.1/src/backend/git_juggler/frontend_dist/assets/index-C-FENuHS.css +1 -0
- git_juggler-0.2.1/src/backend/git_juggler/frontend_dist/assets/index-CnmsXu_6.js +21 -0
- git_juggler-0.2.1/src/backend/git_juggler/frontend_dist/git-juggler.png +0 -0
- git_juggler-0.2.1/src/backend/git_juggler/frontend_dist/index.html +14 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/github_actions.py +82 -4
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/jenkins.py +55 -1
- git_juggler-0.2.1/src/backend/git_juggler/pick_folder.py +48 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/schemas.py +8 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/terminal.py +60 -5
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_ci.py +7 -7
- git_juggler-0.2.1/src/backend/tests/test_ci_poll.py +170 -0
- git_juggler-0.2.1/src/backend/tests/test_ci_poll_route.py +52 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_file_diff.py +19 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_github_actions.py +2 -2
- git_juggler-0.2.1/src/backend/tests/test_pick_folder.py +34 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/index.html +2 -2
- git_juggler-0.2.1/src/frontend/public/git-juggler.png +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/App.css +291 -22
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/App.tsx +13 -12
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/api/client.ts +29 -3
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/api/types.ts +7 -0
- git_juggler-0.2.1/src/frontend/src/components/Badges/CiRunBadge.tsx +92 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Branches/DeleteBranchModal.tsx +5 -1
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/CommandPalette/CommandPalette.tsx +3 -1
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/ContextMenu/CommitContextMenu.tsx +43 -18
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/ContextMenu/CreateTagModal.tsx +4 -9
- git_juggler-0.2.1/src/frontend/src/components/Diff/FileDiffModal.tsx +173 -0
- git_juggler-0.2.1/src/frontend/src/components/Menu/CiPollField.tsx +18 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Menu/DirectoryBrowserModal.tsx +4 -1
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Menu/GitHubSettings.tsx +4 -1
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Menu/JenkinsSettings.tsx +4 -1
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Menu/MainMenu.tsx +14 -1
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Menu/ThemePicker.tsx +4 -3
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Menu/ThemeQuickPicks.tsx +30 -27
- git_juggler-0.2.1/src/frontend/src/components/Pipelines/StageGraph.tsx +159 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Search/SearchBox.tsx +22 -1
- git_juggler-0.2.1/src/frontend/src/components/Sidebar/NewGroupModal.tsx +54 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Sidebar/RepoList.tsx +4 -6
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Tags/DeleteTagModal.tsx +4 -1
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Terminal/TerminalPanel.tsx +47 -30
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Welcome/WelcomeWizard.tsx +4 -1
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/index.css +21 -0
- git_juggler-0.2.1/src/frontend/src/lib/bundledThemes.ts +63 -0
- git_juggler-0.2.1/src/frontend/src/lib/ciPoll.ts +52 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/sideBySide.ts +29 -0
- git_juggler-0.2.1/src/frontend/src/lib/stageGraphLayout.ts +117 -0
- git_juggler-0.2.1/src/frontend/src/lib/stageWindow.ts +27 -0
- git_juggler-0.2.1/src/frontend/src/lib/swallowNextClick.ts +21 -0
- git_juggler-0.2.1/src/frontend/src/state/overlayStack.ts +39 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/state/store.ts +175 -15
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/state/themes.ts +8 -7
- git_juggler-0.2.1/src/frontend/tests/ciPoll.test.ts +55 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/tests/sideBySide.test.ts +26 -1
- git_juggler-0.2.1/src/frontend/tests/stageGraphLayout.test.ts +95 -0
- git_juggler-0.2.1/src/frontend/tests/stageWindow.test.ts +22 -0
- git_juggler-0.1.9/PKG-INFO +0 -11
- git_juggler-0.1.9/src/backend/git_juggler/ci.py +0 -66
- git_juggler-0.1.9/src/backend/git_juggler/frontend_dist/assets/index-ByhhD79y.css +0 -1
- git_juggler-0.1.9/src/backend/git_juggler/frontend_dist/assets/index-C2mljjaE.js +0 -21
- git_juggler-0.1.9/src/backend/git_juggler/frontend_dist/index.html +0 -14
- git_juggler-0.1.9/src/frontend/src/components/Badges/CiRunBadge.tsx +0 -64
- git_juggler-0.1.9/src/frontend/src/components/Diff/FileDiffModal.tsx +0 -105
- {git_juggler-0.1.9 → git_juggler-0.2.1}/.github/workflows/build-package.yml +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/.gitignore +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/CLAUDE.md +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/LICENSE +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/README.md +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/notes.txt +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/plans/github-actions-settings-and-build-status.md +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/plans/jenkins-settings-and-build-status.md +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/scripts/build-package.sh +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/scripts/dev.sh +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/__init__.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_hook_events.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_hooks.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_tracking/__init__.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_tracking/activity_models.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_tracking/claude_sessions.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_tracking/claude_transcripts.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_tracking/git_resolver.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_tracking/opencode_sessions.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_tracking/paths.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/agent_tracking/text.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/browse.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/commit_detail.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/config.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/dev_app.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/frontend_dist/agent-logos/claude.png +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/frontend_dist/agent-logos/opencode.webp +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/frontend_dist/favicon.svg +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/frontend_dist/icons.svg +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/git_data.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/git_utils.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/main.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/repos.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/git_juggler/themes.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_agent_hooks.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_agent_tracking.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_claude_sessions_data.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_git_data.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_jenkins.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_opencode_sessions.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_preferences.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_repos.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/backend/tests/test_themes.py +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/.gitignore +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/README.md +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/package-lock.json +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/package.json +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/public/agent-logos/claude.png +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/public/agent-logos/opencode.webp +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/public/favicon.svg +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/public/icons.svg +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Agents/AgentActivityPanel.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Agents/AgentHoverCard.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Agents/agentFormat.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Badges/BranchBadge.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Badges/StashBadge.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Badges/TagBadge.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Badges/ciStatus.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Badges/icons.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Commits/CommitDetail.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Commits/CommitList.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Commits/CommitRow.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Commits/UncommittedRow.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/ContextMenu/BranchContextMenu.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/ContextMenu/RepoContextMenu.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/ContextMenu/TagContextMenu.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Graph/GraphPanel.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Graph/branchColor.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Graph/computeColumns.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Graph/laneWidth.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Menu/AgentQuickPicks.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Menu/AgentsSettings.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Menu/RepoPathsSettings.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Pipelines/PipelinesPanel.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Pipelines/StageStrip.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Search/MultiSelect.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Search/TriSwitch.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Sidebar/Sidebar.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/Tabs/TabsBar.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/inputs/NumberField.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/inputs/TextField.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/inputs/ToggleField.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/components/inputs/styles.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/index.tsx +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/appTheme.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/branchVisibility.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/color.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/dismissOnOutsideClick.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/flip.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/formatDate.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/preferenceSender.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/scrollToCommit.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/stripAnsi.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/lib/vscodeTheme.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/state/preferenceSync.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/src/state/preferences.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/tests/agentFormat.test.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/tests/computeColumns.test.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/tests/preferenceSender.test.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/tests/vscodeTheme.test.ts +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/tsconfig.app.json +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/tsconfig.json +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/tsconfig.node.json +0 -0
- {git_juggler-0.1.9 → git_juggler-0.2.1}/src/frontend/vite.config.ts +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: git-juggler
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: A local Git dashboard for people juggling agents and too many repos.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: fastapi>=0.110
|
|
9
|
+
Requires-Dist: gitpython>=3.1
|
|
10
|
+
Requires-Dist: pywinpty>=2.0; sys_platform == 'win32'
|
|
11
|
+
Requires-Dist: uvicorn[standard]>=0.29
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# git-juggler
|
|
15
|
+
|
|
16
|
+
A local Git dashboard for people juggling agents and too many repos.
|
|
17
|
+
|
|
18
|
+
git-juggler is a local dashboard for agent-heavy workflows and multi-repo projects. Watch agents work live, follow every repo’s git graph, and see what changed across your workspace without opening a dozen editors.
|
|
19
|
+
|
|
20
|
+
## How to install
|
|
21
|
+
|
|
22
|
+
git-juggler is published on PyPI. The recommended way to install it is with
|
|
23
|
+
[pipx](https://pipx.pypa.io), which puts the `git-juggler` command on your
|
|
24
|
+
PATH in its own isolated environment, without touching your system Python:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
pipx install git-juggler
|
|
28
|
+
git-juggler <path-to-scan> # http://127.0.0.1:8000
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If you don't have pipx yet:
|
|
32
|
+
|
|
33
|
+
**macOS**
|
|
34
|
+
```
|
|
35
|
+
brew install pipx
|
|
36
|
+
pipx ensurepath
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Linux**
|
|
40
|
+
```
|
|
41
|
+
sudo apt install pipx # Debian/Ubuntu
|
|
42
|
+
sudo dnf install pipx # Fedora
|
|
43
|
+
pipx ensurepath
|
|
44
|
+
```
|
|
45
|
+
Many recent distros (like macOS with Homebrew) block a plain global
|
|
46
|
+
`pip install` outside a virtual environment (PEP 668); pipx is the
|
|
47
|
+
supported way around that.
|
|
48
|
+
|
|
49
|
+
**Windows**
|
|
50
|
+
```
|
|
51
|
+
py -m pip install --user pipx
|
|
52
|
+
py -m pipx ensurepath
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Restart your terminal after `ensurepath` so the updated PATH takes effect.
|
|
56
|
+
To upgrade later: `pipx upgrade git-juggler`.
|
|
57
|
+
|
|
58
|
+
## Dev mode
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
python3 -m venv .venv
|
|
62
|
+
.venv/bin/pip install -e .
|
|
63
|
+
.venv/bin/git-juggler <path-to-scan> --reload # http://127.0.0.1:8000
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
cd src/frontend
|
|
68
|
+
npm install
|
|
69
|
+
npm run dev # http://localhost:5173 (proxies /api and /ws)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Open http://localhost:5173.
|
|
73
|
+
|
|
74
|
+
## Production (single port)
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
cd src/frontend && npm install && npm run build
|
|
78
|
+
cd ../.. && .venv/bin/git-juggler <path-to-scan> # http://127.0.0.1:8000
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Build a Python package
|
|
82
|
+
|
|
83
|
+
The Python wheel includes the built frontend assets. The generated assets are
|
|
84
|
+
not committed; `scripts/build-package.sh` builds them and copies them into the
|
|
85
|
+
backend package before creating the distributions.
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
python3 -m venv .venv
|
|
89
|
+
.venv/bin/pip install build
|
|
90
|
+
./scripts/build-package.sh
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The wheel and source distribution are written to `dist/`.
|
|
94
|
+
|
|
95
|
+
Install the wheel locally with:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
pipx install dist/git_juggler-0.1.0-py3-none-any.whl --force
|
|
99
|
+
git-juggler <path-to-scan> # http://127.0.0.1:8000
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
GitHub Actions runs the same build and uploads the distributions as artifacts.
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import Annotated
|
|
5
6
|
|
|
6
7
|
from fastapi import FastAPI, HTTPException, Query, WebSocket, WebSocketDisconnect
|
|
7
8
|
from fastapi.middleware.cors import CORSMiddleware
|
|
@@ -13,7 +14,8 @@ from .agent_hook_events import AgentHookEventReader
|
|
|
13
14
|
from .agent_hooks import hooks_status, install_claude_hooks, install_opencode_hooks
|
|
14
15
|
from .agent_tracking.activity_models import AgentRepositoryScan
|
|
15
16
|
from .browse import browse_directory
|
|
16
|
-
from .
|
|
17
|
+
from .pick_folder import NativePickerUnavailable, pick_folder
|
|
18
|
+
from .ci import get_active_pipelines, get_ci_run_stages, get_ci_runs, poll_ci_runs
|
|
17
19
|
from .commit_detail import get_commit_detail
|
|
18
20
|
from .file_diff import get_commit_file_diff, get_working_file_diff
|
|
19
21
|
from .git_data import (
|
|
@@ -31,6 +33,7 @@ from .schemas import (
|
|
|
31
33
|
AgentHooksResponse,
|
|
32
34
|
AgentRepositoryScanResponse,
|
|
33
35
|
BrowseDirectoryResponse,
|
|
36
|
+
PickFolderResponse,
|
|
34
37
|
CiRunInfo,
|
|
35
38
|
CiStage,
|
|
36
39
|
CommitDetail,
|
|
@@ -81,6 +84,13 @@ def create_app(root_path: Path, frontend_dist: Path | None = None) -> FastAPI:
|
|
|
81
84
|
def api_browse(path: str | None = None) -> BrowseDirectoryResponse:
|
|
82
85
|
return browse_directory(path)
|
|
83
86
|
|
|
87
|
+
@app.post("/api/pick-folder", response_model=PickFolderResponse)
|
|
88
|
+
def api_pick_folder() -> PickFolderResponse:
|
|
89
|
+
try:
|
|
90
|
+
return PickFolderResponse(path=pick_folder())
|
|
91
|
+
except NativePickerUnavailable as e:
|
|
92
|
+
raise HTTPException(status_code=501, detail=str(e))
|
|
93
|
+
|
|
84
94
|
terminal_shell = "cmd" if sys.platform == "win32" else "posix"
|
|
85
95
|
|
|
86
96
|
def _current_config() -> ConfigResponse:
|
|
@@ -201,21 +211,25 @@ def create_app(root_path: Path, frontend_dist: Path | None = None) -> FastAPI:
|
|
|
201
211
|
|
|
202
212
|
@app.get("/api/repos/{repo_id}/commits/{sha}/diff", response_model=FileDiff)
|
|
203
213
|
def api_commit_file_diff(
|
|
204
|
-
repo_id: str,
|
|
214
|
+
repo_id: str,
|
|
215
|
+
sha: str,
|
|
216
|
+
path: str,
|
|
217
|
+
old_path: str | None = None,
|
|
218
|
+
full: bool = False,
|
|
205
219
|
) -> FileDiff:
|
|
206
220
|
repo_path = _resolve_repo_path(repo_id)
|
|
207
221
|
try:
|
|
208
|
-
return get_commit_file_diff(repo_path, sha, path, old_path)
|
|
222
|
+
return get_commit_file_diff(repo_path, sha, path, old_path, full)
|
|
209
223
|
except Exception as exc: # noqa: BLE001 - surfaced as a 404 either way
|
|
210
224
|
raise HTTPException(status_code=404, detail="diff not found") from exc
|
|
211
225
|
|
|
212
226
|
@app.get("/api/repos/{repo_id}/diff", response_model=FileDiff)
|
|
213
227
|
def api_working_file_diff(
|
|
214
|
-
repo_id: str, path: str, old_path: str | None = None
|
|
228
|
+
repo_id: str, path: str, old_path: str | None = None, full: bool = False
|
|
215
229
|
) -> FileDiff:
|
|
216
230
|
repo_path = _resolve_repo_path(repo_id)
|
|
217
231
|
try:
|
|
218
|
-
return get_working_file_diff(repo_path, path, old_path)
|
|
232
|
+
return get_working_file_diff(repo_path, path, old_path, full)
|
|
219
233
|
except Exception as exc: # noqa: BLE001 - surfaced as a 404 either way
|
|
220
234
|
raise HTTPException(status_code=404, detail="diff not found") from exc
|
|
221
235
|
|
|
@@ -229,6 +243,20 @@ def create_app(root_path: Path, frontend_dist: Path | None = None) -> FastAPI:
|
|
|
229
243
|
config.load_jenkins_config(),
|
|
230
244
|
)
|
|
231
245
|
|
|
246
|
+
# Live poll of a repo's pipelines. Without `run`, every active run of the
|
|
247
|
+
# repo (`head_sha` narrows it where the provider can filter): used right
|
|
248
|
+
# after a commit action, when the run may not be known yet. With one or more
|
|
249
|
+
# `run=<provider>:<run_id>` (CiRunInfo.provider / run_id), only those runs,
|
|
250
|
+
# including their final status once finished.
|
|
251
|
+
@app.get("/api/repos/{repo_id}/ci/poll", response_model=list[CiRunInfo])
|
|
252
|
+
def api_ci_poll(
|
|
253
|
+
repo_id: str,
|
|
254
|
+
run: Annotated[list[str] | None, Query()] = None,
|
|
255
|
+
head_sha: str | None = None,
|
|
256
|
+
) -> list[CiRunInfo]:
|
|
257
|
+
path = _resolve_repo_path(repo_id)
|
|
258
|
+
return poll_ci_runs(path, run, head_sha, config.load_github_config(), config.load_jenkins_config())
|
|
259
|
+
|
|
232
260
|
# Stages (GitHub jobs / Jenkins pipeline stages) of one run. `run_id` is the
|
|
233
261
|
# value CiRunInfo.run_id carried; 404 when the provider has no stage data.
|
|
234
262
|
@app.get("/api/repos/{repo_id}/ci/stages", response_model=list[CiStage])
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Protocol
|
|
7
|
+
|
|
8
|
+
from . import github_actions, jenkins
|
|
9
|
+
from .schemas import ActivePipeline, CiRunInfo, CiStage, RepoSummary
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CiProvider(Protocol):
|
|
13
|
+
"""What a CI backend (GitHub Actions, Jenkins) offers the app."""
|
|
14
|
+
|
|
15
|
+
name: str
|
|
16
|
+
|
|
17
|
+
def fetch_completed_runs(self, repo_path: Path, commit_hashes: set[str]) -> dict[str, list[CiRunInfo]]:
|
|
18
|
+
"""Recent runs of the repo keyed by commit hash. Loaded with the commit
|
|
19
|
+
graph and refreshed on its own, slow, interval — never by the run poll."""
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
def poll_runs(self, repo_path: Path, run_ids: list[str] | None, head_sha: str | None) -> list[CiRunInfo]:
|
|
23
|
+
"""`run_ids=None`: every active (queued/running) run of the repo, to
|
|
24
|
+
discover a pipeline that just started (`head_sha` narrows it to one
|
|
25
|
+
commit where the provider can filter). Otherwise exactly those runs,
|
|
26
|
+
whatever their state, so a finished run reports its final status.
|
|
27
|
+
Stages are included for runs that are still going."""
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class GitHubActionsProvider:
|
|
33
|
+
config: dict | None
|
|
34
|
+
name: str = "github_actions"
|
|
35
|
+
|
|
36
|
+
def fetch_completed_runs(self, repo_path: Path, commit_hashes: set[str]) -> dict[str, list[CiRunInfo]]:
|
|
37
|
+
return github_actions.get_github_actions_runs(repo_path, commit_hashes, self.config or {})
|
|
38
|
+
|
|
39
|
+
def poll_runs(self, repo_path: Path, run_ids: list[str] | None, head_sha: str | None) -> list[CiRunInfo]:
|
|
40
|
+
return github_actions.poll_runs(repo_path, self.config, run_ids, head_sha)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class JenkinsProvider:
|
|
45
|
+
config: dict
|
|
46
|
+
name: str = "jenkins"
|
|
47
|
+
|
|
48
|
+
def fetch_completed_runs(self, repo_path: Path, commit_hashes: set[str]) -> dict[str, list[CiRunInfo]]:
|
|
49
|
+
return jenkins.get_jenkins_builds(repo_path, commit_hashes, self.config)
|
|
50
|
+
|
|
51
|
+
def poll_runs(self, repo_path: Path, run_ids: list[str] | None, head_sha: str | None) -> list[CiRunInfo]:
|
|
52
|
+
return jenkins.poll_runs(repo_path, self.config, run_ids, head_sha)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def enabled_providers(github_config: dict | None, jenkins_config: dict | None) -> list[CiProvider]:
|
|
56
|
+
providers: list[CiProvider] = []
|
|
57
|
+
if github_config is None or github_config.get("enabled") is not False:
|
|
58
|
+
providers.append(GitHubActionsProvider(github_config))
|
|
59
|
+
if jenkins_config and jenkins_config.get("enabled") is not False:
|
|
60
|
+
providers.append(JenkinsProvider(jenkins_config))
|
|
61
|
+
return providers
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_ci_runs(
|
|
65
|
+
repo_path: Path,
|
|
66
|
+
commit_hashes: set[str],
|
|
67
|
+
github_config: dict | None,
|
|
68
|
+
jenkins_config: dict | None,
|
|
69
|
+
) -> dict[str, list[CiRunInfo]]:
|
|
70
|
+
by_sha: dict[str, list[CiRunInfo]] = {}
|
|
71
|
+
for provider in enabled_providers(github_config, jenkins_config):
|
|
72
|
+
for sha, runs in provider.fetch_completed_runs(repo_path, commit_hashes).items():
|
|
73
|
+
by_sha.setdefault(sha, []).extend(runs)
|
|
74
|
+
return by_sha
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def poll_ci_runs(
|
|
78
|
+
repo_path: Path,
|
|
79
|
+
run_refs: list[str] | None,
|
|
80
|
+
head_sha: str | None,
|
|
81
|
+
github_config: dict | None,
|
|
82
|
+
jenkins_config: dict | None,
|
|
83
|
+
) -> list[CiRunInfo]:
|
|
84
|
+
"""Poll the CI providers. `run_refs=None` discovers the active runs of every
|
|
85
|
+
provider; otherwise each ref is `"<provider>:<run_id>"` and only the
|
|
86
|
+
providers named there are asked, about just those runs."""
|
|
87
|
+
providers = enabled_providers(github_config, jenkins_config)
|
|
88
|
+
if run_refs is None:
|
|
89
|
+
return [run for provider in providers for run in provider.poll_runs(repo_path, None, head_sha)]
|
|
90
|
+
|
|
91
|
+
ids_by_provider: dict[str, list[str]] = {}
|
|
92
|
+
for ref in run_refs:
|
|
93
|
+
provider_name, _, run_id = ref.partition(":")
|
|
94
|
+
if run_id:
|
|
95
|
+
ids_by_provider.setdefault(provider_name, []).append(run_id)
|
|
96
|
+
return [
|
|
97
|
+
run
|
|
98
|
+
for provider in providers
|
|
99
|
+
if provider.name in ids_by_provider
|
|
100
|
+
for run in provider.poll_runs(repo_path, ids_by_provider[provider.name], head_sha)
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_ci_run_stages(
|
|
105
|
+
repo_path: Path,
|
|
106
|
+
provider: str,
|
|
107
|
+
run_id: str,
|
|
108
|
+
github_config: dict | None,
|
|
109
|
+
jenkins_config: dict | None,
|
|
110
|
+
) -> list[CiStage] | None:
|
|
111
|
+
if provider == "github_actions" and (github_config is None or github_config.get("enabled") is not False):
|
|
112
|
+
return github_actions.get_run_stages(repo_path, github_config, run_id)
|
|
113
|
+
if provider == "jenkins" and jenkins_config and jenkins_config.get("enabled") is not False:
|
|
114
|
+
return jenkins.get_build_stages(repo_path, jenkins_config, run_id)
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _active_runs_for_repo(repo: RepoSummary, github_config: dict | None, jenkins_config: dict | None) -> list[ActivePipeline]:
|
|
119
|
+
runs = poll_ci_runs(Path(repo.path), None, None, github_config, jenkins_config)
|
|
120
|
+
return [ActivePipeline(repo_id=repo.id, repo_name=repo.name, run=run) for run in runs]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def get_active_pipelines(
|
|
124
|
+
repos: list[RepoSummary],
|
|
125
|
+
github_config: dict | None,
|
|
126
|
+
jenkins_config: dict | None,
|
|
127
|
+
) -> list[ActivePipeline]:
|
|
128
|
+
"""Queued/running pipelines across every known repo. Repos are queried
|
|
129
|
+
concurrently since each one is a network round trip."""
|
|
130
|
+
if not repos:
|
|
131
|
+
return []
|
|
132
|
+
with ThreadPoolExecutor(max_workers=min(8, len(repos))) as pool:
|
|
133
|
+
per_repo = pool.map(lambda repo: _active_runs_for_repo(repo, github_config, jenkins_config), repos)
|
|
134
|
+
pipelines = [item for group in per_repo for item in group]
|
|
135
|
+
return sorted(pipelines, key=lambda item: item.run.created_at or "", reverse=True)
|
|
@@ -15,6 +15,9 @@ MAX_PATCH_BYTES = 1_000_000
|
|
|
15
15
|
# for repos that have no HEAD yet.
|
|
16
16
|
_EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
|
|
17
17
|
|
|
18
|
+
# Context lines big enough to make git emit the whole file as a single hunk.
|
|
19
|
+
_FULL_CONTEXT = "-U1000000"
|
|
20
|
+
|
|
18
21
|
_BINARY_RE = re.compile(r"^(Binary files .* differ|GIT binary patch)$", re.MULTILINE)
|
|
19
22
|
|
|
20
23
|
|
|
@@ -37,19 +40,23 @@ def _run_diff(repo: Repo, *args: str) -> bytes:
|
|
|
37
40
|
return out if isinstance(out, bytes) else out.encode()
|
|
38
41
|
|
|
39
42
|
|
|
43
|
+
def _context(full: bool) -> list[str]:
|
|
44
|
+
return [_FULL_CONTEXT] if full else []
|
|
45
|
+
|
|
46
|
+
|
|
40
47
|
def _paths(path: str, old_path: str | None) -> list[str]:
|
|
41
48
|
return [old_path, path] if old_path and old_path != path else [path]
|
|
42
49
|
|
|
43
50
|
|
|
44
|
-
def get_commit_file_diff(repo_path: Path, sha: str, path: str, old_path: str | None = None) -> FileDiff:
|
|
51
|
+
def get_commit_file_diff(repo_path: Path, sha: str, path: str, old_path: str | None = None, full: bool = False) -> FileDiff:
|
|
45
52
|
repo = Repo(repo_path)
|
|
46
53
|
commit = repo.commit(sha)
|
|
47
54
|
base = commit.parents[0].hexsha if commit.parents else _EMPTY_TREE
|
|
48
|
-
raw = _run_diff(repo, "-M", base, commit.hexsha, "--", *_paths(path, old_path))
|
|
55
|
+
raw = _run_diff(repo, "-M", *_context(full), base, commit.hexsha, "--", *_paths(path, old_path))
|
|
49
56
|
return _build_result(raw)
|
|
50
57
|
|
|
51
58
|
|
|
52
|
-
def get_working_file_diff(repo_path: Path, path: str, old_path: str | None = None) -> FileDiff:
|
|
59
|
+
def get_working_file_diff(repo_path: Path, path: str, old_path: str | None = None, full: bool = False) -> FileDiff:
|
|
53
60
|
repo = Repo(repo_path)
|
|
54
61
|
root = Path(repo.working_tree_dir or repo_path).resolve()
|
|
55
62
|
target = (root / path).resolve()
|
|
@@ -64,5 +71,5 @@ def get_working_file_diff(repo_path: Path, path: str, old_path: str | None = Non
|
|
|
64
71
|
return _build_result(raw)
|
|
65
72
|
|
|
66
73
|
base = "HEAD" if repo.head.is_valid() else _EMPTY_TREE
|
|
67
|
-
raw = _run_diff(repo, "-M", base, "--", *_paths(path, old_path))
|
|
74
|
+
raw = _run_diff(repo, "-M", *_context(full), base, "--", *_paths(path, old_path))
|
|
68
75
|
return _build_result(raw)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root,:root[data-theme=dark]{--text:#c8ccd4;--text-dim:#7d8590;--text-h:#f0f2f5;--bg:#0d1117;--panel-bg:#12151b;--hover-bg:#1c2129;--active-bg:#202632;--border:#262b33;--accent:#4c9aff;--accent-fg:#0d1117;--danger:#ff5630;--danger-fg:#ffb4b4;--danger-bg:#4a1f24;--warning:#ffd666;--success:#36b37e;--menu-bg:#12151b;--input-bg:#0d1117;--shadow:#00000080;--shadow-soft:#00000073;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}:root[data-theme=light]{--text:#3a3f46;--text-dim:#6e7781;--text-h:#1f2328;--bg:#fff;--panel-bg:#f6f8fa;--hover-bg:#eef0f2;--active-bg:#e4e7eb;--border:#d0d7de;--accent:#0969da;--accent-fg:#fff;--danger:#cf222e;--danger-fg:#a40e26;--danger-bg:#ffebe9;--warning:#9a6700;--success:#1a7f37;--menu-bg:#fff;--input-bg:#fff;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}:root{color:var(--text);background:var(--bg);-webkit-font-smoothing:antialiased;font:13px/1.4 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}*{box-sizing:border-box}.sidebar-content,.graph-and-list{scrollbar-width:thin;scrollbar-color:color-mix(in srgb, var(--text-dim) 40%, transparent) transparent;scrollbar-width:thin;scrollbar-color:transparent transparent}.sidebar-content:hover,.graph-and-list:hover{scrollbar-color:color-mix(in srgb, var(--text-dim) 40%, transparent) transparent}body{margin:0}#root{width:100vw;height:100vh}.app{width:100vw;height:100vh;display:flex;overflow:hidden}.menu-overlay{background:var(--shadow);z-index:100;justify-content:center;align-items:flex-start;padding-top:12vh;transition:background-color .12s;display:flex;position:fixed;inset:0}.menu-overlay.previewing-theme{background:0 0}.menu-dialog{background:var(--menu-bg);border:1px solid var(--border);width:640px;max-width:calc(100vw - 32px);height:640px;max-height:64vh;box-shadow:0 16px 48px var(--shadow);border-radius:8px;display:flex;overflow:hidden}.menu-sidebar{background:var(--bg);border-right:1px solid var(--border);flex:none;width:140px;padding:8px 0}.command-palette-overlay{background:var(--shadow);z-index:110;justify-content:center;align-items:flex-start;padding-top:12vh;display:flex;position:fixed;inset:0}.command-palette-dialog{background:var(--menu-bg);border:1px solid var(--border);width:560px;max-width:calc(100vw - 32px);max-height:60vh;box-shadow:0 16px 48px var(--shadow);border-radius:8px;flex-direction:column;display:flex;overflow:hidden}.command-palette-input{background:var(--input-bg);border:0;border-bottom:1px solid var(--border);color:var(--text);font:inherit;border-radius:0;flex:none;padding:14px 18px;font-size:15px}.command-palette-input:focus{outline:none}.command-palette-results{flex:1;min-height:0;padding:6px;overflow-y:auto}.command-palette-item{color:var(--text);cursor:pointer;border-radius:5px;justify-content:space-between;align-items:center;gap:12px;padding:8px 10px;font-size:13px;display:flex}.command-palette-item.active{background:var(--active-bg);color:var(--text-h)}.command-palette-item-label{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.command-palette-item-meta{text-overflow:ellipsis;white-space:nowrap;max-width:55%;color:var(--text-dim);flex:none;font-size:11px;overflow:hidden}.menu-section-item{color:var(--text-dim);cursor:pointer;padding:8px 16px;font-size:13px}.menu-section-item:hover{background:var(--hover-bg)}.menu-section-item.active{background:var(--active-bg);color:var(--text-h)}.menu-content{flex:1;min-width:0;padding:20px 24px;overflow-y:auto}.menu-section{flex-direction:column;gap:16px;display:flex}.menu-section>*{margin:0}.menu-hint{color:var(--text-dim);margin:0 0 16px;font-size:12px}.menu-path-list{flex-direction:column;gap:4px;margin-bottom:12px;display:flex}.menu-path-row{background:var(--bg);border:1px solid var(--border);border-radius:4px;justify-content:space-between;align-items:center;gap:8px;padding:6px 10px;display:flex}.menu-path-text{text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;overflow:hidden}.menu-path-remove{cursor:pointer;color:var(--text-dim);text-align:center;border-radius:3px;flex:none;width:18px;height:18px;line-height:18px}.menu-path-remove:hover{background:var(--hover-bg);color:var(--text-h)}.menu-empty{color:var(--text-dim);padding:6px 0;font-size:13px}.keybinding-value{color:var(--text-dim);white-space:nowrap;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px}.keybinding-actions{flex:none;gap:6px;display:flex}.menu-add-path{gap:8px;display:flex}.menu-add-path input{background:var(--input-bg);border:1px solid var(--border);min-width:0;color:var(--text);font:inherit;border-radius:4px;flex:1;padding:6px 8px;font-size:12px}.menu-add-path input:focus{border-color:var(--accent);outline:none}.menu-add-path .menu-add-button{background:var(--accent);color:var(--accent-fg);cursor:pointer;border:none;border-radius:4px;padding:0 14px;font-size:12px;font-weight:600}.menu-add-path .menu-secondary-button{cursor:pointer;border-radius:4px;padding:0 14px;font-size:12px}.menu-error{color:var(--danger-fg);margin-top:10px;font-size:12px}.menu-notice{border:1px solid color-mix(in srgb, var(--accent) 45%, transparent);background:color-mix(in srgb, var(--accent) 12%, transparent);color:var(--text);border-radius:6px;margin:0 0 14px;padding:10px 12px;font-size:12px}.menu-field{color:var(--text-dim);flex-direction:column;gap:5px;margin-bottom:14px;font-size:12px;display:flex}.menu-field>span:first-child{color:var(--text-h);font-size:13px}.menu-field .menu-hint{margin:0 0 3px}.menu-field input,.menu-field textarea,.github-repo-row input{background:var(--input-bg);border:1px solid var(--border);color:var(--text);font:inherit;border-radius:4px;padding:6px 8px;font-size:12px}.menu-field textarea{resize:vertical;min-height:44px}.menu-field input:focus,.menu-field textarea:focus,.github-repo-row input:focus{border-color:var(--accent);outline:none}.create-tag-dialog{background:var(--menu-bg);border:1px solid var(--border);width:420px;max-width:calc(100vw - 32px);max-height:80vh;box-shadow:0 16px 48px var(--shadow);border-radius:8px;flex-direction:column;padding:20px 24px;display:flex;overflow-y:auto}.create-tag-dialog h3{color:var(--text-h);margin:0 0 6px;font-size:15px}.directory-browser-path{color:var(--text-dim);word-break:break-all;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px}.directory-browser-list{border:1px solid var(--border);border-radius:4px;flex-direction:column;gap:2px;max-height:280px;margin:12px 0;padding:4px;display:flex;overflow-y:auto}.directory-browser-entry{cursor:pointer;color:var(--text);border-radius:4px;padding:6px 8px;font-size:13px}.directory-browser-entry:hover{background:var(--hover-bg);color:var(--text-h)}.branch-delete-modal{background:var(--menu-bg);border:1px solid var(--border);width:420px;max-width:calc(100vw - 32px);box-shadow:0 16px 48px var(--shadow);border-radius:8px;flex-direction:column;padding:20px 24px;display:flex}.branch-delete-modal h3{color:var(--text-h);margin:0 0 6px;font-size:15px}.modal-check-row{color:var(--text);align-items:center;gap:8px;margin-top:10px;font-size:12px;display:flex}.menu-primary-button.danger{border-color:var(--danger);color:var(--danger-fg)}.github-repo-mappings{flex-direction:column;gap:8px;display:flex}.github-repo-row{grid-template-columns:minmax(160px,1fr) minmax(80px,.45fr) minmax(80px,.45fr) auto;gap:8px;display:grid}.menu-actions{justify-content:flex-end;gap:8px;margin-top:14px;display:flex}.menu-primary-button,.menu-secondary-button{font:inherit;cursor:pointer;border-radius:4px;padding:6px 10px;font-size:12px}.menu-primary-button{background:var(--accent);border:1px solid var(--accent);color:var(--accent-fg);font-weight:600}.menu-secondary-button{background:var(--bg);border:1px solid var(--border);color:var(--text)}.menu-secondary-button:hover{background:var(--hover-bg);color:var(--text-h)}.menu-secondary-button.danger,.menu-secondary-button.danger:hover{border-color:var(--danger);color:var(--danger-fg)}.menu-setting{justify-content:space-between;align-items:center;gap:16px;display:flex}.menu-setting-main{min-width:0}.menu-setting-label{color:var(--text-h);font-size:13px}.menu-setting .menu-hint{margin:2px 0 0}.agent-hooks-section{flex-direction:column;gap:10px;display:flex}.agent-hooks-section>.menu-actions{margin-top:0}.agent-hooks-section .menu-setting-main .menu-hint{margin:2px 0 0}.agent-hook-grid{gap:12px;margin-top:12px;display:grid}.agent-hook-card{border:1px solid var(--border);background:var(--panel-bg);border-radius:8px;padding:12px}.agent-hook-card-heading{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.agent-hook-status{border:1px solid var(--border);color:var(--text-muted);border-radius:999px;flex:none;padding:2px 8px;font-size:11px}.agent-hook-status.installed{border-color:color-mix(in srgb, var(--success) 45%, transparent);color:var(--success)}.agent-hook-meta{color:var(--text-muted);overflow-wrap:anywhere;margin-top:6px;font-size:12px}.agent-hook-snippet{border:1px solid var(--border);background:var(--bg);max-height:220px;color:var(--text);white-space:pre-wrap;border-radius:6px;margin:10px 0 0;padding:10px;font-size:11px;overflow:auto}.theme-options{gap:6px;display:flex}.theme-options button{background:var(--bg);border:1px solid var(--border);color:var(--text);font:inherit;cursor:pointer;border-radius:4px;padding:5px 12px;font-size:12px}.theme-options button.active{background:var(--accent);border-color:var(--accent);color:var(--accent-fg);font-weight:600}.menu-setting-stacked{flex-direction:column;align-items:stretch;gap:10px}.theme-picker{flex-direction:column;gap:8px;min-width:0;display:flex}.theme-picker-toolbar{gap:8px;display:flex}.theme-picker-toolbar input[type=search]{background:var(--input-bg);border:1px solid var(--border);min-width:0;color:var(--text);font:inherit;border-radius:4px;flex:1;padding:5px 8px;font-size:12px}.theme-picker-toolbar input[type=search]:focus{border-color:var(--accent);outline:none}.theme-list{border:1px solid var(--border);border-radius:6px;max-height:300px;padding:4px;overflow-y:auto}.theme-group-label{letter-spacing:.04em;text-transform:uppercase;color:var(--text-dim);padding:8px 8px 4px;font-size:11px}.theme-row-wrap{align-items:center;display:flex}.theme-row{min-width:0;color:var(--text);font:inherit;text-align:left;cursor:pointer;background:0 0;border:1px solid #0000;border-radius:4px;flex:1;align-items:center;gap:10px;padding:5px 8px;font-size:12px;display:flex}.theme-row:hover,.theme-row:focus-visible{background:var(--hover-bg);color:var(--text-h);outline:none}.theme-row.active{background:var(--active-bg);border-color:var(--accent);color:var(--text-h)}.theme-row-name{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.theme-row-kind{color:var(--text-dim);font-size:11px}.theme-row-pin{color:var(--text-dim);cursor:pointer;background:0 0;border:none;padding:4px 6px;font-size:14px;line-height:1}.theme-row-pin:hover,.theme-row-pin.pinned{color:var(--warning)}.theme-row-remove{color:var(--text-dim);cursor:pointer;background:0 0;border:none;padding:4px 8px;font-size:16px;line-height:1}.theme-row-remove:hover{color:var(--danger)}.theme-swatch{border:1px solid;border-radius:4px;flex:none;align-items:center;gap:3px;height:20px;padding:0 5px 0 0;display:inline-flex;overflow:hidden}.theme-swatch-panel{align-self:stretch;width:8px}.theme-swatch-dot{border-radius:50%;width:7px;height:7px}.switch{cursor:pointer;flex:none;align-items:center;display:inline-flex}.switch input{display:none}.switch-track{background:var(--border);border-radius:999px;width:34px;height:18px;transition:background .15s;position:relative}.switch-thumb{background:#fff;border-radius:50%;width:14px;height:14px;transition:transform .15s;position:absolute;top:2px;left:2px}.switch input:checked+.switch-track{background:var(--accent)}.switch input:checked+.switch-track .switch-thumb{transform:translate(16px)}.sidebar{background:var(--panel-bg);border-right:1px solid var(--border);-webkit-user-select:none;user-select:none;flex-direction:column;flex:none;width:250px;min-width:200px;display:flex;overflow:hidden}.sidebar-content{flex:auto;min-height:0;overflow-y:auto}.sidebar-tabs{border-top:1px solid var(--border);background:var(--panel-bg);flex:none;display:flex}.sidebar-tab{color:var(--text-dim);cursor:pointer;font:inherit;background:0 0;border:0;border-top:2px solid #0000;flex:1;justify-content:center;align-items:center;gap:6px;margin-top:-1px;padding:8px 10px;font-size:12px;display:inline-flex}.sidebar-tab:hover{color:var(--text-h);background:var(--hover-bg)}.sidebar-tab.active{color:var(--text-h);border-top-color:var(--accent)}.sidebar-tab-badge,.tab-agent-badge{background:var(--accent);min-width:15px;color:var(--accent-fg);text-align:center;border-radius:999px;flex:none;padding:0 5px;font-size:10px;line-height:15px}.sidebar-tab-badge.idle,.tab-agent-badge.idle{background:var(--border);color:var(--text-dim)}.sidebar-resize-handle{cursor:col-resize;z-index:4;flex:0 0 5px;margin-left:-3px}.sidebar-resize-handle:hover,.sidebar-resize-handle:active{background:var(--accent);opacity:.5}.repo-search{align-items:center;gap:6px;padding:10px 16px 4px;display:flex}.agent-activity-panel{border:1px solid var(--border);background:color-mix(in srgb, var(--accent) 6%, transparent);border-radius:8px;margin:10px 10px 6px;padding:10px}.agent-activity-heading{color:var(--text-h);justify-content:space-between;align-items:center;gap:8px;margin-bottom:8px;font-size:12px;font-weight:700;display:flex}.agent-scan-time{color:var(--text-dim);font-size:10px;font-weight:500}.agent-discovery-row{color:var(--text-dim);align-items:center;font-size:11px;display:flex}.agent-error{color:var(--danger-fg);margin-top:8px;font-size:11px}.agent-results{flex-direction:column;gap:6px;margin-top:8px;display:flex}.agent-summary,.agent-empty{color:var(--text-dim);font-size:11px}.agent-worktree-card{border:1px solid var(--border);background:var(--panel-bg);border-radius:6px;padding:7px}.agent-worktree-card.active{border-color:color-mix(in srgb, var(--accent) 55%, var(--border));background:color-mix(in srgb, var(--accent) 8%, var(--panel-bg));box-shadow:0 0 0 1px color-mix(in srgb, var(--accent) 25%, transparent)}.agent-scan-card{border-top:1px solid var(--border);border-left:2px solid #0000;flex-direction:column;gap:6px;padding-top:6px;padding-left:8px;display:flex}.agent-scan-card.active{border-left-color:var(--accent);background:color-mix(in srgb, var(--accent) 6%, transparent);border-radius:0 6px 6px 0}.agent-scan-title{color:var(--text-h);font-size:11px;font-weight:700}.agent-command{color:var(--text-dim);text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:9px;overflow:hidden}.agent-session-directory{color:var(--text-dim);text-overflow:ellipsis;white-space:nowrap;font-size:10px;overflow:hidden}.agent-worktree-title{color:var(--text-h);text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:700;overflow:hidden}.agent-worktree-meta{color:var(--text-dim);text-overflow:ellipsis;white-space:nowrap;font-size:10px;line-height:1.45;overflow:hidden}.agent-hover-target{pointer-events:all}.agent-hover-card{z-index:60;background:var(--panel-bg);border:1px solid var(--border);box-shadow:0 8px 24px var(--shadow-soft);color:var(--text);pointer-events:none;border-radius:8px;flex-direction:column;gap:10px;padding:10px 12px;font-size:12px;display:flex;position:fixed}.agent-hover-session{border-left:2px solid #0000;border-radius:0 4px 4px 0;flex-direction:column;gap:3px;padding:4px 6px;display:flex}.agent-hover-session+.agent-hover-session{border-top:1px solid var(--border);margin-top:6px}.agent-hover-session.active{border-left-color:var(--accent);background:color-mix(in srgb, var(--accent) 8%, transparent)}.agent-hover-title,.agent-hover-worktree-title{color:var(--text-h);align-items:center;gap:6px;font-weight:700;display:flex}.agent-hover-title>span:first-child,.agent-hover-worktree-title>span:first-child{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.agent-hover-card .agent-state-pill{flex:none;margin-left:0}.agent-hover-meta{color:var(--text-dim);overflow-wrap:anywhere;font-size:11px;line-height:1.45}.agent-hover-mono{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:10px}.agent-hover-prompt{color:var(--text);overflow-wrap:anywhere;white-space:pre-line;font-size:11px;font-style:italic;line-height:1.4}.agent-hover-worktree{border:1px solid var(--border);border-radius:6px;flex-direction:column;gap:2px;margin-top:4px;padding:6px 8px;display:flex}.agent-hover-worktree.active{border-color:color-mix(in srgb, var(--accent) 55%, var(--border));background:color-mix(in srgb, var(--accent) 8%, transparent)}.agent-state-pill{background:color-mix(in srgb, var(--accent) 18%, transparent);border-radius:8px;margin-left:6px;padding:0 6px;font-size:10px}.agent-state-pill.idle{background:var(--border);color:var(--text-dim)}.agent-evidence-row{flex-wrap:wrap;gap:4px;margin-top:5px;display:flex}.agent-evidence-pill{border:1px solid color-mix(in srgb, var(--accent) 35%, transparent);color:var(--text-dim);border-radius:999px;padding:1px 5px;font-size:9px}.repo-search input{background:var(--input-bg);border:1px solid var(--border);min-width:0;color:var(--text);font:inherit;border-radius:4px;flex:1;padding:4px 8px;font-size:12px}.repo-search input:focus{border-color:var(--accent);outline:none}.repo-bulk-button{border:1px solid var(--border);background:var(--bg);width:26px;height:26px;color:var(--text-dim);cursor:pointer;border-radius:4px;flex:none;line-height:1}.repo-bulk-button:hover{border-color:var(--accent);color:var(--text-h)}.repo-list h2{text-transform:uppercase;letter-spacing:.06em;color:var(--text-dim);margin:0;padding:16px 16px 8px;font-size:13px}.repo-item{cursor:pointer;height:28px;color:var(--text);border-left:3px solid #0000;align-items:center;gap:8px;padding:0 16px;display:flex}.repo-item:hover{background:var(--hover-bg)}.repo-item.active{background:var(--active-bg);border-left-color:var(--accent);color:var(--text-h)}.repo-item[draggable=true]{cursor:grab}.repo-item[draggable=true]:active{cursor:grabbing}.repo-item.dragging{opacity:.45}.repo-names{flex-direction:column;flex:1;justify-content:center;min-width:0;line-height:1.15;display:flex}.repo-name{text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.repo-name-line{align-items:center;gap:5px;min-width:0;display:flex}.repo-agent-dot{background:var(--accent);width:7px;height:7px;box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);border-radius:50%;flex:none}.repo-agent-dot.idle{background:var(--text-dim);box-shadow:none;opacity:.6}.repo-branch{color:var(--text-dim);text-overflow:ellipsis;white-space:nowrap;font-size:10px;overflow:hidden}.repo-bookmark{width:18px;height:20px;color:var(--text-dim);opacity:.5;cursor:pointer;background:0 0;border:none;flex:none;justify-content:center;align-items:center;margin-left:4px;padding:0;display:flex}.repo-bookmark:hover{opacity:1}.repo-bookmark.pinned{color:var(--accent);opacity:1}.repo-bookmark svg{fill:currentColor;width:12px;height:12px}.repo-list .repo-group-heading{box-sizing:border-box;align-items:center;gap:8px;padding:8px 16px;transition:height .12s,padding .12s,opacity .12s,background-color .12s;display:flex;overflow:hidden}.repo-group-heading:hover{background:var(--hover-bg)}.repo-group-name{text-overflow:ellipsis;white-space:nowrap;cursor:grab;flex:auto;align-items:center;min-width:0;margin:-8px 0;padding:8px 0;display:flex;overflow:hidden}.repo-group-name:active{cursor:grabbing}.repo-group-collapse{color:var(--text-dim);cursor:pointer;background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:2px;display:flex}.repo-group-collapse:hover{color:var(--text-h)}.repo-group-collapse svg{transition:transform .12s}.repo-group-collapse.collapsed svg{transform:rotate(-90deg)}.repo-select,.repo-group-select{accent-color:var(--accent);cursor:pointer;flex:none;margin:0}.repo-select{margin-left:auto}.repo-group-heading.dragging{opacity:.45}.repo-group-section{padding-top:4px;padding-bottom:8px;transition:background-color .15s}.repo-group-section.collapsed{padding-bottom:0}.repo-bookmark-overlay{z-index:80;position:fixed;inset:0}.repo-bookmark-menu{background:var(--menu-bg);border:1px solid var(--border);min-width:180px;max-width:260px;box-shadow:0 12px 32px var(--shadow-soft);border-radius:6px;padding:6px;position:fixed}.repo-bookmark-menu-item{width:100%;min-height:28px;color:var(--text);font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:4px;grid-template-columns:18px 1fr;align-items:center;gap:6px;padding:4px 8px;font-size:12px;display:grid}.repo-bookmark-menu-item:hover{background:var(--hover-bg);color:var(--text-h)}.repo-bookmark-menu-item:disabled{cursor:default;opacity:.5}.repo-bookmark-menu-item:disabled:hover{color:var(--text);background:0 0}.repo-bookmark-menu-separator{background:var(--border);height:1px;margin:5px 4px}.repo-empty{color:var(--text-dim);padding:8px 16px;font-size:13px}.main{flex-direction:column;flex:1;min-width:0;display:flex}.main-header{background:var(--panel-bg);border-bottom:1px solid var(--border);flex:none;align-items:stretch;display:flex}.tabs-bar{flex:auto;min-width:0;display:flex;overflow-x:auto}.tab{border-right:1px solid var(--border);color:var(--text-dim);cursor:pointer;white-space:nowrap;-webkit-user-select:none;user-select:none;box-sizing:border-box;align-items:center;gap:8px;padding:5px 8px 5px 12px;transition:width .12s,padding .12s,opacity .12s,background-color .12s;display:flex;overflow:hidden}.tab:active{cursor:grabbing}body.tab-dragging,body.tab-dragging *{cursor:grabbing!important}.tab.dragging{opacity:.45}.tab-ghost{z-index:1000;pointer-events:none;opacity:.85;background:var(--active-bg);transition:none;position:fixed;top:0;left:0;box-shadow:0 4px 12px #00000059}.tab.preview{font-style:italic}.tab.active{background:var(--active-bg);color:var(--text-h);box-shadow:inset 0 -2px var(--accent)}.tab-names{flex-direction:column;line-height:1.2;display:flex}.tab-branch{color:var(--text-dim);font-size:10px}.tab-close{text-align:center;border-radius:3px;width:16px;height:16px;line-height:16px}.tab-close:hover{background:var(--hover-bg);color:var(--text-h)}.search-box{border-left:1px solid var(--border);flex:none;align-items:center;gap:6px;padding:0 10px;display:flex;position:relative}.search-input{background:var(--input-bg);border:1px solid var(--border);width:200px;color:var(--text);font:inherit;border-radius:4px;padding:4px 8px}.search-input:focus{border-color:var(--accent);outline:none}.search-count{color:var(--text-dim);text-align:center;min-width:14px;font-size:12px}.agent-active-marker{pointer-events:none}.agent-active-marker.idle{opacity:.45}.filter-button{border:1px solid var(--border);background:var(--bg);width:26px;height:26px;color:var(--text-dim);cursor:pointer;border-radius:4px;justify-content:center;align-items:center;display:inline-flex;position:relative}.filter-button:hover,.filter-button.active{color:var(--text-h);border-color:var(--accent)}.filter-count{background:var(--accent);color:#fff;text-align:center;border-radius:999px;min-width:15px;height:15px;padding:0 4px;font-size:10px;line-height:15px;position:absolute;top:-7px;right:-7px}.filter-popover{z-index:25;background:var(--menu-bg);border:1px solid var(--border);min-width:360px;max-width:440px;max-height:320px;box-shadow:0 8px 24px var(--shadow-soft);border-radius:6px;flex-direction:column;gap:4px;padding:8px;display:flex;position:absolute;top:calc(100% + 8px);right:10px;overflow-y:auto}.filter-field{grid-template-columns:130px 1fr;align-items:start;gap:8px;padding:2px 0;display:grid}.filter-field span{color:var(--text-dim);white-space:nowrap;padding-top:6px;font-size:12px}.filter-field input{background:var(--input-bg);border:1px solid var(--border);min-width:0;color:var(--text);font:inherit;border-radius:4px;padding:5px 7px;font-size:12px}.filter-field input:focus{border-color:var(--accent);outline:none}.filter-hint{color:var(--text-dim);margin:2px 0;font-size:11px;line-height:1.4}.multiselect{min-width:0;position:relative}.multiselect-trigger{background:var(--bg);border:1px solid var(--border);width:100%;min-width:0;color:var(--text);font:inherit;cursor:pointer;border-radius:4px;justify-content:space-between;align-items:center;gap:6px;padding:5px 7px;font-size:12px;display:flex}.multiselect-trigger:hover,.multiselect-trigger.active{border-color:var(--accent)}.multiselect-trigger-label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.multiselect-caret{color:var(--text-dim);flex:none}.multiselect-overlay{z-index:30;position:fixed;inset:0}.multiselect-panel{z-index:31;background:var(--menu-bg);border:1px solid var(--border);max-height:180px;box-shadow:0 8px 24px var(--shadow-soft);border-radius:6px;padding:4px;position:absolute;top:calc(100% + 4px);left:0;right:0;overflow-y:auto}.multiselect-option{color:var(--text);cursor:pointer;border-radius:4px;align-items:center;gap:6px;padding:5px 6px;font-size:12px;display:flex;overflow:hidden}.multiselect-option:hover{background:var(--active-bg)}.multiselect-option span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.filter-clear-button{border:1px solid var(--border);background:var(--bg);color:var(--text-dim);cursor:pointer;font:inherit;border-radius:4px;align-self:flex-end;padding:4px 7px;font-size:12px}.filter-clear-button:hover{color:var(--text-h);border-color:var(--accent)}.filter-option{text-overflow:ellipsis;white-space:nowrap;width:100%;color:var(--text);cursor:pointer;font:inherit;text-align:left;background:0 0;border:0;border-radius:4px;padding:6px 8px;font-size:12px;overflow:hidden}.filter-option:hover,.filter-option.active{background:var(--active-bg);color:var(--text-h)}.content{flex:1;min-height:0;display:flex;overflow:hidden}.empty-state{color:var(--text-dim);margin:auto}.graph-and-list{flex:1;display:flex;overflow-y:auto}.graph-column{background:var(--panel-bg);flex:none}.list-column{flex:1;min-width:0}.error-banner{background:var(--danger-bg);color:var(--danger-fg);padding:8px 16px;font-size:13px}.loading-banner{color:var(--text-dim);padding:8px 16px;font-size:13px}.load-more-sentinel{color:var(--text-dim);align-items:center;min-height:1px;padding:0 16px;font-size:11px;font-style:italic;display:flex}.load-more-sentinel--visible{height:var(--load-more-height)}.ghost-row-spacer{color:var(--text-dim);opacity:0;align-items:center;height:0;padding:0 16px;font-size:11px;font-style:italic;transition:height .18s,opacity .18s;display:flex;overflow:hidden}.ghost-row-spacer--visible{height:var(--ghost-row-height);opacity:.7}.graph-panel{transition:height .18s}.fetch-band-shift{transition:transform .18s}@keyframes push-edge-flash{0%,to{opacity:.45;stroke-width:2px}50%{opacity:1;stroke-width:4px}}.push-edge{animation:.9s ease-in-out infinite push-edge-flash}.push-spinner{filter:drop-shadow(0 0 3px color-mix(in srgb, var(--accent) 55%, transparent))}.ci-active-dot{filter:drop-shadow(0 0 4px color-mix(in srgb, var(--accent) 80%, transparent))}.commit-row{border-bottom:1px solid var(--border);border-left:3px solid #0000}.commit-row.match{border-left-color:var(--warning)}.commit-row.match .commit-subject{color:var(--warning)}@keyframes commit-row-flash{0%{background-color:color-mix(in srgb, var(--accent) 35%, transparent)}to{background-color:#0000}}.commit-row.flash .commit-row-main{animation:.7s ease-out commit-row-flash}.commit-row-main{cursor:pointer;align-items:center;gap:10px;padding:0 16px;display:flex;overflow:visible}.commit-row-main:hover{background:var(--hover-bg)}.commit-row.expanded .commit-row-main{background:var(--active-bg)}.uncommitted-row .commit-row-main{color:var(--text-h)}.uncommitted-badge{background:color-mix(in srgb, var(--text-dim) 15%, transparent);border-color:var(--text-dim);color:var(--text-dim)}.uncommitted-detail-body{align-items:flex-start;gap:12px;display:flex}.uncommitted-action-panel{border:1px solid var(--border);background:var(--bg);border-radius:6px;flex-direction:column;flex:0 0 220px;gap:8px;padding:8px;display:flex}.uncommitted-message-label{color:var(--text-dim);font-size:11px}.uncommitted-message-input{resize:vertical;background:var(--panel-bg);border:1px solid var(--border);min-height:72px;color:var(--text);font:inherit;border-radius:4px;padding:6px 8px;font-size:12px}.uncommitted-message-input:focus{border-color:var(--accent);outline:none}.uncommitted-action-buttons{gap:8px;display:flex}.uncommitted-action-buttons button{background:var(--active-bg);border:1px solid var(--border);color:var(--text-h);cursor:pointer;font:inherit;border-radius:4px;flex:1;padding:5px 8px;font-size:12px}.uncommitted-action-buttons button:disabled{cursor:not-allowed;opacity:.45}.uncommitted-files{flex:auto;min-width:0}.uncommitted-file-checkbox{flex:none}.commit-hash{color:var(--text-dim);flex:none}.commit-subject{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--text-h);flex:auto;overflow:hidden}.commit-refs{flex:none;gap:4px;display:flex}.commit-author{text-overflow:ellipsis;min-width:0;color:var(--text-dim);white-space:nowrap;flex:0 0 120px;font-size:12px;overflow:hidden}.commit-date{color:var(--text-dim);white-space:nowrap;flex:none;font-size:12px}.badge{white-space:nowrap;border:1px solid;border-radius:999px;align-items:center;gap:3px;padding:1px 7px;font-size:11px;display:inline-flex}.branch-badge.remote{border-style:dashed}.badge-icon{flex:none}.ci-run-badge{color:#24292f;white-space:nowrap;cursor:pointer;background:#fff;border:1px solid #fff;border-radius:999px;align-items:center;gap:4px;padding:1px 7px 1px 3px;font-size:11px;display:inline-flex;position:relative}.ci-run-icon{justify-content:center;align-items:center;width:14px;height:14px;display:inline-flex}.ci-provider-text{font-size:10px;font-weight:800}.ci-run-status{font-weight:800;line-height:1}.ci-run-tooltip{z-index:20;background:var(--menu-bg);border:1px solid var(--border);width:max-content;min-width:420px;box-shadow:0 8px 24px var(--shadow-soft);color:var(--text);white-space:normal;border-radius:6px;padding:8px 10px;display:none;position:absolute;top:calc(100% + 8px);left:0}.ci-run-badge:hover .ci-run-tooltip{flex-direction:column;gap:8px;display:flex}.ci-run-tooltip:before{content:"";height:9px;position:absolute;top:-9px;left:0;right:0}.ci-run-props{grid-template-columns:1fr 1fr;gap:2px 14px;display:grid}.ci-run-props-column{flex-direction:column;gap:2px;min-width:0;display:flex}.ci-run-item{color:var(--text-dim);flex-direction:column;gap:2px;font-size:11px;display:flex}.ci-run-item+.ci-run-item{border-top:1px solid var(--border);padding-top:8px}.ci-run-title{color:var(--text-h);font-weight:600}.commit-detail{background:var(--panel-bg);border-top:1px dashed var(--border);padding:12px 16px 16px 46px}.commit-detail-loading{color:var(--text-dim);height:20px;font-size:13px}.commit-meta{grid-template-columns:100px 1fr;gap:4px 12px;margin:0 0 12px;font-size:13px;display:grid}.commit-meta dt{color:var(--text-dim)}.commit-meta dd{color:var(--text);word-break:break-all;margin:0}.mono{font-family:ui-monospace,Menlo,Consolas,monospace}.commit-message{color:var(--text-h);white-space:pre-wrap;overflow-wrap:anywhere;margin:0 0 12px;font-size:13px;line-height:18px}.commit-files{font-size:13px}.commit-file{align-items:center;gap:8px;height:20px;display:flex}.file-status{text-align:center;width:14px;font-family:ui-monospace,Menlo,Consolas,monospace;font-weight:600}.commit-file.status-added .file-status{color:var(--success)}.commit-file.status-deleted .file-status{color:var(--danger)}.commit-file.status-modified .file-status{color:var(--warning)}.commit-file.status-renamed .file-status{color:var(--accent)}.commit-file.clickable,.file-path.clickable{cursor:pointer}.commit-file.clickable:hover .file-path,.file-path.clickable:hover{color:var(--text-h);text-decoration:underline}.file-diff-overlay{align-items:center;padding-top:0}.file-diff-dialog{background:var(--menu-bg);border:1px solid var(--border);width:min(1400px,100vw - 48px);height:calc(100vh - 64px);box-shadow:0 16px 48px var(--shadow);border-radius:8px;flex-direction:column;display:flex;overflow:hidden}.file-diff-header{border-bottom:1px solid var(--border);align-items:center;gap:10px;padding:10px 14px;display:flex}.file-diff-title{min-width:0;color:var(--text-h);overflow-wrap:anywhere;flex:1;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:13px}.file-diff-body{min-height:0;overflow:hidden}.file-diff-full-toggle{flex:none;gap:8px!important}.file-diff-full-toggle label{white-space:nowrap}.file-diff-message{color:var(--text-dim);text-align:center;padding:24px;font-size:13px}.file-diff-main{flex:1;min-height:0;display:flex}.file-diff-files{border-right:1px solid var(--border);flex:0 0 260px;padding:6px 0;font-size:12px;overflow:auto}.file-diff-file{cursor:pointer;color:var(--text);align-items:center;gap:8px;padding:3px 12px;display:flex}.file-diff-file:hover{background:var(--hover-bg)}.file-diff-file.active{background:var(--active-bg);color:var(--text-h)}.file-diff-file-name{white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}.file-diff-file.status-added .file-status{color:var(--success)}.file-diff-file.status-deleted .file-status{color:var(--danger)}.file-diff-file.status-modified .file-status{color:var(--warning)}.file-diff-file.status-renamed .file-status{color:var(--accent)}.file-diff-body{flex-direction:column;flex:1;min-width:0;display:flex}.file-diff-panes{flex:1;min-height:0;display:flex;position:relative}.file-diff-overview{z-index:0;border-left:1px solid var(--border);background:color-mix(in srgb, var(--text-dim) 6%, transparent);width:28px;position:absolute;top:0;bottom:0;right:0}.file-diff-pane{z-index:1;position:relative}.file-diff-pane::-webkit-scrollbar{width:10px;height:10px}.file-diff-pane-right::-webkit-scrollbar{width:28px}.file-diff-pane::-webkit-scrollbar-track{background:0 0}.file-diff-pane::-webkit-scrollbar-corner{background:0 0}.file-diff-pane::-webkit-scrollbar-thumb{background:color-mix(in srgb, var(--text-dim) 25%, transparent);border-radius:5px}.file-diff-pane::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb, var(--text-dim) 45%, transparent)}.overview-bar{width:45%;min-height:2px;position:absolute}.overview-delete{background:var(--danger);left:2px}.overview-insert{background:var(--success);right:2px}.file-diff-pane{flex:1 1 0;min-width:0;overflow:auto}.file-diff-pane-left{border-right:1px solid var(--border)}.file-diff-table{border-collapse:collapse;width:max-content;min-width:100%;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;line-height:18px}.file-diff-table tr{height:18px}.file-diff-table td{vertical-align:top;padding:0 6px}.diff-num{text-align:right;color:var(--text-dim);-webkit-user-select:none;user-select:none}.diff-code{white-space:pre;color:var(--text)}.diff-delete{background:color-mix(in srgb, var(--danger) 18%, transparent)}.diff-insert{background:color-mix(in srgb, var(--success) 18%, transparent)}.diff-empty{background:color-mix(in srgb, var(--text-dim) 8%, transparent)}.file-diff-table tr.diff-hunk{height:26px}.diff-hunk td{vertical-align:middle;color:var(--accent);background:color-mix(in srgb, var(--accent) 10%, transparent);border-top:1px solid var(--border);border-bottom:1px solid var(--border);padding:0 8px}.terminal-container{border-top:1px solid var(--border);background:var(--panel-bg);flex-direction:column;flex:none;height:260px;display:flex;position:relative}.terminal-container.collapsed{height:auto}.terminal-container.collapsed .terminal-body{display:none}.terminal-resize-handle{cursor:row-resize;z-index:5;height:6px;position:absolute;top:-3px;left:0;right:0}.terminal-resize-handle:hover,.terminal-resize-handle:active{background:var(--accent);opacity:.5}.terminal-header{height:26px;color:var(--text-dim);cursor:pointer;-webkit-user-select:none;user-select:none;justify-content:space-between;align-items:center;padding:0 16px;font-size:12px;display:flex}.terminal-body{flex:1;min-height:0;position:relative}.terminal-instance{height:100%}.terminal-instance.hidden{display:none}.terminal-panel{height:100%;padding:4px 8px}.context-menu{z-index:111;background:var(--menu-bg);border:1px solid var(--border);min-width:140px;box-shadow:0 8px 24px var(--shadow);border-radius:6px;padding:4px;position:fixed}.context-menu-item{color:var(--text);cursor:pointer;border-radius:4px;padding:6px 10px;font-size:13px}.context-menu-item:hover{background:var(--hover-bg);color:var(--text-h)}.pipelines-panel{border:1px solid var(--border);background:color-mix(in srgb, var(--accent) 6%, transparent);border-radius:8px;margin:10px 10px 6px;padding:10px}.pipeline-card{border:1px solid var(--border);background:var(--panel-bg);border-radius:6px;flex-direction:column;gap:4px;padding:7px;display:flex}.pipeline-title{min-width:0;color:var(--text-h);align-items:center;gap:6px;font-size:12px;font-weight:700;text-decoration:none;display:flex}.pipeline-title:hover .pipeline-name{text-decoration:underline}.pipeline-icon{flex:none;justify-content:center;align-items:center;width:14px;height:14px;display:inline-flex}.pipeline-name{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}.pipeline-status{flex:none;font-size:10px;font-weight:600}.pipeline-meta{color:var(--text-dim);font-size:10px;line-height:1.45}.stage-strip{flex-direction:column;gap:4px;display:flex}.stage-strip-row{flex-wrap:wrap;align-items:center;gap:3px 4px;font-size:10px;display:flex}.stage-arrow{color:var(--text-dim)}.stage-chip{border:1px solid var(--border);color:var(--text-h);border-radius:999px;align-items:center;gap:3px;padding:1px 6px;display:inline-flex}.stage-chip.pending{color:var(--text-dim)}.stage-mark{font-weight:800;line-height:1}.stage-duration{color:var(--text-dim)}.stage-steps{color:var(--text-dim);font-size:10px}.stage-steps summary{cursor:pointer}.stage-step{color:var(--text);align-items:center;gap:4px;padding-left:12px;display:flex}.stage-graph{margin:6px 0 2px;font-size:10px;transition:width .22s;display:block;overflow:visible}.stage-graph-item{transition:transform .22s}.stage-graph-link{transition:transform .22s,width .22s}.stage-graph-ring{fill:var(--menu-bg);stroke-width:2px}.stage-graph-ring.running{animation:1.4s ease-in-out infinite stage-graph-pulse}@keyframes stage-graph-pulse{50%{opacity:.55}}.stage-graph-spinner{fill:none;stroke-width:2px;stroke-linecap:round;stroke-dasharray:9 22;transform-box:fill-box;transform-origin:50%;animation:.9s linear infinite stage-graph-spin}@keyframes stage-graph-spin{to{transform:rotate(360deg)}}.stage-graph-mark{text-anchor:middle;dominant-baseline:central;font-size:10px;font-weight:800}.stage-graph-name,.stage-graph-time,.stage-graph-step-name{text-anchor:middle;fill:var(--text-dim)}.stage-graph-job.current .stage-graph-name{fill:var(--text-h);font-weight:700}.stage-graph-job.pending,.stage-graph-summary.pending{opacity:.55}.stage-graph-hit{fill:#0000}.stage-graph-step-dot{stroke-width:2px;transform-box:fill-box;transform-origin:50%;transition:transform .2s}.stage-graph-step-dot.pending{opacity:.55}.stage-graph-step-dot.running{animation:1.4s ease-in-out infinite stage-graph-pulse}.stage-graph-step-dot.hovered{transform:scale(1.5)}.stage-graph-step-name{fill:var(--text-h);opacity:0;transition:opacity .18s}.stage-graph-step-name.shown{opacity:1}.welcome-dialog{flex-direction:column;width:560px;height:620px;max-height:85vh;position:relative}.welcome-close{text-align:center;width:22px;height:22px;color:var(--text-dim);cursor:pointer;border-radius:4px;line-height:22px;position:absolute;top:10px;right:12px}.welcome-close:hover{background:var(--hover-bg);color:var(--text-h)}.welcome-header{flex:none;padding:20px 24px 0}.welcome-header h2{color:var(--text-h);margin:0 0 4px;font-size:17px}.welcome-progress-bar{gap:6px;margin:14px 0 8px;display:flex}.welcome-progress-segment{background:var(--border);border-radius:999px;flex:1;height:3px}.welcome-progress-segment.filled{background:var(--accent)}.welcome-progress-caption{letter-spacing:.06em;text-transform:uppercase;color:var(--text-dim);font-size:11px}.welcome-body{flex:1;min-height:0;padding:16px 24px;overflow-y:auto}.welcome-body h3{color:var(--text-h);margin:0 0 6px;font-size:15px}.welcome-footer{border-top:1px solid var(--border);flex:none;justify-content:space-between;align-items:center;gap:12px;padding:14px 24px;display:flex}.welcome-footer-nav{gap:8px;display:flex}.welcome-skip{color:var(--text-dim);cursor:pointer;font-size:13px}.welcome-skip:hover{color:var(--text-h);text-decoration:underline}.theme-quickpicks{flex-direction:column;gap:14px;display:flex}.theme-quickpick-grid{grid-template-columns:repeat(3,1fr);gap:10px;display:grid}.theme-quickpick-card{background:var(--bg);border:1px solid var(--border);cursor:pointer;border-radius:6px;flex-direction:column;gap:8px;padding:8px;display:flex}.theme-quickpick-card:hover{border-color:color-mix(in srgb, var(--accent) 45%, var(--border))}.theme-quickpick-card.active{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}.theme-quickpick-preview{border:1px solid;border-radius:4px;align-items:flex-end;gap:4px;height:44px;padding:6px;display:flex}.theme-quickpick-bar{border-radius:999px;flex:1;height:4px}.theme-quickpick-chip{opacity:.7;border-radius:999px;width:16px;height:4px}.theme-quickpick-label{color:var(--text-h);text-align:left;font-size:12px;font-weight:600}.theme-quickpick-editor-section{flex-direction:column;gap:8px;display:flex}.theme-quickpick-mini-grid{border:1px solid var(--border);border-radius:6px;grid-template-columns:repeat(4,1fr);align-content:start;gap:6px;max-height:180px;padding:6px;display:grid;overflow-y:auto}.theme-quickpick-mini-card{background:var(--bg);border:1px solid var(--border);cursor:pointer;border-radius:5px;flex-direction:column;gap:4px;min-width:0;padding:5px;display:flex}.theme-quickpick-mini-card:hover{border-color:color-mix(in srgb, var(--accent) 45%, var(--border))}.theme-quickpick-mini-card.active{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}.theme-quickpick-mini-preview{border:1px solid;border-radius:3px;align-items:flex-end;height:20px;padding:3px;display:flex}.theme-quickpick-mini-bar{border-radius:999px;flex:1;height:3px}.theme-quickpick-mini-label{color:var(--text-dim);text-align:left;white-space:nowrap;text-overflow:ellipsis;font-size:10px;overflow:hidden}.theme-quickpick-mini-card.active .theme-quickpick-mini-label{color:var(--text-h)}.agent-quickpicks{flex-direction:column;gap:10px;display:flex}.agent-quickpick-grid{grid-template-columns:repeat(2,1fr);gap:10px;display:grid}.agent-quickpick-card{background:var(--bg);border:1px solid var(--border);border-radius:6px;flex-direction:column;gap:10px;padding:12px;display:flex}.agent-quickpick-heading{align-items:center;gap:8px;display:flex}.agent-quickpick-logo{border:1px solid var(--border);background:#fff;border-radius:6px;flex:none;justify-content:center;align-items:center;width:24px;height:24px;display:flex}.agent-quickpick-logo img{object-fit:contain;width:16px;height:16px;display:block}.agent-quickpick-actions{align-items:center;gap:8px;display:flex}.agent-quickpick-tracking{color:var(--success);font-size:12px;font-weight:600}.agent-quickpick-manual{border-top:1px solid var(--border);padding-top:10px}.theme-loading{color:var(--muted,inherit);align-items:center;gap:8px;padding:8px 4px;font-size:12px;display:flex}.theme-spinner{border:2px solid;border-right-color:#0000;border-radius:50%;width:12px;height:12px;animation:.7s linear infinite theme-spin}@keyframes theme-spin{to{transform:rotate(360deg)}}.xterm{cursor:text;-webkit-user-select:none;user-select:none;position:relative}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{z-index:5;position:absolute;top:0}.xterm .xterm-helper-textarea{opacity:0;z-index:-5;white-space:nowrap;resize:none;border:0;width:0;height:0;margin:0;padding:0;position:absolute;top:0;left:-9999em;overflow:hidden}.xterm .composition-view{color:#fff;white-space:nowrap;z-index:1;background:#000;display:none;position:absolute}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{cursor:default;background-color:#000;position:absolute;inset:0;overflow-y:scroll}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;top:0;left:0}.xterm-char-measure-element{visibility:hidden;line-height:normal;display:inline-block;position:absolute;top:0;left:-9999em}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{z-index:10;color:#0000;pointer-events:none;position:absolute;inset:0}.xterm .xterm-accessibility-tree:not(.debug) ::selection{color:#0000}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre;font-family:monospace}.xterm .xterm-accessibility-tree>div{transform-origin:0;width:fit-content}.xterm .live-region{width:1px;height:1px;position:absolute;left:-9999px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{-webkit-text-decoration:underline double;text-decoration:underline double}.xterm-underline-3{-webkit-text-decoration:underline wavy;text-decoration:underline wavy}.xterm-underline-4{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.xterm-underline-5{-webkit-text-decoration:underline dashed;text-decoration:underline dashed}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:underline overline}.xterm-overline.xterm-underline-2{-webkit-text-decoration:overline double underline;text-decoration:overline double underline}.xterm-overline.xterm-underline-3{-webkit-text-decoration:overline wavy underline;text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{-webkit-text-decoration:overline dotted underline;text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{-webkit-text-decoration:overline dashed underline;text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;pointer-events:none;position:absolute;top:0;right:0}.xterm-decoration-top{z-index:2;position:relative}.xterm .xterm-scrollable-element>.scrollbar{cursor:default}.xterm .xterm-scrollable-element>.scrollbar>.scra{cursor:pointer;font-size:11px!important}.xterm .xterm-scrollable-element>.visible{opacity:1;z-index:11;background:0 0;transition:opacity .1s linear}.xterm .xterm-scrollable-element>.invisible{opacity:0;pointer-events:none}.xterm .xterm-scrollable-element>.invisible.fade{transition:opacity .8s linear}.xterm .xterm-scrollable-element>.shadow{display:none;position:absolute}.xterm .xterm-scrollable-element>.shadow.top{width:100%;height:3px;box-shadow:var(--vscode-scrollbar-shadow,#000) 0 6px 6px -6px inset;display:block;top:0;left:3px}.xterm .xterm-scrollable-element>.shadow.left{width:3px;height:100%;box-shadow:var(--vscode-scrollbar-shadow,#000) 6px 0 6px -6px inset;display:block;top:3px;left:0}.xterm .xterm-scrollable-element>.shadow.top-left-corner{width:3px;height:3px;display:block;top:0;left:0}.xterm .xterm-scrollable-element>.shadow.top.left{box-shadow:var(--vscode-scrollbar-shadow,#000) 6px 0 6px -6px inset}
|