feat(electrobun): final 5% — full integration, real data, polish
1. Claude CLI: additionalDirectories + worktreeName passthrough 2. Agent-store: reads settings (default_cwd, provider model, permission) 3. Project hydration: SQLite replaces hardcoded PROJECTS, add/remove UI 4. Group hydration: SQLite groups, add/delete in sidebar 5. Terminal auto-spawn: reads default_cwd from settings 6. Context tab: real tokens from agent-store, file refs, turn count 7. Memory tab: Memora DB integration (read-only, graceful if missing) 8. Docs tab: markdown viewer (files.list + files.read + inline renderer) 9. SSH tab: CRUD connections, spawn PTY with ssh command 10. Error handling: global unhandledrejection → toast notifications 11. Notifications: agent done/error/stall → toasts, 15min stall timer 12. Command palette: all 18 commands (was 10) +1,198 lines, 13 files. Electrobun now 100% feature-complete vs Tauri v3.
This commit is contained in:
parent
4826b9dffa
commit
8e756d3523
13 changed files with 1199 additions and 239 deletions
|
|
@ -8,6 +8,7 @@ import { btmsgDb } from "./btmsg-db.ts";
|
|||
import { bttaskDb } from "./bttask-db.ts";
|
||||
import { SidecarManager } from "./sidecar-manager.ts";
|
||||
import type { PtyRPCSchema } from "../shared/pty-rpc-schema.ts";
|
||||
import { Database } from "bun:sqlite";
|
||||
import { randomUUID } from "crypto";
|
||||
import { SearchDb } from "./search-db.ts";
|
||||
import { checkForUpdates, getLastCheckTimestamp } from "./updater.ts";
|
||||
|
|
@ -188,6 +189,16 @@ const rpc = BrowserView.defineRPC<PtyRPCSchema>({
|
|||
}
|
||||
},
|
||||
|
||||
"settings.deleteProject": ({ id }) => {
|
||||
try {
|
||||
settingsDb.deleteProject(id);
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
console.error("[settings.deleteProject]", err);
|
||||
return { ok: false };
|
||||
}
|
||||
},
|
||||
|
||||
// ── Custom Themes handlers ───────────────────────────────────────────
|
||||
|
||||
"themes.getCustom": () => {
|
||||
|
|
@ -303,6 +314,26 @@ const rpc = BrowserView.defineRPC<PtyRPCSchema>({
|
|||
}
|
||||
},
|
||||
|
||||
"groups.create": ({ id, name, icon, position }) => {
|
||||
try {
|
||||
settingsDb.createGroup(id, name, icon, position);
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
console.error("[groups.create]", err);
|
||||
return { ok: false };
|
||||
}
|
||||
},
|
||||
|
||||
"groups.delete": ({ id }) => {
|
||||
try {
|
||||
settingsDb.deleteGroup(id);
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
console.error("[groups.delete]", err);
|
||||
return { ok: false };
|
||||
}
|
||||
},
|
||||
|
||||
// ── Project clone handler ────────────────────────────────────────────
|
||||
|
||||
"project.clone": async ({ projectId, branchName }) => {
|
||||
|
|
@ -456,7 +487,7 @@ const rpc = BrowserView.defineRPC<PtyRPCSchema>({
|
|||
|
||||
// ── Agent handlers ──────────────────────────────────────────────────
|
||||
|
||||
"agent.start": ({ sessionId, provider, prompt, cwd, model, systemPrompt, maxTurns, permissionMode, claudeConfigDir, extraEnv }) => {
|
||||
"agent.start": ({ sessionId, provider, prompt, cwd, model, systemPrompt, maxTurns, permissionMode, claudeConfigDir, extraEnv, additionalDirectories, worktreeName }) => {
|
||||
try {
|
||||
const result = sidecarManager.startSession(sessionId, provider, prompt, {
|
||||
cwd,
|
||||
|
|
@ -466,6 +497,8 @@ const rpc = BrowserView.defineRPC<PtyRPCSchema>({
|
|||
permissionMode,
|
||||
claudeConfigDir,
|
||||
extraEnv,
|
||||
additionalDirectories,
|
||||
worktreeName,
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
|
|
@ -970,6 +1003,58 @@ const rpc = BrowserView.defineRPC<PtyRPCSchema>({
|
|||
}
|
||||
},
|
||||
|
||||
// ── Memora handlers (read-only) ─────────────────────────────────────
|
||||
|
||||
"memora.search": ({ query, limit }) => {
|
||||
try {
|
||||
const dbPath = join(homedir(), ".local", "share", "memora", "memories.db");
|
||||
if (!fs.existsSync(dbPath)) return { memories: [] };
|
||||
const db = new Database(dbPath, { readonly: true });
|
||||
try {
|
||||
const rows = db
|
||||
.query("SELECT id, content, tags, metadata, created_at as createdAt, updated_at as updatedAt FROM memories WHERE content LIKE ? ORDER BY updated_at DESC LIMIT ?")
|
||||
.all(`%${query}%`, limit ?? 20) as Array<{
|
||||
id: number; content: string; tags: string;
|
||||
metadata: string; createdAt: string; updatedAt: string;
|
||||
}>;
|
||||
return { memories: rows };
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[memora.search]", err);
|
||||
return { memories: [] };
|
||||
}
|
||||
},
|
||||
|
||||
"memora.list": ({ limit, tag }) => {
|
||||
try {
|
||||
const dbPath = join(homedir(), ".local", "share", "memora", "memories.db");
|
||||
if (!fs.existsSync(dbPath)) return { memories: [] };
|
||||
const db = new Database(dbPath, { readonly: true });
|
||||
try {
|
||||
let sql = "SELECT id, content, tags, metadata, created_at as createdAt, updated_at as updatedAt FROM memories";
|
||||
const params: unknown[] = [];
|
||||
if (tag) {
|
||||
sql += " WHERE tags LIKE ?";
|
||||
params.push(`%${tag}%`);
|
||||
}
|
||||
sql += " ORDER BY updated_at DESC LIMIT ?";
|
||||
params.push(limit ?? 20);
|
||||
const rows = db.query(sql).all(...params) as Array<{
|
||||
id: number; content: string; tags: string;
|
||||
metadata: string; createdAt: string; updatedAt: string;
|
||||
}>;
|
||||
return { memories: rows };
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[memora.list]", err);
|
||||
return { memories: [] };
|
||||
}
|
||||
},
|
||||
|
||||
// ── Telemetry handler ────────────────────────────────────────────────
|
||||
|
||||
"telemetry.log": ({ level, message, attributes }) => {
|
||||
|
|
|
|||
|
|
@ -181,6 +181,10 @@ export class SettingsDb {
|
|||
.run(id, json);
|
||||
}
|
||||
|
||||
deleteProject(id: string): void {
|
||||
this.db.query("DELETE FROM projects WHERE id = ?").run(id);
|
||||
}
|
||||
|
||||
listProjects(): ProjectConfig[] {
|
||||
const rows = this.db
|
||||
.query<{ config: string }, []>("SELECT config FROM projects")
|
||||
|
|
@ -202,6 +206,16 @@ export class SettingsDb {
|
|||
.all();
|
||||
}
|
||||
|
||||
createGroup(id: string, name: string, icon: string, position: number): void {
|
||||
this.db
|
||||
.query("INSERT INTO groups (id, name, icon, position) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET name = excluded.name, icon = excluded.icon, position = excluded.position")
|
||||
.run(id, name, icon, position);
|
||||
}
|
||||
|
||||
deleteGroup(id: string): void {
|
||||
this.db.query("DELETE FROM groups WHERE id = ?").run(id);
|
||||
}
|
||||
|
||||
// ── Custom Themes ─────────────────────────────────────────────────────────
|
||||
|
||||
getCustomThemes(): CustomTheme[] {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ export interface StartSessionOptions {
|
|||
permissionMode?: string;
|
||||
claudeConfigDir?: string;
|
||||
extraEnv?: Record<string, string>;
|
||||
additionalDirectories?: string[];
|
||||
worktreeName?: string;
|
||||
}
|
||||
|
||||
type MessageCallback = (sessionId: string, messages: AgentMessage[]) => void;
|
||||
|
|
@ -222,7 +224,7 @@ export class SidecarManager {
|
|||
});
|
||||
|
||||
// Send the query command to the runner
|
||||
const queryMsg = {
|
||||
const queryMsg: Record<string, unknown> = {
|
||||
type: "query",
|
||||
sessionId,
|
||||
prompt,
|
||||
|
|
@ -235,6 +237,13 @@ export class SidecarManager {
|
|||
extraEnv: validateExtraEnv(options.extraEnv),
|
||||
};
|
||||
|
||||
if (options.additionalDirectories?.length) {
|
||||
queryMsg.additionalDirectories = options.additionalDirectories;
|
||||
}
|
||||
if (options.worktreeName) {
|
||||
queryMsg.worktreeName = options.worktreeName;
|
||||
}
|
||||
|
||||
this.writeToProcess(sessionId, queryMsg);
|
||||
|
||||
return { ok: true };
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue