feat: Add quick action to open repository in browser

Add "Open Repository in Browser" to the Quick Actions menu (Cmd+K) for
git repositories. Supports GitHub, GitLab, Bitbucket, and other common
hosts by parsing SSH and HTTPS remote URLs.

Claude ID: ac8e7811-8742-4991-b9ce-9c03629b8288
Maestro ID: 5a166b38-b7e9-47f0-a8ff-0113c65f2682
This commit is contained in:
Pedram Amini
2025-11-28 17:56:57 -06:00
parent ab02fab0b8
commit ecf92ffa12
2 changed files with 62 additions and 0 deletions

View File

@@ -220,6 +220,14 @@ export function QuickActionsModal(props: QuickActionsModalProps) {
setQuickActionOpen(false);
} }] : []),
...(activeSession?.isGitRepo ? [{ id: 'gitLog', label: 'View Git Log', shortcut: shortcuts.viewGitLog, action: () => { setGitLogOpen(true); setQuickActionOpen(false); } }] : []),
...(activeSession?.isGitRepo ? [{ id: 'openRepo', label: 'Open Repository in Browser', action: async () => {
const cwd = activeSession.inputMode === 'terminal' ? (activeSession.shellCwd || activeSession.cwd) : activeSession.cwd;
const browserUrl = await gitService.getRemoteBrowserUrl(cwd);
if (browserUrl) {
window.maestro.shell.openExternal(browserUrl);
}
setQuickActionOpen(false);
} }] : []),
{ id: 'devtools', label: 'Toggle JavaScript Console', action: () => { window.maestro.devtools.toggle(); setQuickActionOpen(false); } },
{ id: 'about', label: 'About Maestro', action: () => { setAboutModalOpen(true); setQuickActionOpen(false); } },
{ id: 'goToFiles', label: 'Go to Files Tab', action: () => { setRightPanelOpen(true); setActiveRightTab('files'); setQuickActionOpen(false); } },

View File

@@ -22,6 +22,43 @@ export interface GitNumstat {
}>;
}
/**
* Convert a git remote URL to a browser-friendly URL
* Supports GitHub, GitLab, Bitbucket, and other common hosts
*/
function remoteUrlToBrowserUrl(remoteUrl: string): string | null {
if (!remoteUrl) return null;
let url = remoteUrl.trim();
// Handle SSH format: git@github.com:user/repo.git
if (url.startsWith('git@')) {
// git@github.com:user/repo.git -> https://github.com/user/repo
url = url
.replace(/^git@/, 'https://')
.replace(/:([^/])/, '/$1') // Replace first : with / (but not :// from https)
.replace(/\.git$/, '');
return url;
}
// Handle HTTPS format: https://github.com/user/repo.git
if (url.startsWith('https://') || url.startsWith('http://')) {
url = url.replace(/\.git$/, '');
return url;
}
// Handle SSH format without git@: ssh://git@github.com/user/repo.git
if (url.startsWith('ssh://')) {
url = url
.replace(/^ssh:\/\/git@/, 'https://')
.replace(/^ssh:\/\//, 'https://')
.replace(/\.git$/, '');
return url;
}
return null;
}
export const gitService = {
/**
* Check if a directory is a git repository
@@ -115,5 +152,22 @@ export const gitService = {
console.error('Git numstat error:', error);
return { files: [] };
}
},
/**
* Get the browser-friendly URL for the remote repository
* Returns null if no remote or URL cannot be parsed
*/
async getRemoteBrowserUrl(cwd: string): Promise<string | null> {
try {
const result = await window.maestro.git.remote(cwd);
if (result.stdout) {
return remoteUrlToBrowserUrl(result.stdout);
}
return null;
} catch (error) {
console.error('Git remote error:', error);
return null;
}
}
};