feat: add open in Cursor button with SSH remote support

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
centdix
2026-02-21 21:19:34 +00:00
parent 5c1f69ddcd
commit 83f21510f3
5 changed files with 92 additions and 1 deletions
+1
View File
@@ -178,6 +178,7 @@ async function handleApi(req: Request, url: URL): Promise<Response> {
]);
return {
...wt,
dir: wtDir ?? null,
status: st?.status ?? "",
elapsed: st?.elapsed ?? "",
title: st?.title ?? "",
+16
View File
@@ -5,6 +5,7 @@
import Terminal from "./lib/Terminal.svelte";
import ConfirmDialog from "./lib/ConfirmDialog.svelte";
import CreateWorktreeDialog from "./lib/CreateWorktreeDialog.svelte";
import SettingsDialog from "./lib/SettingsDialog.svelte";
import type { WorktreeInfo } from "./lib/types";
import type { Profile, Agent } from "./lib/api";
import * as api from "./lib/api";
@@ -13,8 +14,11 @@
let selectedBranch = $state<string | null>(null);
let removeBranch = $state<string | null>(null);
let removingBranches = $state<Set<string>>(new Set());
const SSH_STORAGE_KEY = "wt-ssh-host";
let showCreateDialog = $state(false);
let showSettingsDialog = $state(false);
let creating = $state(false);
let sshHost = $state(localStorage.getItem(SSH_STORAGE_KEY) ?? "");
let visibleWorktrees = $derived(
worktrees.filter((w) => w.path === "(here)" || w.branch === "main" || w.mux === "✓")
@@ -145,6 +149,10 @@
<div class="flex justify-between"><span>Navigate</span><kbd class="opacity-60">Cmd+Up/Down</kbd></div>
<div class="flex justify-between"><span>New worktree</span><kbd class="opacity-60">Cmd+K</kbd></div>
<div class="flex justify-between"><span>Remove</span><kbd class="opacity-60">Cmd+D</kbd></div>
<button
class="mt-1 text-[11px] text-muted hover:text-accent cursor-pointer text-left bg-transparent border-none p-0"
onclick={() => (showSettingsDialog = true)}
>Settings</button>
</div>
</aside>
@@ -152,6 +160,7 @@
<TopBar
name={selectedBranch}
worktree={selectedWorktree}
{sshHost}
onremove={() => { if (selectedBranch) removeBranch = selectedBranch; }}
/>
@@ -188,3 +197,10 @@
oncancel={() => (removeBranch = null)}
/>
{/if}
{#if showSettingsDialog}
<SettingsDialog
onsave={(host) => { sshHost = host; showSettingsDialog = false; }}
onclose={() => (showSettingsDialog = false)}
/>
{/if}
@@ -0,0 +1,56 @@
<script lang="ts">
const STORAGE_KEY = "wt-ssh-host";
let { onsave, onclose }: {
onsave: (sshHost: string) => void;
onclose: () => void;
} = $props();
let sshHost = $state(localStorage.getItem(STORAGE_KEY) ?? "");
let dialogEl: HTMLDialogElement;
$effect(() => {
dialogEl?.showModal();
});
function handleSave() {
const trimmed = sshHost.trim();
if (trimmed) {
localStorage.setItem(STORAGE_KEY, trimmed);
} else {
localStorage.removeItem(STORAGE_KEY);
}
onsave(trimmed);
}
const btn = "px-3 py-1.5 rounded-md border border-edge bg-surface text-primary text-xs cursor-pointer hover:bg-hover";
</script>
<dialog bind:this={dialogEl} onclose={onclose} class="bg-sidebar text-primary border border-edge rounded-xl p-6 max-w-[380px] w-[90%]">
<form method="dialog" onsubmit={(e) => { e.preventDefault(); handleSave(); }}>
<h2 class="text-base mb-4">Settings</h2>
<div class="mb-4">
<label class="block text-xs text-muted mb-1.5" for="ssh-host">
SSH Host <span class="opacity-60">(for "Open in Cursor")</span>
</label>
<input
id="ssh-host"
type="text"
class="w-full px-2.5 py-1.5 rounded-md border border-edge bg-surface text-primary text-[13px] placeholder:text-muted/50 outline-none focus:border-accent"
placeholder="e.g. devbox or 10.0.0.5"
bind:value={sshHost}
autofocus
/>
<p class="text-[11px] text-muted mt-1.5">
Must match an entry in your local <code class="text-accent/80">~/.ssh/config</code>. Leave empty for local mode.
</p>
</div>
<div class="flex justify-end gap-2">
<button type="button" class={btn} onclick={onclose}>Cancel</button>
<button
type="submit"
class="{btn} !bg-accent !text-white !border-accent hover:!opacity-90"
>Save</button>
</div>
</form>
</dialog>
+18 -1
View File
@@ -1,12 +1,22 @@
<script lang="ts">
import type { WorktreeInfo } from "./types";
let { name, worktree, onremove }: {
let { name, worktree, sshHost, onremove }: {
name: string | null;
worktree: WorktreeInfo | undefined;
sshHost: string;
onremove: () => void;
} = $props();
let cursorUrl = $derived.by(() => {
const dir = worktree?.dir;
if (!dir) return null;
if (sshHost) {
return `cursor://vscode-remote/ssh-remote+${sshHost}${dir}`;
}
return `cursor://file${dir}`;
});
const btn = "px-3 py-1.5 rounded-md border border-edge bg-surface text-primary text-xs cursor-pointer hover:bg-hover";
</script>
@@ -29,6 +39,13 @@
class="text-[11px] px-1.5 py-0.5 rounded border font-mono no-underline hover:opacity-80 {worktree.frontendRunning ? 'text-success border-success/40' : 'text-muted border-edge pointer-events-none'}"
>FE :{worktree.frontendPort}</a>
{/if}
{#if cursorUrl}
<a
href={cursorUrl}
class="text-[11px] px-1.5 py-0.5 rounded border border-accent/40 text-accent font-mono no-underline hover:opacity-80"
title="Open in Cursor"
>Cursor</a>
{/if}
</div>
{#if name}
<div class="flex gap-2 items-center">
+1
View File
@@ -3,6 +3,7 @@ export interface WorktreeInfo {
agent: string;
mux: string;
path: string;
dir: string | null;
status: string;
elapsed: string;
title: string;