mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
Desktop terminal access should behave like an IDE: active project sessions open a bottom panel in the session working directory while keeping a full terminal tab available for dedicated use. The panel has constrained resizing and cleanup so session tab state remains isolated, and terminal guidance points users to the bundled claude-haha command for extension setup. Constraint: Desktop bundles the user-facing CLI as claude-haha while claude-sidecar remains internal Rejected: Always opening a standalone terminal tab | loses the current project context and diverges from common IDE behavior Rejected: Exposing claude-sidecar in terminal guidance | it is an internal launcher, not the supportable user command Confidence: high Scope-risk: moderate Directive: Keep bottom terminals keyed by session id and pass session workDir/projectPath into spawned terminals Tested: bun run check:desktop Tested: git diff --check Tested: Computer Use E2E against built macOS app during implementation Not-tested: bun run quality:pr is blocked by existing branch policy requiring allow-cli-core-change approval
59 lines
1.6 KiB
TypeScript
59 lines
1.6 KiB
TypeScript
import { isTauriRuntime } from '../lib/desktopRuntime'
|
|
|
|
export type TerminalSpawnResult = {
|
|
session_id: number
|
|
shell: string
|
|
cwd: string
|
|
}
|
|
|
|
export type TerminalOutputPayload = {
|
|
session_id: number
|
|
data: string
|
|
}
|
|
|
|
export type TerminalExitPayload = {
|
|
session_id: number
|
|
code: number
|
|
signal?: string | null
|
|
}
|
|
|
|
type Unlisten = () => void
|
|
|
|
async function invoke<T>(command: string, args?: Record<string, unknown>): Promise<T> {
|
|
if (!isTauriRuntime()) {
|
|
throw new Error('Terminal is available in the desktop app runtime.')
|
|
}
|
|
const api = await import('@tauri-apps/api/core')
|
|
return api.invoke<T>(command, args)
|
|
}
|
|
|
|
export const terminalApi = {
|
|
isAvailable: isTauriRuntime,
|
|
|
|
spawn(input: { cols: number; rows: number; cwd?: string }) {
|
|
return invoke<TerminalSpawnResult>('terminal_spawn', input)
|
|
},
|
|
|
|
write(sessionId: number, data: string) {
|
|
return invoke<void>('terminal_write', { sessionId, data })
|
|
},
|
|
|
|
resize(sessionId: number, cols: number, rows: number) {
|
|
return invoke<void>('terminal_resize', { sessionId, cols, rows })
|
|
},
|
|
|
|
kill(sessionId: number) {
|
|
return invoke<void>('terminal_kill', { sessionId })
|
|
},
|
|
|
|
async onOutput(handler: (payload: TerminalOutputPayload) => void): Promise<Unlisten> {
|
|
const events = await import('@tauri-apps/api/event')
|
|
return events.listen<TerminalOutputPayload>('terminal-output', (event) => handler(event.payload))
|
|
},
|
|
|
|
async onExit(handler: (payload: TerminalExitPayload) => void): Promise<Unlisten> {
|
|
const events = await import('@tauri-apps/api/event')
|
|
return events.listen<TerminalExitPayload>('terminal-exit', (event) => handler(event.payload))
|
|
},
|
|
}
|