feat(v0.3): H4 — live Claude status badge + notifications + setup-hooks
- terminal-session: handle 'status' frame → claudeStatus + onClaudeStatus - tabs: per-tab Claude glyph (⚙ working / ⏳ waiting / ✓ idle); inactive 'waiting' tab gets an amber highlight + a browser notification (permission requested on first tab switch) - scripts/setup-hooks.mjs + 'npm run setup-hooks': idempotently install/remove command hooks in ~/.claude/settings.json (inline curl to $WEBTERM_HOOK_URL with $WEBTERM_SESSION; no-op outside web-terminal); backs up settings.json - Browser-verified end-to-end: PermissionRequest→⏳, PreToolUse→⚙, Stop→✓; inactive waiting tab shows amber + glyph. 205 tests green.
This commit is contained in:
@@ -12,7 +12,7 @@ import { Terminal } from '@xterm/xterm'
|
||||
import { FitAddon } from '@xterm/addon-fit'
|
||||
import { SearchAddon } from '@xterm/addon-search'
|
||||
import { WebLinksAddon } from '@xterm/addon-web-links'
|
||||
import type { ClientMessage, ServerMessage } from '../src/types.js'
|
||||
import type { ClaudeStatus, ClientMessage, ServerMessage } from '../src/types.js'
|
||||
import { folderFromTitle } from './title-util.js'
|
||||
|
||||
const RESET = '\x1b[0m'
|
||||
@@ -51,6 +51,8 @@ export interface TerminalSessionOpts {
|
||||
onTitle?: (title: string) => void
|
||||
/** Optional: fired when connection status changes (for the tab status dot). */
|
||||
onStatus?: (status: SessionStatus) => void
|
||||
/** Optional: fired when Claude Code activity changes (from a hook, H2). */
|
||||
onClaudeStatus?: (status: ClaudeStatus, detail?: string) => void
|
||||
}
|
||||
|
||||
export class TerminalSession {
|
||||
@@ -64,7 +66,9 @@ export class TerminalSession {
|
||||
private readonly onActivity: (() => void) | undefined
|
||||
private readonly onTitle: ((title: string) => void) | undefined
|
||||
private readonly onStatus: ((status: SessionStatus) => void) | undefined
|
||||
private readonly onClaudeStatus: ((status: ClaudeStatus, detail?: string) => void) | undefined
|
||||
private statusValue: SessionStatus = 'connecting'
|
||||
private claudeStatusValue: ClaudeStatus = 'unknown'
|
||||
|
||||
private ws: WebSocket | null = null
|
||||
private sessionId: string | null
|
||||
@@ -83,6 +87,7 @@ export class TerminalSession {
|
||||
this.onActivity = opts.onActivity
|
||||
this.onTitle = opts.onTitle
|
||||
this.onStatus = opts.onStatus
|
||||
this.onClaudeStatus = opts.onClaudeStatus
|
||||
|
||||
this.el = document.createElement('div')
|
||||
this.el.className = 'term-pane'
|
||||
@@ -118,6 +123,11 @@ export class TerminalSession {
|
||||
return this.statusValue
|
||||
}
|
||||
|
||||
/** Current Claude Code activity (from hooks, H2). */
|
||||
get claudeStatus(): ClaudeStatus {
|
||||
return this.claudeStatusValue
|
||||
}
|
||||
|
||||
private setStatus(s: SessionStatus): void {
|
||||
this.statusValue = s
|
||||
this.onStatus?.(s)
|
||||
@@ -206,6 +216,11 @@ export class TerminalSession {
|
||||
if (this.el.style.display === 'none') this.onActivity?.()
|
||||
break
|
||||
}
|
||||
case 'status': {
|
||||
this.claudeStatusValue = msg.status
|
||||
this.onClaudeStatus?.(msg.status, msg.detail)
|
||||
break
|
||||
}
|
||||
case 'exit': {
|
||||
this.setStatus('exited')
|
||||
const reason = msg.reason ? ` (${msg.reason})` : ''
|
||||
|
||||
Reference in New Issue
Block a user