git-juggler 0.2.14__tar.gz → 0.2.16__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 (217) hide show
  1. {git_juggler-0.2.14 → git_juggler-0.2.16}/.github/workflows/build-package.yml +28 -3
  2. {git_juggler-0.2.14 → git_juggler-0.2.16}/PKG-INFO +1 -1
  3. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/agents/hook_events.py +45 -0
  4. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/api/ci.py +2 -2
  5. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/api/config.py +22 -12
  6. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/api/git.py +24 -2
  7. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/api/repos.py +1 -1
  8. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/api/terminal.py +1 -1
  9. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/config.py +30 -2
  10. git_juggler-0.2.14/src/backend/git_juggler/frontend_dist/assets/index-BDHOc_fm.js → git_juggler-0.2.16/src/backend/git_juggler/frontend_dist/assets/index-BOaJc15H.js +19 -16
  11. git_juggler-0.2.14/src/backend/git_juggler/frontend_dist/assets/index-D0W6hS5M.css → git_juggler-0.2.16/src/backend/git_juggler/frontend_dist/assets/index-DRsp7rcC.css +1 -1
  12. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/frontend_dist/index.html +2 -2
  13. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/git/command.py +13 -2
  14. git_juggler-0.2.16/src/backend/git_juggler/git/conflicts.py +175 -0
  15. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/git/data.py +30 -3
  16. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/git/refs.py +6 -1
  17. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/git/status.py +5 -1
  18. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/main.py +17 -1
  19. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/repo_discovery.py +39 -20
  20. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/schemas.py +49 -0
  21. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/test_ci_poll_route.py +1 -1
  22. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/test_claude_sessions_data.py +80 -0
  23. git_juggler-0.2.16/src/backend/tests/test_git_conflicts.py +77 -0
  24. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/test_git_data.py +29 -0
  25. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/test_preferences.py +33 -1
  26. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/test_repos.py +31 -1
  27. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/App.css +93 -5
  28. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/App.tsx +43 -11
  29. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/api/client.ts +10 -1
  30. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/api/types.ts +43 -0
  31. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/CommandPalette/CommandPalette.tsx +1 -0
  32. git_juggler-0.2.16/src/frontend/src/components/Conflicts/ConflictsPanel.tsx +267 -0
  33. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/ContextMenu/BranchContextMenu.tsx +16 -1
  34. git_juggler-0.2.16/src/frontend/src/components/ContextMenu/CreateTagModal.tsx +67 -0
  35. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/ContextMenu/RepoContextMenu.tsx +4 -6
  36. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Menu/JenkinsSettings.tsx +33 -20
  37. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Menu/RepoPathsSettings.tsx +69 -44
  38. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Sidebar/RepoList.tsx +2 -7
  39. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Tabs/TabsBar.tsx +0 -6
  40. git_juggler-0.2.16/src/frontend/src/components/Tags/CreateTagDialog.tsx +178 -0
  41. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Terminal/TerminalPanel.tsx +3 -2
  42. git_juggler-0.2.16/src/frontend/src/components/inputs/TemplateInput.tsx +236 -0
  43. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/lib/agentRings.ts +4 -3
  44. git_juggler-0.2.16/src/frontend/src/lib/jenkinsUrlTemplate.ts +80 -0
  45. git_juggler-0.2.16/src/frontend/src/lib/templateTokens.ts +62 -0
  46. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/state/graph.ts +5 -0
  47. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/state/repos.ts +5 -0
  48. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/state/storeCore.ts +122 -9
  49. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/state/terminal.ts +1 -0
  50. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/styles/sidebar.css +0 -7
  51. git_juggler-0.2.16/src/frontend/src/styles/terminal.css +265 -0
  52. git_juggler-0.2.16/src/frontend/tests/templateTokens.test.ts +80 -0
  53. git_juggler-0.2.14/src/frontend/src/components/ContextMenu/CreateTagModal.tsx +0 -129
  54. git_juggler-0.2.14/src/frontend/src/styles/terminal.css +0 -64
  55. {git_juggler-0.2.14 → git_juggler-0.2.16}/.gitignore +0 -0
  56. {git_juggler-0.2.14 → git_juggler-0.2.16}/CLAUDE.md +0 -0
  57. {git_juggler-0.2.14 → git_juggler-0.2.16}/LICENSE +0 -0
  58. {git_juggler-0.2.14 → git_juggler-0.2.16}/README.md +0 -0
  59. {git_juggler-0.2.14 → git_juggler-0.2.16}/notes.txt +0 -0
  60. {git_juggler-0.2.14 → git_juggler-0.2.16}/pyproject.toml +0 -0
  61. {git_juggler-0.2.14 → git_juggler-0.2.16}/resources/git-juggler-demo-1.gif +0 -0
  62. {git_juggler-0.2.14 → git_juggler-0.2.16}/resources/git-juggler-demo-2.png +0 -0
  63. {git_juggler-0.2.14 → git_juggler-0.2.16}/resources/git-juggler-demo-3.png +0 -0
  64. {git_juggler-0.2.14 → git_juggler-0.2.16}/resources/git-juggler-demo-4.png +0 -0
  65. {git_juggler-0.2.14 → git_juggler-0.2.16}/scripts/build-package.sh +0 -0
  66. {git_juggler-0.2.14 → git_juggler-0.2.16}/scripts/dev.sh +0 -0
  67. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/__init__.py +0 -0
  68. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/agents/__init__.py +0 -0
  69. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/agents/hooks.py +0 -0
  70. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/agents/tracking/__init__.py +0 -0
  71. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/agents/tracking/activity_models.py +0 -0
  72. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/agents/tracking/claude_sessions.py +0 -0
  73. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/agents/tracking/claude_transcripts.py +0 -0
  74. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/agents/tracking/git_resolver.py +0 -0
  75. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/agents/tracking/opencode_sessions.py +0 -0
  76. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/agents/tracking/paths.py +0 -0
  77. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/agents/tracking/text.py +0 -0
  78. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/api/__init__.py +0 -0
  79. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/api/agents.py +0 -0
  80. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/app.py +0 -0
  81. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/ci/__init__.py +0 -0
  82. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/ci/github_actions.py +0 -0
  83. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/ci/http.py +0 -0
  84. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/ci/jenkins.py +0 -0
  85. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/ci/run_cache.py +0 -0
  86. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/dev_app.py +0 -0
  87. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/frontend_dist/agent-logos/claude.png +0 -0
  88. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/frontend_dist/agent-logos/opencode.webp +0 -0
  89. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/frontend_dist/ci-logos/jenkins.webp +0 -0
  90. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/frontend_dist/git-juggler.png +0 -0
  91. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/frontend_dist/logo.png +0 -0
  92. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/git/__init__.py +0 -0
  93. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/git/detail.py +0 -0
  94. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/git/diff.py +0 -0
  95. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/git/gitfiles.py +0 -0
  96. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/git/utils.py +0 -0
  97. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/terminal.py +0 -0
  98. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/themes.py +0 -0
  99. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/ui/__init__.py +0 -0
  100. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/git_juggler/ui/folder_picker.py +0 -0
  101. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/conftest.py +0 -0
  102. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/hook_event_files.py +0 -0
  103. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/test_agent_hooks.py +0 -0
  104. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/test_agent_tracking.py +0 -0
  105. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/test_ci.py +0 -0
  106. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/test_ci_poll.py +0 -0
  107. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/test_commit_detail.py +0 -0
  108. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/test_file_diff.py +0 -0
  109. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/test_github_actions.py +0 -0
  110. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/test_jenkins.py +0 -0
  111. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/test_opencode_sessions.py +0 -0
  112. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/test_pick_folder.py +0 -0
  113. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/test_run_cache.py +0 -0
  114. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/backend/tests/test_themes.py +0 -0
  115. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/.gitignore +0 -0
  116. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/README.md +0 -0
  117. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/index.html +0 -0
  118. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/package-lock.json +0 -0
  119. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/package.json +0 -0
  120. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/public/agent-logos/claude.png +0 -0
  121. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/public/agent-logos/opencode.webp +0 -0
  122. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/public/ci-logos/jenkins.webp +0 -0
  123. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/public/git-juggler.png +0 -0
  124. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/public/logo.png +0 -0
  125. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Agents/AgentActivityPanel.tsx +0 -0
  126. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Agents/AgentHoverCard.tsx +0 -0
  127. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Agents/AgentStatusPopover.tsx +0 -0
  128. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Agents/agentFormat.ts +0 -0
  129. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Badges/BranchBadge.tsx +0 -0
  130. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Badges/CiRunBadge.tsx +0 -0
  131. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Badges/StashBadge.tsx +0 -0
  132. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Badges/TagBadge.tsx +0 -0
  133. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Badges/ciStatus.tsx +0 -0
  134. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Branches/DeleteBranchModal.tsx +0 -0
  135. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/CIRunPopover.tsx +0 -0
  136. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Commits/CommitDetail.tsx +0 -0
  137. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Commits/CommitList.tsx +0 -0
  138. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Commits/CommitRow.tsx +0 -0
  139. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Commits/LineCounts.tsx +0 -0
  140. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Commits/UncommittedRow.tsx +0 -0
  141. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/ContextMenu/CommitContextMenu.tsx +0 -0
  142. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/ContextMenu/CreateBranchModal.tsx +0 -0
  143. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/ContextMenu/TagContextMenu.tsx +0 -0
  144. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Diff/FileDiffModal.tsx +0 -0
  145. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Graph/GraphPanel.tsx +0 -0
  146. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Graph/branchColor.ts +0 -0
  147. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Graph/computeColumns.ts +0 -0
  148. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Graph/laneWidth.ts +0 -0
  149. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/GraphToolbar/GraphToolbar.tsx +0 -0
  150. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Menu/AgentQuickPicks.tsx +0 -0
  151. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Menu/AgentsSettings.tsx +0 -0
  152. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Menu/CiPollField.tsx +0 -0
  153. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Menu/DirectoryBrowserModal.tsx +0 -0
  154. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Menu/GitHubSettings.tsx +0 -0
  155. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Menu/MainMenu.tsx +0 -0
  156. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Menu/ThemePicker.tsx +0 -0
  157. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Menu/ThemeQuickPicks.tsx +0 -0
  158. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Pipelines/PipelinesPanel.tsx +0 -0
  159. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Search/MultiSelect.tsx +0 -0
  160. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Search/SearchBox.tsx +0 -0
  161. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Sidebar/NewGroupModal.tsx +0 -0
  162. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Sidebar/Sidebar.tsx +0 -0
  163. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Sidebar/panelKit.tsx +0 -0
  164. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Tags/DeleteTagModal.tsx +0 -0
  165. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/Welcome/WelcomeWizard.tsx +0 -0
  166. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/icons.tsx +0 -0
  167. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/inputs/NumberField.tsx +0 -0
  168. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/inputs/TextField.tsx +0 -0
  169. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/inputs/ToggleField.tsx +0 -0
  170. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/components/inputs/styles.ts +0 -0
  171. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/index.css +0 -0
  172. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/index.tsx +0 -0
  173. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/lib/agentStatusPopoverData.ts +0 -0
  174. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/lib/appTheme.ts +0 -0
  175. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/lib/branchVisibility.ts +0 -0
  176. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/lib/bundledThemes.ts +0 -0
  177. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/lib/ciPoll.ts +0 -0
  178. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/lib/ciRunPopoverData.ts +0 -0
  179. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/lib/color.ts +0 -0
  180. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/lib/dismissOnOutsideClick.ts +0 -0
  181. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/lib/flip.ts +0 -0
  182. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/lib/formatDate.ts +0 -0
  183. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/lib/preferenceSender.ts +0 -0
  184. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/lib/scrollToCommit.ts +0 -0
  185. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/lib/sideBySide.ts +0 -0
  186. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/lib/stageWindow.ts +0 -0
  187. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/lib/stripAnsi.ts +0 -0
  188. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/lib/swallowNextClick.ts +0 -0
  189. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/lib/vscodeTheme.ts +0 -0
  190. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/state/agents.ts +0 -0
  191. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/state/ci.ts +0 -0
  192. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/state/overlayStack.ts +0 -0
  193. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/state/overlays.ts +0 -0
  194. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/state/preferenceSync.ts +0 -0
  195. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/state/preferences.ts +0 -0
  196. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/state/settings.ts +0 -0
  197. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/state/store.ts +0 -0
  198. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/state/tabs.ts +0 -0
  199. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/state/themes.ts +0 -0
  200. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/styles/ci.css +0 -0
  201. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/styles/commits.css +0 -0
  202. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/styles/graph.css +0 -0
  203. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/src/styles/menu.css +0 -0
  204. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/tests/agentFormat.test.ts +0 -0
  205. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/tests/agentRings.test.ts +0 -0
  206. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/tests/agentStatusPopoverData.test.ts +0 -0
  207. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/tests/ciPoll.test.ts +0 -0
  208. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/tests/ciRunPopoverData.test.ts +0 -0
  209. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/tests/computeColumns.test.ts +0 -0
  210. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/tests/preferenceSender.test.ts +0 -0
  211. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/tests/sideBySide.test.ts +0 -0
  212. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/tests/stageWindow.test.ts +0 -0
  213. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/tests/vscodeTheme.test.ts +0 -0
  214. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/tsconfig.app.json +0 -0
  215. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/tsconfig.json +0 -0
  216. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/tsconfig.node.json +0 -0
  217. {git_juggler-0.2.14 → git_juggler-0.2.16}/src/frontend/vite.config.ts +0 -0
@@ -17,7 +17,7 @@ jobs:
17
17
  fi
18
18
  fi
19
19
 
20
- build:
20
+ test:
21
21
  needs: validate-tag
22
22
  runs-on: ubuntu-latest
23
23
 
@@ -39,8 +39,8 @@ jobs:
39
39
  with:
40
40
  python-version: "3.12"
41
41
 
42
- - name: Install Python build tooling
43
- run: python -m pip install --upgrade pip build pytest
42
+ - name: Install Python test tooling
43
+ run: python -m pip install --upgrade pip pytest
44
44
 
45
45
  - name: Install backend test dependencies
46
46
  run: python -m pip install -e .
@@ -56,6 +56,31 @@ jobs:
56
56
  working-directory: src/frontend
57
57
  run: npm test
58
58
 
59
+ build:
60
+ needs: test
61
+ runs-on: ubuntu-latest
62
+
63
+ steps:
64
+ - name: Check out repository
65
+ uses: actions/checkout@v4
66
+ with:
67
+ fetch-depth: 0 # full history + tags, needed by hatch-vcs to derive the version
68
+
69
+ - name: Set up Node.js
70
+ uses: actions/setup-node@v4
71
+ with:
72
+ node-version: 22
73
+ cache: npm
74
+ cache-dependency-path: src/frontend/package-lock.json
75
+
76
+ - name: Set up Python
77
+ uses: actions/setup-python@v5
78
+ with:
79
+ python-version: "3.12"
80
+
81
+ - name: Install Python build tooling
82
+ run: python -m pip install --upgrade pip build
83
+
59
84
  - name: Build frontend and Python distributions
60
85
  run: ./scripts/build-package.sh
61
86
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: git-juggler
3
- Version: 0.2.14
3
+ Version: 0.2.16
4
4
  Summary: A local Git dashboard for people juggling agents and too many repos.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -230,6 +230,8 @@ class AgentHookEventReader:
230
230
  self._cached_signature: tuple | None = None
231
231
  self._cached_scans: list[AgentRepositoryScan] = []
232
232
  self._cached_valid_until = 0
233
+ self._last_active_heads: dict[tuple[tuple[str, str], str], GitWorktreeInfo] = {}
234
+ self._idle_heads: dict[tuple[tuple[str, str], str], GitWorktreeInfo] = {}
233
235
 
234
236
  def recent_scans(self, now: int | None = None) -> list[AgentRepositoryScan]:
235
237
  """One scan per open agent session.
@@ -294,6 +296,7 @@ class AgentHookEventReader:
294
296
  opencode_signature = db_signature(self.opencode_db_path)
295
297
  scans: list[AgentRepositoryScan] = []
296
298
  next_change: int | None = None
299
+ seen_head_keys: set[tuple[tuple[str, str], str]] = set()
297
300
  for session_key, session_events in sessions.items():
298
301
  # A session is open until its end hook fires (or its process dies),
299
302
  # however long it has been quiet.
@@ -374,6 +377,8 @@ class AgentHookEventReader:
374
377
  elif busy is True:
375
378
  # Working right now: the most recently touched worktree is where.
376
379
  max(activities.values(), key=lambda item: item.last_activity).state = "active"
380
+ for activity in activities.values():
381
+ self._pin_idle_head(session_key, activity, seen_head_keys)
377
382
  details: SessionDetails | None = None
378
383
  if card is not None and session_id:
379
384
  details = self._session_details(card, session_id)
@@ -404,8 +409,48 @@ class AgentHookEventReader:
404
409
  parent_provider=provider if parent_opencode_session is not None else None,
405
410
  )
406
411
  )
412
+ self._prune_head_state(seen_head_keys)
407
413
  return scans, next_change
408
414
 
415
+ def _pin_idle_head(
416
+ self,
417
+ session_key: tuple[str, str],
418
+ activity: AgentWorktreeActivity,
419
+ seen: set[tuple[tuple[str, str], str]],
420
+ ) -> None:
421
+ key = (session_key, activity.worktree_path)
422
+ seen.add(key)
423
+ current = self._worktree_info(activity)
424
+ if activity.state == "active":
425
+ self._idle_heads.pop(key, None)
426
+ self._last_active_heads[key] = current
427
+ return
428
+
429
+ anchor = self._idle_heads.get(key)
430
+ if anchor is None or anchor.repository_id != activity.repository_id:
431
+ anchor = self._last_active_heads.get(key)
432
+ if anchor is None or anchor.repository_id != activity.repository_id:
433
+ anchor = current
434
+ self._idle_heads[key] = anchor
435
+ activity.branch = anchor.branch
436
+ activity.commit = anchor.commit
437
+
438
+ def _prune_head_state(self, seen: set[tuple[tuple[str, str], str]]) -> None:
439
+ for cache in (self._last_active_heads, self._idle_heads):
440
+ for key in list(cache):
441
+ if key not in seen:
442
+ del cache[key]
443
+
444
+ @staticmethod
445
+ def _worktree_info(activity: AgentWorktreeActivity) -> GitWorktreeInfo:
446
+ return GitWorktreeInfo(
447
+ repository_id=activity.repository_id,
448
+ worktree_path=activity.worktree_path,
449
+ common_git_dir=activity.repository_id,
450
+ branch=activity.branch,
451
+ commit=activity.commit,
452
+ )
453
+
409
454
  @staticmethod
410
455
  def _since_last_prompt(events: list[HookEvent]) -> list[HookEvent]:
411
456
  """The session's events from its latest user prompt onward.
@@ -25,7 +25,7 @@ router = APIRouter()
25
25
 
26
26
 
27
27
  def _resolve_repo_path(repo_id: str) -> Path:
28
- path = resolve_repo_path(config.load_repo_paths(), repo_id)
28
+ path = resolve_repo_path(config.load_repo_paths(), repo_id, config.load_individual_repo_paths())
29
29
  if path is None:
30
30
  raise HTTPException(status_code=404, detail="repo not found")
31
31
  return path
@@ -82,7 +82,7 @@ def api_ci_stages(repo_id: str, provider: str, run_id: str) -> list[CiStage]:
82
82
  @router.get("/api/ci/recent", response_model=list[ActivePipeline])
83
83
  def api_ci_recent(limit: Annotated[int, Query(ge=1, le=50)] = 10) -> list[ActivePipeline]:
84
84
  return get_recent_pipelines(
85
- list_repos(config.load_repo_paths()),
85
+ list_repos(config.load_repo_paths(), config.load_individual_repo_paths()),
86
86
  config.load_github_config(),
87
87
  config.load_jenkins_config(),
88
88
  limit,
@@ -25,6 +25,7 @@ def create_config_router(root_path: Path) -> APIRouter:
25
25
  def _current_config() -> ConfigResponse:
26
26
  return ConfigResponse(
27
27
  repo_paths=[str(p) for p in config.load_repo_paths()],
28
+ individual_repo_paths=[str(p) for p in config.load_individual_repo_paths()],
28
29
  pinned_repo_paths=config.load_pinned_repo_paths(),
29
30
  repo_groups=config.load_repo_groups(),
30
31
  excluded_paths=config.load_excluded_paths(),
@@ -32,27 +33,36 @@ def create_config_router(root_path: Path) -> APIRouter:
32
33
  github=config.load_github_config(),
33
34
  jenkins=config.load_jenkins_config(),
34
35
  terminal_shell=terminal_shell,
36
+ terminal_helper_command=[sys.executable, "-m", "git_juggler.main"],
35
37
  )
36
38
 
37
39
  @router.get("/api/config", response_model=ConfigResponse)
38
40
  def api_get_config() -> ConfigResponse:
39
41
  return _current_config()
40
42
 
43
+ def _resolve_dirs(raw_paths: list[str], *, require_git: bool) -> list[Path]:
44
+ resolved: list[Path] = []
45
+ seen: set[str] = set()
46
+ for raw in raw_paths:
47
+ path = Path(raw).expanduser().resolve()
48
+ if not path.is_dir():
49
+ raise HTTPException(status_code=400, detail=f"not a directory: {raw}")
50
+ if require_git and not (path / ".git").exists():
51
+ raise HTTPException(status_code=400, detail=f"not a git repository: {raw}")
52
+ key = str(path)
53
+ if key in seen:
54
+ continue
55
+ seen.add(key)
56
+ resolved.append(path)
57
+ return resolved
58
+
41
59
  @router.put("/api/config", response_model=ConfigResponse)
42
60
  def api_update_config(body: ConfigUpdateRequest) -> ConfigResponse:
43
61
  if body.repo_paths is not None:
44
- resolved: list[Path] = []
45
- seen: set[str] = set()
46
- for raw in body.repo_paths:
47
- path = Path(raw).expanduser().resolve()
48
- if not path.is_dir():
49
- raise HTTPException(status_code=400, detail=f"not a directory: {raw}")
50
- key = str(path)
51
- if key in seen:
52
- continue
53
- seen.add(key)
54
- resolved.append(path)
55
- config.save_repo_paths(resolved)
62
+ config.save_repo_paths(_resolve_dirs(body.repo_paths, require_git=False))
63
+
64
+ if body.individual_repo_paths is not None:
65
+ config.save_individual_repo_paths(_resolve_dirs(body.individual_repo_paths, require_git=True))
56
66
 
57
67
  if body.pinned_repo_paths is not None:
58
68
  deduped = list(dict.fromkeys(body.pinned_repo_paths))
@@ -5,18 +5,19 @@ from pathlib import Path
5
5
  from fastapi import APIRouter, HTTPException, Query
6
6
 
7
7
  from .. import config
8
+ from ..git.conflicts import create_resolution_token, get_conflict_file_content
8
9
  from ..git.data import GRAPH_PAGE_SIZE, HistoryChangedError, get_graph, get_repo_status
9
10
  from ..git.detail import get_commit_detail
10
11
  from ..git.diff import get_commit_file_diff, get_working_file_diff
11
12
  from ..repo_discovery import resolve_repo_path
12
- from ..schemas import CommitDetail, FileDiff, GraphResponse, RepoStatusResponse
13
+ from ..schemas import CommitDetail, ConflictFileContent, ConflictResolutionTokenRequest, ConflictResolutionTokenResponse, FileDiff, GraphResponse, RepoStatusResponse
13
14
 
14
15
 
15
16
  router = APIRouter()
16
17
 
17
18
 
18
19
  def _resolve_repo_path(repo_id: str) -> Path:
19
- path = resolve_repo_path(config.load_repo_paths(), repo_id)
20
+ path = resolve_repo_path(config.load_repo_paths(), repo_id, config.load_individual_repo_paths())
20
21
  if path is None:
21
22
  raise HTTPException(status_code=404, detail="repo not found")
22
23
  return path
@@ -75,3 +76,24 @@ def api_working_file_diff(
75
76
  return get_working_file_diff(repo_path, path, old_path, full)
76
77
  except Exception as exc: # noqa: BLE001 - surfaced as a 404 either way
77
78
  raise HTTPException(status_code=404, detail="diff not found") from exc
79
+
80
+
81
+ @router.get("/api/repos/{repo_id}/conflicts/content", response_model=ConflictFileContent)
82
+ def api_conflict_file_content(repo_id: str, path: str) -> ConflictFileContent:
83
+ repo_path = _resolve_repo_path(repo_id)
84
+ try:
85
+ return get_conflict_file_content(repo_path, path)
86
+ except Exception as exc: # noqa: BLE001 - surfaced as a 404 either way
87
+ raise HTTPException(status_code=404, detail="conflict file not found") from exc
88
+
89
+
90
+ @router.post("/api/repos/{repo_id}/conflicts/resolution-token", response_model=ConflictResolutionTokenResponse)
91
+ def api_create_conflict_resolution_token(
92
+ repo_id: str, request: ConflictResolutionTokenRequest
93
+ ) -> ConflictResolutionTokenResponse:
94
+ if request.repo_id != repo_id:
95
+ raise HTTPException(status_code=400, detail="repo mismatch")
96
+ _resolve_repo_path(repo_id)
97
+ return ConflictResolutionTokenResponse(
98
+ token=create_resolution_token(repo_id, request.path, request.content)
99
+ )
@@ -13,7 +13,7 @@ router = APIRouter()
13
13
 
14
14
  @router.get("/api/repos", response_model=list[RepoSummary])
15
15
  def api_list_repos() -> list[RepoSummary]:
16
- return list_repos(config.load_repo_paths())
16
+ return list_repos(config.load_repo_paths(), config.load_individual_repo_paths())
17
17
 
18
18
 
19
19
  @router.get("/api/repos/scan-progress", response_model=RepoScanProgress)
@@ -17,7 +17,7 @@ def create_terminal_router(root_path: Path) -> APIRouter:
17
17
  repo_id = websocket.query_params.get("repo")
18
18
  cwd = root_path
19
19
  if repo_id:
20
- resolved = resolve_repo_path(config.load_repo_paths(), repo_id)
20
+ resolved = resolve_repo_path(config.load_repo_paths(), repo_id, config.load_individual_repo_paths())
21
21
  if resolved is None:
22
22
  await websocket.close(code=1008)
23
23
  return
@@ -5,11 +5,22 @@ from pathlib import Path
5
5
 
6
6
  from pydantic import ValidationError
7
7
 
8
- from .schemas import Preferences
8
+ from .schemas import JenkinsConfig, JenkinsRuleConfig, Preferences
9
9
 
10
10
  CONFIG_DIR = Path.home() / ".config" / "git-juggler"
11
11
  CONFIG_PATH = CONFIG_DIR / "config.json"
12
12
 
13
+ # A fresh install ships one example Jenkins rule so the settings show how
14
+ # pipeline URL placeholders work. It has no repos, so nothing is ever requested
15
+ # from the placeholder host until the user assigns some. The settings' "Add
16
+ # rule" starts from the same rule (lib/jenkinsUrlTemplate.ts in the frontend).
17
+ EXAMPLE_JENKINS_BASE_URL = "https://jenkins.example.com"
18
+ EXAMPLE_JENKINS_RULE = JenkinsRuleConfig(
19
+ id="example",
20
+ name="Example rule",
21
+ job_url=EXAMPLE_JENKINS_BASE_URL + "/job/{repo_name_url}/job/{branch_name_url}",
22
+ )
23
+
13
24
 
14
25
  def _load_raw() -> dict:
15
26
  if not CONFIG_PATH.exists():
@@ -41,6 +52,21 @@ def save_repo_paths(paths: list[Path]) -> None:
41
52
  _save_raw(data)
42
53
 
43
54
 
55
+ def load_individual_repo_paths() -> list[Path]:
56
+ """Repos the user added one by one, on top of whatever the search paths
57
+ (``repo_paths``) turn up."""
58
+ raw = _load_raw().get("individual_repo_paths", [])
59
+ if not isinstance(raw, list):
60
+ return []
61
+ return [Path(p) for p in raw if isinstance(p, str)]
62
+
63
+
64
+ def save_individual_repo_paths(paths: list[Path]) -> None:
65
+ data = _load_raw()
66
+ data["individual_repo_paths"] = [str(p) for p in paths]
67
+ _save_raw(data)
68
+
69
+
44
70
  def load_pinned_repo_paths() -> list[str]:
45
71
  raw = _load_raw().get("pinned_repo_paths", [])
46
72
  if not isinstance(raw, list):
@@ -201,10 +227,12 @@ def save_jenkins_config(jenkins: dict | None) -> None:
201
227
 
202
228
  def ensure_seeded(default_path: Path) -> None:
203
229
  """On first run (no config file yet), seed it with the CLI-provided path
204
- so existing single-path usage keeps working without extra setup."""
230
+ so existing single-path usage keeps working without extra setup, plus the
231
+ example Jenkins rule."""
205
232
  if CONFIG_PATH.exists():
206
233
  return
207
234
  save_repo_paths([default_path])
235
+ save_jenkins_config(JenkinsConfig(base_url=EXAMPLE_JENKINS_BASE_URL, rules=[EXAMPLE_JENKINS_RULE]).model_dump())
208
236
 
209
237
 
210
238
  def reset_to_factory(default_path: Path) -> None: