feat(v2): scaffold Tauri 2.x + Svelte 5 project (Phase 1)
- Tauri 2.10 + Svelte 5.45 + TypeScript + Vite 7 - Catppuccin Mocha theme with CSS variables and semantic aliases - CSS Grid layout: sidebar (260px) + workspace, responsive breakpoints for ultrawide (3440px+) and narrow (<1200px) - Component structure: Layout/, Terminal/, Agent/, Markdown/, Sidebar/ - Svelte 5 stores with $state runes: sessions, agents, layout - SDK message adapter (abstracts Agent SDK wire format) - PTY bridge (Tauri IPC wrapper, stubbed for Phase 2) - Node.js sidecar entry point (stdio NDJSON, stubbed for Phase 3) - Rust modules: pty, sidecar, watcher, session (stubbed) - Vite dev server on port 9700 - Build verified: binary + .deb + .rpm + AppImage all produced
26
v2/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
sidecar/dist
|
||||
sidecar/node_modules
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
12
v2/index.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>BTerminal</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
1501
v2/package-lock.json
generated
Normal file
24
v2/package.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "bterminal-v2",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json",
|
||||
"tauri": "cargo tauri",
|
||||
"tauri:dev": "cargo tauri dev",
|
||||
"tauri:build": "cargo tauri build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||
"@tsconfig/svelte": "^5.0.6",
|
||||
"@types/node": "^24.10.1",
|
||||
"svelte": "^5.45.2",
|
||||
"svelte-check": "^4.3.4",
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^7.3.1"
|
||||
}
|
||||
}
|
||||
42
v2/sidecar/agent-runner.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Agent Runner — Node.js sidecar entry point
|
||||
// Spawned by Rust backend, communicates via stdio NDJSON
|
||||
// Phase 3: full Agent SDK integration
|
||||
|
||||
import { stdin, stdout, stderr } from 'process';
|
||||
import { createInterface } from 'readline';
|
||||
|
||||
const rl = createInterface({ input: stdin });
|
||||
|
||||
function send(msg: Record<string, unknown>) {
|
||||
stdout.write(JSON.stringify(msg) + '\n');
|
||||
}
|
||||
|
||||
function log(message: string) {
|
||||
stderr.write(`[sidecar] ${message}\n`);
|
||||
}
|
||||
|
||||
rl.on('line', (line: string) => {
|
||||
try {
|
||||
const msg = JSON.parse(line);
|
||||
handleMessage(msg);
|
||||
} catch {
|
||||
log(`Invalid JSON: ${line}`);
|
||||
}
|
||||
});
|
||||
|
||||
function handleMessage(msg: Record<string, unknown>) {
|
||||
switch (msg.type) {
|
||||
case 'ping':
|
||||
send({ type: 'pong' });
|
||||
break;
|
||||
case 'query':
|
||||
// Phase 3: call Agent SDK query()
|
||||
send({ type: 'error', message: 'Agent SDK not yet integrated — Phase 3' });
|
||||
break;
|
||||
default:
|
||||
send({ type: 'error', message: `Unknown message type: ${msg.type}` });
|
||||
}
|
||||
}
|
||||
|
||||
log('Sidecar started');
|
||||
send({ type: 'ready' });
|
||||
12
v2/sidecar/package.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "bterminal-sidecar",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "esbuild agent-runner.ts --bundle --platform=node --target=node20 --outfile=dist/agent-runner.mjs --format=esm"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "0.25.4"
|
||||
}
|
||||
}
|
||||
4
v2/src-tauri/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
/gen/schemas
|
||||
24
v2/src-tauri/Cargo.toml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "bterminal"
|
||||
version = "0.1.0"
|
||||
description = "Multi-session Claude agent dashboard"
|
||||
authors = ["DexterFromLab"]
|
||||
license = "MIT"
|
||||
edition = "2021"
|
||||
rust-version = "1.77.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
name = "bterminal_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.5.6", features = [] }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
log = "0.4"
|
||||
tauri = { version = "2.10.3", features = [] }
|
||||
tauri-plugin-log = "2"
|
||||
3
v2/src-tauri/build.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
11
v2/src-tauri/capabilities/default.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "enables the default permissions",
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default"
|
||||
]
|
||||
}
|
||||
BIN
v2/src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
v2/src-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
v2/src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
v2/src-tauri/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 9 KiB |
BIN
v2/src-tauri/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
v2/src-tauri/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
v2/src-tauri/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
v2/src-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
v2/src-tauri/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
v2/src-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
v2/src-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
v2/src-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
v2/src-tauri/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
v2/src-tauri/icons/icon.icns
Normal file
BIN
v2/src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
v2/src-tauri/icons/icon.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
16
v2/src-tauri/src/lib.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
if cfg!(debug_assertions) {
|
||||
app.handle().plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
.level(log::LevelFilter::Info)
|
||||
.build(),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
6
v2/src-tauri/src/main.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
bterminal_lib::run();
|
||||
}
|
||||
2
v2/src-tauri/src/pty.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
// PTY management via portable-pty
|
||||
// Phase 2: spawn, resize, I/O streaming to frontend
|
||||
2
v2/src-tauri/src/session.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
// Session persistence via rusqlite
|
||||
// Phase 4: CRUD, layout save/restore
|
||||
2
v2/src-tauri/src/sidecar.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
// Node.js sidecar lifecycle management
|
||||
// Phase 3: spawn, restart, health check, stdio NDJSON communication
|
||||
2
v2/src-tauri/src/watcher.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
// File watcher for markdown viewer
|
||||
// Phase 4: notify crate, debounce, Tauri events
|
||||
38
v2/src-tauri/tauri.conf.json
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "bterminal",
|
||||
"version": "0.1.0",
|
||||
"identifier": "com.dexterfromlab.bterminal",
|
||||
"build": {
|
||||
"frontendDist": "../dist",
|
||||
"devUrl": "http://localhost:9700",
|
||||
"beforeDevCommand": "npm run dev",
|
||||
"beforeBuildCommand": "npm run build"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "BTerminal",
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"resizable": true,
|
||||
"fullscreen": false,
|
||||
"decorations": true
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
}
|
||||
}
|
||||
27
v2/src/App.svelte
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<script lang="ts">
|
||||
import SessionList from './lib/components/Sidebar/SessionList.svelte';
|
||||
import TilingGrid from './lib/components/Layout/TilingGrid.svelte';
|
||||
</script>
|
||||
|
||||
<aside class="sidebar">
|
||||
<SessionList />
|
||||
</aside>
|
||||
<main class="workspace">
|
||||
<TilingGrid />
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.sidebar {
|
||||
background: var(--bg-secondary);
|
||||
border-right: 1px solid var(--border);
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.workspace {
|
||||
background: var(--bg-primary);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
61
v2/src/app.css
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
@import './lib/styles/catppuccin.css';
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: var(--sidebar-width) 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
/* Ultrawide: show right panel */
|
||||
@media (min-width: 3440px) {
|
||||
#app {
|
||||
grid-template-columns: var(--sidebar-width) 1fr var(--right-panel-width);
|
||||
}
|
||||
}
|
||||
|
||||
/* Narrow: collapse sidebar to icons */
|
||||
@media (max-width: 1200px) {
|
||||
#app {
|
||||
grid-template-columns: 48px 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--ctp-surface2);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--ctp-overlay0);
|
||||
}
|
||||
30
v2/src/lib/adapters/pty-bridge.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// PTY Bridge — IPC wrapper for Rust PTY backend
|
||||
// Phase 2: terminal spawn, resize, input/output streaming
|
||||
|
||||
export interface PtyOptions {
|
||||
shell?: string;
|
||||
cwd?: string;
|
||||
env?: Record<string, string>;
|
||||
cols?: number;
|
||||
rows?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn a new PTY session via Tauri IPC.
|
||||
* Phase 2: implement with @tauri-apps/api invoke
|
||||
*/
|
||||
export async function spawnPty(_options: PtyOptions): Promise<string> {
|
||||
throw new Error('Not implemented — Phase 2');
|
||||
}
|
||||
|
||||
export async function writePty(_id: string, _data: string): Promise<void> {
|
||||
throw new Error('Not implemented — Phase 2');
|
||||
}
|
||||
|
||||
export async function resizePty(_id: string, _cols: number, _rows: number): Promise<void> {
|
||||
throw new Error('Not implemented — Phase 2');
|
||||
}
|
||||
|
||||
export async function killPty(_id: string): Promise<void> {
|
||||
throw new Error('Not implemented — Phase 2');
|
||||
}
|
||||
25
v2/src/lib/adapters/sdk-messages.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// SDK Message Adapter — insulates UI from Claude Agent SDK wire format changes
|
||||
// This is the ONLY place that knows SDK internals.
|
||||
// Phase 3: full implementation
|
||||
|
||||
export interface AgentMessage {
|
||||
id: string;
|
||||
type: 'text' | 'tool_call' | 'tool_result' | 'subagent_spawn' | 'subagent_stop' | 'status' | 'cost' | 'unknown';
|
||||
parentId?: string;
|
||||
content: unknown;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapt a raw SDK message to our internal format.
|
||||
* When SDK changes wire format, only this function needs updating.
|
||||
*/
|
||||
export function adaptSDKMessage(raw: Record<string, unknown>): AgentMessage {
|
||||
// Phase 3: implement based on actual SDK message types
|
||||
return {
|
||||
id: (raw.id as string) ?? crypto.randomUUID(),
|
||||
type: 'unknown',
|
||||
content: raw,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
}
|
||||
54
v2/src/lib/components/Layout/PaneContainer.svelte
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
let { title, children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="pane-container">
|
||||
<div class="pane-header">
|
||||
<span class="pane-title">{title}</span>
|
||||
</div>
|
||||
<div class="pane-content">
|
||||
{@render children()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.pane-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: var(--border-radius);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.pane-header {
|
||||
height: var(--pane-header-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pane-title {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.pane-content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
44
v2/src/lib/components/Layout/PaneHeader.svelte
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<script lang="ts">
|
||||
interface Props {
|
||||
title: string;
|
||||
status?: 'idle' | 'running' | 'error' | 'done';
|
||||
}
|
||||
|
||||
let { title, status = 'idle' }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="pane-header">
|
||||
<span class="pane-title">{title}</span>
|
||||
{#if status !== 'idle'}
|
||||
<span class="status-indicator {status}">{status}</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.pane-header {
|
||||
height: var(--pane-header-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 10px;
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pane-title {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.status-indicator.running { color: var(--ctp-blue); }
|
||||
.status-indicator.error { color: var(--ctp-red); }
|
||||
.status-indicator.done { color: var(--ctp-green); }
|
||||
</style>
|
||||
58
v2/src/lib/components/Layout/TilingGrid.svelte
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<script lang="ts">
|
||||
import PaneContainer from './PaneContainer.svelte';
|
||||
|
||||
// Phase 2: dynamic pane management, resize, presets
|
||||
// For now: single empty pane as placeholder
|
||||
</script>
|
||||
|
||||
<div class="tiling-grid">
|
||||
<PaneContainer title="Welcome">
|
||||
<div class="welcome">
|
||||
<h1>BTerminal v2</h1>
|
||||
<p>Claude Agent Mission Control</p>
|
||||
<div class="status">
|
||||
<span class="badge">Phase 1 — Scaffold</span>
|
||||
</div>
|
||||
</div>
|
||||
</PaneContainer>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.tiling-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
gap: var(--pane-gap);
|
||||
height: 100%;
|
||||
padding: var(--pane-gap);
|
||||
}
|
||||
|
||||
.welcome {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
gap: 8px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.welcome h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.welcome p {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
background: var(--bg-surface);
|
||||
color: var(--accent);
|
||||
padding: 4px 12px;
|
||||
border-radius: var(--border-radius);
|
||||
font-size: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
39
v2/src/lib/components/Sidebar/SessionList.svelte
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<script lang="ts">
|
||||
// Phase 4: session CRUD, groups, types
|
||||
</script>
|
||||
|
||||
<div class="session-list">
|
||||
<div class="header">
|
||||
<h2>Sessions</h2>
|
||||
</div>
|
||||
<div class="empty-state">
|
||||
<p>No sessions yet.</p>
|
||||
<p class="hint">Phase 2 will add terminal sessions.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.session-list {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.header h2 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin-top: 4px;
|
||||
font-size: 11px;
|
||||
color: var(--ctp-overlay0);
|
||||
}
|
||||
</style>
|
||||
34
v2/src/lib/stores/agents.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// Agent tracking state — Svelte 5 runes
|
||||
// Phase 3: SDK agent lifecycle, subagent tree
|
||||
|
||||
export type AgentStatus = 'idle' | 'running' | 'thinking' | 'waiting' | 'done' | 'error';
|
||||
|
||||
export interface AgentState {
|
||||
id: string;
|
||||
sessionId: string;
|
||||
parentId?: string;
|
||||
status: AgentStatus;
|
||||
model?: string;
|
||||
costUsd?: number;
|
||||
tokensIn?: number;
|
||||
tokensOut?: number;
|
||||
}
|
||||
|
||||
let agents = $state<AgentState[]>([]);
|
||||
|
||||
export function getAgents() {
|
||||
return agents;
|
||||
}
|
||||
|
||||
export function getAgentTree(rootId: string): AgentState[] {
|
||||
const result: AgentState[] = [];
|
||||
const root = agents.find(a => a.id === rootId);
|
||||
if (!root) return result;
|
||||
|
||||
result.push(root);
|
||||
const children = agents.filter(a => a.parentId === rootId);
|
||||
for (const child of children) {
|
||||
result.push(...getAgentTree(child.id));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
28
v2/src/lib/stores/layout.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Layout state management — Svelte 5 runes
|
||||
// Phase 2: pane positions, resize, presets
|
||||
|
||||
export type LayoutPreset = '1-col' | '2-col' | '3-col' | '2x2' | 'master-stack';
|
||||
|
||||
export interface PaneState {
|
||||
id: string;
|
||||
sessionId: string;
|
||||
row: number;
|
||||
col: number;
|
||||
rowSpan: number;
|
||||
colSpan: number;
|
||||
}
|
||||
|
||||
let activePreset = $state<LayoutPreset>('1-col');
|
||||
let panes = $state<PaneState[]>([]);
|
||||
|
||||
export function getActivePreset() {
|
||||
return activePreset;
|
||||
}
|
||||
|
||||
export function setPreset(preset: LayoutPreset) {
|
||||
activePreset = preset;
|
||||
}
|
||||
|
||||
export function getPanes() {
|
||||
return panes;
|
||||
}
|
||||
26
v2/src/lib/stores/sessions.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Session state management — Svelte 5 runes
|
||||
// Phase 4: full session CRUD, persistence
|
||||
|
||||
export type SessionType = 'terminal' | 'agent' | 'markdown';
|
||||
|
||||
export interface Session {
|
||||
id: string;
|
||||
type: SessionType;
|
||||
title: string;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
// Reactive session list
|
||||
let sessions = $state<Session[]>([]);
|
||||
|
||||
export function getSessions() {
|
||||
return sessions;
|
||||
}
|
||||
|
||||
export function addSession(session: Session) {
|
||||
sessions.push(session);
|
||||
}
|
||||
|
||||
export function removeSession(id: string) {
|
||||
sessions = sessions.filter(s => s.id !== id);
|
||||
}
|
||||
52
v2/src/lib/styles/catppuccin.css
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/* Catppuccin Mocha — https://catppuccin.com/palette */
|
||||
:root {
|
||||
--ctp-rosewater: #f5e0dc;
|
||||
--ctp-flamingo: #f2cdcd;
|
||||
--ctp-pink: #f5c2e7;
|
||||
--ctp-mauve: #cba6f7;
|
||||
--ctp-red: #f38ba8;
|
||||
--ctp-maroon: #eba0ac;
|
||||
--ctp-peach: #fab387;
|
||||
--ctp-yellow: #f9e2af;
|
||||
--ctp-green: #a6e3a1;
|
||||
--ctp-teal: #94e2d5;
|
||||
--ctp-sky: #89dceb;
|
||||
--ctp-sapphire: #74c7ec;
|
||||
--ctp-blue: #89b4fa;
|
||||
--ctp-lavender: #b4befe;
|
||||
--ctp-text: #cdd6f4;
|
||||
--ctp-subtext1: #bac2de;
|
||||
--ctp-subtext0: #a6adc8;
|
||||
--ctp-overlay2: #9399b2;
|
||||
--ctp-overlay1: #7f849c;
|
||||
--ctp-overlay0: #6c7086;
|
||||
--ctp-surface2: #585b70;
|
||||
--ctp-surface1: #45475a;
|
||||
--ctp-surface0: #313244;
|
||||
--ctp-base: #1e1e2e;
|
||||
--ctp-mantle: #181825;
|
||||
--ctp-crust: #11111b;
|
||||
|
||||
/* Semantic aliases */
|
||||
--bg-primary: var(--ctp-base);
|
||||
--bg-secondary: var(--ctp-mantle);
|
||||
--bg-tertiary: var(--ctp-crust);
|
||||
--bg-surface: var(--ctp-surface0);
|
||||
--bg-surface-hover: var(--ctp-surface1);
|
||||
--text-primary: var(--ctp-text);
|
||||
--text-secondary: var(--ctp-subtext1);
|
||||
--text-muted: var(--ctp-overlay1);
|
||||
--border: var(--ctp-surface1);
|
||||
--accent: var(--ctp-blue);
|
||||
--accent-hover: var(--ctp-sapphire);
|
||||
--success: var(--ctp-green);
|
||||
--warning: var(--ctp-yellow);
|
||||
--error: var(--ctp-red);
|
||||
|
||||
/* Layout */
|
||||
--sidebar-width: 260px;
|
||||
--right-panel-width: 380px;
|
||||
--pane-header-height: 32px;
|
||||
--pane-gap: 2px;
|
||||
--border-radius: 4px;
|
||||
}
|
||||
9
v2/src/main.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { mount } from 'svelte'
|
||||
import './app.css'
|
||||
import App from './App.svelte'
|
||||
|
||||
const app = mount(App, {
|
||||
target: document.getElementById('app')!,
|
||||
})
|
||||
|
||||
export default app
|
||||
8
v2/svelte.config.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
/** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */
|
||||
export default {
|
||||
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
}
|
||||
21
v2/tsconfig.app.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"types": ["svelte", "vite/client"],
|
||||
"noEmit": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||
* Note that setting allowJs false does not prevent the use
|
||||
* of JS in `.svelte` files.
|
||||
*/
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"moduleDetection": "force"
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||
}
|
||||
7
v2/tsconfig.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
26
v2/tsconfig.node.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"types": ["node"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
11
v2/vite.config.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [svelte()],
|
||||
server: {
|
||||
port: 9700,
|
||||
strictPort: true,
|
||||
},
|
||||
clearScreen: false,
|
||||
})
|
||||