From 2ee2aaaffb8bd53fe5f97c71d2eacc7eb074b25a Mon Sep 17 00:00:00 2001 From: Vassiliy Yegorov Date: Mon, 15 Jun 2026 17:25:53 +0700 Subject: [PATCH] Update version to 0.1.10 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 --- Cargo.lock | 10 +-- Cargo.toml | 2 +- .../plans/2026-06-15-session-persistence.md | 1 + Makefile | 20 +++--- app/src-tauri/Cargo.lock | 2 +- app/src-tauri/tauri.conf.json | 6 +- app/src/App.tsx | 24 ++++++- app/src/SurfacePicker.tsx | 70 +++++++++++++++++++ app/src/TopBar.tsx | 2 +- app/src/Wizard.tsx | 21 ++---- app/src/agents.ts | 22 ++++++ landing/index.html | 17 ++--- 12 files changed, 151 insertions(+), 46 deletions(-) create mode 100644 app/src/SurfacePicker.tsx create mode 100644 app/src/agents.ts diff --git a/Cargo.lock b/Cargo.lock index 7ae97e0..6789cee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -869,7 +869,7 @@ dependencies = [ [[package]] name = "spacesh-cli" -version = "0.1.7" +version = "0.1.10" dependencies = [ "anyhow", "clap", @@ -881,7 +881,7 @@ dependencies = [ [[package]] name = "spacesh-core" -version = "0.1.7" +version = "0.1.10" dependencies = [ "alacritty_terminal", "serde", @@ -891,7 +891,7 @@ dependencies = [ [[package]] name = "spacesh-proto" -version = "0.1.7" +version = "0.1.10" dependencies = [ "bytes", "serde", @@ -903,7 +903,7 @@ dependencies = [ [[package]] name = "spacesh-pty" -version = "0.1.7" +version = "0.1.10" dependencies = [ "anyhow", "bytes", @@ -913,7 +913,7 @@ dependencies = [ [[package]] name = "spaceshd" -version = "0.1.7" +version = "0.1.10" dependencies = [ "anyhow", "base64", diff --git a/Cargo.toml b/Cargo.toml index 2a8a8e8..25fc59e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ members = [ [workspace.package] edition = "2021" -version = "0.1.7" +version = "0.1.10" [workspace.dependencies] tokio = { version = "1", features = ["full"] } diff --git a/DOCS/superpowers/plans/2026-06-15-session-persistence.md b/DOCS/superpowers/plans/2026-06-15-session-persistence.md index abb094f..ad9516d 100644 --- a/DOCS/superpowers/plans/2026-06-15-session-persistence.md +++ b/DOCS/superpowers/plans/2026-06-15-session-persistence.md @@ -318,6 +318,7 @@ In `crates/spaceshd/src/config.rs`, add the struct and a default table, and exte const DEFAULT_RESUME: &[(&str, &[&str])] = &[ ("claude", &["--continue"]), ("codex", &["resume"]), + ("deepseek", &["resume"]), ]; #[derive(Debug, Clone, Default, Deserialize, Serialize)] diff --git a/Makefile b/Makefile index 99d060c..6a83618 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,8 @@ NATIVE_DMG_DIR := $(APP_DIR)/src-tauri/target/release/bundle/dmg NATIVE_TRIPLE := $(shell rustc -vV 2>/dev/null | awk '/^host:/{print $$2}') SIDECAR_DIR := $(APP_DIR)/src-tauri/bin BUNDLE_CONFIG := src-tauri/tauri.bundle.conf.json -APP_BUNDLE := $(APP_DIR)/src-tauri/target/$(TAURI_TARGET)/release/bundle/macos/spacesh.app -NATIVE_APP_BUNDLE := $(APP_DIR)/src-tauri/target/release/bundle/macos/spacesh.app +APP_BUNDLE := $(APP_DIR)/src-tauri/target/$(TAURI_TARGET)/release/bundle/macos/spaceshell.app +NATIVE_APP_BUNDLE := $(APP_DIR)/src-tauri/target/release/bundle/macos/spaceshell.app APP_VERSION := $(shell node -p "require('./$(APP_DIR)/src-tauri/tauri.conf.json').version" 2>/dev/null || echo 0.0.0) LANDING_IMAGE := spacesh-landing @@ -19,7 +19,7 @@ REPO ?= spacesh # ---- Gitea generic package registry (versioned .dmg downloads) ---- GITEA_URL ?= https://git.realmanual.ru -GITEA_OWNER ?= realmanual +GITEA_OWNER ?= pub GITEA_PKG ?= spacesh GITEA_TOKEN ?= # token with package:write; pass via env/CLI, never commit @@ -99,16 +99,16 @@ kill-daemon: ## stop a running spaceshd so a freshly-built one takes over .PHONY: install install: kill-daemon ## install the native .app to /Applications, restart daemon, clear quarantine - rm -rf /Applications/spacesh.app + rm -rf /Applications/spacesh.app /Applications/spaceshell.app # drop the pre-rename app too cp -R "$(NATIVE_APP_BUNDLE)" /Applications/ - xattr -dr com.apple.quarantine /Applications/spacesh.app - @echo "Installed (native). Quit & relaunch spacesh; the bundled daemon restarts." + xattr -dr com.apple.quarantine /Applications/spaceshell.app + @echo "Installed (native). Quit & relaunch spaceshell; the bundled daemon restarts." .PHONY: install-universal install-universal: kill-daemon ## install the universal .app to /Applications - rm -rf /Applications/spacesh.app + rm -rf /Applications/spacesh.app /Applications/spaceshell.app cp -R "$(APP_BUNDLE)" /Applications/ - xattr -dr com.apple.quarantine /Applications/spacesh.app + xattr -dr com.apple.quarantine /Applications/spaceshell.app .PHONY: reinstall reinstall: app-bundle install ## fast self-update: build .app (no dmg), reinstall, restart daemon @@ -162,10 +162,10 @@ _publish-dmg: VER=$$(node -p "require('./$(APP_DIR)/src-tauri/tauri.conf.json').version"); \ DMG=$$(ls -t $(DMG_DIR)/*.dmg 2>/dev/null | head -1); \ if [ -z "$$DMG" ]; then echo "no .dmg in $(DMG_DIR) — run make dmg first"; exit 1; fi; \ - URL="$(GITEA_URL)/api/packages/$(GITEA_OWNER)/generic/$(GITEA_PKG)/$$VER/spacesh-$$VER.dmg"; \ + URL="$(GITEA_URL)/api/packages/$(GITEA_OWNER)/generic/$(GITEA_PKG)/$$VER/spaceshell-$$VER.dmg"; \ echo "Publishing $$DMG → $$URL"; \ curl --fail-with-body -sS -H "Authorization: token $(GITEA_TOKEN)" --upload-file "$$DMG" "$$URL" && \ - echo "Published spacesh-$$VER.dmg to Gitea Packages ($(GITEA_OWNER)/$(GITEA_PKG)@$$VER)" + echo "Published spaceshell-$$VER.dmg to Gitea Packages ($(GITEA_OWNER)/$(GITEA_PKG)@$$VER)" .PHONY: deploy-stack deploy-stack: ## sync compose+proxy.conf to prod and pull/up (manual; CI does this on push) diff --git a/app/src-tauri/Cargo.lock b/app/src-tauri/Cargo.lock index d679ecb..70ada4f 100644 --- a/app/src-tauri/Cargo.lock +++ b/app/src-tauri/Cargo.lock @@ -3440,7 +3440,7 @@ dependencies = [ [[package]] name = "spacesh-proto" -version = "0.1.7" +version = "0.1.10" dependencies = [ "bytes", "serde", diff --git a/app/src-tauri/tauri.conf.json b/app/src-tauri/tauri.conf.json index ca278cb..8a60a5a 100644 --- a/app/src-tauri/tauri.conf.json +++ b/app/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", - "productName": "spacesh", - "version": "0.1.7", + "productName": "spaceshell", + "version": "0.1.10", "identifier": "xyz.spacesh.app", "build": { "frontendDist": "../dist", @@ -12,7 +12,7 @@ "app": { "windows": [ { - "title": "spacesh", + "title": "spaceshell", "width": 1100, "height": 720 } diff --git a/app/src/App.tsx b/app/src/App.tsx index 63d8176..e0d19e1 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -4,6 +4,8 @@ import { Sidebar } from "./Sidebar"; import { TopBar } from "./TopBar"; import { CenterToolbar } from "./CenterToolbar"; import { Wizard } from "./Wizard"; +import { SurfacePicker } from "./SurfacePicker"; +import { PRESETS } from "./PresetPicker"; import { ConfirmDelete } from "./ConfirmDelete"; import { Settings } from "./Settings"; import { EventCenter } from "./EventCenter"; @@ -31,6 +33,8 @@ export function App() { const [states, setStates] = useState>({}); const [events, setEvents] = useState([]); const [wizard, setWizard] = useState(false); + // Pending additive preset awaiting the per-panel "what to open" choice. + const [pendingPreset, setPendingPreset] = useState<{ id: string; delta: number; base: number } | null>(null); const [deleteTarget, setDeleteTarget] = useState(null); const [settingsOpen, setSettingsOpen] = useState(false); const [eventsOpen, setEventsOpen] = useState(() => loadFlag("spacesh.eventsOpen", true)); @@ -183,7 +187,13 @@ export function App() { setWizard(true)} onDelete={setDeleteTarget} health={health} connected={connected} />
{active && ( - { if (active) void applyPreset(active.id, p, []); }} onOpenSearch={() => { if (effectiveFocus) { setSearchSurfaceId(effectiveFocus); setSearchNonce((n) => n + 1); } }} /> + { + if (!active) return; + const target = PRESETS.find((x) => x.id === p)?.slots ?? leaves.length; + const delta = target - leaves.length; + if (delta <= 0) { void applyPreset(active.id, p, []); return; } // reshape only — no new panels + setPendingPreset({ id: p, delta, base: leaves.length }); + }} onOpenSearch={() => { if (effectiveFocus) { setSearchSurfaceId(effectiveFocus); setSearchNonce((n) => n + 1); } }} /> )}
{active @@ -202,6 +212,18 @@ export function App() {
{settingsOpen && config && setSettingsOpen(false)} onReload={() => { void loadHealth(); void refresh(); }} />} {wizard && { setWizard(false); setActiveId(id); void refresh(); }} onCancel={() => setWizard(false)} />} + {pendingPreset && active && ( + setPendingPreset(null)} + onConfirm={(specs) => { + const padded = [...Array(pendingPreset.base).fill({}), ...specs]; // align to daemon's slots.get(existing.len()+j) + const wsId = active.id; + setPendingPreset(null); + void applyPreset(wsId, pendingPreset.id, padded).then(() => void refresh()); + }} + /> + )} {deleteTarget && ( void; onCancel: () => void }) { + const [installed, setInstalled] = useState([]); + const [choices, setChoices] = useState([]); + const [customCmds, setCustomCmds] = useState([]); + 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 ( +
+
e.stopPropagation()} + onKeyDown={onKeyDown} + style={{ width: 420, background: "#0E1116", border: "1px solid #323C49", borderRadius: 14, padding: 24, color: "#E6EDF3" }} + > +
{count > 1 ? `Open ${count} new panels` : "Open new panel"}
+
Choose what to run in each new panel.
+
1 ? "1fr 1fr" : "1fr", gap: 8, marginBottom: 20 }}> + {Array.from({ length: count }, (_, i) => { + const val = choices[i] ?? SHELL; + return ( +
+ + {val === CUSTOM && ( + 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 }} /> + )} +
+ ); + })} +
+
+ + +
+
+
+ ); +} diff --git a/app/src/TopBar.tsx b/app/src/TopBar.tsx index 169ca6e..3110230 100644 --- a/app/src/TopBar.tsx +++ b/app/src/TopBar.tsx @@ -138,7 +138,7 @@ export function TopBar({
- {active?.name ?? "spacesh"} + {active?.name ?? "spaceshell"} {active && ( <> diff --git a/app/src/Wizard.tsx b/app/src/Wizard.tsx index ea672bd..270e887 100644 --- a/app/src/Wizard.tsx +++ b/app/src/Wizard.tsx @@ -1,10 +1,7 @@ import { useEffect, useRef, useState } from "react"; import { PresetPicker, PRESETS } from "./PresetPicker"; import { openWorkspace, applyPreset, whichAgents } from "./socketBridge"; - -// Agents we know about; only the installed ones are offered (probed via whichAgents). -const KNOWN_AGENTS = ["claude", "codex", "gemini"]; -const CUSTOM = "custom…"; +import { KNOWN_AGENTS, SHELL, CUSTOM, agentLabel, specForChoice } from "./agents"; export function Wizard({ onDone, onCancel }: { onDone: (workspaceId: string) => void; onCancel: () => void }) { const [path, setPath] = useState("."); @@ -16,7 +13,7 @@ export function Wizard({ onDone, onCancel }: { onDone: (workspaceId: string) => const [error, setError] = useState(null); const pathRef = useRef(null); const slots = PRESETS.find((p) => p.id === preset)?.slots ?? 1; - const agentChoices = ["shell", ...installed, CUSTOM]; + const agentChoices = [SHELL, ...installed, CUSTOM]; // Grab focus on open — otherwise keystrokes leak to the xterm panel behind us // (its helper textarea sits at z-index 1000 and keeps the live focus). @@ -34,15 +31,7 @@ export function Wizard({ onDone, onCancel }: { onDone: (workspaceId: string) => setError(null); try { const ws = await openWorkspace(path); - const slotSpecs = Array.from({ length: slots }, (_, i) => { - const a = agents[i] ?? "shell"; - if (a === "shell") return {}; - if (a === CUSTOM) { - const parts = (customCmds[i] ?? "").trim().split(/\s+/).filter(Boolean); - return parts.length ? { command: parts[0], args: parts.slice(1) } : {}; - } - return { command: a }; - }); + const slotSpecs = Array.from({ length: slots }, (_, i) => specForChoice(agents[i] ?? SHELL, customCmds[i] ?? "")); await applyPreset(ws, preset, slotSpecs); onDone(ws); } catch (e) { @@ -76,12 +65,12 @@ export function Wizard({ onDone, onCancel }: { onDone: (workspaceId: string) =>
{Array.from({ length: slots }, (_, i) => { - const val = agents[i] ?? "shell"; + const val = agents[i] ?? SHELL; return (
{val === CUSTOM && ( - spacesh — терминал-воркспейс для AI-агентов на macOS + spaceshell — терминал-воркспейс для AI-агентов на macOS - + @@ -812,7 +812,7 @@
@@ -928,6 +928,7 @@ Codex Gemini opencode + deepseek shell
@@ -946,7 +947,7 @@

Решение

-

spacesh разрывает эту связь.

+

spaceshell разрывает эту связь.

Сессиями владеет фоновый демон, а не окно. Интерфейс — всего лишь вид поверх него.

@@ -999,7 +1000,7 @@

CLI как первый класс

spacesh status --json, focus, new-surface, notify — те же команды, что и в интерфейсе, плюс shell-completions. - Встраивай spacesh в свои пайплайны. + Встраивай spaceshell в свои пайплайны.

@@ -1117,7 +1118,7 @@