diff --git a/src/components/command-palette/CommandPalette.tsx b/src/components/command-palette/CommandPalette.tsx
index 3634fe35..2c67271c 100644
--- a/src/components/command-palette/CommandPalette.tsx
+++ b/src/components/command-palette/CommandPalette.tsx
@@ -1,11 +1,14 @@
import * as React from 'react';
import { useNavigate } from 'react-router-dom';
import {
+ ArrowDownToLine,
+ ArrowUpFromLine,
Bell,
Bot,
FileText,
GitBranch,
GitCommit,
+ GitMerge,
Info,
KeyRound,
ListChecks,
@@ -13,6 +16,7 @@ import {
MessageSquarePlus,
Palette,
Plug,
+ RefreshCw,
Settings,
SunMoon,
} from 'lucide-react';
@@ -35,6 +39,8 @@ import { useSessionsSource } from './sources/useSessionsSource';
import { useFilesSource } from './sources/useFilesSource';
import { useCommitsSource } from './sources/useCommitsSource';
import { useSessionMessageSearch } from './sources/useSessionMessageSearch';
+import { useBranchesSource } from './sources/useBranchesSource';
+import { useGitActions } from './sources/useGitActions';
type Mode = 'mixed' | 'actions' | 'files' | 'commits';
@@ -104,6 +110,8 @@ export default function CommandPalette({
const { items: messageMatches } = useSessionMessageSearch(projectId, query, open && mode === 'mixed');
const { items: files } = useFilesSource(projectId, open && (mode === 'mixed' || mode === 'files'));
const { items: commits } = useCommitsSource(projectId, open && (mode === 'mixed' || mode === 'commits'));
+ const { items: branches } = useBranchesSource(projectId, open && (mode === 'mixed' || mode === 'actions'));
+ const git = useGitActions(projectId);
const showActions = mode === 'mixed' || mode === 'actions';
const showSessions = mode === 'mixed';
@@ -206,6 +214,45 @@ export default function CommandPalette({
)}
+ {showActions && projectId && (
+
+ run(() => { void git.fetch(); onShowTab?.('git'); })}
+ >
+
+ Git: Fetch
+
+ run(() => { void git.pull(); onShowTab?.('git'); })}
+ >
+
+ Git: Pull
+
+ run(() => { void git.push(); onShowTab?.('git'); })}
+ >
+
+ Git: Push
+
+ {branches
+ .filter((b) => !b.isCurrent && !b.isRemote)
+ .slice(0, 30)
+ .map((b) => (
+ run(() => { void git.checkout(b.name); onShowTab?.('git'); })}
+ >
+
+ Switch to branch: {b.name}
+
+ ))}
+
+ )}
+
{showActions && (
{SETTINGS_TABS.map(({ id, label, keywords, icon: Icon }) => (
diff --git a/src/components/command-palette/sources/useBranchesSource.ts b/src/components/command-palette/sources/useBranchesSource.ts
new file mode 100644
index 00000000..3e276a4a
--- /dev/null
+++ b/src/components/command-palette/sources/useBranchesSource.ts
@@ -0,0 +1,47 @@
+import { useEffect, useState } from 'react';
+
+import { authenticatedFetch } from '../../../utils/api';
+
+export type BranchResult = {
+ name: string;
+ isCurrent: boolean;
+ isRemote: boolean;
+};
+
+interface BranchesResponse {
+ branches?: Array<{ name: string; current?: boolean; isRemote?: boolean }>;
+}
+
+export function useBranchesSource(projectId: string | undefined, enabled: boolean) {
+ const [items, setItems] = useState([]);
+
+ useEffect(() => {
+ if (!enabled || !projectId) {
+ setItems([]);
+ return;
+ }
+ let cancelled = false;
+ const params = new URLSearchParams({ project: projectId });
+ authenticatedFetch(`/api/git/branches?${params.toString()}`)
+ .then((r) => r.json() as Promise)
+ .then((data) => {
+ if (cancelled) return;
+ const list = data.branches ?? [];
+ setItems(
+ list.map((b) => ({
+ name: b.name,
+ isCurrent: Boolean(b.current),
+ isRemote: Boolean(b.isRemote),
+ })),
+ );
+ })
+ .catch(() => {
+ if (!cancelled) setItems([]);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [projectId, enabled]);
+
+ return { items };
+}
diff --git a/src/components/command-palette/sources/useGitActions.ts b/src/components/command-palette/sources/useGitActions.ts
new file mode 100644
index 00000000..cf765f34
--- /dev/null
+++ b/src/components/command-palette/sources/useGitActions.ts
@@ -0,0 +1,38 @@
+import { useCallback } from 'react';
+
+import { authenticatedFetch } from '../../../utils/api';
+
+async function postGit(path: string, body: Record) {
+ const res = await authenticatedFetch(path, {
+ method: 'POST',
+ body: JSON.stringify(body),
+ });
+ return res.json();
+}
+
+export function useGitActions(projectId: string | undefined) {
+ const fetch = useCallback(() => {
+ if (!projectId) return Promise.resolve();
+ return postGit('/api/git/fetch', { project: projectId });
+ }, [projectId]);
+
+ const pull = useCallback(() => {
+ if (!projectId) return Promise.resolve();
+ return postGit('/api/git/pull', { project: projectId });
+ }, [projectId]);
+
+ const push = useCallback(() => {
+ if (!projectId) return Promise.resolve();
+ return postGit('/api/git/push', { project: projectId });
+ }, [projectId]);
+
+ const checkout = useCallback(
+ (branch: string) => {
+ if (!projectId) return Promise.resolve();
+ return postGit('/api/git/checkout', { project: projectId, branch });
+ },
+ [projectId],
+ );
+
+ return { fetch, pull, push, checkout };
+}