Update version to 0.1.10
Build / Build & push landing (push) Successful in 14s
Build / Deploy to prod (push) Successful in 7s
Build / Notify Max (push) Successful in 2s

Add deepseek to resume commands

Rename app to spaceshell

Add SurfacePicker component for preset panel configuration

Extract agent selection logic to shared agents.ts

Update landing
This commit is contained in:
2026-06-15 17:25:53 +07:00
parent 333b051e9d
commit 2ee2aaaffb
12 changed files with 151 additions and 46 deletions
+70
View File
@@ -0,0 +1,70 @@
import { useEffect, useState } from "react";
import { whichAgents } from "./socketBridge";
import { KNOWN_AGENTS, SHELL, CUSTOM, agentLabel, specForChoice } from "./agents";
type SlotSpec = { command?: string; args?: string[] };
/**
* Asks what to open in each new panel before a preset spawns it: Terminal
* (shell), one of the installed CLIs (claude/codex/gemini/deepseek), or a
* custom command. `count` is the number of new panels the preset will add.
*/
export function SurfacePicker({ count, onConfirm, onCancel }: { count: number; onConfirm: (specs: SlotSpec[]) => void; onCancel: () => void }) {
const [installed, setInstalled] = useState<string[]>([]);
const [choices, setChoices] = useState<string[]>([]);
const [customCmds, setCustomCmds] = useState<string[]>([]);
const choiceList = [SHELL, ...installed, CUSTOM];
useEffect(() => { void whichAgents(KNOWN_AGENTS).then(setInstalled).catch(() => {}); }, []);
function confirm() {
const specs = Array.from({ length: count }, (_, i) => specForChoice(choices[i] ?? SHELL, customCmds[i] ?? ""));
onConfirm(specs);
}
function onKeyDown(e: React.KeyboardEvent) {
e.stopPropagation();
if (e.key === "Escape") { e.preventDefault(); onCancel(); }
else if (e.key === "Enter" && (e.target as HTMLElement).tagName !== "SELECT") { e.preventDefault(); confirm(); }
}
return (
<div
onMouseDown={onCancel}
style={{ position: "fixed", inset: 0, zIndex: 2000, background: "#000A", display: "flex", alignItems: "center", justifyContent: "center" }}
>
<div
onMouseDown={(e) => e.stopPropagation()}
onKeyDown={onKeyDown}
style={{ width: 420, background: "#0E1116", border: "1px solid #323C49", borderRadius: 14, padding: 24, color: "#E6EDF3" }}
>
<div style={{ fontWeight: 700, fontSize: 16, marginBottom: 4 }}>{count > 1 ? `Open ${count} new panels` : "Open new panel"}</div>
<div style={{ fontSize: 12, color: "#8B97A6", marginBottom: 16 }}>Choose what to run in each new panel.</div>
<div style={{ display: "grid", gridTemplateColumns: count > 1 ? "1fr 1fr" : "1fr", gap: 8, marginBottom: 20 }}>
{Array.from({ length: count }, (_, i) => {
const val = choices[i] ?? SHELL;
return (
<div key={i} style={{ display: "flex", flexDirection: "column", gap: 6 }}>
<select value={val} onChange={(e) => setChoices((c) => { const n = [...c]; n[i] = e.target.value; return n; })}
style={{ padding: 8, background: "#1A2029", color: "#E6EDF3", border: "1px solid #323C49", borderRadius: 6 }}>
{choiceList.map((c) => <option key={c} value={c}>{agentLabel(c)}</option>)}
</select>
{val === CUSTOM && (
<input value={customCmds[i] ?? ""} placeholder="e.g. npm run dev" autoFocus
onChange={(e) => setCustomCmds((c) => { const n = [...c]; n[i] = e.target.value; return n; })}
style={{ padding: 8, background: "#0A0D12", color: "#E6EDF3", border: "1px solid #4C8DFF", borderRadius: 6, fontFamily: "monospace", fontSize: 12 }} />
)}
</div>
);
})}
</div>
<div style={{ display: "flex", justifyContent: "flex-end", gap: 10 }}>
<button onClick={onCancel} style={{ padding: "8px 16px" }}>Cancel</button>
<button onClick={confirm} style={{ padding: "8px 16px", background: "#4C8DFF", color: "#0A0D12", border: "none", borderRadius: 8, fontWeight: 700 }}>
Open
</button>
</div>
</div>
</div>
);
}