{"owner":"wavetermdev","repo":"waveterm","hasSkills":true,"totalSkillsCount":14,"totalTokensCount":28067,"categories":["anthropic-skill","copilot-instructions","claude-rule"],"hasMcp":false,"mcpConfig":null,"found":[".kilocode/skills/context-menu/SKILL.md",".kilocode/skills/electron-api/SKILL.md",".kilocode/skills/waveenv/SKILL.md",".kilocode/skills/wps-events/SKILL.md",".kilocode/skills/create-view/SKILL.md",".kilocode/skills/add-rpc/SKILL.md",".kilocode/skills/add-wshcmd/SKILL.md",".kilocode/rules/overview.md",".kilocode/rules/rules.md",".github/copilot-instructions.md",".kilocode/skills/add-config/SKILL.md","CLAUDE.md",".roo/rules/overview.md",".roo/rules/rules.md"],"skills":{".kilocode/skills/context-menu/SKILL.md":"---\nname: context-menu\ndescription: Guide for creating and displaying context menus in Wave Terminal. Use when implementing right-click menus, adding context menu items, creating submenus, or handling menu interactions with checkboxes and separators.\n---\n\n# Context Menu Quick Reference\n\nThis guide provides a quick overview of how to create and display a context menu using our system.\n\n---\n\n## ContextMenuItem Type\n\nDefine each menu item using the `ContextMenuItem` type:\n\n```ts\ntype ContextMenuItem = {\n  label?: string;\n  type?: \"separator\" | \"normal\" | \"submenu\" | \"checkbox\" | \"radio\";\n  role?: string; // Electron role (optional)\n  click?: () => void; // Callback for item selection (not needed if role is set)\n  submenu?: ContextMenuItem[]; // For nested menus\n  checked?: boolean; // For checkbox or radio items\n  visible?: boolean;\n  enabled?: boolean;\n  sublabel?: string;\n};\n```\n\n---\n\n## Import and Show the Menu\n\nImport the context menu module:\n\n```ts\nimport { ContextMenuModel } from \"@/app/store/contextmenu\";\n```\n\nTo display the context menu, call:\n\n```ts\nContextMenuModel.getInstance().showContextMenu(menu, event);\n```\n\n- **menu**: An array of `ContextMenuItem`.\n- **event**: The mouse event that triggered the context menu (typically from an onContextMenu handler).\n\n---\n\n## Basic Example\n\nA simple context menu with a separator:\n\n```ts\nconst menu: ContextMenuItem[] = [\n  {\n    label: \"New File\",\n    click: () => {\n      /* create a new file */\n    },\n  },\n  {\n    label: \"New Folder\",\n    click: () => {\n      /* create a new folder */\n    },\n  },\n  { type: \"separator\" },\n  {\n    label: \"Rename\",\n    click: () => {\n      /* rename item */\n    },\n  },\n];\n\nContextMenuModel.getInstance().showContextMenu(menu, e);\n```\n\n---\n\n## Example with Submenu and Checkboxes\n\nToggle settings using a submenu with checkbox items:\n\n```ts\nconst isClearOnStart = true; // Example setting\n\nconst menu: ContextMenuItem[] = [\n  {\n    label: \"Clear Output On Restart\",\n    submenu: [\n      {\n        label: \"On\",\n        type: \"checkbox\",\n        checked: isClearOnStart,\n        click: () => {\n          // Set the config to enable clear on restart\n        },\n      },\n      {\n        label: \"Off\",\n        type: \"checkbox\",\n        checked: !isClearOnStart,\n        click: () => {\n          // Set the config to disable clear on restart\n        },\n      },\n    ],\n  },\n];\n\nContextMenuModel.getInstance().showContextMenu(menu, e);\n```\n\n---\n\n## Editing a Config File Example\n\nOpen a configuration file (e.g., `widgets.json`) in preview mode:\n\n```ts\n{\n    label: \"Edit widgets.json\",\n    click: () => {\n        fireAndForget(async () => {\n            const path = `${getApi().getConfigDir()}/widgets.json`;\n            const blockDef: BlockDef = {\n                meta: { view: \"preview\", file: path },\n            };\n            await createBlock(blockDef, false, true);\n        });\n    },\n}\n```\n\n---\n\n## Summary\n\n- **Menu Definition**: Use the `ContextMenuItem` type.\n- **Actions**: Use `click` for actions; use `submenu` for nested options.\n- **Separators**: Use `type: \"separator\"` to group items.\n- **Toggles**: Use `type: \"checkbox\"` or `\"radio\"` with the `checked` property.\n- **Displaying**: Use `ContextMenuModel.getInstance().showContextMenu(menu, event)` to render the menu.\n\n## Common Use Cases\n\n### File/Folder Operations\nContext menus are commonly used for file operations like creating, renaming, and deleting files or folders.\n\n### Settings Toggles\nUse checkbox menu items to toggle settings on and off, with the `checked` property reflecting the current state.\n\n### Nested Options\nUse `submenu` to organize related options hierarchically, keeping the top-level menu clean and organized.\n\n### Conditional Items\nUse the `visible` and `enabled` properties to dynamically show or disable menu items based on the current state.\n",".kilocode/skills/electron-api/SKILL.md":"---\nname: electron-api\ndescription: Guide for adding new Electron APIs to Wave Terminal. Use when implementing new frontend-to-electron communications via preload/IPC.\n---\n\n# Adding Electron APIs\n\nElectron APIs allow the frontend to call Electron main process functionality directly via IPC.\n\n## Four Files to Edit\n\n1. [`frontend/types/custom.d.ts`](frontend/types/custom.d.ts) - TypeScript [`ElectronApi`](frontend/types/custom.d.ts:82) type\n2. [`emain/preload.ts`](emain/preload.ts) - Expose method via `contextBridge`\n3. [`emain/emain-ipc.ts`](emain/emain-ipc.ts) - Implement IPC handler\n4. [`frontend/preview/preview-electron-api.ts`](frontend/preview/preview-electron-api.ts) - Add a no-op stub to keep the `previewElectronApi` object in sync with the `ElectronApi` type\n\n## Three Communication Patterns\n\n1. **Sync** - `ipcRenderer.sendSync()` + `ipcMain.on()` + `event.returnValue = ...`\n2. **Async** - `ipcRenderer.invoke()` + `ipcMain.handle()`\n3. **Fire-and-forget** - `ipcRenderer.send()` + `ipcMain.on()`\n\n## Example: Async Method\n\n### 1. Define TypeScript Interface\n\nIn [`frontend/types/custom.d.ts`](frontend/types/custom.d.ts):\n\n```typescript\ntype ElectronApi = {\n    captureScreenshot: (rect: Electron.Rectangle) => Promise<string>; // capture-screenshot\n};\n```\n\n### 2. Expose in Preload\n\nIn [`emain/preload.ts`](emain/preload.ts):\n\n```typescript\ncontextBridge.exposeInMainWorld(\"api\", {\n    captureScreenshot: (rect: Rectangle) => ipcRenderer.invoke(\"capture-screenshot\", rect),\n});\n```\n\n### 3. Implement Handler\n\nIn [`emain/emain-ipc.ts`](emain/emain-ipc.ts):\n\n```typescript\nelectron.ipcMain.handle(\"capture-screenshot\", async (event, rect) => {\n    const tabView = getWaveTabViewByWebContentsId(event.sender.id);\n    if (!tabView) throw new Error(\"No tab view found\");\n    const image = await tabView.webContents.capturePage(rect);\n    return `data:image/png;base64,${image.toPNG().toString(\"base64\")}`;\n});\n```\n\n### 4. Add Preview Stub\n\nIn [`frontend/preview/preview-electron-api.ts`](frontend/preview/preview-electron-api.ts):\n\n```typescript\ncaptureScreenshot: (_rect: Electron.Rectangle) => Promise.resolve(\"\"),\n```\n\n### 5. Call from Frontend\n\n```typescript\nimport { getApi } from \"@/store/global\";\n\nconst dataUrl = await getApi().captureScreenshot({ x: 0, y: 0, width: 800, height: 600 });\n```\n\n## Example: Sync Method\n\n### 1. Define\n\n```typescript\ntype ElectronApi = {\n    getUserName: () => string; // get-user-name\n};\n```\n\n### 2. Preload\n\n```typescript\ngetUserName: () => ipcRenderer.sendSync(\"get-user-name\"),\n```\n\n### 3. Handler (⚠️ MUST set event.returnValue or browser hangs)\n\n```typescript\nelectron.ipcMain.on(\"get-user-name\", (event) => {\n    event.returnValue = process.env.USER || \"unknown\";\n});\n```\n\n### 4. Call\n\n```typescript\nimport { getApi } from \"@/store/global\";\n\nconst userName = getApi().getUserName(); // blocks until returns\n```\n\n## Example: Fire-and-Forget\n\n### 1. Define\n\n```typescript\ntype ElectronApi = {\n    openExternal: (url: string) => void; // open-external\n};\n```\n\n### 2. Preload\n\n```typescript\nopenExternal: (url) => ipcRenderer.send(\"open-external\", url),\n```\n\n### 3. Handler\n\n```typescript\nelectron.ipcMain.on(\"open-external\", (event, url) => {\n    electron.shell.openExternal(url);\n});\n```\n\n## Example: Event Listener\n\n### 1. Define\n\n```typescript\ntype ElectronApi = {\n    onZoomFactorChange: (callback: (zoomFactor: number) => void) => void; // zoom-factor-change\n};\n```\n\n### 2. Preload\n\n```typescript\nonZoomFactorChange: (callback) => \n    ipcRenderer.on(\"zoom-factor-change\", (_event, zoomFactor) => callback(zoomFactor)),\n```\n\n### 3. Send from Main\n\n```typescript\nwebContents.send(\"zoom-factor-change\", newZoomFactor);\n```\n\n## Quick Reference\n\n**Use Sync when:**\n- Getting config/env vars\n- Quick lookups, no I/O\n- ⚠️ **CRITICAL**: Always set `event.returnValue` or browser hangs\n\n**Use Async when:**\n- File operations\n- Network requests\n- Can fail or take time\n\n**Use Fire-and-forget when:**\n- No return value needed\n- Triggering actions\n\n**Electron API vs RPC:**\n- Electron API: Native OS features, window management, Electron APIs\n- RPC: Database, backend logic, remote servers\n\n## Checklist\n\n- [ ] Add to [`ElectronApi`](frontend/types/custom.d.ts:82) in [`custom.d.ts`](frontend/types/custom.d.ts)\n- [ ] Include IPC channel name in comment\n- [ ] Expose in [`preload.ts`](emain/preload.ts)\n- [ ] Implement in [`emain-ipc.ts`](emain/emain-ipc.ts)\n- [ ] Add no-op stub to [`preview-electron-api.ts`](frontend/preview/preview-electron-api.ts)\n- [ ] IPC channel names match exactly\n- [ ] **For sync**: Set `event.returnValue` (or browser hangs!)\n- [ ] Test end-to-end\n",".kilocode/skills/waveenv/SKILL.md":"---\nname: waveenv\ndescription: Guide for creating WaveEnv narrowings in Wave Terminal. Use when writing a named subset type of WaveEnv for a component tree, documenting environmental dependencies, or enabling mock environments for preview/test server usage.\n---\n\n# WaveEnv Narrowing Skill\n\n## Purpose\n\nA WaveEnv narrowing creates a _named subset type_ of `WaveEnv` that:\n\n1. Documents exactly which parts of the environment a component tree actually uses.\n2. Forms a type contract so callers and tests know what to provide.\n3. Enables mocking in the preview/test server — you only need to implement what's listed.\n\n## When To Create One\n\nCreate a narrowing whenever you are writing a component (or group of components) that you want to test in the preview server, or when you want to make the environmental dependencies of a component tree explicit.\n\n## Core Principle: Only Include What You Use\n\n**Only list the fields, methods, atoms, and keys that the component tree actually accesses.** If you don't call `wos`, don't include `wos`. If you only call one RPC command, only list that one command. The narrowing is a precise dependency declaration — not a copy of `WaveEnv`.\n\n## File Location\n\n- **Separate file** (preferred for shared/complex envs): name it `<feature>env.ts` next to the component, e.g. `frontend/app/block/blockenv.ts`.\n- **Inline** (acceptable for small, single-file components): export the type directly from the component file, e.g. `WidgetsEnv` in `frontend/app/workspace/widgets.tsx`.\n\n## Imports Required\n\n```ts\nimport {\n  MetaKeyAtomFnType, // only if you use getBlockMetaKeyAtom or getTabMetaKeyAtom\n  ConnConfigKeyAtomFnType, // only if you use getConnConfigKeyAtom\n  SettingsKeyAtomFnType, // only if you use getSettingsKeyAtom\n  WaveEnv,\n  WaveEnvSubset,\n} from \"@/app/waveenv/waveenv\";\n```\n\n## The Shape\n\n```ts\nexport type MyEnv = WaveEnvSubset<{\n  // --- Simple WaveEnv properties ---\n  // Copy the type verbatim from WaveEnv with WaveEnv[\"key\"] syntax.\n  isDev: WaveEnv[\"isDev\"];\n  createBlock: WaveEnv[\"createBlock\"];\n  showContextMenu: WaveEnv[\"showContextMenu\"];\n  platform: WaveEnv[\"platform\"];\n\n  // --- electron: list only the methods you call ---\n  electron: {\n    openExternal: WaveEnv[\"electron\"][\"openExternal\"];\n  };\n\n  // --- rpc: list only the commands you call ---\n  rpc: {\n    ActivityCommand: WaveEnv[\"rpc\"][\"ActivityCommand\"];\n    ConnEnsureCommand: WaveEnv[\"rpc\"][\"ConnEnsureCommand\"];\n  };\n\n  // --- atoms: list only the atoms you read ---\n  atoms: {\n    modalOpen: WaveEnv[\"atoms\"][\"modalOpen\"];\n    fullConfigAtom: WaveEnv[\"atoms\"][\"fullConfigAtom\"];\n  };\n\n  // --- wos: always take the whole thing, no sub-typing needed ---\n  wos: WaveEnv[\"wos\"];\n\n  // --- services: list only the services you call; no method-level narrowing ---\n  services: {\n    block: WaveEnv[\"services\"][\"block\"];\n    workspace: WaveEnv[\"services\"][\"workspace\"];\n  };\n\n  // --- key-parameterized atom factories: enumerate the keys you use ---\n  getSettingsKeyAtom: SettingsKeyAtomFnType<\"app:focusfollowscursor\" | \"window:magnifiedblockopacity\">;\n  getBlockMetaKeyAtom: MetaKeyAtomFnType<\"view\" | \"frame:title\" | \"connection\">;\n  getTabMetaKeyAtom: MetaKeyAtomFnType<\"tabid\" | \"name\">;\n  getConnConfigKeyAtom: ConnConfigKeyAtomFnType<\"conn:wshenabled\">;\n\n  // --- other atom helpers: copy verbatim ---\n  getConnStatusAtom: WaveEnv[\"getConnStatusAtom\"];\n  getLocalHostDisplayNameAtom: WaveEnv[\"getLocalHostDisplayNameAtom\"];\n  getConfigBackgroundAtom: WaveEnv[\"getConfigBackgroundAtom\"];\n}>;\n```\n\n### Automatically Included Fields\n\nEvery `WaveEnvSubset<T>` automatically includes the mock fields — you never need to declare them:\n\n- `isMock: boolean`\n- `mockSetWaveObj: <T extends WaveObj>(oref: string, obj: T) => void`\n- `mockModels?: Map<any, any>`\n\n### Rules for Each Section\n\n| Section                    | Pattern                                                | Notes                                                                                              |\n| -------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |\n| `electron`                 | `electron: { method: WaveEnv[\"electron\"][\"method\"]; }` | List every method called; omit the rest.                                                           |\n| `rpc`                      | `rpc: { Cmd: WaveEnv[\"rpc\"][\"Cmd\"]; }`                 | List every RPC command called; omit the rest.                                                      |\n| `atoms`                    | `atoms: { atom: WaveEnv[\"atoms\"][\"atom\"]; }`           | List every atom read; omit the rest.                                                               |\n| `wos`                      | `wos: WaveEnv[\"wos\"]`                                  | Take the whole `wos` object (no sub-typing needed), but **only add it if `wos` is actually used**. |\n| `services`                 | `services: { svc: WaveEnv[\"services\"][\"svc\"]; }`       | List each service used; take the whole service object (no method-level narrowing).                 |\n| `getSettingsKeyAtom`       | `SettingsKeyAtomFnType<\"key1\" \\| \"key2\">`              | Union all settings keys accessed.                                                                  |\n| `getBlockMetaKeyAtom`      | `MetaKeyAtomFnType<\"key1\" \\| \"key2\">`                  | Union all block meta keys accessed.                                                                |\n| `getTabMetaKeyAtom`        | `MetaKeyAtomFnType<\"key1\" \\| \"key2\">`                  | Union all tab meta keys accessed.                                                                  |\n| `getConnConfigKeyAtom`     | `ConnConfigKeyAtomFnType<\"key1\">`                      | Union all conn config keys accessed.                                                               |\n| All other `WaveEnv` fields | `WaveEnv[\"fieldName\"]`                                 | Copy type verbatim.                                                                                |\n\n## Using the Narrowed Type in Components\n\n```ts\nimport { useWaveEnv } from \"@/app/waveenv/waveenv\";\nimport { MyEnv } from \"./myenv\";\n\nconst MyComponent = memo(() => {\n    const env = useWaveEnv<MyEnv>();\n    // TypeScript now enforces you only access what's in MyEnv.\n    const val = useAtomValue(env.getSettingsKeyAtom(\"app:focusfollowscursor\"));\n    ...\n});\n```\n\nThe generic parameter on `useWaveEnv<MyEnv>()` casts the context to your narrowed type. The real production `WaveEnv` satisfies every narrowing; mock envs only need to implement the listed subset.\n\n## Real Examples\n\n- `BlockEnv` in `frontend/app/block/blockenv.ts` — complex narrowing with all section types, in a separate file.\n- `WidgetsEnv` in `frontend/app/workspace/widgets.tsx` — smaller narrowing defined inline in the component file.\n",".kilocode/skills/wps-events/SKILL.md":"---\nname: wps-events\ndescription: Guide for working with Wave Terminal's WPS (Wave PubSub) event system. Use when implementing new event types, publishing events, subscribing to events, or adding asynchronous communication between components.\n---\n\n# WPS Events Guide\n\n## Overview\n\nWPS (Wave PubSub) is Wave Terminal's publish-subscribe event system that enables different parts of the application to communicate asynchronously. The system uses a broker pattern to route events from publishers to subscribers based on event types and scopes.\n\n## Key Files\n\n- `pkg/wps/wpstypes.go` - Event type constants and data structures\n- `pkg/wps/wps.go` - Broker implementation and core logic\n- `pkg/wcore/wcore.go` - Example usage patterns\n\n## Event Structure\n\nEvents in WPS have the following structure:\n\n```go\ntype WaveEvent struct {\n    Event   string   `json:\"event\"`      // Event type constant\n    Scopes  []string `json:\"scopes,omitempty\"` // Optional scopes for targeted delivery\n    Sender  string   `json:\"sender,omitempty\"` // Optional sender identifier\n    Persist int      `json:\"persist,omitempty\"` // Number of events to persist in history\n    Data    any      `json:\"data,omitempty\"`    // Event payload\n}\n```\n\n## Adding a New Event Type\n\n### Step 1: Define the Event Constant\n\nAdd your event type constant to `pkg/wps/wpstypes.go`:\n\n```go\nconst (\n    Event_BlockClose       = \"blockclose\"\n    Event_ConnChange       = \"connchange\"\n    // ... other events ...\n    Event_YourNewEvent     = \"your:newevent\"  // type: YourEventData (or \"none\" if no data)\n)\n```\n\n**Naming Convention:**\n\n- Use descriptive PascalCase for the constant name with `Event_` prefix\n- Use lowercase with colons for the string value (e.g., \"namespace:eventname\")\n- Group related events with the same namespace prefix\n- Always add a `// type: <TypeName>` comment; use `// type: none` if no data is sent\n\n### Step 2: Add to AllEvents\n\nAdd your new constant to the `AllEvents` slice in `pkg/wps/wpstypes.go`:\n\n```go\nvar AllEvents []string = []string{\n    // ... existing events ...\n    Event_YourNewEvent,\n}\n```\n\n### Step 3: Register in WaveEventDataTypes (REQUIRED)\n\nYou **must** add an entry to `WaveEventDataTypes` in `pkg/tsgen/tsgenevent.go`. This drives TypeScript type generation for the event's `data` field:\n\n```go\nvar WaveEventDataTypes = map[string]reflect.Type{\n    // ... existing entries ...\n    wps.Event_YourNewEvent: reflect.TypeOf(YourEventData{}),        // value type\n    // wps.Event_YourNewEvent: reflect.TypeOf((*YourEventData)(nil)), // pointer type\n    // wps.Event_YourNewEvent: nil,                                   // no data (type: none)\n}\n```\n\n- Use `reflect.TypeOf(YourType{})` for value types\n- Use `reflect.TypeOf((*YourType)(nil))` for pointer types\n- Use `nil` if no data is sent for the event\n\n### Step 4: Define Event Data Structure (Optional)\n\nIf your event carries structured data, define a type for it:\n\n```go\ntype YourEventData struct {\n    Field1 string `json:\"field1\"`\n    Field2 int    `json:\"field2\"`\n}\n```\n\n### Step 5: Expose Type to Frontend (If Needed)\n\nIf your event data type isn't already exposed via an RPC call, you need to add it to `pkg/tsgen/tsgen.go` so TypeScript types are generated:\n\n```go\n// add extra types to generate here\nvar ExtraTypes = []any{\n    waveobj.ORef{},\n    // ... other types ...\n    uctypes.RateLimitInfo{},  // Example: already added\n    YourEventData{},          // Add your new type here\n}\n```\n\nThen run code generation:\n\n```bash\ntask generate\n```\n\nThis will update `frontend/types/gotypes.d.ts` with TypeScript definitions for your type, ensuring type safety in the frontend when handling these events.\n\n## Publishing Events\n\n### Basic Publishing\n\nTo publish an event, use the global broker:\n\n```go\nimport \"github.com/wavetermdev/waveterm/pkg/wps\"\n\nwps.Broker.Publish(wps.WaveEvent{\n    Event: wps.Event_YourNewEvent,\n    Data:  yourData,\n})\n```\n\n### Publishing with Scopes\n\nScopes allow targeted event delivery. Subscribers can filter events by scope:\n\n```go\nwps.Broker.Publish(wps.WaveEvent{\n    Event:  wps.Event_WaveObjUpdate,\n    Scopes: []string{oref.String()},  // Target specific object\n    Data:   updateData,\n})\n```\n\n### Publishing in a Goroutine\n\nTo avoid blocking the caller, publish events asynchronously:\n\n```go\ngo func() {\n    wps.Broker.Publish(wps.WaveEvent{\n        Event: wps.Event_YourNewEvent,\n        Data:  data,\n    })\n}()\n```\n\n**When to use goroutines:**\n\n- When publishing from performance-critical code paths\n- When the event is informational and doesn't need immediate delivery\n- When publishing from code that holds locks (to prevent deadlocks)\n\n### Event Persistence\n\nEvents can be persisted in memory for late subscribers:\n\n```go\nwps.Broker.Publish(wps.WaveEvent{\n    Event:   wps.Event_YourNewEvent,\n    Persist: 100,  // Keep last 100 events\n    Data:    data,\n})\n```\n\n## Complete Example: Rate Limit Updates\n\nThis example shows how rate limit information is published when AI chat responses include rate limit headers.\n\n### 1. Define the Event Type\n\nIn `pkg/wps/wpstypes.go`:\n\n```go\nconst (\n    // ... other events ...\n    Event_WaveAIRateLimit  = \"waveai:ratelimit\"\n)\n```\n\n### 2. Publish the Event\n\nIn `pkg/aiusechat/usechat.go`:\n\n```go\nimport \"github.com/wavetermdev/waveterm/pkg/wps\"\n\nfunc updateRateLimit(info *uctypes.RateLimitInfo) {\n    if info == nil {\n        return\n    }\n    rateLimitLock.Lock()\n    defer rateLimitLock.Unlock()\n    globalRateLimitInfo = info\n\n    // Publish event in goroutine to avoid blocking\n    go func() {\n        wps.Broker.Publish(wps.WaveEvent{\n            Event: wps.Event_WaveAIRateLimit,\n            Data:  info,  // RateLimitInfo struct\n        })\n    }()\n}\n```\n\n### 3. Subscribe to the Event (Frontend)\n\nIn the frontend, subscribe to events via WebSocket:\n\n```typescript\n// Subscribe to rate limit updates\nconst subscription = {\n  event: \"waveai:ratelimit\",\n  allscopes: true, // Receive all rate limit events\n};\n```\n\n## Subscribing to Events\n\n### From Go Code\n\n```go\n// Subscribe to all events of a type\nwps.Broker.Subscribe(routeId, wps.SubscriptionRequest{\n    Event:     wps.Event_YourNewEvent,\n    AllScopes: true,\n})\n\n// Subscribe to specific scopes\nwps.Broker.Subscribe(routeId, wps.SubscriptionRequest{\n    Event:  wps.Event_WaveObjUpdate,\n    Scopes: []string{\"workspace:123\"},\n})\n\n// Unsubscribe\nwps.Broker.Unsubscribe(routeId, wps.Event_YourNewEvent)\n```\n\n### Scope Matching\n\nScopes support wildcard matching:\n\n- `*` matches a single scope segment\n- `**` matches multiple scope segments\n\n```go\n// Subscribe to all workspace events\nwps.Broker.Subscribe(routeId, wps.SubscriptionRequest{\n    Event:  wps.Event_WaveObjUpdate,\n    Scopes: []string{\"workspace:*\"},\n})\n```\n\n## Best Practices\n\n1. **Use Namespaces**: Prefix event names with a namespace (e.g., `waveai:`, `workspace:`, `block:`)\n\n2. **Don't Block**: Use goroutines when publishing from performance-critical code or while holding locks\n\n3. **Type-Safe Data**: Define struct types for event data rather than using maps\n\n4. **Scope Wisely**: Use scopes to limit event delivery and reduce unnecessary processing\n\n5. **Document Events**: Add comments explaining when events are fired and what data they carry\n\n6. **Consider Persistence**: Use `Persist` for events that late subscribers might need (like status updates). This is normally not used. We normally do a live RPC call to get the current value and then subscribe for updates.\n\n## Common Event Patterns\n\n### Status Updates\n\n```go\nwps.Broker.Publish(wps.WaveEvent{\n    Event:   wps.Event_ControllerStatus,\n    Scopes:  []string{blockId},\n    Persist: 1,  // Keep only latest status\n    Data:    statusData,\n})\n```\n\n### Object Updates\n\n```go\nwps.Broker.Publish(wps.WaveEvent{\n    Event:  wps.Event_WaveObjUpdate,\n    Scopes: []string{oref.String()},\n    Data: waveobj.WaveObjUpdate{\n        UpdateType: waveobj.UpdateType_Update,\n        OType:      obj.GetOType(),\n        OID:        waveobj.GetOID(obj),\n        Obj:        obj,\n    },\n})\n```\n\n### Batch Updates\n\n```go\n// Helper function for multiple updates\nfunc (b *BrokerType) SendUpdateEvents(updates waveobj.UpdatesRtnType) {\n    for _, update := range updates {\n        b.Publish(WaveEvent{\n            Event:  Event_WaveObjUpdate,\n            Scopes: []string{waveobj.MakeORef(update.OType, update.OID).String()},\n            Data:   update,\n        })\n    }\n}\n```\n\n## Debugging\n\nTo debug event flow:\n\n1. Check broker subscription map: `wps.Broker.SubMap`\n2. View persisted events: `wps.Broker.ReadEventHistory(eventType, scope, maxItems)`\n3. Add logging in publish/subscribe methods\n4. Monitor WebSocket traffic in browser dev tools\n\n## Quick Reference\n\nWhen adding a new event:\n\n- [ ] Add event constant to [`pkg/wps/wpstypes.go`](pkg/wps/wpstypes.go) with a `// type: <TypeName>` comment (use `none` if no data)\n- [ ] Add the constant to `AllEvents` in [`pkg/wps/wpstypes.go`](pkg/wps/wpstypes.go)\n- [ ] **REQUIRED**: Add an entry to `WaveEventDataTypes` in [`pkg/tsgen/tsgenevent.go`](pkg/tsgen/tsgenevent.go) — use `nil` for events with no data\n- [ ] Define event data structure (if needed)\n- [ ] Add data type to `pkg/tsgen/tsgen.go` for frontend use (if not already exposed via RPC)\n- [ ] Run `task generate` to update TypeScript types\n- [ ] Publish events using `wps.Broker.Publish()`\n- [ ] Use goroutines for non-blocking publish when appropriate\n- [ ] Subscribe to events in relevant components\n",".kilocode/skills/create-view/SKILL.md":"---\nname: create-view\ndescription: Guide for implementing a new view type in Wave Terminal. Use when creating a new view component, implementing the ViewModel interface, registering a new view type in BlockRegistry, or adding a new content type to display within blocks.\n---\n\n# Creating a New View in Wave Terminal\n\nThis guide explains how to implement a new view type in Wave Terminal. Views are the core content components displayed within blocks in the terminal interface.\n\n## Architecture Overview\n\nWave Terminal uses a **Model-View architecture** where:\n\n- **ViewModel** - Contains all state, logic, and UI configuration as Jotai atoms\n- **ViewComponent** - Pure React component that renders the UI using the model\n- **BlockFrame** - Wraps views with a header, connection management, and standard controls\n\nThe separation between model and component ensures:\n\n- Models can update state without React hooks\n- Components remain pure and testable\n- State is centralized in Jotai atoms for easy access\n\n## ViewModel Interface\n\nEvery view must implement the `ViewModel` interface defined in `frontend/types/custom.d.ts`:\n\n```typescript\ninterface ViewModel {\n  // Required: The type identifier for this view (e.g., \"term\", \"web\", \"preview\")\n  viewType: string;\n\n  // Required: The React component that renders this view\n  viewComponent: ViewComponent<ViewModel>;\n\n  // Optional: Icon shown in block header (FontAwesome icon name or IconButtonDecl)\n  viewIcon?: jotai.Atom<string | IconButtonDecl>;\n\n  // Optional: Display name shown in block header (e.g., \"Terminal\", \"Web\", \"Preview\")\n  viewName?: jotai.Atom<string>;\n\n  // Optional: Additional header elements (text, buttons, inputs) shown after the name\n  viewText?: jotai.Atom<string | HeaderElem[]>;\n\n  // Optional: Icon button shown before the view name in header\n  preIconButton?: jotai.Atom<IconButtonDecl>;\n\n  // Optional: Icon buttons shown at the end of the header (before settings/close)\n  endIconButtons?: jotai.Atom<IconButtonDecl[]>;\n\n  // Optional: Custom background styling for the block\n  blockBg?: jotai.Atom<MetaType>;\n\n  // Optional: If true, completely hides the block header\n  noHeader?: jotai.Atom<boolean>;\n\n  // Optional: If true, shows connection picker in header for remote connections\n  manageConnection?: jotai.Atom<boolean>;\n\n  // Optional: If true, filters out 'nowsh' connections from connection picker\n  filterOutNowsh?: jotai.Atom<boolean>;\n\n  // Optional: If true, removes default padding from content area\n  noPadding?: jotai.Atom<boolean>;\n\n  // Optional: Atoms for managing in-block search functionality\n  searchAtoms?: SearchAtoms;\n\n  // Optional: Returns whether this is a basic terminal (for multi-input feature)\n  isBasicTerm?: (getFn: jotai.Getter) => boolean;\n\n  // Optional: Returns context menu items for the settings dropdown\n  getSettingsMenuItems?: () => ContextMenuItem[];\n\n  // Optional: Focuses the view when called, returns true if successful\n  giveFocus?: () => boolean;\n\n  // Optional: Handles keyboard events, returns true if handled\n  keyDownHandler?: (e: WaveKeyboardEvent) => boolean;\n\n  // Optional: Cleanup when block is closed\n  dispose?: () => void;\n}\n```\n\n### Key Concepts\n\n**Atoms**: All UI-related properties must be Jotai atoms. This enables:\n\n- Reactive updates when state changes\n- Access from anywhere via `globalStore.get()`/`globalStore.set()`\n- Derived atoms that compute values from other atoms\n\n**ViewComponent**: The React component receives these props:\n\n```typescript\ntype ViewComponentProps<T extends ViewModel> = {\n  blockId: string; // Unique ID for this block\n  blockRef: React.RefObject<HTMLDivElement>; // Ref to block container\n  contentRef: React.RefObject<HTMLDivElement>; // Ref to content area\n  model: T; // Your ViewModel instance\n};\n```\n\n## Step-by-Step Guide\n\n### 1. Create the View Model Class\n\nCreate a new file for your view model (e.g., `frontend/app/view/myview/myview-model.ts`):\n\n```typescript\nimport { BlockNodeModel } from \"@/app/block/blocktypes\";\nimport { globalStore } from \"@/app/store/jotaiStore\";\nimport { WOS, useBlockAtom } from \"@/store/global\";\nimport * as jotai from \"jotai\";\nimport { MyView } from \"./myview\";\n\nexport class MyViewModel implements ViewModel {\n  viewType: string;\n  blockId: string;\n  nodeModel: BlockNodeModel;\n  blockAtom: jotai.Atom<Block>;\n\n  // Define your atoms (simple field initializers)\n  viewIcon = jotai.atom<string>(\"circle\");\n  viewName = jotai.atom<string>(\"My View\");\n  noPadding = jotai.atom<boolean>(true);\n\n  // Derived atom (created in constructor)\n  viewText!: jotai.Atom<HeaderElem[]>;\n\n  constructor(blockId: string, nodeModel: BlockNodeModel) {\n    this.viewType = \"myview\";\n    this.blockId = blockId;\n    this.nodeModel = nodeModel;\n    this.blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);\n\n    // Create derived atoms that depend on block data or other atoms\n    this.viewText = jotai.atom((get) => {\n      const blockData = get(this.blockAtom);\n      const rtn: HeaderElem[] = [];\n\n      // Add header buttons/text based on state\n      rtn.push({\n        elemtype: \"iconbutton\",\n        icon: \"refresh\",\n        title: \"Refresh\",\n        click: () => this.refresh(),\n      });\n\n      return rtn;\n    });\n  }\n\n  get viewComponent(): ViewComponent {\n    return MyView;\n  }\n\n  refresh() {\n    // Update state using globalStore\n    // Never use React hooks in model methods\n    console.log(\"refreshing...\");\n  }\n\n  giveFocus(): boolean {\n    // Focus your view component\n    return true;\n  }\n\n  dispose() {\n    // Cleanup resources (unsubscribe from events, etc.)\n  }\n}\n```\n\n### 2. Create the View Component\n\nCreate your React component (e.g., `frontend/app/view/myview/myview.tsx`):\n\n```typescript\nimport { ViewComponentProps } from \"@/app/block/blocktypes\";\nimport { MyViewModel } from \"./myview-model\";\nimport { useAtomValue } from \"jotai\";\nimport \"./myview.scss\";\n\nexport const MyView: React.FC<ViewComponentProps<MyViewModel>> = ({\n    blockId,\n    model,\n    contentRef\n}) => {\n    // Use atoms from the model (these are React hooks - call at top level!)\n    const blockData = useAtomValue(model.blockAtom);\n\n    return (\n        <div className=\"myview-container\" ref={contentRef}>\n            <div>Block ID: {blockId}</div>\n            <div>View: {model.viewType}</div>\n            {/* Your view content here */}\n        </div>\n    );\n};\n```\n\n### 3. Register the View\n\nAdd your view to the `BlockRegistry` in `frontend/app/block/blockregistry.ts`:\n\n```typescript\nimport { MyViewModel } from \"@/app/view/myview/myview-model\";\n\nconst BlockRegistry: Map<string, ViewModelClass> = new Map();\nBlockRegistry.set(\"term\", TermViewModel);\nBlockRegistry.set(\"preview\", PreviewModel);\nBlockRegistry.set(\"web\", WebViewModel);\n// ... existing registrations ...\nBlockRegistry.set(\"myview\", MyViewModel); // Add your view here\n```\n\nThe registry key (e.g., `\"myview\"`) becomes the view type used in block metadata.\n\n### 4. Create Blocks with Your View\n\nUsers can create blocks with your view type:\n\n- Via CLI: `wsh view myview`\n- Via RPC: Use the block's `meta.view` field set to `\"myview\"`\n\n## Real-World Examples\n\n### Example 1: Terminal View (`term-model.ts`)\n\nThe terminal view demonstrates:\n\n- **Connection management** via `manageConnection` atom\n- **Dynamic header buttons** showing shell status (play/restart)\n- **Mode switching** between terminal and vdom views\n- **Custom keyboard handling** for terminal-specific shortcuts\n- **Focus management** to focus the xterm.js instance\n- **Shell integration status** showing AI capability indicators\n\nKey features:\n\n```typescript\nthis.manageConnection = jotai.atom((get) => {\n  const termMode = get(this.termMode);\n  if (termMode == \"vdom\") return false;\n  return true; // Show connection picker for regular terminal mode\n});\n\nthis.endIconButtons = jotai.atom((get) => {\n  const shellProcStatus = get(this.shellProcStatus);\n  const buttons: IconButtonDecl[] = [];\n\n  if (shellProcStatus == \"running\") {\n    buttons.push({\n      elemtype: \"iconbutton\",\n      icon: \"refresh\",\n      title: \"Restart Shell\",\n      click: this.forceRestartController.bind(this),\n    });\n  }\n  return buttons;\n});\n```\n\n### Example 2: Web View (`webview.tsx`)\n\nThe web view shows:\n\n- **Complex header controls** (back/forward/home/URL input)\n- **State management** for loading, URL, and navigation\n- **Event handling** for webview navigation events\n- **Custom styling** with `noPadding` for full-bleed content\n- **Media controls** showing play/pause/mute when media is active\n\nKey features:\n\n```typescript\nthis.viewText = jotai.atom((get) => {\n  const url = get(this.url);\n  const rtn: HeaderElem[] = [];\n\n  // Navigation buttons\n  rtn.push({\n    elemtype: \"iconbutton\",\n    icon: \"chevron-left\",\n    click: this.handleBack.bind(this),\n    disabled: this.shouldDisableBackButton(),\n  });\n\n  // URL input with nested controls\n  rtn.push({\n    elemtype: \"div\",\n    className: \"block-frame-div-url\",\n    children: [\n      {\n        elemtype: \"input\",\n        value: url,\n        onChange: this.handleUrlChange.bind(this),\n        onKeyDown: this.handleKeyDown.bind(this),\n      },\n      {\n        elemtype: \"iconbutton\",\n        icon: \"rotate-right\",\n        click: this.handleRefresh.bind(this),\n      },\n    ],\n  });\n\n  return rtn;\n});\n```\n\n## Header Elements (`HeaderElem`)\n\nThe `viewText` atom can return an array of these element types:\n\n```typescript\n// Icon button\n{\n    elemtype: \"iconbutton\",\n    icon: \"refresh\",\n    title: \"Tooltip text\",\n    click: () => { /* handler */ },\n    disabled?: boolean,\n    iconColor?: string,\n    iconSpin?: boolean,\n    noAction?: boolean,  // Shows icon but no click action\n}\n\n// Text element\n{\n    elemtype: \"text\",\n    text: \"Display text\",\n    className?: string,\n    noGrow?: boolean,\n    ref?: React.RefObject<HTMLElement>,\n    onClick?: (e: React.MouseEvent) => void,\n}\n\n// Text button\n{\n    elemtype: \"textbutton\",\n    text: \"Button text\",\n    className?: string,\n    title: \"Tooltip\",\n    onClick: (e: React.MouseEvent) => void,\n}\n\n// Input field\n{\n    elemtype: \"input\",\n    value: string,\n    className?: string,\n    onChange: (e: React.ChangeEvent<HTMLInputElement>) => void,\n    onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void,\n    onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void,\n    onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void,\n    ref?: React.RefObject<HTMLInputElement>,\n}\n\n// Container with children\n{\n    elemtype: \"div\",\n    className?: string,\n    children: HeaderElem[],\n    onMouseOver?: (e: React.MouseEvent) => void,\n    onMouseOut?: (e: React.MouseEvent) => void,\n}\n\n// Menu button (dropdown)\n{\n    elemtype: \"menubutton\",\n    // ... MenuButtonProps ...\n}\n```\n\n## Best Practices\n\n### Jotai Model Pattern\n\nFollow these rules for Jotai atoms in models:\n\n1. **Simple atoms as field initializers**:\n\n   ```typescript\n   viewIcon = jotai.atom<string>(\"circle\");\n   noPadding = jotai.atom<boolean>(true);\n   ```\n\n2. **Derived atoms in constructor** (need dependency on other atoms):\n\n   ```typescript\n   constructor(blockId: string, nodeModel: BlockNodeModel) {\n       this.viewText = jotai.atom((get) => {\n           const blockData = get(this.blockAtom);\n           return [/* computed based on blockData */];\n       });\n   }\n   ```\n\n3. **Models never use React hooks** - Use `globalStore.get()`/`set()`:\n\n   ```typescript\n   refresh() {\n       const currentData = globalStore.get(this.blockAtom);\n       globalStore.set(this.dataAtom, newData);\n   }\n   ```\n\n4. **Components use hooks for atoms**:\n   ```typescript\n   const data = useAtomValue(model.dataAtom);\n   const [value, setValue] = useAtom(model.valueAtom);\n   ```\n\n### State Management\n\n- All view state should live in atoms on the model\n- Use `useBlockAtom()` helper for block-scoped atoms that persist\n- Use `globalStore` for imperative access outside React components\n- Subscribe to Wave events using `waveEventSubscribe()`\n\n### Styling\n\n- Create a `.scss` file for your view styles\n- Use Tailwind utilities where possible (v4)\n- Add `noPadding: atom(true)` for full-bleed content\n- Use `blockBg` atom to customize block background\n\n### Focus Management\n\nImplement `giveFocus()` to focus your view when:\n\n- Block gains focus via keyboard navigation\n- User clicks the block\n- Return `true` if successfully focused, `false` otherwise\n\n### Keyboard Handling\n\nImplement `keyDownHandler(e: WaveKeyboardEvent)` for:\n\n- View-specific keyboard shortcuts\n- Return `true` if event was handled (prevents propagation)\n- Use `keyutil.checkKeyPressed(waveEvent, \"Cmd:K\")` for shortcut checks\n\n### Cleanup\n\nImplement `dispose()` to:\n\n- Unsubscribe from Wave events\n- Unregister routes/handlers\n- Clear timers/intervals\n- Release resources\n\n### Connection Management\n\nFor views that need remote connections:\n\n```typescript\nthis.manageConnection = jotai.atom(true); // Show connection picker\nthis.filterOutNowsh = jotai.atom(true); // Hide nowsh connections\n```\n\nAccess connection status:\n\n```typescript\nconst connStatus = jotai.atom((get) => {\n  const blockData = get(this.blockAtom);\n  const connName = blockData?.meta?.connection;\n  return get(getConnStatusAtom(connName));\n});\n```\n\n## Common Patterns\n\n### Reading Block Metadata\n\n```typescript\nimport { getBlockMetaKeyAtom } from \"@/store/global\";\n\n// In constructor:\nthis.someFlag = getBlockMetaKeyAtom(blockId, \"myview:flag\");\n\n// In component:\nconst flag = useAtomValue(model.someFlag);\n```\n\n### Configuration Overrides\n\nWave has a hierarchical config system (global → connection → block):\n\n```typescript\nimport { getOverrideConfigAtom } from \"@/store/global\";\n\nthis.settingAtom = jotai.atom((get) => {\n  // Checks block meta, then connection config, then global settings\n  return get(getOverrideConfigAtom(this.blockId, \"myview:setting\")) ?? defaultValue;\n});\n```\n\n### Updating Block Metadata\n\n```typescript\nimport { RpcApi } from \"@/app/store/wshclientapi\";\nimport { TabRpcClient } from \"@/app/store/wshrpcutil\";\nimport { WOS } from \"@/store/global\";\n\nawait RpcApi.SetMetaCommand(TabRpcClient, {\n  oref: WOS.makeORef(\"block\", this.blockId),\n  meta: { \"myview:key\": value },\n});\n```\n\n## Additional Resources\n\n- `frontend/app/block/blockframe-header.tsx` - Block header rendering\n- `frontend/app/view/term/term-model.ts` - Complex view example\n- `frontend/app/view/webview/webview.tsx` - Navigation UI example\n- `frontend/types/custom.d.ts` - Type definitions\n",".kilocode/skills/add-rpc/SKILL.md":"---\nname: add-rpc\ndescription: Guide for adding new RPC calls to Wave Terminal. Use when implementing new RPC commands, adding server-client communication methods, or extending the RPC interface with new functionality.\n---\n\n# Adding RPC Calls Guide\n\n## Overview\n\nWave Terminal uses a WebSocket-based RPC (Remote Procedure Call) system for communication between different components. The RPC system allows the frontend, backend, electron main process, remote servers, and terminal blocks to communicate with each other through well-defined commands.\n\nThis guide covers how to add a new RPC command to the system.\n\n## Key Files\n\n- `pkg/wshrpc/wshrpctypes.go` - RPC interface and type definitions\n- `pkg/wshrpc/wshserver/wshserver.go` - Main server implementation (most common)\n- `emain/emain-wsh.ts` - Electron main process implementation\n- `frontend/app/store/tabrpcclient.ts` - Frontend tab implementation\n- `pkg/wshrpc/wshremote/wshremote.go` - Remote server implementation\n- `frontend/app/view/term/term-wsh.tsx` - Terminal block implementation\n\n## RPC Command Structure\n\nRPC commands in Wave Terminal follow these conventions:\n\n- **Method names** must end with `Command`\n- **First parameter** must be `context.Context`\n- **Remaining parameters** are a regular Go parameter list (zero or more typed args)\n- **Return values** can be either just an error, or one return value plus an error\n- **Streaming commands** return a channel instead of a direct value\n\n## Adding a New RPC Call\n\n### Step 1: Define the Command in the Interface\n\nAdd your command to the `WshRpcInterface` in `pkg/wshrpc/wshrpctypes.go`:\n\n```go\ntype WshRpcInterface interface {\n    // ... existing commands ...\n    \n    // Add your new command\n    YourNewCommand(ctx context.Context, data CommandYourNewData) (*YourNewResponse, error)\n}\n```\n\n**Method Signature Rules:**\n\n- Method name must end with `Command`\n- First parameter must be `ctx context.Context`\n- Remaining parameters are a regular Go parameter list (zero or more)\n- Return either `error` or `(ReturnType, error)`\n- For streaming, return `chan RespOrErrorUnion[T]`\n\n### Step 2: Define Request and Response Types\n\nIf your command needs structured input or output, define types in the same file:\n\n```go\ntype CommandYourNewData struct {\n    FieldOne   string `json:\"fieldone\"`\n    FieldTwo   int    `json:\"fieldtwo\"`\n    SomeId     string `json:\"someid\"`\n}\n\ntype YourNewResponse struct {\n    ResultField string `json:\"resultfield\"`\n    Success     bool   `json:\"success\"`\n}\n```\n\n**Type Naming Conventions:**\n\n- Request types: `Command[Name]Data` (e.g., `CommandGetMetaData`)\n- Response types: `[Name]Response` or `Command[Name]RtnData` (e.g., `CommandResolveIdsRtnData`)\n- Use `json` struct tags with lowercase field names\n- Follow existing patterns in the file for consistency\n\n### Step 3: Generate Bindings\n\nAfter modifying `pkg/wshrpc/wshrpctypes.go`, run code generation to create TypeScript bindings and Go helper code:\n\n```bash\ntask generate\n```\n\nThis command will:\n- Generate TypeScript type definitions in `frontend/types/gotypes.d.ts`\n- Create RPC client bindings\n- Update routing code\n\n**Note:** If generation fails, check that your method signature follows all the rules above.\n\n### Step 4: Implement the Command\n\nChoose where to implement your command based on what it needs to do:\n\n#### A. Main Server Implementation (Most Common)\n\nImplement in `pkg/wshrpc/wshserver/wshserver.go`:\n\n```go\nfunc (ws *WshServer) YourNewCommand(ctx context.Context, data wshrpc.CommandYourNewData) (*wshrpc.YourNewResponse, error) {\n    // Validate input\n    if data.SomeId == \"\" {\n        return nil, fmt.Errorf(\"someid is required\")\n    }\n    \n    // Implement your logic\n    result := doSomething(data)\n    \n    // Return response\n    return &wshrpc.YourNewResponse{\n        ResultField: result,\n        Success:     true,\n    }, nil\n}\n```\n\n**Use main server when:**\n- Accessing the database\n- Managing blocks, tabs, or workspaces\n- Coordinating between components\n- Handling file operations on the main filesystem\n\n#### B. Electron Implementation\n\nImplement in `emain/emain-wsh.ts`:\n\n```typescript\nasync handle_yournew(rh: RpcResponseHelper, data: CommandYourNewData): Promise<YourNewResponse> {\n    // Electron-specific logic\n    const result = await electronAPI.doSomething(data);\n    \n    return {\n        resultfield: result,\n        success: true,\n    };\n}\n```\n\n**Use Electron when:**\n- Accessing native OS features\n- Managing application windows\n- Using Electron APIs (notifications, system tray, etc.)\n- Handling encryption/decryption with safeStorage\n\n#### C. Frontend Tab Implementation\n\nImplement in `frontend/app/store/tabrpcclient.ts`:\n\n```typescript\nasync handle_yournew(rh: RpcResponseHelper, data: CommandYourNewData): Promise<YourNewResponse> {\n    // Access frontend state/models\n    const layoutModel = getLayoutModelForStaticTab();\n    \n    // Implement tab-specific logic\n    const result = layoutModel.doSomething(data);\n    \n    return {\n        resultfield: result,\n        success: true,\n    };\n}\n```\n\n**Use tab client when:**\n- Accessing React state or Jotai atoms\n- Manipulating UI layout\n- Capturing screenshots\n- Reading frontend-only data\n\n#### D. Remote Server Implementation\n\nImplement in `pkg/wshrpc/wshremote/wshremote.go`:\n\n```go\nfunc (impl *ServerImpl) RemoteYourNewCommand(ctx context.Context, data wshrpc.CommandRemoteYourNewData) (*wshrpc.YourNewResponse, error) {\n    // Remote filesystem or process operations\n    result, err := performRemoteOperation(data)\n    if err != nil {\n        return nil, fmt.Errorf(\"remote operation failed: %w\", err)\n    }\n    \n    return &wshrpc.YourNewResponse{\n        ResultField: result,\n        Success:     true,\n    }, nil\n}\n```\n\n**Use remote server when:**\n- Operating on remote filesystems\n- Executing commands on remote hosts\n- Managing remote processes\n- Convention: prefix command name with `Remote` (e.g., `RemoteGetInfoCommand`)\n\n#### E. Terminal Block Implementation\n\nImplement in `frontend/app/view/term/term-wsh.tsx`:\n\n```typescript\nasync handle_yournew(rh: RpcResponseHelper, data: CommandYourNewData): Promise<YourNewResponse> {\n    // Access terminal-specific data\n    const termWrap = this.model.termRef.current;\n    \n    // Implement terminal logic\n    const result = termWrap.doSomething(data);\n    \n    return {\n        resultfield: result,\n        success: true,\n    };\n}\n```\n\n**Use terminal client when:**\n- Accessing terminal buffer/scrollback\n- Managing VDOM contexts\n- Reading terminal-specific state\n- Interacting with xterm.js\n\n## Complete Example: Adding GetWaveInfo Command\n\n### 1. Define Interface\n\nIn `pkg/wshrpc/wshrpctypes.go`:\n\n```go\ntype WshRpcInterface interface {\n    // ... other commands ...\n    WaveInfoCommand(ctx context.Context) (*WaveInfoData, error)\n}\n\ntype WaveInfoData struct {\n    Version      string            `json:\"version\"`\n    BuildTime    string            `json:\"buildtime\"`\n    ConfigPath   string            `json:\"configpath\"`\n    DataPath     string            `json:\"datapath\"`\n}\n```\n\n### 2. Generate Bindings\n\n```bash\ntask generate\n```\n\n### 3. Implement in Main Server\n\nIn `pkg/wshrpc/wshserver/wshserver.go`:\n\n```go\nfunc (ws *WshServer) WaveInfoCommand(ctx context.Context) (*wshrpc.WaveInfoData, error) {\n    return &wshrpc.WaveInfoData{\n        Version:    wavebase.WaveVersion,\n        BuildTime:  wavebase.BuildTime,\n        ConfigPath: wavebase.GetConfigDir(),\n        DataPath:   wavebase.GetWaveDataDir(),\n    }, nil\n}\n```\n\n### 4. Call from Frontend\n\n```typescript\nimport { RpcApi } from \"@/app/store/wshclientapi\";\n\n// Call the RPC\nconst info = await RpcApi.WaveInfoCommand(TabRpcClient);\nconsole.log(\"Wave Version:\", info.version);\n```\n\n## Streaming Commands\n\nFor commands that return data progressively, use channels:\n\n### Define Streaming Interface\n\n```go\ntype WshRpcInterface interface {\n    StreamYourDataCommand(ctx context.Context, request YourDataRequest) chan RespOrErrorUnion[YourDataType]\n}\n```\n\n### Implement Streaming Command\n\n```go\nfunc (ws *WshServer) StreamYourDataCommand(ctx context.Context, request wshrpc.YourDataRequest) chan wshrpc.RespOrErrorUnion[wshrpc.YourDataType] {\n    rtn := make(chan wshrpc.RespOrErrorUnion[wshrpc.YourDataType])\n    \n    go func() {\n        defer close(rtn)\n        defer func() {\n            panichandler.PanicHandler(\"StreamYourDataCommand\", recover())\n        }()\n        \n        // Stream data\n        for i := 0; i < 10; i++ {\n            select {\n            case <-ctx.Done():\n                return\n            default:\n                rtn <- wshrpc.RespOrErrorUnion[wshrpc.YourDataType]{\n                    Response: wshrpc.YourDataType{\n                        Value: i,\n                    },\n                }\n                time.Sleep(100 * time.Millisecond)\n            }\n        }\n    }()\n    \n    return rtn\n}\n```\n\n## Best Practices\n\n1. **Validation First**: Always validate input parameters at the start of your implementation\n\n2. **Descriptive Names**: Use clear, action-oriented command names (e.g., `GetFullConfigCommand`, not `ConfigCommand`)\n\n3. **Error Handling**: Return descriptive errors with context:\n   ```go\n   return nil, fmt.Errorf(\"error creating block: %w\", err)\n   ```\n\n4. **Context Awareness**: Respect context cancellation for long-running operations:\n   ```go\n   select {\n   case <-ctx.Done():\n       return ctx.Err()\n   default:\n       // continue\n   }\n   ```\n\n5. **Consistent Types**: Follow existing naming patterns for request/response types\n\n6. **JSON Tags**: Always use lowercase JSON tags matching frontend conventions\n\n7. **Documentation**: Add comments explaining complex commands or special behaviors\n\n8. **Type Safety**: Leverage TypeScript generation - your types will be checked on both ends\n\n9. **Panic Recovery**: Use `panichandler.PanicHandler` in goroutines to prevent crashes\n\n10. **Route Awareness**: For multi-route scenarios, use `wshutil.GetRpcSourceFromContext(ctx)` to identify callers\n\n## Common Command Patterns\n\n### Simple Query\n\n```go\nfunc (ws *WshServer) GetSomethingCommand(ctx context.Context, id string) (*Something, error) {\n    obj, err := wstore.DBGet[*Something](ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"error getting something: %w\", err)\n    }\n    return obj, nil\n}\n```\n\n### Mutation with Updates\n\n```go\nfunc (ws *WshServer) UpdateSomethingCommand(ctx context.Context, data wshrpc.CommandUpdateData) error {\n    ctx = waveobj.ContextWithUpdates(ctx)\n    \n    // Make changes\n    err := wstore.UpdateObject(ctx, data.ORef, data.Updates)\n    if err != nil {\n        return fmt.Errorf(\"error updating: %w\", err)\n    }\n    \n    // Broadcast updates\n    updates := waveobj.ContextGetUpdatesRtn(ctx)\n    wps.Broker.SendUpdateEvents(updates)\n    \n    return nil\n}\n```\n\n### Command with Side Effects\n\n```go\nfunc (ws *WshServer) DoActionCommand(ctx context.Context, data wshrpc.CommandActionData) error {\n    // Perform action\n    result, err := performAction(data)\n    if err != nil {\n        return err\n    }\n    \n    // Publish event about the action\n    go func() {\n        wps.Broker.Publish(wps.WaveEvent{\n            Event: wps.Event_ActionComplete,\n            Data:  result,\n        })\n    }()\n    \n    return nil\n}\n```\n\n## Troubleshooting\n\n### Command Not Found\n\n- Ensure method name ends with `Command`\n- Verify you ran `task generate`\n- Check that the interface is in `WshRpcInterface`\n\n### Type Mismatch Errors\n\n- Run `task generate` after changing types\n- Ensure JSON tags are lowercase\n- Verify TypeScript code is using generated types\n\n### Command Times Out\n\n- Check for blocking operations\n- Ensure context is passed through\n- Consider using a streaming command for long operations\n\n### Routing Issues\n\n- For remote commands, ensure they're implemented in correct location\n- Check route configuration in RpcContext\n- Verify authentication for secured routes\n\n## Quick Reference\n\nWhen adding a new RPC command:\n\n- [ ] Add method to `WshRpcInterface` in `pkg/wshrpc/wshrpctypes.go` (must end with `Command`)\n- [ ] Define request/response types with JSON tags (if needed)\n- [ ] Run `task generate` to create bindings\n- [ ] Implement in appropriate location:\n  - [ ] `wshserver.go` for main server (most common)\n  - [ ] `emain-wsh.ts` for Electron\n  - [ ] `tabrpcclient.ts` for frontend\n  - [ ] `wshremote.go` for remote (prefix with `Remote`)\n  - [ ] `term-wsh.tsx` for terminal\n- [ ] Add input validation\n- [ ] Handle errors with context\n- [ ] Test the command end-to-end\n\n## Related Documentation\n\n- **WPS Events**: See the `wps-events` skill - Publishing events from RPC commands\n",".kilocode/skills/add-wshcmd/SKILL.md":"---\nname: add-wshcmd\ndescription: Guide for adding new wsh commands to Wave Terminal. Use when implementing new CLI commands, adding command-line functionality, or extending the wsh command interface.\n---\n\n# Adding a New wsh Command to Wave Terminal\n\nThis guide explains how to add a new command to the `wsh` CLI tool.\n\n## wsh Command System Overview\n\nWave Terminal's `wsh` command provides CLI access to Wave Terminal features. The system uses:\n\n1. **Cobra Framework** - CLI command structure and parsing\n2. **Command Files** - Individual command implementations in `cmd/wsh/cmd/wshcmd-*.go`\n3. **RPC Client** - Communication with Wave Terminal backend via `RpcClient`\n4. **Activity Tracking** - Telemetry for command usage analytics\n5. **Documentation** - User-facing docs in `docs/docs/wsh-reference.mdx`\n\nCommands are registered in their `init()` functions and execute through the Cobra framework.\n\n## Step-by-Step Guide\n\n### Step 1: Create Command File\n\nCreate a new file in `cmd/wsh/cmd/` named `wshcmd-[commandname].go`:\n\n```go\n// Copyright 2025, Command Line Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n    \"fmt\"\n\n    \"github.com/spf13/cobra\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient\"\n)\n\nvar myCommandCmd = &cobra.Command{\n    Use:   \"mycommand [args]\",\n    Short: \"Brief description of what this command does\",\n    Long: `Detailed description of the command.\nCan include multiple lines and examples of usage.`,\n    RunE:                  myCommandRun,\n    PreRunE:               preRunSetupRpcClient,  // Include if command needs RPC\n    DisableFlagsInUseLine: true,\n}\n\n// Flag variables\nvar (\n    myCommandFlagExample string\n    myCommandFlagVerbose bool\n)\n\nfunc init() {\n    // Add command to root\n    rootCmd.AddCommand(myCommandCmd)\n    \n    // Define flags\n    myCommandCmd.Flags().StringVarP(&myCommandFlagExample, \"example\", \"e\", \"\", \"example flag description\")\n    myCommandCmd.Flags().BoolVarP(&myCommandFlagVerbose, \"verbose\", \"v\", false, \"enable verbose output\")\n}\n\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    // Always track activity for telemetry\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Validate arguments\n    if len(args) == 0 {\n        OutputHelpMessage(cmd)\n        return fmt.Errorf(\"requires at least one argument\")\n    }\n    \n    // Command implementation\n    fmt.Printf(\"Command executed successfully\\n\")\n    return nil\n}\n```\n\n**File Naming Convention:**\n- Use `wshcmd-[commandname].go` format\n- Use lowercase, hyphenated names for multi-word commands\n- Examples: `wshcmd-getvar.go`, `wshcmd-setmeta.go`, `wshcmd-ai.go`\n\n### Step 2: Command Structure\n\n#### Basic Command Structure\n\n```go\nvar myCommandCmd = &cobra.Command{\n    Use:   \"mycommand [required] [optional...]\",\n    Short: \"One-line description (shown in help)\",\n    Long:  `Detailed multi-line description`,\n    \n    // Argument validation\n    Args:    cobra.MinimumNArgs(1),  // Or cobra.ExactArgs(1), cobra.NoArgs, etc.\n    \n    // Execution function\n    RunE:    myCommandRun,\n    \n    // Pre-execution setup (if needed)\n    PreRunE: preRunSetupRpcClient,  // Sets up RPC client for backend communication\n    \n    // Example usage (optional)\n    Example: \"  wsh mycommand foo\\n  wsh mycommand --flag bar\",\n    \n    // Disable flag notation in usage line\n    DisableFlagsInUseLine: true,\n}\n```\n\n**Key Fields:**\n- `Use`: Command name and argument pattern\n- `Short`: Brief description for command list\n- `Long`: Detailed description shown in help\n- `Args`: Argument validator (optional)\n- `RunE`: Main execution function (returns error)\n- `PreRunE`: Setup function that runs before `RunE`\n- `Example`: Usage examples (optional)\n- `DisableFlagsInUseLine`: Clean up help display\n\n#### When to Use PreRunE\n\nInclude `PreRunE: preRunSetupRpcClient` if your command:\n- Communicates with the Wave Terminal backend\n- Needs access to `RpcClient` \n- Requires JWT authentication (WAVETERM_JWT env var)\n- Makes RPC calls via `wshclient.*Command()` functions\n\n**Don't include PreRunE** for commands that:\n- Only manipulate local state\n- Don't need backend communication\n- Are purely informational/local operations\n\n### Step 3: Implement Command Logic\n\n#### Command Function Pattern\n\n```go\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    // Step 1: Always track activity (for telemetry)\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Step 2: Validate arguments and flags\n    if len(args) != 1 {\n        OutputHelpMessage(cmd)\n        return fmt.Errorf(\"requires exactly one argument\")\n    }\n    \n    // Step 3: Parse/prepare data\n    targetArg := args[0]\n    \n    // Step 4: Make RPC call if needed\n    result, err := wshclient.SomeCommand(RpcClient, wshrpc.CommandSomeData{\n        Field: targetArg,\n    }, &wshrpc.RpcOpts{Timeout: 2000})\n    if err != nil {\n        return fmt.Errorf(\"executing command: %w\", err)\n    }\n    \n    // Step 5: Output results\n    fmt.Printf(\"Result: %s\\n\", result)\n    return nil\n}\n```\n\n**Important Patterns:**\n\n1. **Activity Tracking**: Always include deferred `sendActivity()` call\n   ```go\n   defer func() {\n       sendActivity(\"commandname\", rtnErr == nil)\n   }()\n   ```\n\n2. **Error Handling**: Return errors, don't call `os.Exit()`\n   ```go\n   if err != nil {\n       return fmt.Errorf(\"context: %w\", err)\n   }\n   ```\n\n3. **Output**: Use standard `fmt` package for output\n   ```go\n   fmt.Printf(\"Success message\\n\")\n   fmt.Fprintf(os.Stderr, \"Error message\\n\")\n   ```\n\n4. **Help Messages**: Show help when arguments are invalid\n   ```go\n   if len(args) == 0 {\n       OutputHelpMessage(cmd)\n       return fmt.Errorf(\"requires arguments\")\n   }\n   ```\n\n5. **Exit Codes**: Set custom exit code via `WshExitCode`\n   ```go\n   if notFound {\n       WshExitCode = 1\n       return nil  // Don't return error, just set exit code\n   }\n   ```\n\n### Step 4: Define Flags\n\nAdd flags in the `init()` function:\n\n```go\nvar (\n    // Declare flag variables at package level\n    myCommandFlagString string\n    myCommandFlagBool   bool\n    myCommandFlagInt    int\n)\n\nfunc init() {\n    rootCmd.AddCommand(myCommandCmd)\n    \n    // String flag with short version\n    myCommandCmd.Flags().StringVarP(&myCommandFlagString, \"name\", \"n\", \"default\", \"description\")\n    \n    // Boolean flag\n    myCommandCmd.Flags().BoolVarP(&myCommandFlagBool, \"verbose\", \"v\", false, \"enable verbose\")\n    \n    // Integer flag\n    myCommandCmd.Flags().IntVar(&myCommandFlagInt, \"count\", 10, \"set count\")\n    \n    // Flag without short version\n    myCommandCmd.Flags().StringVar(&myCommandFlagString, \"longname\", \"\", \"description\")\n}\n```\n\n**Flag Types:**\n- `StringVar/StringVarP` - String values\n- `BoolVar/BoolVarP` - Boolean flags\n- `IntVar/IntVarP` - Integer values\n- The `P` suffix versions include a short flag name\n\n**Flag Naming:**\n- Use camelCase for variable names: `myCommandFlagName`\n- Use kebab-case for flag names: `--flag-name`\n- Prefix variable names with command name for clarity\n\n### Step 5: Working with Block Arguments\n\nMany commands operate on blocks. Use the standard block resolution pattern:\n\n```go\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Resolve block using the -b/--block flag\n    fullORef, err := resolveBlockArg()\n    if err != nil {\n        return err\n    }\n    \n    // Use the blockid in RPC call\n    err = wshclient.SomeCommand(RpcClient, wshrpc.CommandSomeData{\n        BlockId: fullORef.OID,\n    }, &wshrpc.RpcOpts{Timeout: 2000})\n    if err != nil {\n        return fmt.Errorf(\"command failed: %w\", err)\n    }\n    \n    return nil\n}\n```\n\n**Block Resolution:**\n- The `-b/--block` flag is defined globally in `wshcmd-root.go`\n- `resolveBlockArg()` resolves the block argument to a full ORef\n- Supports: `this`, `tab`, full UUIDs, 8-char prefixes, block numbers\n- Default is `\"this\"` (current block)\n\n**Alternative: Manual Block Resolution**\n\n```go\n// Get tab ID from environment\ntabId := os.Getenv(\"WAVETERM_TABID\")\nif tabId == \"\" {\n    return fmt.Errorf(\"WAVETERM_TABID not set\")\n}\n\n// Create route for tab-level operations\nroute := wshutil.MakeTabRouteId(tabId)\n\n// Use route in RPC call\nerr := wshclient.SomeCommand(RpcClient, commandData, &wshrpc.RpcOpts{\n    Route:   route,\n    Timeout: 2000,\n})\n```\n\n### Step 6: Making RPC Calls\n\nUse the `wshclient` package to make RPC calls:\n\n```go\nimport (\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient\"\n)\n\n// Simple RPC call\nresult, err := wshclient.GetMetaCommand(RpcClient, wshrpc.CommandGetMetaData{\n    ORef: *fullORef,\n}, &wshrpc.RpcOpts{Timeout: 2000})\nif err != nil {\n    return fmt.Errorf(\"getting metadata: %w\", err)\n}\n\n// RPC call with routing\nerr := wshclient.SetMetaCommand(RpcClient, wshrpc.CommandSetMetaData{\n    ORef: *fullORef,\n    Meta: metaMap,\n}, &wshrpc.RpcOpts{\n    Route:   route,\n    Timeout: 5000,\n})\nif err != nil {\n    return fmt.Errorf(\"setting metadata: %w\", err)\n}\n```\n\n**RPC Options:**\n- `Timeout`: Request timeout in milliseconds (typically 2000-5000)\n- `Route`: Route ID for targeting specific components\n- Available routes: `wshutil.ControlRoute`, `wshutil.MakeTabRouteId(tabId)`\n\n### Step 7: Add Documentation\n\nAdd your command to `docs/docs/wsh-reference.mdx`:\n\n````markdown\n## mycommand\n\nBrief description of what the command does.\n\n```sh\nwsh mycommand [args] [flags]\n```\n\nDetailed explanation of the command's purpose and behavior.\n\nFlags:\n- `-n, --name <value>` - description of this flag\n- `-v, --verbose` - enable verbose output\n- `-b, --block <blockid>` - specify target block (default: current block)\n\nExamples:\n\n```sh\n# Basic usage\nwsh mycommand arg1\n\n# With flags\nwsh mycommand --name value arg1\n\n# With block targeting\nwsh mycommand -b 2 arg1\n\n# Complex example\nwsh mycommand -v --name \"example\" arg1 arg2\n```\n\nAdditional notes, tips, or warnings about the command.\n\n---\n````\n\n**Documentation Guidelines:**\n- Place in alphabetical order with other commands\n- Include command signature with argument pattern\n- List all flags with short and long versions\n- Provide practical examples (at least 3-5)\n- Explain common use cases and patterns\n- Add tips or warnings if relevant\n- Use `---` separator between commands\n\n### Step 8: Test Your Command\n\nBuild and test the command:\n\n```bash\n# Build wsh\ntask build:wsh\n\n# Or build everything\ntask build\n\n# Test the command\n./bin/wsh/wsh mycommand --help\n./bin/wsh/wsh mycommand arg1 arg2\n```\n\n**Testing Checklist:**\n- [ ] Help message displays correctly\n- [ ] Required arguments validated\n- [ ] Flags work as expected\n- [ ] Error messages are clear\n- [ ] Success cases work correctly\n- [ ] RPC calls complete successfully\n- [ ] Output is formatted correctly\n\n## Complete Examples\n\n### Example 1: Simple Command with No RPC\n\n**Use case:** A command that prints Wave Terminal version info\n\n#### Command File (`cmd/wsh/cmd/wshcmd-version.go`)\n\n```go\n// Copyright 2025, Command Line Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n    \"github.com/spf13/cobra\"\n    \"github.com/wavetermdev/waveterm/pkg/wavebase\"\n)\n\nvar versionCmd = &cobra.Command{\n    Use:   \"version\",\n    Short: \"Print Wave Terminal version\",\n    RunE:  versionRun,\n}\n\nfunc init() {\n    rootCmd.AddCommand(versionCmd)\n}\n\nfunc versionRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"version\", rtnErr == nil)\n    }()\n    \n    fmt.Printf(\"Wave Terminal %s\\n\", wavebase.WaveVersion)\n    return nil\n}\n```\n\n#### Documentation\n\n````markdown\n## version\n\nPrint the current Wave Terminal version.\n\n```sh\nwsh version\n```\n\nExamples:\n\n```sh\n# Print version\nwsh version\n```\n````\n\n### Example 2: Command with Flags and RPC\n\n**Use case:** A command to update block title\n\n#### Command File (`cmd/wsh/cmd/wshcmd-settitle.go`)\n\n```go\n// Copyright 2025, Command Line Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n    \"fmt\"\n\n    \"github.com/spf13/cobra\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient\"\n)\n\nvar setTitleCmd = &cobra.Command{\n    Use:   \"settitle [title]\",\n    Short: \"Set block title\",\n    Long:  `Set the title for the current or specified block.`,\n    Args:  cobra.ExactArgs(1),\n    RunE:  setTitleRun,\n    PreRunE: preRunSetupRpcClient,\n    DisableFlagsInUseLine: true,\n}\n\nvar setTitleIcon string\n\nfunc init() {\n    rootCmd.AddCommand(setTitleCmd)\n    setTitleCmd.Flags().StringVarP(&setTitleIcon, \"icon\", \"i\", \"\", \"set block icon\")\n}\n\nfunc setTitleRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"settitle\", rtnErr == nil)\n    }()\n    \n    title := args[0]\n    \n    // Resolve block\n    fullORef, err := resolveBlockArg()\n    if err != nil {\n        return err\n    }\n    \n    // Build metadata map\n    meta := make(map[string]interface{})\n    meta[\"title\"] = title\n    if setTitleIcon != \"\" {\n        meta[\"icon\"] = setTitleIcon\n    }\n    \n    // Make RPC call\n    err = wshclient.SetMetaCommand(RpcClient, wshrpc.CommandSetMetaData{\n        ORef: *fullORef,\n        Meta: meta,\n    }, &wshrpc.RpcOpts{Timeout: 2000})\n    if err != nil {\n        return fmt.Errorf(\"setting title: %w\", err)\n    }\n    \n    fmt.Printf(\"title updated\\n\")\n    return nil\n}\n```\n\n#### Documentation\n\n````markdown\n## settitle\n\nSet the title for a block.\n\n```sh\nwsh settitle [title]\n```\n\nUpdate the display title for the current or specified block. Optionally set an icon as well.\n\nFlags:\n- `-i, --icon <icon>` - set block icon along with title\n- `-b, --block <blockid>` - specify target block (default: current block)\n\nExamples:\n\n```sh\n# Set title for current block\nwsh settitle \"My Terminal\"\n\n# Set title and icon\nwsh settitle --icon \"terminal\" \"Development Shell\"\n\n# Set title for specific block\nwsh settitle -b 2 \"Build Output\"\n```\n````\n\n### Example 3: Subcommands\n\n**Use case:** Command with multiple subcommands (like `wsh conn`)\n\n#### Command File (`cmd/wsh/cmd/wshcmd-mygroup.go`)\n\n```go\n// Copyright 2025, Command Line Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n    \"fmt\"\n\n    \"github.com/spf13/cobra\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient\"\n)\n\nvar myGroupCmd = &cobra.Command{\n    Use:   \"mygroup\",\n    Short: \"Manage something\",\n}\n\nvar myGroupListCmd = &cobra.Command{\n    Use:   \"list\",\n    Short: \"List items\",\n    RunE:  myGroupListRun,\n    PreRunE: preRunSetupRpcClient,\n}\n\nvar myGroupAddCmd = &cobra.Command{\n    Use:   \"add [name]\",\n    Short: \"Add an item\",\n    Args:  cobra.ExactArgs(1),\n    RunE:  myGroupAddRun,\n    PreRunE: preRunSetupRpcClient,\n}\n\nfunc init() {\n    // Add parent command\n    rootCmd.AddCommand(myGroupCmd)\n    \n    // Add subcommands\n    myGroupCmd.AddCommand(myGroupListCmd)\n    myGroupCmd.AddCommand(myGroupAddCmd)\n}\n\nfunc myGroupListRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mygroup:list\", rtnErr == nil)\n    }()\n    \n    // Implementation\n    fmt.Printf(\"Listing items...\\n\")\n    return nil\n}\n\nfunc myGroupAddRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mygroup:add\", rtnErr == nil)\n    }()\n    \n    name := args[0]\n    fmt.Printf(\"Adding item: %s\\n\", name)\n    return nil\n}\n```\n\n#### Documentation\n\n````markdown\n## mygroup\n\nManage something with subcommands.\n\n### list\n\nList all items.\n\n```sh\nwsh mygroup list\n```\n\n### add\n\nAdd a new item.\n\n```sh\nwsh mygroup add [name]\n```\n\nExamples:\n\n```sh\n# List items\nwsh mygroup list\n\n# Add an item\nwsh mygroup add \"new-item\"\n```\n````\n\n## Common Patterns\n\n### Reading from Stdin\n\n```go\nimport \"io\"\n\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Check if reading from stdin (using \"-\" convention)\n    var data []byte\n    var err error\n    \n    if len(args) > 0 && args[0] == \"-\" {\n        data, err = io.ReadAll(os.Stdin)\n        if err != nil {\n            return fmt.Errorf(\"reading stdin: %w\", err)\n        }\n    } else {\n        // Read from file or other source\n        data, err = os.ReadFile(args[0])\n        if err != nil {\n            return fmt.Errorf(\"reading file: %w\", err)\n        }\n    }\n    \n    // Process data\n    fmt.Printf(\"Read %d bytes\\n\", len(data))\n    return nil\n}\n```\n\n### JSON File Input\n\n```go\nimport (\n    \"encoding/json\"\n    \"io\"\n)\n\nfunc loadJSONFile(filepath string) (map[string]interface{}, error) {\n    var data []byte\n    var err error\n    \n    if filepath == \"-\" {\n        data, err = io.ReadAll(os.Stdin)\n        if err != nil {\n            return nil, fmt.Errorf(\"reading stdin: %w\", err)\n        }\n    } else {\n        data, err = os.ReadFile(filepath)\n        if err != nil {\n            return nil, fmt.Errorf(\"reading file: %w\", err)\n        }\n    }\n    \n    var result map[string]interface{}\n    if err := json.Unmarshal(data, &result); err != nil {\n        return nil, fmt.Errorf(\"parsing JSON: %w\", err)\n    }\n    \n    return result, nil\n}\n```\n\n### Conditional Output (TTY Detection)\n\n```go\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    isTty := getIsTty()\n    \n    // Output value\n    fmt.Printf(\"%s\", value)\n    \n    // Add newline only if TTY (for better piping experience)\n    if isTty {\n        fmt.Printf(\"\\n\")\n    }\n    \n    return nil\n}\n```\n\n### Environment Variable Access\n\n```go\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Get block ID from environment\n    blockId := os.Getenv(\"WAVETERM_BLOCKID\")\n    if blockId == \"\" {\n        return fmt.Errorf(\"WAVETERM_BLOCKID not set\")\n    }\n    \n    // Get tab ID from environment\n    tabId := os.Getenv(\"WAVETERM_TABID\")\n    if tabId == \"\" {\n        return fmt.Errorf(\"WAVETERM_TABID not set\")\n    }\n    \n    fmt.Printf(\"Block: %s, Tab: %s\\n\", blockId, tabId)\n    return nil\n}\n```\n\n## Best Practices\n\n### Command Design\n\n1. **Single Responsibility**: Each command should do one thing well\n2. **Composable**: Design commands to work with pipes and other commands\n3. **Consistent**: Follow existing wsh command patterns and conventions\n4. **Documented**: Provide clear help text and examples\n\n### Error Handling\n\n1. **Context**: Wrap errors with context using `fmt.Errorf(\"context: %w\", err)`\n2. **User-Friendly**: Make error messages clear and actionable\n3. **No Panics**: Return errors instead of calling `os.Exit()` or `panic()`\n4. **Exit Codes**: Use `WshExitCode` for custom exit codes\n\n### Output\n\n1. **Structured**: Use consistent formatting for output\n2. **Quiet by Default**: Only output what's necessary\n3. **Verbose Flag**: Optionally provide `-v` for detailed output\n4. **Stderr for Errors**: Use `fmt.Fprintf(os.Stderr, ...)` for error messages\n\n### Flags\n\n1. **Short Versions**: Provide `-x` short versions for common flags\n2. **Sensible Defaults**: Choose defaults that work for most users\n3. **Boolean Flags**: Use for on/off options\n4. **String Flags**: Use for values that need user input\n\n### RPC Calls\n\n1. **Timeouts**: Always specify reasonable timeouts\n2. **Error Context**: Wrap RPC errors with operation context\n3. **Retries**: Don't retry automatically; let user retry command\n4. **Routes**: Use appropriate routes for different operations\n\n## Common Pitfalls\n\n### 1. Forgetting Activity Tracking\n\n**Problem**: Command usage not tracked in telemetry\n\n**Solution**: Always include deferred `sendActivity()` call:\n```go\ndefer func() {\n    sendActivity(\"commandname\", rtnErr == nil)\n}()\n```\n\n### 2. Using os.Exit() Instead of Returning Error\n\n**Problem**: Breaks defer statements and cleanup\n\n**Solution**: Return errors from RunE function:\n```go\n// Bad\nif err != nil {\n    fmt.Fprintf(os.Stderr, \"error: %v\\n\", err)\n    os.Exit(1)\n}\n\n// Good\nif err != nil {\n    return fmt.Errorf(\"operation failed: %w\", err)\n}\n```\n\n### 3. Not Validating Arguments\n\n**Problem**: Command crashes with nil pointer or index out of range\n\n**Solution**: Validate arguments early and show help:\n```go\nif len(args) == 0 {\n    OutputHelpMessage(cmd)\n    return fmt.Errorf(\"requires at least one argument\")\n}\n```\n\n### 4. Forgetting to Add to init()\n\n**Problem**: Command not available when running wsh\n\n**Solution**: Always add command in `init()` function:\n```go\nfunc init() {\n    rootCmd.AddCommand(myCommandCmd)\n}\n```\n\n### 5. Inconsistent Output\n\n**Problem**: Inconsistent use of output methods\n\n**Solution**: Use standard `fmt` package functions:\n```go\n// For stdout\nfmt.Printf(\"output\\n\")\n\n// For stderr\nfmt.Fprintf(os.Stderr, \"error message\\n\")\n```\n\n## Quick Reference Checklist\n\nWhen adding a new wsh command:\n\n- [ ] Create `cmd/wsh/cmd/wshcmd-[commandname].go`\n- [ ] Define command struct with Use, Short, Long descriptions\n- [ ] Add `PreRunE: preRunSetupRpcClient` if using RPC\n- [ ] Implement command function with activity tracking\n- [ ] Add command to `rootCmd` in `init()` function\n- [ ] Define flags in `init()` function if needed\n- [ ] Add documentation to `docs/docs/wsh-reference.mdx`\n- [ ] Build and test: `task build:wsh`\n- [ ] Test help: `wsh [commandname] --help`\n- [ ] Test all flag combinations\n- [ ] Test error cases\n\n## Related Files\n\n- **Root Command**: `cmd/wsh/cmd/wshcmd-root.go` - Main command setup and utilities\n- **RPC Client**: `pkg/wshrpc/wshclient/` - Client functions for RPC calls\n- **RPC Types**: `pkg/wshrpc/wshrpctypes.go` - RPC request/response data structures\n- **Documentation**: `docs/docs/wsh-reference.mdx` - User-facing command reference\n- **Examples**: `cmd/wsh/cmd/wshcmd-*.go` - Existing command implementations\n",".kilocode/rules/overview.md":"# Wave Terminal - High Level Architecture Overview\n\n## Project Description\n\nWave Terminal is an open-source AI-native terminal built for seamless workflows. It's an Electron application that serves as a command line terminal host (it hosts CLI applications rather than running inside a CLI). The application combines a React frontend with a Go backend server to provide a modern terminal experience with advanced features.\n\n## Top-Level Directory Structure\n\n```\nwaveterm/\n├── emain/              # Electron main process code\n├── frontend/           # React application (renderer process)\n├── cmd/                # Go command-line applications\n├── pkg/                # Go packages/modules\n├── db/                 # Database migrations\n├── docs/               # Documentation (Docusaurus)\n├── build/              # Build configuration and assets\n├── assets/             # Application assets (icons, images)\n├── public/             # Static public assets\n├── tests/              # Test files\n├── .github/            # GitHub workflows and configuration\n└── Configuration files (package.json, tsconfig.json, etc.)\n```\n\n## Architecture Components\n\n### 1. Electron Main Process (`emain/`)\n\nThe Electron main process handles the native desktop application layer:\n\n**Key Files:**\n\n- [`emain.ts`](emain/emain.ts) - Main entry point, application lifecycle management\n- [`emain-window.ts`](emain/emain-window.ts) - Window management (`WaveBrowserWindow` class)\n- [`emain-tabview.ts`](emain/emain-tabview.ts) - Tab view management (`WaveTabView` class)\n- [`emain-wavesrv.ts`](emain/emain-wavesrv.ts) - Go backend server integration\n- [`emain-wsh.ts`](emain/emain-wsh.ts) - WSH (Wave Shell) client integration\n- [`emain-ipc.ts`](emain/emain-ipc.ts) - IPC handlers for frontend ↔ main process communication\n- [`emain-menu.ts`](emain/emain-menu.ts) - Application menu system\n- [`updater.ts`](emain/updater.ts) - Auto-update functionality\n- [`preload.ts`](emain/preload.ts) - Preload script for renderer security\n- [`preload-webview.ts`](emain/preload-webview.ts) - Webview preload script\n\n### 2. Frontend React Application (`frontend/`)\n\nThe React application runs in the Electron renderer process:\n\n**Structure:**\n\n```\nfrontend/\n├── app/                # Main application code\n│   ├── app.tsx         # Root App component\n│   ├── aipanel/        # AI panel UI\n│   ├── block/          # Block-based UI components\n│   ├── element/        # Reusable UI elements\n│   ├── hook/           # Custom React hooks\n│   ├── modals/         # Modal components\n│   ├── store/          # State management (Jotai)\n│   ├── tab/            # Tab components\n│   ├── view/           # Different view types\n│   │   ├── codeeditor/ # Code editor (Monaco)\n│   │   ├── preview/    # File preview\n│   │   ├── sysinfo/    # System info view\n│   │   ├── term/       # Terminal view\n│   │   ├── tsunami/    # Tsunami builder view\n│   │   ├── vdom/       # Virtual DOM view\n│   │   ├── waveai/     # AI chat integration\n│   │   ├── waveconfig/ # Config editor view\n│   │   └── webview/    # Web view\n│   └── workspace/      # Workspace management\n├── builder/            # Builder app entry\n├── layout/             # Layout system\n├── preview/            # Standalone preview renderer\n├── types/              # TypeScript type definitions\n└── util/               # Utility functions\n```\n\n**Key Technologies:**\n\n- Electron (desktop application shell)\n- React 19 with TypeScript\n- Jotai for state management\n- Monaco Editor for code editing\n- XTerm.js for terminal emulation\n- Tailwind CSS v4 for styling\n- SCSS for additional styling (deprecated, new components should use Tailwind)\n- Vite / electron-vite for bundling\n- Task (Taskfile.yml) for build and code generation commands\n\n### 3. Go Backend Server (`cmd/server/`)\n\nThe Go backend server handles all heavy lifting operations:\n\n**Entry Point:** [`main-server.go`](cmd/server/main-server.go)\n\n### 4. Go Packages (`pkg/`)\n\nThe Go codebase is organized into modular packages:\n\n**Key Packages:**\n\n- `wstore/` - Database and storage layer\n- `wconfig/` - Configuration management\n- `wcore/` - Core business logic\n- `wshrpc/` - RPC communication system\n- `wshutil/` - WSH (Wave Shell) utilities\n- `blockcontroller/` - Block execution management\n- `remote/` - Remote connection handling\n- `filestore/` - File storage system\n- `web/` - Web server and WebSocket handling\n- `telemetry/` - Usage analytics and telemetry\n- `waveobj/` - Core data objects\n- `service/` - Service layer\n- `wps/` - Wave PubSub event system\n- `waveai/` - AI functionality\n- `shellexec/` - Shell execution\n- `util/` - Common utilities\n\n### 5. Command Line Tools (`cmd/`)\n\nKey Go command-line utilities:\n\n- `wsh/` - Wave Shell command-line tool\n- `server/` - Main backend server\n- `generatego/` - Code generation\n- `generateschema/` - Schema generation\n- `generatets/` - TypeScript generation\n\n## Communication Architecture\n\nThe core communication system is built around the **WSH RPC (Wave Shell RPC)** system, which provides a unified interface for all inter-process communication: frontend ↔ Go backend, Electron main process ↔ backend, and backend ↔ remote systems (SSH, WSL).\n\n### WSH RPC System (`pkg/wshrpc/`)\n\nThe WSH RPC system is the backbone of Wave Terminal's communication architecture:\n\n**Key Components:**\n\n- [`wshrpctypes.go`](pkg/wshrpc/wshrpctypes.go) - Core RPC interface and type definitions (source of truth for all RPC commands)\n- [`wshserver/`](pkg/wshrpc/wshserver/) - Server-side RPC implementation\n- [`wshremote/`](pkg/wshrpc/wshremote/) - Remote connection handling\n- [`wshclient.go`](pkg/wshrpc/wshclient.go) - Go client for making RPC calls\n- [`frontend/app/store/wshclientapi.ts`](frontend/app/store/wshclientapi.ts) - Generated TypeScript RPC client\n\n**Routing:** Callers address RPC calls using _routes_ (e.g. a block ID, connection name, or `\"waveapp\"`) rather than caring about the underlying transport. The RPC layer resolves the route to the correct transport (WebSocket, Unix socket, SSH tunnel, stdio) automatically. This means the same RPC interface works whether the target is local or a remote SSH connection.\n\n## Development Notes\n\n- **Build commands** - Use `task` (Taskfile.yml) for all build, generate, and packaging commands\n- **Code generation** - Run `task generate` after modifying Go types in `pkg/wshrpc/wshrpctypes.go`, `pkg/wconfig/settingsconfig.go`, or `pkg/waveobj/wtypemeta.go`\n- **Testing** - Vitest for frontend unit tests; standard `go test` for Go packages\n- **Database migrations** - SQL migration files in `db/migrations-wstore/` and `db/migrations-filestore/`\n- **Documentation** - Docusaurus site in `docs/`\n",".kilocode/rules/rules.md":"Wave Terminal is a modern terminal which provides graphical blocks, dynamic layout, workspaces, and SSH connection management. It is cross platform and built on electron.\n\n### Project Structure\n\nIt has a TypeScript/React frontend and a Go backend. They talk together over `wshrpc` a custom RPC protocol that is implemented over websocket (and domain sockets).\n\n### Coding Guidelines\n\n- **Go Conventions**:\n  - Don't use custom enum types in Go. Instead, use string constants (e.g., `const StatusRunning = \"running\"` rather than creating a custom type like `type Status string`).\n  - Use string constants for status values, packet types, and other string-based enumerations.\n  - in Go code, prefer using Printf() vs Println()\n  - use \"Make\" as opposed to \"New\" for struct initialization func names\n  - in general const decls go at the top of the file (before types and functions)\n  - NEVER run `go build` (especially in weird sub-package directories). we can tell if everything compiles by seeing there are no problems/errors.\n- **Synchronization**:\n  - Always prefer to use the `lock.Lock(); defer lock.Unlock()` pattern for synchronization if possible\n  - Avoid inline lock/unlock pairs - instead create helper functions that use the defer pattern\n  - When accessing shared data structures (maps, slices, etc.), ensure proper locking\n  - Example: Instead of `gc.lock.Lock(); gc.map[key]++; gc.lock.Unlock()`, create a helper function like `getNextValue(key string) int { gc.lock.Lock(); defer gc.lock.Unlock(); gc.map[key]++; return gc.map[key] }`\n- **TypeScript Imports**:\n  - Use `@/...` for imports from different parts of the project (configured in `tsconfig.json` as `\"@/*\": [\"frontend/*\"]`).\n  - Prefer relative imports (`\"./name\"`) only within the same directory.\n  - Use named exports exclusively; avoid default exports. It's acceptable to export functions directly (e.g., React Components).\n  - Our indent is 4 spaces\n- **JSON Field Naming**: All fields must be lowercase, without underscores.\n- **TypeScript Conventions**\n  - **Type Handling**:\n    - In TypeScript we have strict null checks off, so no need to add \"| null\" to all the types.\n    - In TypeScript for Jotai atoms, if we want to write, we need to type the atom as a PrimitiveAtom<Type>\n    - Jotai has a bug with strict null checks off where if you create a null atom, e.g. atom(null) it does not \"type\" correctly. That's no issue, just cast it to the proper PrimitiveAtom type (no \"| null\") and it will work fine.\n    - Generally never use \"=== undefined\" or \"!== undefined\". This is bad style. Just use a \"== null\" or \"!= null\" unless it is a very specific case where we need to distinguish undefined from null.\n  - **Coding Style**:\n    - Use all lowercase filenames (except where case is actually important like Taskfile.yml)\n    - Import the \"cn\" function from \"@/util/util\" to do classname / clsx class merge (it uses twMerge underneath)\n    - Do NOT create private fields in classes (they are impossible to inspect)\n    - Use PascalCase for global consts at the top of files\n  - **Component Practices**:\n    - Make sure to add cursor-pointer to buttons/links and clickable items\n    - NEVER use cursor-help (it looks terrible)\n    - useAtom() and useAtomValue() are react HOOKS, so they must be called at the component level not inline in JSX\n    - If you use React.memo(), make sure to add a displayName for the component\n  - Other\n    - never use atob() or btoa() (not UTF-8 safe). use functions in frontend/util/util.ts for base64 decoding and encoding\n- In general, when writing functions, we prefer _early returns_ rather than putting the majority of a function inside of an if block.\n\n### Styling\n\n- We use **Tailwind v4** to style. Custom stuff is defined in frontend/tailwindsetup.css\n- _never_ use cursor-help, or cursor-not-allowed (it looks terrible)\n- We have custom CSS setup as well, so it is a hybrid system. For new code we prefer tailwind, and are working to migrate code to all use tailwind.\n- For accent buttons, use \"bg-accent/80 text-primary rounded hover:bg-accent transition-colors cursor-pointer\" (if you do \"bg-accent hover:bg-accent/80\" it looks weird as on hover the button gets darker instead of lighter)\n\n### RPC System\n\nTo define a new RPC call, add the new definition to `pkg/wshrpc/wshrpctypes.go` including any input/output data that is required. After modifying wshrpctypes.go run `task generate` to generate the client APIs.\n\nFor normal \"server\" RPCs (where a frontend client is calling the main server) you should implement the RPC call in `pkg/wshrpc/wshserver.go`.\n\n### Electron API\n\nFrom within the FE to get the electron API (e.g. the preload functions):\n\n```ts\nimport { getApi } from \"@/store/global\";\n\ngetApi().getIsDev();\n```\n\nThe full API is defined in custom.d.ts as type ElectronApi.\n\n### Code Generation\n\n- **TypeScript Types**: TypeScript types are automatically generated from Go types. After modifying Go types in `pkg/wshrpc/wshrpctypes.go`, run `task generate` to update the TypeScript type definitions in `frontend/types/gotypes.d.ts`.\n- **Manual Edits**: Do not manually edit generated files like `frontend/types/gotypes.d.ts` or `frontend/app/store/wshclientapi.ts`. Instead, modify the source Go types and run `task generate`.\n\n### Frontend Architecture\n\n- The application uses Jotai for state management.\n- When working with Jotai atoms that need to be updated, define them as `PrimitiveAtom<Type>` rather than just `atom<Type>`.\n\n### Notes\n\n- **CRITICAL: Completion format MUST be: \"Done: [one-line description]\"**\n- **Keep your Task Completed summaries VERY short**\n- **No lengthy pre-completion summaries** - Do not provide detailed explanations of implementation before using attempt_completion\n- **No recaps of changes** - Skip explaining what was done before completion\n- **Go directly to completion** - After making changes, proceed directly to attempt_completion without summarizing\n- The project is currently an un-released POC / MVP. Do not worry about backward compatibility when making changes\n- With React hooks, always complete all hook calls at the top level before any conditional returns (including jotai hook calls useAtom and useAtomValue); when a user explicitly tells you a function handles null inputs, trust them and stop trying to \"protect\" it with unnecessary checks or workarounds.\n- **Match response length to question complexity** - For simple, direct questions in Ask mode (especially those that can be answered in 1-2 sentences), provide equally brief answers. Save detailed explanations for complex topics or when explicitly requested.\n- **CRITICAL** - useAtomValue and useAtom are React HOOKS. They cannot be used inline in JSX code, they must appear at the top of a component in the hooks area of the react code.\n- for simple functions, we prefer `if (!cond) { return }; functionality;` pattern over `if (cond) { functionality }` because it produces less indentation and is easier to follow.\n- It is now 2026, so if you write new files, or update files use 2026 for the copyright year\n- React.MutableRefObject is deprecated, just use React.RefObject now (in React 19 RefObject is always mutable)\n\n### Strict Comment Rules\n\n- **NEVER add comments that merely describe what code is doing**:\n  - ❌ `mutex.Lock() // Lock the mutex`\n  - ❌ `counter++ // Increment the counter`\n  - ❌ `buffer.Write(data) // Write data to buffer`\n  - ❌ `// Header component for app run list` (above AppRunListHeader)\n  - ❌ `// Updated function to include onClick parameter`\n  - ❌ `// Changed padding calculation`\n  - ❌ `// Removed unnecessary div`\n  - ❌ `// Using the model's width value here`\n- **Only use comments for**:\n  - Explaining WHY a particular approach was chosen\n  - Documenting non-obvious edge cases or side effects\n  - Warning about potential pitfalls in usage\n  - Explaining complex algorithms that can't be simplified\n- **When in doubt, leave it out**. No comment is better than a redundant comment.\n- **Never add comments explaining code changes** - The code should speak for itself, and version control tracks changes. The one exception to this rule is if it is a very unobvious implementation. Something that someone would typically implement in a different (wrong) way. Then the comment helps us remember WHY we changed it to a less obvious implementation.\n- **Never remove existing comments** unless specifically directed by the user. Comments that are already defined in existing code have been vetted by the user.\n\n### Jotai Model Pattern (our rules)\n\n- **Atoms live on the model.**\n- **Simple atoms:** define as **field initializers**.\n- **Atoms that depend on values/other atoms:** create in the **constructor**.\n- Models **never use React hooks**; they use `globalStore.get/set`.\n- It's fine to call model methods from **event handlers** or **`useEffect`**.\n- Models use the **singleton pattern** with a `private static instance` field, a `private constructor`, and a `static getInstance()` method.\n- The constructor is `private`; callers always use `getInstance()`.\n\n```ts\n// model/MyModel.ts\nimport * as jotai from \"jotai\";\nimport { globalStore } from \"@/app/store/jotaiStore\";\n\nexport class MyModel {\n  private static instance: MyModel | null = null;\n\n  // simple atoms (field init)\n  statusAtom = jotai.atom<\"idle\" | \"running\" | \"error\">(\"idle\");\n  outputAtom = jotai.atom(\"\");\n\n  // ctor-built atoms (need types)\n  lengthAtom!: jotai.Atom<number>;\n  thresholdedAtom!: jotai.Atom<boolean>;\n\n  private constructor(initialThreshold = 20) {\n    this.lengthAtom = jotai.atom((get) => get(this.outputAtom).length);\n    this.thresholdedAtom = jotai.atom((get) => get(this.lengthAtom) > initialThreshold);\n  }\n\n  static getInstance(): MyModel {\n    if (!MyModel.instance) {\n      MyModel.instance = new MyModel();\n    }\n    return MyModel.instance;\n  }\n\n  static resetInstance(): void {\n    MyModel.instance = null;\n  }\n\n  async doWork() {\n    globalStore.set(this.statusAtom, \"running\");\n    // ... do work ...\n    globalStore.set(this.statusAtom, \"idle\");\n  }\n}\n```\n\n```tsx\n// component usage (events & effects OK)\nimport { useAtomValue } from \"jotai\";\n\nfunction Panel() {\n  const model = MyModel.getInstance();\n  const status = useAtomValue(model.statusAtom);\n  const isBig = useAtomValue(model.thresholdedAtom);\n\n  const onClick = () => model.doWork();\n\n  return (\n    <div>\n      {status} • {String(isBig)}\n    </div>\n  );\n}\n```\n\n**Remember:** singleton pattern with `getInstance()`, `private constructor`, atoms on the model, simple-as-fields, ctor for dependent/derived, updates via `globalStore.set/get`.\n**Note** Older models may not use the singleton pattern\n\n### Tool Use\n\nDo NOT use write_to_file unless it is a new file or very short. Always prefer to use replace_in_file. Often your diffs fail when a file may be out of date in your cache vs the actual on-disk format. You should RE-READ the file and try to create diffs again if your diffs fail rather than fall back to write_to_file. If you feel like your ONLY option is to use write_to_file please ask first.\n\nAlso when adding content to the end of files prefer to use the new append_file tool rather than trying to create a diff (as your diffs are often not specific enough and end up inserting code in the middle of existing functions).\n\n### Directory Awareness\n\n- **ALWAYS verify the current working directory before executing commands**\n- Either run \"pwd\" first to verify the directory, or do a \"cd\" to the correct absolute directory before running commands\n- When running tests, do not \"cd\" to the pkg directory and then run the test. This screws up the cwd and you never recover. run the test from the project root instead.\n\n### Testing / Compiling Go Code\n\nNo need to run a `go build` or a `go run` to just check if the Go code compiles. VSCode's errors/problems cover this well.\nIf there are no Go errors in VSCode you can assume the code compiles fine.\n",".github/copilot-instructions.md":"# Wave Terminal — Copilot Instructions\n\n## Project Rules\n\n- See the overview of the project in `.kilocode/rules/overview.md`\n- Read and follow all guidelines in `.kilocode/rules/rules.md`\n\n---\n\n## Skill Guides\n\nThis project uses a set of \"skill\" guides — focused how-to documents for common implementation tasks. When your task matches one of the descriptions below, **read the linked SKILL.md file before proceeding** and follow its instructions precisely.\n\n| Skill        | File                                     | Description                                                                                                                                                                                                                                 |\n| ------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| add-config   | `.kilocode/skills/add-config/SKILL.md`   | Guide for adding new configuration settings to Wave Terminal. Use when adding a new setting to the configuration system, implementing a new config key, or adding user-customizable settings.                                               |\n| add-rpc      | `.kilocode/skills/add-rpc/SKILL.md`      | Guide for adding new RPC calls to Wave Terminal. Use when implementing new RPC commands, adding server-client communication methods, or extending the RPC interface with new functionality.                                                 |\n| add-wshcmd   | `.kilocode/skills/add-wshcmd/SKILL.md`   | Guide for adding new wsh commands to Wave Terminal. Use when implementing new CLI commands, adding command-line functionality, or extending the wsh command interface.                                                                      |\n| context-menu | `.kilocode/skills/context-menu/SKILL.md` | Guide for creating and displaying context menus in Wave Terminal. Use when implementing right-click menus, adding context menu items, creating submenus, or handling menu interactions with checkboxes and separators.                      |\n| create-view  | `.kilocode/skills/create-view/SKILL.md`  | Guide for implementing a new view type in Wave Terminal. Use when creating a new view component, implementing the ViewModel interface, registering a new view type in BlockRegistry, or adding a new content type to display within blocks. |\n| electron-api | `.kilocode/skills/electron-api/SKILL.md` | Guide for adding new Electron APIs to Wave Terminal. Use when implementing new frontend-to-electron communications via preload/IPC.                                                                                                         |\n| waveenv      | `.kilocode/skills/waveenv/SKILL.md`      | Guide for creating WaveEnv narrowings in Wave Terminal. Use when writing a named subset type of WaveEnv for a component tree, documenting environmental dependencies, or enabling mock environments for preview/test server usage.          |\n| wps-events   | `.kilocode/skills/wps-events/SKILL.md`   | Guide for working with Wave Terminal's WPS (Wave PubSub) event system. Use when implementing new event types, publishing events, subscribing to events, or adding asynchronous communication between components.                            |\n\n> **How skills work:** Each skill is a self-contained guide covering the exact files to edit, patterns to follow, and steps to take for a specific type of task in this codebase. If your task matches a skill's description, open that SKILL.md and treat it as your primary reference for the implementation.\n\n---\n\n## Preview Server\n\nTo run the standalone component preview (no Electron, no backend required):\n\n```\ntask preview\n```\n\nThis runs `cd frontend/preview && npx vite` and serves at **http://localhost:7007** (port configured in `frontend/preview/vite.config.ts`).\n\nTo build a static preview: `task build:preview`\n\n**Do NOT use any of the following to start the preview — they all launch the full Electron app or serve the wrong content:**\n\n- `npm run dev` — runs `electron-vite dev`, launches Electron\n- `npm run start` — also launches Electron\n- `npx vite` from the repo root — uses the Electron-Vite config, not the preview app\n- Serving the `dist/` directory — the preview app is never built there; it has its own build output\n",".kilocode/skills/add-config/SKILL.md":"---\nname: add-config\ndescription: Guide for adding new configuration settings to Wave Terminal. Use when adding a new setting to the configuration system, implementing a new config key, or adding user-customizable settings.\n---\n\n# Adding a New Configuration Setting to Wave Terminal\n\nThis guide explains how to add a new configuration setting to Wave Terminal's hierarchical configuration system.\n\n## Configuration System Overview\n\nWave Terminal uses a hierarchical configuration system with:\n\n1. **Go Struct Definitions** - Type-safe configuration structure in `pkg/wconfig/settingsconfig.go`\n2. **JSON Schema** - Auto-generated validation schema in `schema/settings.json`\n3. **Default Values** - Built-in defaults in `pkg/wconfig/defaultconfig/settings.json`\n4. **User Configuration** - User overrides in `~/.config/waveterm/settings.json`\n5. **Block Metadata** - Block-level overrides in `pkg/waveobj/wtypemeta.go`\n6. **Documentation** - User-facing docs in `docs/docs/config.mdx`\n\nSettings cascade from defaults → user settings → connection config → block overrides.\n\n## Step-by-Step Guide\n\n### Step 1: Add to Go Struct Definition\n\nEdit `pkg/wconfig/settingsconfig.go` and add your new field to the `SettingsType` struct:\n\n```go\ntype SettingsType struct {\n    // ... existing fields ...\n\n    // Add your new field with appropriate JSON tag\n    MyNewSetting string `json:\"mynew:setting,omitempty\"`\n\n    // For different types:\n    MyBoolSetting   bool    `json:\"mynew:boolsetting,omitempty\"`\n    MyNumberSetting float64 `json:\"mynew:numbersetting,omitempty\"`\n    MyIntSetting    *int64  `json:\"mynew:intsetting,omitempty\"`    // Use pointer for optional ints\n    MyArraySetting  []string `json:\"mynew:arraysetting,omitempty\"`\n}\n```\n\n**Naming Conventions:**\n\n- Use namespace prefixes (e.g., `term:`, `window:`, `ai:`, `web:`, `app:`)\n- Use lowercase with colons as separators\n- Field names should be descriptive and follow Go naming conventions\n- Use `omitempty` tag to exclude empty values from JSON\n\n**Type Guidelines:**\n\n- Use `*int64` and `*float64` for optional numeric values\n- Use `*bool` for optional boolean values (or `bool` if default is false)\n- Use `string` for text values\n- Use `[]string` for arrays\n- Use `float64` for numbers that can be decimals\n\n**Namespace Organization:**\n\n- `app:*` - Application-level settings\n- `term:*` - Terminal-specific settings\n- `window:*` - Window and UI settings\n- `ai:*` - AI-related settings\n- `web:*` - Web browser settings\n- `editor:*` - Code editor settings\n- `conn:*` - Connection settings\n\n### Step 1.5: Add to Block Metadata (Optional)\n\nIf your setting should support block-level overrides, also add it to `pkg/waveobj/wtypemeta.go`:\n\n```go\ntype MetaTSType struct {\n    // ... existing fields ...\n\n    // Add your new field with matching JSON tag and type\n    MyNewSetting *string `json:\"mynew:setting,omitempty\"`  // Use pointer for optional values\n\n    // For different types:\n    MyBoolSetting   *bool    `json:\"mynew:boolsetting,omitempty\"`\n    MyNumberSetting *float64 `json:\"mynew:numbersetting,omitempty\"`\n    MyIntSetting    *int     `json:\"mynew:intsetting,omitempty\"`\n    MyArraySetting  []string `json:\"mynew:arraysetting,omitempty\"`\n}\n```\n\n**Block Metadata Guidelines:**\n\n- Use pointer types (`*string`, `*bool`, `*int`, `*float64`) for optional overrides\n- JSON tags should exactly match the corresponding settings field\n- This enables the hierarchical config system: block metadata → connection config → global settings\n- Only add settings here that make sense to override per-block or per-connection\n\n### Step 2: Set Default Value (Optional)\n\nIf your setting should have a default value, add it to `pkg/wconfig/defaultconfig/settings.json`:\n\n```json\n{\n  \"ai:preset\": \"ai@global\",\n  \"ai:model\": \"gpt-5-mini\",\n  // ... existing defaults ...\n\n  \"mynew:setting\": \"default value\",\n  \"mynew:boolsetting\": true,\n  \"mynew:numbersetting\": 42.5,\n  \"mynew:intsetting\": 100\n}\n```\n\n**Default Value Guidelines:**\n\n- Only add defaults for settings that should have non-zero/non-empty initial values\n- Ensure defaults make sense for typical user experience\n- Keep defaults conservative and safe\n- Boolean settings often don't need defaults if `false` is the correct default\n\n### Step 3: Update Documentation\n\nAdd your new setting to the configuration table in `docs/docs/config.mdx`:\n\n```markdown\n| Key Name            | Type     | Function                                  |\n| ------------------- | -------- | ----------------------------------------- |\n| mynew:setting       | string   | Description of what this setting controls |\n| mynew:boolsetting   | bool     | Enable/disable some feature               |\n| mynew:numbersetting | float    | Numeric setting for some parameter        |\n| mynew:intsetting    | int      | Integer setting for some configuration    |\n| mynew:arraysetting  | string[] | Array of strings for multiple values      |\n```\n\n**Documentation Guidelines:**\n\n- Provide clear, concise descriptions\n- For new settings in upcoming releases, add `<VersionBadge version=\"v0.14\" />`\n- Update the default configuration example if you added defaults\n- Explain what values are valid and what they do\n\n### Step 4: Regenerate Schema and TypeScript Types\n\nRun the generate task to automatically regenerate the JSON schema and TypeScript types:\n\n```bash\ntask generate\n```\n\n**What this does:**\n\n- Runs `task build:schema` (automatically generates JSON schema from Go structs)\n- Generates TypeScript type definitions in `frontend/types/gotypes.d.ts`\n- Generates RPC client APIs\n- Generates metadata constants\n\n**Important:** The JSON schema in `schema/settings.json` is **automatically generated** from the Go struct definitions - you don't need to edit it manually.\n\n### Step 5: Use in Frontend Code\n\nAccess your new setting in React components:\n\n```typescript\nimport { getOverrideConfigAtom, getSettingsKeyAtom, useAtomValue } from \"@/store/global\";\n\n// In a React component\nconst MyComponent = ({ blockId }: { blockId: string }) => {\n    // Use override config atom for hierarchical resolution\n    // This automatically checks: block metadata → connection config → global settings → default\n    const mySettingAtom = getOverrideConfigAtom(blockId, \"mynew:setting\");\n    const mySetting = useAtomValue(mySettingAtom) ?? \"fallback value\";\n\n    // For global-only settings (no block overrides)\n    const globalOnlySetting = useAtomValue(getSettingsKeyAtom(\"mynew:globalsetting\")) ?? \"fallback\";\n\n    return <div>Setting value: {mySetting}</div>;\n};\n```\n\n**Frontend Configuration Patterns:**\n\n```typescript\n// 1. Settings with block-level overrides (recommended for most view/display settings)\nconst termFontSize = useAtomValue(getOverrideConfigAtom(blockId, \"term:fontsize\")) ?? 12;\n\n// 2. Global-only settings (app-wide settings that don't vary by block)\nconst appGlobalHotkey = useAtomValue(getSettingsKeyAtom(\"app:globalhotkey\")) ?? \"\";\n\n// 3. Connection-specific settings\nconst connStatus = useAtomValue(getConnStatusAtom(connectionName));\n```\n\n**When to use each pattern:**\n\n- Use `getOverrideConfigAtom()` for settings that can vary by block or connection (most UI/display settings)\n- Use `getSettingsKeyAtom()` for app-level settings that are always global\n- Always provide a fallback value with `??` operator\n\n### Step 6: Use in Backend Code\n\nAccess settings in Go code:\n\n```go\n// Get the full config\nfullConfig := wconfig.GetWatcher().GetFullConfig()\n\n// Access your setting\nmyValue := fullConfig.Settings.MyNewSetting\n\n// For optional values (pointers)\nif fullConfig.Settings.MyIntSetting != nil {\n    intValue := *fullConfig.Settings.MyIntSetting\n    // Use intValue\n}\n```\n\n## Complete Examples\n\n### Example 1: Simple Boolean Setting (No Block Override)\n\n**Use case:** Add a setting to hide the AI button globally\n\n#### 1. Go Struct (`pkg/wconfig/settingsconfig.go`)\n\n```go\ntype SettingsType struct {\n    // ... existing fields ...\n    AppHideAiButton bool `json:\"app:hideaibutton,omitempty\"`\n}\n```\n\n#### 2. Default Value (`pkg/wconfig/defaultconfig/settings.json`)\n\n```json\n{\n  \"app:hideaibutton\": false\n}\n```\n\n#### 3. Documentation (`docs/docs/config.mdx`)\n\n```markdown\n| app:hideaibutton <VersionBadge version=\"v0.14\" /> | bool | Hide the AI button in the tab bar (defaults to false) |\n```\n\n#### 4. Generate Types\n\n```bash\ntask generate\n```\n\n#### 5. Frontend Usage\n\n```typescript\nimport { getSettingsKeyAtom } from \"@/store/global\";\n\nconst TabBar = () => {\n    const hideAiButton = useAtomValue(getSettingsKeyAtom(\"app:hideaibutton\"));\n\n    if (hideAiButton) {\n        return null; // Don't render AI button\n    }\n\n    return <button>AI</button>;\n};\n```\n\n#### 6. Usage Examples\n\n```bash\n# Set in settings file\nwsh setconfig app:hideaibutton=true\n\n# Or edit ~/.config/waveterm/settings.json\n{\n  \"app:hideaibutton\": true\n}\n```\n\n### Example 2: Terminal Setting with Block Override\n\n**Use case:** Add a terminal bell sound setting that can be overridden per block\n\n#### 1. Go Struct (`pkg/wconfig/settingsconfig.go`)\n\n```go\ntype SettingsType struct {\n    // ... existing fields ...\n    TermBellSound string `json:\"term:bellsound,omitempty\"`\n}\n```\n\n#### 2. Block Metadata (`pkg/waveobj/wtypemeta.go`)\n\n```go\ntype MetaTSType struct {\n    // ... existing fields ...\n    TermBellSound *string `json:\"term:bellsound,omitempty\"`  // Pointer for optional override\n}\n```\n\n#### 3. Default Value (`pkg/wconfig/defaultconfig/settings.json`)\n\n```json\n{\n  \"term:bellsound\": \"default\"\n}\n```\n\n#### 4. Documentation (`docs/docs/config.mdx`)\n\n```markdown\n| term:bellsound <VersionBadge version=\"v0.14\" /> | string | Sound to play for terminal bell (\"default\", \"none\", or custom sound file path) |\n```\n\n#### 5. Generate Types\n\n```bash\ntask generate\n```\n\n#### 6. Frontend Usage\n\n```typescript\nimport { getOverrideConfigAtom } from \"@/store/global\";\n\nconst TerminalView = ({ blockId }: { blockId: string }) => {\n    // Use override config for hierarchical resolution\n    const bellSoundAtom = getOverrideConfigAtom(blockId, \"term:bellsound\");\n    const bellSound = useAtomValue(bellSoundAtom) ?? \"default\";\n\n    const playBellSound = () => {\n        if (bellSound === \"none\") return;\n        // Play the bell sound\n    };\n\n    return <div>Terminal with bell: {bellSound}</div>;\n};\n```\n\n#### 7. Usage Examples\n\n```bash\n# Set globally in settings file\nwsh setconfig term:bellsound=\"custom.wav\"\n\n# Set for current block only\nwsh setmeta term:bellsound=\"none\"\n\n# Set for specific block\nwsh setmeta --block BLOCK_ID term:bellsound=\"beep\"\n\n# Or edit ~/.config/waveterm/settings.json\n{\n  \"term:bellsound\": \"custom.wav\"\n}\n```\n\n## Configuration Patterns\n\n### Clear/Reset Pattern\n\nEach namespace can have a \"clear\" field for resetting all settings in that namespace:\n\n```go\nAppClear  bool `json:\"app:*,omitempty\"`\nTermClear bool `json:\"term:*,omitempty\"`\n```\n\n### Optional vs Required Settings\n\n- Use pointer types (`*bool`, `*int64`, `*float64`) for truly optional settings\n- Use regular types for settings that should always have a value\n- Provide sensible defaults for important settings\n\n### Block-Level Overrides via RPC\n\nSettings can be overridden at the block level using metadata:\n\n```typescript\nimport { RpcApi } from \"@/app/store/wshclientapi\";\nimport { TabRpcClient } from \"@/app/store/wshrpcutil\";\nimport { WOS } from \"@/store/global\";\n\n// Set block-specific override\nawait RpcApi.SetMetaCommand(TabRpcClient, {\n  oref: WOS.makeORef(\"block\", blockId),\n  meta: { \"mynew:setting\": \"block-specific value\" },\n});\n```\n\n## Common Pitfalls\n\n### 1. Forgetting to Run `task generate`\n\n**Problem:** TypeScript types not updated, schema out of sync\n\n**Solution:** Always run `task generate` after modifying Go structs\n\n### 2. Type Mismatch Between Settings and Metadata\n\n**Problem:** Settings uses `string`, metadata uses `*int`\n\n**Solution:** Ensure types match (except metadata uses pointers for optionals)\n\n### 3. Not Providing Fallback Values\n\n**Problem:** Component breaks if setting is undefined\n\n**Solution:** Always use `??` operator with fallback:\n\n```typescript\nconst value = useAtomValue(getSettingsKeyAtom(\"key\")) ?? \"default\";\n```\n\n### 4. Using Wrong Config Atom\n\n**Problem:** Using `getSettingsKeyAtom()` for settings that need block overrides\n\n**Solution:** Use `getOverrideConfigAtom()` for any setting in `MetaTSType`\n\n## Best Practices\n\n### Naming\n\n- **Use descriptive names**: `term:fontsize` not `term:fs`\n- **Follow namespace conventions**: Group related settings with common prefix\n- **Use consistent casing**: Always lowercase with colons\n\n### Types\n\n- **Use `bool`** for simple on/off settings (no pointer if false is default)\n- **Use `*bool`** only if you need to distinguish unset from false\n- **Use `*int64`/`*float64`** for optional numeric values\n- **Use `string`** for text, paths, or enum-like values\n- **Use `[]string`** for lists\n\n### Defaults\n\n- **Provide sensible defaults** for settings users will commonly change\n- **Omit defaults** for advanced/optional settings\n- **Keep defaults safe** - don't enable experimental features by default\n- **Document defaults** clearly in config.mdx\n\n### Block Overrides\n\n- **Enable for view/display settings**: Font sizes, colors, themes, etc.\n- **Don't enable for app-wide settings**: Global hotkeys, window behavior, etc.\n- **Consider the use case**: Would a user want different values per block or connection?\n\n### Documentation\n\n- **Be specific**: Explain what the setting does and what values are valid\n- **Provide examples**: Show common use cases\n- **Add version badges**: Mark new settings with `<VersionBadge version=\"v0.x\" />`\n- **Keep it current**: Update docs when behavior changes\n\n## Quick Reference\n\nWhen adding a new configuration setting:\n\n- [ ] Add field to `SettingsType` in `pkg/wconfig/settingsconfig.go`\n- [ ] Add field to `MetaTSType` in `pkg/waveobj/wtypemeta.go` (if block override needed)\n- [ ] Add default to `pkg/wconfig/defaultconfig/settings.json` (if needed)\n- [ ] Document in `docs/docs/config.mdx`\n- [ ] Run `task generate` to update TypeScript types\n- [ ] Use appropriate atom (`getOverrideConfigAtom` or `getSettingsKeyAtom`) in frontend\n\n## Related Documentation\n\n- **User Documentation**: `docs/docs/config.mdx` - User-facing configuration docs\n- **Type Definitions**: `pkg/wconfig/settingsconfig.go` - Go struct definitions\n- **Metadata Types**: `pkg/waveobj/wtypemeta.go` - Block metadata definitions\n","CLAUDE.md":"@.kilocode/rules/rules.md\n\n---\n\n## Skill Guides\n\nThis project uses a set of \"skill\" guides — focused how-to documents for common implementation tasks. When your task matches one of the descriptions below, **read the linked SKILL.md file before proceeding** and follow its instructions precisely.\n\n| Skill        | File                                     | Description                                                                                                                                                                                                                                 |\n| ------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| add-config   | `.kilocode/skills/add-config/SKILL.md`   | Guide for adding new configuration settings to Wave Terminal. Use when adding a new setting to the configuration system, implementing a new config key, or adding user-customizable settings.                                               |\n| add-rpc      | `.kilocode/skills/add-rpc/SKILL.md`      | Guide for adding new RPC calls to Wave Terminal. Use when implementing new RPC commands, adding server-client communication methods, or extending the RPC interface with new functionality.                                                 |\n| add-wshcmd   | `.kilocode/skills/add-wshcmd/SKILL.md`   | Guide for adding new wsh commands to Wave Terminal. Use when implementing new CLI commands, adding command-line functionality, or extending the wsh command interface.                                                                      |\n| context-menu | `.kilocode/skills/context-menu/SKILL.md` | Guide for creating and displaying context menus in Wave Terminal. Use when implementing right-click menus, adding context menu items, creating submenus, or handling menu interactions with checkboxes and separators.                      |\n| create-view  | `.kilocode/skills/create-view/SKILL.md`  | Guide for implementing a new view type in Wave Terminal. Use when creating a new view component, implementing the ViewModel interface, registering a new view type in BlockRegistry, or adding a new content type to display within blocks. |\n| electron-api | `.kilocode/skills/electron-api/SKILL.md` | Guide for adding new Electron APIs to Wave Terminal. Use when implementing new frontend-to-electron communications via preload/IPC.                                                                                                         |\n| waveenv      | `.kilocode/skills/waveenv/SKILL.md`      | Guide for creating WaveEnv narrowings in Wave Terminal. Use when writing a named subset type of WaveEnv for a component tree, documenting environmental dependencies, or enabling mock environments for preview/test server usage.          |\n| wps-events   | `.kilocode/skills/wps-events/SKILL.md`   | Guide for working with Wave Terminal's WPS (Wave PubSub) event system. Use when implementing new event types, publishing events, subscribing to events, or adding asynchronous communication between components.                            |\n",".roo/rules/overview.md":"# Rules Skill\n\nPath: `.roo/rules/overview.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.roo/rules/overview.md)",".roo/rules/rules.md":"# Rules Skill\n\nPath: `.roo/rules/rules.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.roo/rules/rules.md)"},"files":{".kilocode/skills/context-menu/SKILL.md":"---\nname: context-menu\ndescription: Guide for creating and displaying context menus in Wave Terminal. Use when implementing right-click menus, adding context menu items, creating submenus, or handling menu interactions with checkboxes and separators.\n---\n\n# Context Menu Quick Reference\n\nThis guide provides a quick overview of how to create and display a context menu using our system.\n\n---\n\n## ContextMenuItem Type\n\nDefine each menu item using the `ContextMenuItem` type:\n\n```ts\ntype ContextMenuItem = {\n  label?: string;\n  type?: \"separator\" | \"normal\" | \"submenu\" | \"checkbox\" | \"radio\";\n  role?: string; // Electron role (optional)\n  click?: () => void; // Callback for item selection (not needed if role is set)\n  submenu?: ContextMenuItem[]; // For nested menus\n  checked?: boolean; // For checkbox or radio items\n  visible?: boolean;\n  enabled?: boolean;\n  sublabel?: string;\n};\n```\n\n---\n\n## Import and Show the Menu\n\nImport the context menu module:\n\n```ts\nimport { ContextMenuModel } from \"@/app/store/contextmenu\";\n```\n\nTo display the context menu, call:\n\n```ts\nContextMenuModel.getInstance().showContextMenu(menu, event);\n```\n\n- **menu**: An array of `ContextMenuItem`.\n- **event**: The mouse event that triggered the context menu (typically from an onContextMenu handler).\n\n---\n\n## Basic Example\n\nA simple context menu with a separator:\n\n```ts\nconst menu: ContextMenuItem[] = [\n  {\n    label: \"New File\",\n    click: () => {\n      /* create a new file */\n    },\n  },\n  {\n    label: \"New Folder\",\n    click: () => {\n      /* create a new folder */\n    },\n  },\n  { type: \"separator\" },\n  {\n    label: \"Rename\",\n    click: () => {\n      /* rename item */\n    },\n  },\n];\n\nContextMenuModel.getInstance().showContextMenu(menu, e);\n```\n\n---\n\n## Example with Submenu and Checkboxes\n\nToggle settings using a submenu with checkbox items:\n\n```ts\nconst isClearOnStart = true; // Example setting\n\nconst menu: ContextMenuItem[] = [\n  {\n    label: \"Clear Output On Restart\",\n    submenu: [\n      {\n        label: \"On\",\n        type: \"checkbox\",\n        checked: isClearOnStart,\n        click: () => {\n          // Set the config to enable clear on restart\n        },\n      },\n      {\n        label: \"Off\",\n        type: \"checkbox\",\n        checked: !isClearOnStart,\n        click: () => {\n          // Set the config to disable clear on restart\n        },\n      },\n    ],\n  },\n];\n\nContextMenuModel.getInstance().showContextMenu(menu, e);\n```\n\n---\n\n## Editing a Config File Example\n\nOpen a configuration file (e.g., `widgets.json`) in preview mode:\n\n```ts\n{\n    label: \"Edit widgets.json\",\n    click: () => {\n        fireAndForget(async () => {\n            const path = `${getApi().getConfigDir()}/widgets.json`;\n            const blockDef: BlockDef = {\n                meta: { view: \"preview\", file: path },\n            };\n            await createBlock(blockDef, false, true);\n        });\n    },\n}\n```\n\n---\n\n## Summary\n\n- **Menu Definition**: Use the `ContextMenuItem` type.\n- **Actions**: Use `click` for actions; use `submenu` for nested options.\n- **Separators**: Use `type: \"separator\"` to group items.\n- **Toggles**: Use `type: \"checkbox\"` or `\"radio\"` with the `checked` property.\n- **Displaying**: Use `ContextMenuModel.getInstance().showContextMenu(menu, event)` to render the menu.\n\n## Common Use Cases\n\n### File/Folder Operations\nContext menus are commonly used for file operations like creating, renaming, and deleting files or folders.\n\n### Settings Toggles\nUse checkbox menu items to toggle settings on and off, with the `checked` property reflecting the current state.\n\n### Nested Options\nUse `submenu` to organize related options hierarchically, keeping the top-level menu clean and organized.\n\n### Conditional Items\nUse the `visible` and `enabled` properties to dynamically show or disable menu items based on the current state.\n",".kilocode/skills/electron-api/SKILL.md":"---\nname: electron-api\ndescription: Guide for adding new Electron APIs to Wave Terminal. Use when implementing new frontend-to-electron communications via preload/IPC.\n---\n\n# Adding Electron APIs\n\nElectron APIs allow the frontend to call Electron main process functionality directly via IPC.\n\n## Four Files to Edit\n\n1. [`frontend/types/custom.d.ts`](frontend/types/custom.d.ts) - TypeScript [`ElectronApi`](frontend/types/custom.d.ts:82) type\n2. [`emain/preload.ts`](emain/preload.ts) - Expose method via `contextBridge`\n3. [`emain/emain-ipc.ts`](emain/emain-ipc.ts) - Implement IPC handler\n4. [`frontend/preview/preview-electron-api.ts`](frontend/preview/preview-electron-api.ts) - Add a no-op stub to keep the `previewElectronApi` object in sync with the `ElectronApi` type\n\n## Three Communication Patterns\n\n1. **Sync** - `ipcRenderer.sendSync()` + `ipcMain.on()` + `event.returnValue = ...`\n2. **Async** - `ipcRenderer.invoke()` + `ipcMain.handle()`\n3. **Fire-and-forget** - `ipcRenderer.send()` + `ipcMain.on()`\n\n## Example: Async Method\n\n### 1. Define TypeScript Interface\n\nIn [`frontend/types/custom.d.ts`](frontend/types/custom.d.ts):\n\n```typescript\ntype ElectronApi = {\n    captureScreenshot: (rect: Electron.Rectangle) => Promise<string>; // capture-screenshot\n};\n```\n\n### 2. Expose in Preload\n\nIn [`emain/preload.ts`](emain/preload.ts):\n\n```typescript\ncontextBridge.exposeInMainWorld(\"api\", {\n    captureScreenshot: (rect: Rectangle) => ipcRenderer.invoke(\"capture-screenshot\", rect),\n});\n```\n\n### 3. Implement Handler\n\nIn [`emain/emain-ipc.ts`](emain/emain-ipc.ts):\n\n```typescript\nelectron.ipcMain.handle(\"capture-screenshot\", async (event, rect) => {\n    const tabView = getWaveTabViewByWebContentsId(event.sender.id);\n    if (!tabView) throw new Error(\"No tab view found\");\n    const image = await tabView.webContents.capturePage(rect);\n    return `data:image/png;base64,${image.toPNG().toString(\"base64\")}`;\n});\n```\n\n### 4. Add Preview Stub\n\nIn [`frontend/preview/preview-electron-api.ts`](frontend/preview/preview-electron-api.ts):\n\n```typescript\ncaptureScreenshot: (_rect: Electron.Rectangle) => Promise.resolve(\"\"),\n```\n\n### 5. Call from Frontend\n\n```typescript\nimport { getApi } from \"@/store/global\";\n\nconst dataUrl = await getApi().captureScreenshot({ x: 0, y: 0, width: 800, height: 600 });\n```\n\n## Example: Sync Method\n\n### 1. Define\n\n```typescript\ntype ElectronApi = {\n    getUserName: () => string; // get-user-name\n};\n```\n\n### 2. Preload\n\n```typescript\ngetUserName: () => ipcRenderer.sendSync(\"get-user-name\"),\n```\n\n### 3. Handler (⚠️ MUST set event.returnValue or browser hangs)\n\n```typescript\nelectron.ipcMain.on(\"get-user-name\", (event) => {\n    event.returnValue = process.env.USER || \"unknown\";\n});\n```\n\n### 4. Call\n\n```typescript\nimport { getApi } from \"@/store/global\";\n\nconst userName = getApi().getUserName(); // blocks until returns\n```\n\n## Example: Fire-and-Forget\n\n### 1. Define\n\n```typescript\ntype ElectronApi = {\n    openExternal: (url: string) => void; // open-external\n};\n```\n\n### 2. Preload\n\n```typescript\nopenExternal: (url) => ipcRenderer.send(\"open-external\", url),\n```\n\n### 3. Handler\n\n```typescript\nelectron.ipcMain.on(\"open-external\", (event, url) => {\n    electron.shell.openExternal(url);\n});\n```\n\n## Example: Event Listener\n\n### 1. Define\n\n```typescript\ntype ElectronApi = {\n    onZoomFactorChange: (callback: (zoomFactor: number) => void) => void; // zoom-factor-change\n};\n```\n\n### 2. Preload\n\n```typescript\nonZoomFactorChange: (callback) => \n    ipcRenderer.on(\"zoom-factor-change\", (_event, zoomFactor) => callback(zoomFactor)),\n```\n\n### 3. Send from Main\n\n```typescript\nwebContents.send(\"zoom-factor-change\", newZoomFactor);\n```\n\n## Quick Reference\n\n**Use Sync when:**\n- Getting config/env vars\n- Quick lookups, no I/O\n- ⚠️ **CRITICAL**: Always set `event.returnValue` or browser hangs\n\n**Use Async when:**\n- File operations\n- Network requests\n- Can fail or take time\n\n**Use Fire-and-forget when:**\n- No return value needed\n- Triggering actions\n\n**Electron API vs RPC:**\n- Electron API: Native OS features, window management, Electron APIs\n- RPC: Database, backend logic, remote servers\n\n## Checklist\n\n- [ ] Add to [`ElectronApi`](frontend/types/custom.d.ts:82) in [`custom.d.ts`](frontend/types/custom.d.ts)\n- [ ] Include IPC channel name in comment\n- [ ] Expose in [`preload.ts`](emain/preload.ts)\n- [ ] Implement in [`emain-ipc.ts`](emain/emain-ipc.ts)\n- [ ] Add no-op stub to [`preview-electron-api.ts`](frontend/preview/preview-electron-api.ts)\n- [ ] IPC channel names match exactly\n- [ ] **For sync**: Set `event.returnValue` (or browser hangs!)\n- [ ] Test end-to-end\n",".kilocode/skills/waveenv/SKILL.md":"---\nname: waveenv\ndescription: Guide for creating WaveEnv narrowings in Wave Terminal. Use when writing a named subset type of WaveEnv for a component tree, documenting environmental dependencies, or enabling mock environments for preview/test server usage.\n---\n\n# WaveEnv Narrowing Skill\n\n## Purpose\n\nA WaveEnv narrowing creates a _named subset type_ of `WaveEnv` that:\n\n1. Documents exactly which parts of the environment a component tree actually uses.\n2. Forms a type contract so callers and tests know what to provide.\n3. Enables mocking in the preview/test server — you only need to implement what's listed.\n\n## When To Create One\n\nCreate a narrowing whenever you are writing a component (or group of components) that you want to test in the preview server, or when you want to make the environmental dependencies of a component tree explicit.\n\n## Core Principle: Only Include What You Use\n\n**Only list the fields, methods, atoms, and keys that the component tree actually accesses.** If you don't call `wos`, don't include `wos`. If you only call one RPC command, only list that one command. The narrowing is a precise dependency declaration — not a copy of `WaveEnv`.\n\n## File Location\n\n- **Separate file** (preferred for shared/complex envs): name it `<feature>env.ts` next to the component, e.g. `frontend/app/block/blockenv.ts`.\n- **Inline** (acceptable for small, single-file components): export the type directly from the component file, e.g. `WidgetsEnv` in `frontend/app/workspace/widgets.tsx`.\n\n## Imports Required\n\n```ts\nimport {\n  MetaKeyAtomFnType, // only if you use getBlockMetaKeyAtom or getTabMetaKeyAtom\n  ConnConfigKeyAtomFnType, // only if you use getConnConfigKeyAtom\n  SettingsKeyAtomFnType, // only if you use getSettingsKeyAtom\n  WaveEnv,\n  WaveEnvSubset,\n} from \"@/app/waveenv/waveenv\";\n```\n\n## The Shape\n\n```ts\nexport type MyEnv = WaveEnvSubset<{\n  // --- Simple WaveEnv properties ---\n  // Copy the type verbatim from WaveEnv with WaveEnv[\"key\"] syntax.\n  isDev: WaveEnv[\"isDev\"];\n  createBlock: WaveEnv[\"createBlock\"];\n  showContextMenu: WaveEnv[\"showContextMenu\"];\n  platform: WaveEnv[\"platform\"];\n\n  // --- electron: list only the methods you call ---\n  electron: {\n    openExternal: WaveEnv[\"electron\"][\"openExternal\"];\n  };\n\n  // --- rpc: list only the commands you call ---\n  rpc: {\n    ActivityCommand: WaveEnv[\"rpc\"][\"ActivityCommand\"];\n    ConnEnsureCommand: WaveEnv[\"rpc\"][\"ConnEnsureCommand\"];\n  };\n\n  // --- atoms: list only the atoms you read ---\n  atoms: {\n    modalOpen: WaveEnv[\"atoms\"][\"modalOpen\"];\n    fullConfigAtom: WaveEnv[\"atoms\"][\"fullConfigAtom\"];\n  };\n\n  // --- wos: always take the whole thing, no sub-typing needed ---\n  wos: WaveEnv[\"wos\"];\n\n  // --- services: list only the services you call; no method-level narrowing ---\n  services: {\n    block: WaveEnv[\"services\"][\"block\"];\n    workspace: WaveEnv[\"services\"][\"workspace\"];\n  };\n\n  // --- key-parameterized atom factories: enumerate the keys you use ---\n  getSettingsKeyAtom: SettingsKeyAtomFnType<\"app:focusfollowscursor\" | \"window:magnifiedblockopacity\">;\n  getBlockMetaKeyAtom: MetaKeyAtomFnType<\"view\" | \"frame:title\" | \"connection\">;\n  getTabMetaKeyAtom: MetaKeyAtomFnType<\"tabid\" | \"name\">;\n  getConnConfigKeyAtom: ConnConfigKeyAtomFnType<\"conn:wshenabled\">;\n\n  // --- other atom helpers: copy verbatim ---\n  getConnStatusAtom: WaveEnv[\"getConnStatusAtom\"];\n  getLocalHostDisplayNameAtom: WaveEnv[\"getLocalHostDisplayNameAtom\"];\n  getConfigBackgroundAtom: WaveEnv[\"getConfigBackgroundAtom\"];\n}>;\n```\n\n### Automatically Included Fields\n\nEvery `WaveEnvSubset<T>` automatically includes the mock fields — you never need to declare them:\n\n- `isMock: boolean`\n- `mockSetWaveObj: <T extends WaveObj>(oref: string, obj: T) => void`\n- `mockModels?: Map<any, any>`\n\n### Rules for Each Section\n\n| Section                    | Pattern                                                | Notes                                                                                              |\n| -------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |\n| `electron`                 | `electron: { method: WaveEnv[\"electron\"][\"method\"]; }` | List every method called; omit the rest.                                                           |\n| `rpc`                      | `rpc: { Cmd: WaveEnv[\"rpc\"][\"Cmd\"]; }`                 | List every RPC command called; omit the rest.                                                      |\n| `atoms`                    | `atoms: { atom: WaveEnv[\"atoms\"][\"atom\"]; }`           | List every atom read; omit the rest.                                                               |\n| `wos`                      | `wos: WaveEnv[\"wos\"]`                                  | Take the whole `wos` object (no sub-typing needed), but **only add it if `wos` is actually used**. |\n| `services`                 | `services: { svc: WaveEnv[\"services\"][\"svc\"]; }`       | List each service used; take the whole service object (no method-level narrowing).                 |\n| `getSettingsKeyAtom`       | `SettingsKeyAtomFnType<\"key1\" \\| \"key2\">`              | Union all settings keys accessed.                                                                  |\n| `getBlockMetaKeyAtom`      | `MetaKeyAtomFnType<\"key1\" \\| \"key2\">`                  | Union all block meta keys accessed.                                                                |\n| `getTabMetaKeyAtom`        | `MetaKeyAtomFnType<\"key1\" \\| \"key2\">`                  | Union all tab meta keys accessed.                                                                  |\n| `getConnConfigKeyAtom`     | `ConnConfigKeyAtomFnType<\"key1\">`                      | Union all conn config keys accessed.                                                               |\n| All other `WaveEnv` fields | `WaveEnv[\"fieldName\"]`                                 | Copy type verbatim.                                                                                |\n\n## Using the Narrowed Type in Components\n\n```ts\nimport { useWaveEnv } from \"@/app/waveenv/waveenv\";\nimport { MyEnv } from \"./myenv\";\n\nconst MyComponent = memo(() => {\n    const env = useWaveEnv<MyEnv>();\n    // TypeScript now enforces you only access what's in MyEnv.\n    const val = useAtomValue(env.getSettingsKeyAtom(\"app:focusfollowscursor\"));\n    ...\n});\n```\n\nThe generic parameter on `useWaveEnv<MyEnv>()` casts the context to your narrowed type. The real production `WaveEnv` satisfies every narrowing; mock envs only need to implement the listed subset.\n\n## Real Examples\n\n- `BlockEnv` in `frontend/app/block/blockenv.ts` — complex narrowing with all section types, in a separate file.\n- `WidgetsEnv` in `frontend/app/workspace/widgets.tsx` — smaller narrowing defined inline in the component file.\n",".kilocode/skills/wps-events/SKILL.md":"---\nname: wps-events\ndescription: Guide for working with Wave Terminal's WPS (Wave PubSub) event system. Use when implementing new event types, publishing events, subscribing to events, or adding asynchronous communication between components.\n---\n\n# WPS Events Guide\n\n## Overview\n\nWPS (Wave PubSub) is Wave Terminal's publish-subscribe event system that enables different parts of the application to communicate asynchronously. The system uses a broker pattern to route events from publishers to subscribers based on event types and scopes.\n\n## Key Files\n\n- `pkg/wps/wpstypes.go` - Event type constants and data structures\n- `pkg/wps/wps.go` - Broker implementation and core logic\n- `pkg/wcore/wcore.go` - Example usage patterns\n\n## Event Structure\n\nEvents in WPS have the following structure:\n\n```go\ntype WaveEvent struct {\n    Event   string   `json:\"event\"`      // Event type constant\n    Scopes  []string `json:\"scopes,omitempty\"` // Optional scopes for targeted delivery\n    Sender  string   `json:\"sender,omitempty\"` // Optional sender identifier\n    Persist int      `json:\"persist,omitempty\"` // Number of events to persist in history\n    Data    any      `json:\"data,omitempty\"`    // Event payload\n}\n```\n\n## Adding a New Event Type\n\n### Step 1: Define the Event Constant\n\nAdd your event type constant to `pkg/wps/wpstypes.go`:\n\n```go\nconst (\n    Event_BlockClose       = \"blockclose\"\n    Event_ConnChange       = \"connchange\"\n    // ... other events ...\n    Event_YourNewEvent     = \"your:newevent\"  // type: YourEventData (or \"none\" if no data)\n)\n```\n\n**Naming Convention:**\n\n- Use descriptive PascalCase for the constant name with `Event_` prefix\n- Use lowercase with colons for the string value (e.g., \"namespace:eventname\")\n- Group related events with the same namespace prefix\n- Always add a `// type: <TypeName>` comment; use `// type: none` if no data is sent\n\n### Step 2: Add to AllEvents\n\nAdd your new constant to the `AllEvents` slice in `pkg/wps/wpstypes.go`:\n\n```go\nvar AllEvents []string = []string{\n    // ... existing events ...\n    Event_YourNewEvent,\n}\n```\n\n### Step 3: Register in WaveEventDataTypes (REQUIRED)\n\nYou **must** add an entry to `WaveEventDataTypes` in `pkg/tsgen/tsgenevent.go`. This drives TypeScript type generation for the event's `data` field:\n\n```go\nvar WaveEventDataTypes = map[string]reflect.Type{\n    // ... existing entries ...\n    wps.Event_YourNewEvent: reflect.TypeOf(YourEventData{}),        // value type\n    // wps.Event_YourNewEvent: reflect.TypeOf((*YourEventData)(nil)), // pointer type\n    // wps.Event_YourNewEvent: nil,                                   // no data (type: none)\n}\n```\n\n- Use `reflect.TypeOf(YourType{})` for value types\n- Use `reflect.TypeOf((*YourType)(nil))` for pointer types\n- Use `nil` if no data is sent for the event\n\n### Step 4: Define Event Data Structure (Optional)\n\nIf your event carries structured data, define a type for it:\n\n```go\ntype YourEventData struct {\n    Field1 string `json:\"field1\"`\n    Field2 int    `json:\"field2\"`\n}\n```\n\n### Step 5: Expose Type to Frontend (If Needed)\n\nIf your event data type isn't already exposed via an RPC call, you need to add it to `pkg/tsgen/tsgen.go` so TypeScript types are generated:\n\n```go\n// add extra types to generate here\nvar ExtraTypes = []any{\n    waveobj.ORef{},\n    // ... other types ...\n    uctypes.RateLimitInfo{},  // Example: already added\n    YourEventData{},          // Add your new type here\n}\n```\n\nThen run code generation:\n\n```bash\ntask generate\n```\n\nThis will update `frontend/types/gotypes.d.ts` with TypeScript definitions for your type, ensuring type safety in the frontend when handling these events.\n\n## Publishing Events\n\n### Basic Publishing\n\nTo publish an event, use the global broker:\n\n```go\nimport \"github.com/wavetermdev/waveterm/pkg/wps\"\n\nwps.Broker.Publish(wps.WaveEvent{\n    Event: wps.Event_YourNewEvent,\n    Data:  yourData,\n})\n```\n\n### Publishing with Scopes\n\nScopes allow targeted event delivery. Subscribers can filter events by scope:\n\n```go\nwps.Broker.Publish(wps.WaveEvent{\n    Event:  wps.Event_WaveObjUpdate,\n    Scopes: []string{oref.String()},  // Target specific object\n    Data:   updateData,\n})\n```\n\n### Publishing in a Goroutine\n\nTo avoid blocking the caller, publish events asynchronously:\n\n```go\ngo func() {\n    wps.Broker.Publish(wps.WaveEvent{\n        Event: wps.Event_YourNewEvent,\n        Data:  data,\n    })\n}()\n```\n\n**When to use goroutines:**\n\n- When publishing from performance-critical code paths\n- When the event is informational and doesn't need immediate delivery\n- When publishing from code that holds locks (to prevent deadlocks)\n\n### Event Persistence\n\nEvents can be persisted in memory for late subscribers:\n\n```go\nwps.Broker.Publish(wps.WaveEvent{\n    Event:   wps.Event_YourNewEvent,\n    Persist: 100,  // Keep last 100 events\n    Data:    data,\n})\n```\n\n## Complete Example: Rate Limit Updates\n\nThis example shows how rate limit information is published when AI chat responses include rate limit headers.\n\n### 1. Define the Event Type\n\nIn `pkg/wps/wpstypes.go`:\n\n```go\nconst (\n    // ... other events ...\n    Event_WaveAIRateLimit  = \"waveai:ratelimit\"\n)\n```\n\n### 2. Publish the Event\n\nIn `pkg/aiusechat/usechat.go`:\n\n```go\nimport \"github.com/wavetermdev/waveterm/pkg/wps\"\n\nfunc updateRateLimit(info *uctypes.RateLimitInfo) {\n    if info == nil {\n        return\n    }\n    rateLimitLock.Lock()\n    defer rateLimitLock.Unlock()\n    globalRateLimitInfo = info\n\n    // Publish event in goroutine to avoid blocking\n    go func() {\n        wps.Broker.Publish(wps.WaveEvent{\n            Event: wps.Event_WaveAIRateLimit,\n            Data:  info,  // RateLimitInfo struct\n        })\n    }()\n}\n```\n\n### 3. Subscribe to the Event (Frontend)\n\nIn the frontend, subscribe to events via WebSocket:\n\n```typescript\n// Subscribe to rate limit updates\nconst subscription = {\n  event: \"waveai:ratelimit\",\n  allscopes: true, // Receive all rate limit events\n};\n```\n\n## Subscribing to Events\n\n### From Go Code\n\n```go\n// Subscribe to all events of a type\nwps.Broker.Subscribe(routeId, wps.SubscriptionRequest{\n    Event:     wps.Event_YourNewEvent,\n    AllScopes: true,\n})\n\n// Subscribe to specific scopes\nwps.Broker.Subscribe(routeId, wps.SubscriptionRequest{\n    Event:  wps.Event_WaveObjUpdate,\n    Scopes: []string{\"workspace:123\"},\n})\n\n// Unsubscribe\nwps.Broker.Unsubscribe(routeId, wps.Event_YourNewEvent)\n```\n\n### Scope Matching\n\nScopes support wildcard matching:\n\n- `*` matches a single scope segment\n- `**` matches multiple scope segments\n\n```go\n// Subscribe to all workspace events\nwps.Broker.Subscribe(routeId, wps.SubscriptionRequest{\n    Event:  wps.Event_WaveObjUpdate,\n    Scopes: []string{\"workspace:*\"},\n})\n```\n\n## Best Practices\n\n1. **Use Namespaces**: Prefix event names with a namespace (e.g., `waveai:`, `workspace:`, `block:`)\n\n2. **Don't Block**: Use goroutines when publishing from performance-critical code or while holding locks\n\n3. **Type-Safe Data**: Define struct types for event data rather than using maps\n\n4. **Scope Wisely**: Use scopes to limit event delivery and reduce unnecessary processing\n\n5. **Document Events**: Add comments explaining when events are fired and what data they carry\n\n6. **Consider Persistence**: Use `Persist` for events that late subscribers might need (like status updates). This is normally not used. We normally do a live RPC call to get the current value and then subscribe for updates.\n\n## Common Event Patterns\n\n### Status Updates\n\n```go\nwps.Broker.Publish(wps.WaveEvent{\n    Event:   wps.Event_ControllerStatus,\n    Scopes:  []string{blockId},\n    Persist: 1,  // Keep only latest status\n    Data:    statusData,\n})\n```\n\n### Object Updates\n\n```go\nwps.Broker.Publish(wps.WaveEvent{\n    Event:  wps.Event_WaveObjUpdate,\n    Scopes: []string{oref.String()},\n    Data: waveobj.WaveObjUpdate{\n        UpdateType: waveobj.UpdateType_Update,\n        OType:      obj.GetOType(),\n        OID:        waveobj.GetOID(obj),\n        Obj:        obj,\n    },\n})\n```\n\n### Batch Updates\n\n```go\n// Helper function for multiple updates\nfunc (b *BrokerType) SendUpdateEvents(updates waveobj.UpdatesRtnType) {\n    for _, update := range updates {\n        b.Publish(WaveEvent{\n            Event:  Event_WaveObjUpdate,\n            Scopes: []string{waveobj.MakeORef(update.OType, update.OID).String()},\n            Data:   update,\n        })\n    }\n}\n```\n\n## Debugging\n\nTo debug event flow:\n\n1. Check broker subscription map: `wps.Broker.SubMap`\n2. View persisted events: `wps.Broker.ReadEventHistory(eventType, scope, maxItems)`\n3. Add logging in publish/subscribe methods\n4. Monitor WebSocket traffic in browser dev tools\n\n## Quick Reference\n\nWhen adding a new event:\n\n- [ ] Add event constant to [`pkg/wps/wpstypes.go`](pkg/wps/wpstypes.go) with a `// type: <TypeName>` comment (use `none` if no data)\n- [ ] Add the constant to `AllEvents` in [`pkg/wps/wpstypes.go`](pkg/wps/wpstypes.go)\n- [ ] **REQUIRED**: Add an entry to `WaveEventDataTypes` in [`pkg/tsgen/tsgenevent.go`](pkg/tsgen/tsgenevent.go) — use `nil` for events with no data\n- [ ] Define event data structure (if needed)\n- [ ] Add data type to `pkg/tsgen/tsgen.go` for frontend use (if not already exposed via RPC)\n- [ ] Run `task generate` to update TypeScript types\n- [ ] Publish events using `wps.Broker.Publish()`\n- [ ] Use goroutines for non-blocking publish when appropriate\n- [ ] Subscribe to events in relevant components\n",".kilocode/skills/create-view/SKILL.md":"---\nname: create-view\ndescription: Guide for implementing a new view type in Wave Terminal. Use when creating a new view component, implementing the ViewModel interface, registering a new view type in BlockRegistry, or adding a new content type to display within blocks.\n---\n\n# Creating a New View in Wave Terminal\n\nThis guide explains how to implement a new view type in Wave Terminal. Views are the core content components displayed within blocks in the terminal interface.\n\n## Architecture Overview\n\nWave Terminal uses a **Model-View architecture** where:\n\n- **ViewModel** - Contains all state, logic, and UI configuration as Jotai atoms\n- **ViewComponent** - Pure React component that renders the UI using the model\n- **BlockFrame** - Wraps views with a header, connection management, and standard controls\n\nThe separation between model and component ensures:\n\n- Models can update state without React hooks\n- Components remain pure and testable\n- State is centralized in Jotai atoms for easy access\n\n## ViewModel Interface\n\nEvery view must implement the `ViewModel` interface defined in `frontend/types/custom.d.ts`:\n\n```typescript\ninterface ViewModel {\n  // Required: The type identifier for this view (e.g., \"term\", \"web\", \"preview\")\n  viewType: string;\n\n  // Required: The React component that renders this view\n  viewComponent: ViewComponent<ViewModel>;\n\n  // Optional: Icon shown in block header (FontAwesome icon name or IconButtonDecl)\n  viewIcon?: jotai.Atom<string | IconButtonDecl>;\n\n  // Optional: Display name shown in block header (e.g., \"Terminal\", \"Web\", \"Preview\")\n  viewName?: jotai.Atom<string>;\n\n  // Optional: Additional header elements (text, buttons, inputs) shown after the name\n  viewText?: jotai.Atom<string | HeaderElem[]>;\n\n  // Optional: Icon button shown before the view name in header\n  preIconButton?: jotai.Atom<IconButtonDecl>;\n\n  // Optional: Icon buttons shown at the end of the header (before settings/close)\n  endIconButtons?: jotai.Atom<IconButtonDecl[]>;\n\n  // Optional: Custom background styling for the block\n  blockBg?: jotai.Atom<MetaType>;\n\n  // Optional: If true, completely hides the block header\n  noHeader?: jotai.Atom<boolean>;\n\n  // Optional: If true, shows connection picker in header for remote connections\n  manageConnection?: jotai.Atom<boolean>;\n\n  // Optional: If true, filters out 'nowsh' connections from connection picker\n  filterOutNowsh?: jotai.Atom<boolean>;\n\n  // Optional: If true, removes default padding from content area\n  noPadding?: jotai.Atom<boolean>;\n\n  // Optional: Atoms for managing in-block search functionality\n  searchAtoms?: SearchAtoms;\n\n  // Optional: Returns whether this is a basic terminal (for multi-input feature)\n  isBasicTerm?: (getFn: jotai.Getter) => boolean;\n\n  // Optional: Returns context menu items for the settings dropdown\n  getSettingsMenuItems?: () => ContextMenuItem[];\n\n  // Optional: Focuses the view when called, returns true if successful\n  giveFocus?: () => boolean;\n\n  // Optional: Handles keyboard events, returns true if handled\n  keyDownHandler?: (e: WaveKeyboardEvent) => boolean;\n\n  // Optional: Cleanup when block is closed\n  dispose?: () => void;\n}\n```\n\n### Key Concepts\n\n**Atoms**: All UI-related properties must be Jotai atoms. This enables:\n\n- Reactive updates when state changes\n- Access from anywhere via `globalStore.get()`/`globalStore.set()`\n- Derived atoms that compute values from other atoms\n\n**ViewComponent**: The React component receives these props:\n\n```typescript\ntype ViewComponentProps<T extends ViewModel> = {\n  blockId: string; // Unique ID for this block\n  blockRef: React.RefObject<HTMLDivElement>; // Ref to block container\n  contentRef: React.RefObject<HTMLDivElement>; // Ref to content area\n  model: T; // Your ViewModel instance\n};\n```\n\n## Step-by-Step Guide\n\n### 1. Create the View Model Class\n\nCreate a new file for your view model (e.g., `frontend/app/view/myview/myview-model.ts`):\n\n```typescript\nimport { BlockNodeModel } from \"@/app/block/blocktypes\";\nimport { globalStore } from \"@/app/store/jotaiStore\";\nimport { WOS, useBlockAtom } from \"@/store/global\";\nimport * as jotai from \"jotai\";\nimport { MyView } from \"./myview\";\n\nexport class MyViewModel implements ViewModel {\n  viewType: string;\n  blockId: string;\n  nodeModel: BlockNodeModel;\n  blockAtom: jotai.Atom<Block>;\n\n  // Define your atoms (simple field initializers)\n  viewIcon = jotai.atom<string>(\"circle\");\n  viewName = jotai.atom<string>(\"My View\");\n  noPadding = jotai.atom<boolean>(true);\n\n  // Derived atom (created in constructor)\n  viewText!: jotai.Atom<HeaderElem[]>;\n\n  constructor(blockId: string, nodeModel: BlockNodeModel) {\n    this.viewType = \"myview\";\n    this.blockId = blockId;\n    this.nodeModel = nodeModel;\n    this.blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);\n\n    // Create derived atoms that depend on block data or other atoms\n    this.viewText = jotai.atom((get) => {\n      const blockData = get(this.blockAtom);\n      const rtn: HeaderElem[] = [];\n\n      // Add header buttons/text based on state\n      rtn.push({\n        elemtype: \"iconbutton\",\n        icon: \"refresh\",\n        title: \"Refresh\",\n        click: () => this.refresh(),\n      });\n\n      return rtn;\n    });\n  }\n\n  get viewComponent(): ViewComponent {\n    return MyView;\n  }\n\n  refresh() {\n    // Update state using globalStore\n    // Never use React hooks in model methods\n    console.log(\"refreshing...\");\n  }\n\n  giveFocus(): boolean {\n    // Focus your view component\n    return true;\n  }\n\n  dispose() {\n    // Cleanup resources (unsubscribe from events, etc.)\n  }\n}\n```\n\n### 2. Create the View Component\n\nCreate your React component (e.g., `frontend/app/view/myview/myview.tsx`):\n\n```typescript\nimport { ViewComponentProps } from \"@/app/block/blocktypes\";\nimport { MyViewModel } from \"./myview-model\";\nimport { useAtomValue } from \"jotai\";\nimport \"./myview.scss\";\n\nexport const MyView: React.FC<ViewComponentProps<MyViewModel>> = ({\n    blockId,\n    model,\n    contentRef\n}) => {\n    // Use atoms from the model (these are React hooks - call at top level!)\n    const blockData = useAtomValue(model.blockAtom);\n\n    return (\n        <div className=\"myview-container\" ref={contentRef}>\n            <div>Block ID: {blockId}</div>\n            <div>View: {model.viewType}</div>\n            {/* Your view content here */}\n        </div>\n    );\n};\n```\n\n### 3. Register the View\n\nAdd your view to the `BlockRegistry` in `frontend/app/block/blockregistry.ts`:\n\n```typescript\nimport { MyViewModel } from \"@/app/view/myview/myview-model\";\n\nconst BlockRegistry: Map<string, ViewModelClass> = new Map();\nBlockRegistry.set(\"term\", TermViewModel);\nBlockRegistry.set(\"preview\", PreviewModel);\nBlockRegistry.set(\"web\", WebViewModel);\n// ... existing registrations ...\nBlockRegistry.set(\"myview\", MyViewModel); // Add your view here\n```\n\nThe registry key (e.g., `\"myview\"`) becomes the view type used in block metadata.\n\n### 4. Create Blocks with Your View\n\nUsers can create blocks with your view type:\n\n- Via CLI: `wsh view myview`\n- Via RPC: Use the block's `meta.view` field set to `\"myview\"`\n\n## Real-World Examples\n\n### Example 1: Terminal View (`term-model.ts`)\n\nThe terminal view demonstrates:\n\n- **Connection management** via `manageConnection` atom\n- **Dynamic header buttons** showing shell status (play/restart)\n- **Mode switching** between terminal and vdom views\n- **Custom keyboard handling** for terminal-specific shortcuts\n- **Focus management** to focus the xterm.js instance\n- **Shell integration status** showing AI capability indicators\n\nKey features:\n\n```typescript\nthis.manageConnection = jotai.atom((get) => {\n  const termMode = get(this.termMode);\n  if (termMode == \"vdom\") return false;\n  return true; // Show connection picker for regular terminal mode\n});\n\nthis.endIconButtons = jotai.atom((get) => {\n  const shellProcStatus = get(this.shellProcStatus);\n  const buttons: IconButtonDecl[] = [];\n\n  if (shellProcStatus == \"running\") {\n    buttons.push({\n      elemtype: \"iconbutton\",\n      icon: \"refresh\",\n      title: \"Restart Shell\",\n      click: this.forceRestartController.bind(this),\n    });\n  }\n  return buttons;\n});\n```\n\n### Example 2: Web View (`webview.tsx`)\n\nThe web view shows:\n\n- **Complex header controls** (back/forward/home/URL input)\n- **State management** for loading, URL, and navigation\n- **Event handling** for webview navigation events\n- **Custom styling** with `noPadding` for full-bleed content\n- **Media controls** showing play/pause/mute when media is active\n\nKey features:\n\n```typescript\nthis.viewText = jotai.atom((get) => {\n  const url = get(this.url);\n  const rtn: HeaderElem[] = [];\n\n  // Navigation buttons\n  rtn.push({\n    elemtype: \"iconbutton\",\n    icon: \"chevron-left\",\n    click: this.handleBack.bind(this),\n    disabled: this.shouldDisableBackButton(),\n  });\n\n  // URL input with nested controls\n  rtn.push({\n    elemtype: \"div\",\n    className: \"block-frame-div-url\",\n    children: [\n      {\n        elemtype: \"input\",\n        value: url,\n        onChange: this.handleUrlChange.bind(this),\n        onKeyDown: this.handleKeyDown.bind(this),\n      },\n      {\n        elemtype: \"iconbutton\",\n        icon: \"rotate-right\",\n        click: this.handleRefresh.bind(this),\n      },\n    ],\n  });\n\n  return rtn;\n});\n```\n\n## Header Elements (`HeaderElem`)\n\nThe `viewText` atom can return an array of these element types:\n\n```typescript\n// Icon button\n{\n    elemtype: \"iconbutton\",\n    icon: \"refresh\",\n    title: \"Tooltip text\",\n    click: () => { /* handler */ },\n    disabled?: boolean,\n    iconColor?: string,\n    iconSpin?: boolean,\n    noAction?: boolean,  // Shows icon but no click action\n}\n\n// Text element\n{\n    elemtype: \"text\",\n    text: \"Display text\",\n    className?: string,\n    noGrow?: boolean,\n    ref?: React.RefObject<HTMLElement>,\n    onClick?: (e: React.MouseEvent) => void,\n}\n\n// Text button\n{\n    elemtype: \"textbutton\",\n    text: \"Button text\",\n    className?: string,\n    title: \"Tooltip\",\n    onClick: (e: React.MouseEvent) => void,\n}\n\n// Input field\n{\n    elemtype: \"input\",\n    value: string,\n    className?: string,\n    onChange: (e: React.ChangeEvent<HTMLInputElement>) => void,\n    onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void,\n    onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void,\n    onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void,\n    ref?: React.RefObject<HTMLInputElement>,\n}\n\n// Container with children\n{\n    elemtype: \"div\",\n    className?: string,\n    children: HeaderElem[],\n    onMouseOver?: (e: React.MouseEvent) => void,\n    onMouseOut?: (e: React.MouseEvent) => void,\n}\n\n// Menu button (dropdown)\n{\n    elemtype: \"menubutton\",\n    // ... MenuButtonProps ...\n}\n```\n\n## Best Practices\n\n### Jotai Model Pattern\n\nFollow these rules for Jotai atoms in models:\n\n1. **Simple atoms as field initializers**:\n\n   ```typescript\n   viewIcon = jotai.atom<string>(\"circle\");\n   noPadding = jotai.atom<boolean>(true);\n   ```\n\n2. **Derived atoms in constructor** (need dependency on other atoms):\n\n   ```typescript\n   constructor(blockId: string, nodeModel: BlockNodeModel) {\n       this.viewText = jotai.atom((get) => {\n           const blockData = get(this.blockAtom);\n           return [/* computed based on blockData */];\n       });\n   }\n   ```\n\n3. **Models never use React hooks** - Use `globalStore.get()`/`set()`:\n\n   ```typescript\n   refresh() {\n       const currentData = globalStore.get(this.blockAtom);\n       globalStore.set(this.dataAtom, newData);\n   }\n   ```\n\n4. **Components use hooks for atoms**:\n   ```typescript\n   const data = useAtomValue(model.dataAtom);\n   const [value, setValue] = useAtom(model.valueAtom);\n   ```\n\n### State Management\n\n- All view state should live in atoms on the model\n- Use `useBlockAtom()` helper for block-scoped atoms that persist\n- Use `globalStore` for imperative access outside React components\n- Subscribe to Wave events using `waveEventSubscribe()`\n\n### Styling\n\n- Create a `.scss` file for your view styles\n- Use Tailwind utilities where possible (v4)\n- Add `noPadding: atom(true)` for full-bleed content\n- Use `blockBg` atom to customize block background\n\n### Focus Management\n\nImplement `giveFocus()` to focus your view when:\n\n- Block gains focus via keyboard navigation\n- User clicks the block\n- Return `true` if successfully focused, `false` otherwise\n\n### Keyboard Handling\n\nImplement `keyDownHandler(e: WaveKeyboardEvent)` for:\n\n- View-specific keyboard shortcuts\n- Return `true` if event was handled (prevents propagation)\n- Use `keyutil.checkKeyPressed(waveEvent, \"Cmd:K\")` for shortcut checks\n\n### Cleanup\n\nImplement `dispose()` to:\n\n- Unsubscribe from Wave events\n- Unregister routes/handlers\n- Clear timers/intervals\n- Release resources\n\n### Connection Management\n\nFor views that need remote connections:\n\n```typescript\nthis.manageConnection = jotai.atom(true); // Show connection picker\nthis.filterOutNowsh = jotai.atom(true); // Hide nowsh connections\n```\n\nAccess connection status:\n\n```typescript\nconst connStatus = jotai.atom((get) => {\n  const blockData = get(this.blockAtom);\n  const connName = blockData?.meta?.connection;\n  return get(getConnStatusAtom(connName));\n});\n```\n\n## Common Patterns\n\n### Reading Block Metadata\n\n```typescript\nimport { getBlockMetaKeyAtom } from \"@/store/global\";\n\n// In constructor:\nthis.someFlag = getBlockMetaKeyAtom(blockId, \"myview:flag\");\n\n// In component:\nconst flag = useAtomValue(model.someFlag);\n```\n\n### Configuration Overrides\n\nWave has a hierarchical config system (global → connection → block):\n\n```typescript\nimport { getOverrideConfigAtom } from \"@/store/global\";\n\nthis.settingAtom = jotai.atom((get) => {\n  // Checks block meta, then connection config, then global settings\n  return get(getOverrideConfigAtom(this.blockId, \"myview:setting\")) ?? defaultValue;\n});\n```\n\n### Updating Block Metadata\n\n```typescript\nimport { RpcApi } from \"@/app/store/wshclientapi\";\nimport { TabRpcClient } from \"@/app/store/wshrpcutil\";\nimport { WOS } from \"@/store/global\";\n\nawait RpcApi.SetMetaCommand(TabRpcClient, {\n  oref: WOS.makeORef(\"block\", this.blockId),\n  meta: { \"myview:key\": value },\n});\n```\n\n## Additional Resources\n\n- `frontend/app/block/blockframe-header.tsx` - Block header rendering\n- `frontend/app/view/term/term-model.ts` - Complex view example\n- `frontend/app/view/webview/webview.tsx` - Navigation UI example\n- `frontend/types/custom.d.ts` - Type definitions\n",".kilocode/skills/add-rpc/SKILL.md":"---\nname: add-rpc\ndescription: Guide for adding new RPC calls to Wave Terminal. Use when implementing new RPC commands, adding server-client communication methods, or extending the RPC interface with new functionality.\n---\n\n# Adding RPC Calls Guide\n\n## Overview\n\nWave Terminal uses a WebSocket-based RPC (Remote Procedure Call) system for communication between different components. The RPC system allows the frontend, backend, electron main process, remote servers, and terminal blocks to communicate with each other through well-defined commands.\n\nThis guide covers how to add a new RPC command to the system.\n\n## Key Files\n\n- `pkg/wshrpc/wshrpctypes.go` - RPC interface and type definitions\n- `pkg/wshrpc/wshserver/wshserver.go` - Main server implementation (most common)\n- `emain/emain-wsh.ts` - Electron main process implementation\n- `frontend/app/store/tabrpcclient.ts` - Frontend tab implementation\n- `pkg/wshrpc/wshremote/wshremote.go` - Remote server implementation\n- `frontend/app/view/term/term-wsh.tsx` - Terminal block implementation\n\n## RPC Command Structure\n\nRPC commands in Wave Terminal follow these conventions:\n\n- **Method names** must end with `Command`\n- **First parameter** must be `context.Context`\n- **Remaining parameters** are a regular Go parameter list (zero or more typed args)\n- **Return values** can be either just an error, or one return value plus an error\n- **Streaming commands** return a channel instead of a direct value\n\n## Adding a New RPC Call\n\n### Step 1: Define the Command in the Interface\n\nAdd your command to the `WshRpcInterface` in `pkg/wshrpc/wshrpctypes.go`:\n\n```go\ntype WshRpcInterface interface {\n    // ... existing commands ...\n    \n    // Add your new command\n    YourNewCommand(ctx context.Context, data CommandYourNewData) (*YourNewResponse, error)\n}\n```\n\n**Method Signature Rules:**\n\n- Method name must end with `Command`\n- First parameter must be `ctx context.Context`\n- Remaining parameters are a regular Go parameter list (zero or more)\n- Return either `error` or `(ReturnType, error)`\n- For streaming, return `chan RespOrErrorUnion[T]`\n\n### Step 2: Define Request and Response Types\n\nIf your command needs structured input or output, define types in the same file:\n\n```go\ntype CommandYourNewData struct {\n    FieldOne   string `json:\"fieldone\"`\n    FieldTwo   int    `json:\"fieldtwo\"`\n    SomeId     string `json:\"someid\"`\n}\n\ntype YourNewResponse struct {\n    ResultField string `json:\"resultfield\"`\n    Success     bool   `json:\"success\"`\n}\n```\n\n**Type Naming Conventions:**\n\n- Request types: `Command[Name]Data` (e.g., `CommandGetMetaData`)\n- Response types: `[Name]Response` or `Command[Name]RtnData` (e.g., `CommandResolveIdsRtnData`)\n- Use `json` struct tags with lowercase field names\n- Follow existing patterns in the file for consistency\n\n### Step 3: Generate Bindings\n\nAfter modifying `pkg/wshrpc/wshrpctypes.go`, run code generation to create TypeScript bindings and Go helper code:\n\n```bash\ntask generate\n```\n\nThis command will:\n- Generate TypeScript type definitions in `frontend/types/gotypes.d.ts`\n- Create RPC client bindings\n- Update routing code\n\n**Note:** If generation fails, check that your method signature follows all the rules above.\n\n### Step 4: Implement the Command\n\nChoose where to implement your command based on what it needs to do:\n\n#### A. Main Server Implementation (Most Common)\n\nImplement in `pkg/wshrpc/wshserver/wshserver.go`:\n\n```go\nfunc (ws *WshServer) YourNewCommand(ctx context.Context, data wshrpc.CommandYourNewData) (*wshrpc.YourNewResponse, error) {\n    // Validate input\n    if data.SomeId == \"\" {\n        return nil, fmt.Errorf(\"someid is required\")\n    }\n    \n    // Implement your logic\n    result := doSomething(data)\n    \n    // Return response\n    return &wshrpc.YourNewResponse{\n        ResultField: result,\n        Success:     true,\n    }, nil\n}\n```\n\n**Use main server when:**\n- Accessing the database\n- Managing blocks, tabs, or workspaces\n- Coordinating between components\n- Handling file operations on the main filesystem\n\n#### B. Electron Implementation\n\nImplement in `emain/emain-wsh.ts`:\n\n```typescript\nasync handle_yournew(rh: RpcResponseHelper, data: CommandYourNewData): Promise<YourNewResponse> {\n    // Electron-specific logic\n    const result = await electronAPI.doSomething(data);\n    \n    return {\n        resultfield: result,\n        success: true,\n    };\n}\n```\n\n**Use Electron when:**\n- Accessing native OS features\n- Managing application windows\n- Using Electron APIs (notifications, system tray, etc.)\n- Handling encryption/decryption with safeStorage\n\n#### C. Frontend Tab Implementation\n\nImplement in `frontend/app/store/tabrpcclient.ts`:\n\n```typescript\nasync handle_yournew(rh: RpcResponseHelper, data: CommandYourNewData): Promise<YourNewResponse> {\n    // Access frontend state/models\n    const layoutModel = getLayoutModelForStaticTab();\n    \n    // Implement tab-specific logic\n    const result = layoutModel.doSomething(data);\n    \n    return {\n        resultfield: result,\n        success: true,\n    };\n}\n```\n\n**Use tab client when:**\n- Accessing React state or Jotai atoms\n- Manipulating UI layout\n- Capturing screenshots\n- Reading frontend-only data\n\n#### D. Remote Server Implementation\n\nImplement in `pkg/wshrpc/wshremote/wshremote.go`:\n\n```go\nfunc (impl *ServerImpl) RemoteYourNewCommand(ctx context.Context, data wshrpc.CommandRemoteYourNewData) (*wshrpc.YourNewResponse, error) {\n    // Remote filesystem or process operations\n    result, err := performRemoteOperation(data)\n    if err != nil {\n        return nil, fmt.Errorf(\"remote operation failed: %w\", err)\n    }\n    \n    return &wshrpc.YourNewResponse{\n        ResultField: result,\n        Success:     true,\n    }, nil\n}\n```\n\n**Use remote server when:**\n- Operating on remote filesystems\n- Executing commands on remote hosts\n- Managing remote processes\n- Convention: prefix command name with `Remote` (e.g., `RemoteGetInfoCommand`)\n\n#### E. Terminal Block Implementation\n\nImplement in `frontend/app/view/term/term-wsh.tsx`:\n\n```typescript\nasync handle_yournew(rh: RpcResponseHelper, data: CommandYourNewData): Promise<YourNewResponse> {\n    // Access terminal-specific data\n    const termWrap = this.model.termRef.current;\n    \n    // Implement terminal logic\n    const result = termWrap.doSomething(data);\n    \n    return {\n        resultfield: result,\n        success: true,\n    };\n}\n```\n\n**Use terminal client when:**\n- Accessing terminal buffer/scrollback\n- Managing VDOM contexts\n- Reading terminal-specific state\n- Interacting with xterm.js\n\n## Complete Example: Adding GetWaveInfo Command\n\n### 1. Define Interface\n\nIn `pkg/wshrpc/wshrpctypes.go`:\n\n```go\ntype WshRpcInterface interface {\n    // ... other commands ...\n    WaveInfoCommand(ctx context.Context) (*WaveInfoData, error)\n}\n\ntype WaveInfoData struct {\n    Version      string            `json:\"version\"`\n    BuildTime    string            `json:\"buildtime\"`\n    ConfigPath   string            `json:\"configpath\"`\n    DataPath     string            `json:\"datapath\"`\n}\n```\n\n### 2. Generate Bindings\n\n```bash\ntask generate\n```\n\n### 3. Implement in Main Server\n\nIn `pkg/wshrpc/wshserver/wshserver.go`:\n\n```go\nfunc (ws *WshServer) WaveInfoCommand(ctx context.Context) (*wshrpc.WaveInfoData, error) {\n    return &wshrpc.WaveInfoData{\n        Version:    wavebase.WaveVersion,\n        BuildTime:  wavebase.BuildTime,\n        ConfigPath: wavebase.GetConfigDir(),\n        DataPath:   wavebase.GetWaveDataDir(),\n    }, nil\n}\n```\n\n### 4. Call from Frontend\n\n```typescript\nimport { RpcApi } from \"@/app/store/wshclientapi\";\n\n// Call the RPC\nconst info = await RpcApi.WaveInfoCommand(TabRpcClient);\nconsole.log(\"Wave Version:\", info.version);\n```\n\n## Streaming Commands\n\nFor commands that return data progressively, use channels:\n\n### Define Streaming Interface\n\n```go\ntype WshRpcInterface interface {\n    StreamYourDataCommand(ctx context.Context, request YourDataRequest) chan RespOrErrorUnion[YourDataType]\n}\n```\n\n### Implement Streaming Command\n\n```go\nfunc (ws *WshServer) StreamYourDataCommand(ctx context.Context, request wshrpc.YourDataRequest) chan wshrpc.RespOrErrorUnion[wshrpc.YourDataType] {\n    rtn := make(chan wshrpc.RespOrErrorUnion[wshrpc.YourDataType])\n    \n    go func() {\n        defer close(rtn)\n        defer func() {\n            panichandler.PanicHandler(\"StreamYourDataCommand\", recover())\n        }()\n        \n        // Stream data\n        for i := 0; i < 10; i++ {\n            select {\n            case <-ctx.Done():\n                return\n            default:\n                rtn <- wshrpc.RespOrErrorUnion[wshrpc.YourDataType]{\n                    Response: wshrpc.YourDataType{\n                        Value: i,\n                    },\n                }\n                time.Sleep(100 * time.Millisecond)\n            }\n        }\n    }()\n    \n    return rtn\n}\n```\n\n## Best Practices\n\n1. **Validation First**: Always validate input parameters at the start of your implementation\n\n2. **Descriptive Names**: Use clear, action-oriented command names (e.g., `GetFullConfigCommand`, not `ConfigCommand`)\n\n3. **Error Handling**: Return descriptive errors with context:\n   ```go\n   return nil, fmt.Errorf(\"error creating block: %w\", err)\n   ```\n\n4. **Context Awareness**: Respect context cancellation for long-running operations:\n   ```go\n   select {\n   case <-ctx.Done():\n       return ctx.Err()\n   default:\n       // continue\n   }\n   ```\n\n5. **Consistent Types**: Follow existing naming patterns for request/response types\n\n6. **JSON Tags**: Always use lowercase JSON tags matching frontend conventions\n\n7. **Documentation**: Add comments explaining complex commands or special behaviors\n\n8. **Type Safety**: Leverage TypeScript generation - your types will be checked on both ends\n\n9. **Panic Recovery**: Use `panichandler.PanicHandler` in goroutines to prevent crashes\n\n10. **Route Awareness**: For multi-route scenarios, use `wshutil.GetRpcSourceFromContext(ctx)` to identify callers\n\n## Common Command Patterns\n\n### Simple Query\n\n```go\nfunc (ws *WshServer) GetSomethingCommand(ctx context.Context, id string) (*Something, error) {\n    obj, err := wstore.DBGet[*Something](ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"error getting something: %w\", err)\n    }\n    return obj, nil\n}\n```\n\n### Mutation with Updates\n\n```go\nfunc (ws *WshServer) UpdateSomethingCommand(ctx context.Context, data wshrpc.CommandUpdateData) error {\n    ctx = waveobj.ContextWithUpdates(ctx)\n    \n    // Make changes\n    err := wstore.UpdateObject(ctx, data.ORef, data.Updates)\n    if err != nil {\n        return fmt.Errorf(\"error updating: %w\", err)\n    }\n    \n    // Broadcast updates\n    updates := waveobj.ContextGetUpdatesRtn(ctx)\n    wps.Broker.SendUpdateEvents(updates)\n    \n    return nil\n}\n```\n\n### Command with Side Effects\n\n```go\nfunc (ws *WshServer) DoActionCommand(ctx context.Context, data wshrpc.CommandActionData) error {\n    // Perform action\n    result, err := performAction(data)\n    if err != nil {\n        return err\n    }\n    \n    // Publish event about the action\n    go func() {\n        wps.Broker.Publish(wps.WaveEvent{\n            Event: wps.Event_ActionComplete,\n            Data:  result,\n        })\n    }()\n    \n    return nil\n}\n```\n\n## Troubleshooting\n\n### Command Not Found\n\n- Ensure method name ends with `Command`\n- Verify you ran `task generate`\n- Check that the interface is in `WshRpcInterface`\n\n### Type Mismatch Errors\n\n- Run `task generate` after changing types\n- Ensure JSON tags are lowercase\n- Verify TypeScript code is using generated types\n\n### Command Times Out\n\n- Check for blocking operations\n- Ensure context is passed through\n- Consider using a streaming command for long operations\n\n### Routing Issues\n\n- For remote commands, ensure they're implemented in correct location\n- Check route configuration in RpcContext\n- Verify authentication for secured routes\n\n## Quick Reference\n\nWhen adding a new RPC command:\n\n- [ ] Add method to `WshRpcInterface` in `pkg/wshrpc/wshrpctypes.go` (must end with `Command`)\n- [ ] Define request/response types with JSON tags (if needed)\n- [ ] Run `task generate` to create bindings\n- [ ] Implement in appropriate location:\n  - [ ] `wshserver.go` for main server (most common)\n  - [ ] `emain-wsh.ts` for Electron\n  - [ ] `tabrpcclient.ts` for frontend\n  - [ ] `wshremote.go` for remote (prefix with `Remote`)\n  - [ ] `term-wsh.tsx` for terminal\n- [ ] Add input validation\n- [ ] Handle errors with context\n- [ ] Test the command end-to-end\n\n## Related Documentation\n\n- **WPS Events**: See the `wps-events` skill - Publishing events from RPC commands\n",".kilocode/skills/add-wshcmd/SKILL.md":"---\nname: add-wshcmd\ndescription: Guide for adding new wsh commands to Wave Terminal. Use when implementing new CLI commands, adding command-line functionality, or extending the wsh command interface.\n---\n\n# Adding a New wsh Command to Wave Terminal\n\nThis guide explains how to add a new command to the `wsh` CLI tool.\n\n## wsh Command System Overview\n\nWave Terminal's `wsh` command provides CLI access to Wave Terminal features. The system uses:\n\n1. **Cobra Framework** - CLI command structure and parsing\n2. **Command Files** - Individual command implementations in `cmd/wsh/cmd/wshcmd-*.go`\n3. **RPC Client** - Communication with Wave Terminal backend via `RpcClient`\n4. **Activity Tracking** - Telemetry for command usage analytics\n5. **Documentation** - User-facing docs in `docs/docs/wsh-reference.mdx`\n\nCommands are registered in their `init()` functions and execute through the Cobra framework.\n\n## Step-by-Step Guide\n\n### Step 1: Create Command File\n\nCreate a new file in `cmd/wsh/cmd/` named `wshcmd-[commandname].go`:\n\n```go\n// Copyright 2025, Command Line Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n    \"fmt\"\n\n    \"github.com/spf13/cobra\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient\"\n)\n\nvar myCommandCmd = &cobra.Command{\n    Use:   \"mycommand [args]\",\n    Short: \"Brief description of what this command does\",\n    Long: `Detailed description of the command.\nCan include multiple lines and examples of usage.`,\n    RunE:                  myCommandRun,\n    PreRunE:               preRunSetupRpcClient,  // Include if command needs RPC\n    DisableFlagsInUseLine: true,\n}\n\n// Flag variables\nvar (\n    myCommandFlagExample string\n    myCommandFlagVerbose bool\n)\n\nfunc init() {\n    // Add command to root\n    rootCmd.AddCommand(myCommandCmd)\n    \n    // Define flags\n    myCommandCmd.Flags().StringVarP(&myCommandFlagExample, \"example\", \"e\", \"\", \"example flag description\")\n    myCommandCmd.Flags().BoolVarP(&myCommandFlagVerbose, \"verbose\", \"v\", false, \"enable verbose output\")\n}\n\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    // Always track activity for telemetry\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Validate arguments\n    if len(args) == 0 {\n        OutputHelpMessage(cmd)\n        return fmt.Errorf(\"requires at least one argument\")\n    }\n    \n    // Command implementation\n    fmt.Printf(\"Command executed successfully\\n\")\n    return nil\n}\n```\n\n**File Naming Convention:**\n- Use `wshcmd-[commandname].go` format\n- Use lowercase, hyphenated names for multi-word commands\n- Examples: `wshcmd-getvar.go`, `wshcmd-setmeta.go`, `wshcmd-ai.go`\n\n### Step 2: Command Structure\n\n#### Basic Command Structure\n\n```go\nvar myCommandCmd = &cobra.Command{\n    Use:   \"mycommand [required] [optional...]\",\n    Short: \"One-line description (shown in help)\",\n    Long:  `Detailed multi-line description`,\n    \n    // Argument validation\n    Args:    cobra.MinimumNArgs(1),  // Or cobra.ExactArgs(1), cobra.NoArgs, etc.\n    \n    // Execution function\n    RunE:    myCommandRun,\n    \n    // Pre-execution setup (if needed)\n    PreRunE: preRunSetupRpcClient,  // Sets up RPC client for backend communication\n    \n    // Example usage (optional)\n    Example: \"  wsh mycommand foo\\n  wsh mycommand --flag bar\",\n    \n    // Disable flag notation in usage line\n    DisableFlagsInUseLine: true,\n}\n```\n\n**Key Fields:**\n- `Use`: Command name and argument pattern\n- `Short`: Brief description for command list\n- `Long`: Detailed description shown in help\n- `Args`: Argument validator (optional)\n- `RunE`: Main execution function (returns error)\n- `PreRunE`: Setup function that runs before `RunE`\n- `Example`: Usage examples (optional)\n- `DisableFlagsInUseLine`: Clean up help display\n\n#### When to Use PreRunE\n\nInclude `PreRunE: preRunSetupRpcClient` if your command:\n- Communicates with the Wave Terminal backend\n- Needs access to `RpcClient` \n- Requires JWT authentication (WAVETERM_JWT env var)\n- Makes RPC calls via `wshclient.*Command()` functions\n\n**Don't include PreRunE** for commands that:\n- Only manipulate local state\n- Don't need backend communication\n- Are purely informational/local operations\n\n### Step 3: Implement Command Logic\n\n#### Command Function Pattern\n\n```go\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    // Step 1: Always track activity (for telemetry)\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Step 2: Validate arguments and flags\n    if len(args) != 1 {\n        OutputHelpMessage(cmd)\n        return fmt.Errorf(\"requires exactly one argument\")\n    }\n    \n    // Step 3: Parse/prepare data\n    targetArg := args[0]\n    \n    // Step 4: Make RPC call if needed\n    result, err := wshclient.SomeCommand(RpcClient, wshrpc.CommandSomeData{\n        Field: targetArg,\n    }, &wshrpc.RpcOpts{Timeout: 2000})\n    if err != nil {\n        return fmt.Errorf(\"executing command: %w\", err)\n    }\n    \n    // Step 5: Output results\n    fmt.Printf(\"Result: %s\\n\", result)\n    return nil\n}\n```\n\n**Important Patterns:**\n\n1. **Activity Tracking**: Always include deferred `sendActivity()` call\n   ```go\n   defer func() {\n       sendActivity(\"commandname\", rtnErr == nil)\n   }()\n   ```\n\n2. **Error Handling**: Return errors, don't call `os.Exit()`\n   ```go\n   if err != nil {\n       return fmt.Errorf(\"context: %w\", err)\n   }\n   ```\n\n3. **Output**: Use standard `fmt` package for output\n   ```go\n   fmt.Printf(\"Success message\\n\")\n   fmt.Fprintf(os.Stderr, \"Error message\\n\")\n   ```\n\n4. **Help Messages**: Show help when arguments are invalid\n   ```go\n   if len(args) == 0 {\n       OutputHelpMessage(cmd)\n       return fmt.Errorf(\"requires arguments\")\n   }\n   ```\n\n5. **Exit Codes**: Set custom exit code via `WshExitCode`\n   ```go\n   if notFound {\n       WshExitCode = 1\n       return nil  // Don't return error, just set exit code\n   }\n   ```\n\n### Step 4: Define Flags\n\nAdd flags in the `init()` function:\n\n```go\nvar (\n    // Declare flag variables at package level\n    myCommandFlagString string\n    myCommandFlagBool   bool\n    myCommandFlagInt    int\n)\n\nfunc init() {\n    rootCmd.AddCommand(myCommandCmd)\n    \n    // String flag with short version\n    myCommandCmd.Flags().StringVarP(&myCommandFlagString, \"name\", \"n\", \"default\", \"description\")\n    \n    // Boolean flag\n    myCommandCmd.Flags().BoolVarP(&myCommandFlagBool, \"verbose\", \"v\", false, \"enable verbose\")\n    \n    // Integer flag\n    myCommandCmd.Flags().IntVar(&myCommandFlagInt, \"count\", 10, \"set count\")\n    \n    // Flag without short version\n    myCommandCmd.Flags().StringVar(&myCommandFlagString, \"longname\", \"\", \"description\")\n}\n```\n\n**Flag Types:**\n- `StringVar/StringVarP` - String values\n- `BoolVar/BoolVarP` - Boolean flags\n- `IntVar/IntVarP` - Integer values\n- The `P` suffix versions include a short flag name\n\n**Flag Naming:**\n- Use camelCase for variable names: `myCommandFlagName`\n- Use kebab-case for flag names: `--flag-name`\n- Prefix variable names with command name for clarity\n\n### Step 5: Working with Block Arguments\n\nMany commands operate on blocks. Use the standard block resolution pattern:\n\n```go\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Resolve block using the -b/--block flag\n    fullORef, err := resolveBlockArg()\n    if err != nil {\n        return err\n    }\n    \n    // Use the blockid in RPC call\n    err = wshclient.SomeCommand(RpcClient, wshrpc.CommandSomeData{\n        BlockId: fullORef.OID,\n    }, &wshrpc.RpcOpts{Timeout: 2000})\n    if err != nil {\n        return fmt.Errorf(\"command failed: %w\", err)\n    }\n    \n    return nil\n}\n```\n\n**Block Resolution:**\n- The `-b/--block` flag is defined globally in `wshcmd-root.go`\n- `resolveBlockArg()` resolves the block argument to a full ORef\n- Supports: `this`, `tab`, full UUIDs, 8-char prefixes, block numbers\n- Default is `\"this\"` (current block)\n\n**Alternative: Manual Block Resolution**\n\n```go\n// Get tab ID from environment\ntabId := os.Getenv(\"WAVETERM_TABID\")\nif tabId == \"\" {\n    return fmt.Errorf(\"WAVETERM_TABID not set\")\n}\n\n// Create route for tab-level operations\nroute := wshutil.MakeTabRouteId(tabId)\n\n// Use route in RPC call\nerr := wshclient.SomeCommand(RpcClient, commandData, &wshrpc.RpcOpts{\n    Route:   route,\n    Timeout: 2000,\n})\n```\n\n### Step 6: Making RPC Calls\n\nUse the `wshclient` package to make RPC calls:\n\n```go\nimport (\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient\"\n)\n\n// Simple RPC call\nresult, err := wshclient.GetMetaCommand(RpcClient, wshrpc.CommandGetMetaData{\n    ORef: *fullORef,\n}, &wshrpc.RpcOpts{Timeout: 2000})\nif err != nil {\n    return fmt.Errorf(\"getting metadata: %w\", err)\n}\n\n// RPC call with routing\nerr := wshclient.SetMetaCommand(RpcClient, wshrpc.CommandSetMetaData{\n    ORef: *fullORef,\n    Meta: metaMap,\n}, &wshrpc.RpcOpts{\n    Route:   route,\n    Timeout: 5000,\n})\nif err != nil {\n    return fmt.Errorf(\"setting metadata: %w\", err)\n}\n```\n\n**RPC Options:**\n- `Timeout`: Request timeout in milliseconds (typically 2000-5000)\n- `Route`: Route ID for targeting specific components\n- Available routes: `wshutil.ControlRoute`, `wshutil.MakeTabRouteId(tabId)`\n\n### Step 7: Add Documentation\n\nAdd your command to `docs/docs/wsh-reference.mdx`:\n\n````markdown\n## mycommand\n\nBrief description of what the command does.\n\n```sh\nwsh mycommand [args] [flags]\n```\n\nDetailed explanation of the command's purpose and behavior.\n\nFlags:\n- `-n, --name <value>` - description of this flag\n- `-v, --verbose` - enable verbose output\n- `-b, --block <blockid>` - specify target block (default: current block)\n\nExamples:\n\n```sh\n# Basic usage\nwsh mycommand arg1\n\n# With flags\nwsh mycommand --name value arg1\n\n# With block targeting\nwsh mycommand -b 2 arg1\n\n# Complex example\nwsh mycommand -v --name \"example\" arg1 arg2\n```\n\nAdditional notes, tips, or warnings about the command.\n\n---\n````\n\n**Documentation Guidelines:**\n- Place in alphabetical order with other commands\n- Include command signature with argument pattern\n- List all flags with short and long versions\n- Provide practical examples (at least 3-5)\n- Explain common use cases and patterns\n- Add tips or warnings if relevant\n- Use `---` separator between commands\n\n### Step 8: Test Your Command\n\nBuild and test the command:\n\n```bash\n# Build wsh\ntask build:wsh\n\n# Or build everything\ntask build\n\n# Test the command\n./bin/wsh/wsh mycommand --help\n./bin/wsh/wsh mycommand arg1 arg2\n```\n\n**Testing Checklist:**\n- [ ] Help message displays correctly\n- [ ] Required arguments validated\n- [ ] Flags work as expected\n- [ ] Error messages are clear\n- [ ] Success cases work correctly\n- [ ] RPC calls complete successfully\n- [ ] Output is formatted correctly\n\n## Complete Examples\n\n### Example 1: Simple Command with No RPC\n\n**Use case:** A command that prints Wave Terminal version info\n\n#### Command File (`cmd/wsh/cmd/wshcmd-version.go`)\n\n```go\n// Copyright 2025, Command Line Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n    \"github.com/spf13/cobra\"\n    \"github.com/wavetermdev/waveterm/pkg/wavebase\"\n)\n\nvar versionCmd = &cobra.Command{\n    Use:   \"version\",\n    Short: \"Print Wave Terminal version\",\n    RunE:  versionRun,\n}\n\nfunc init() {\n    rootCmd.AddCommand(versionCmd)\n}\n\nfunc versionRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"version\", rtnErr == nil)\n    }()\n    \n    fmt.Printf(\"Wave Terminal %s\\n\", wavebase.WaveVersion)\n    return nil\n}\n```\n\n#### Documentation\n\n````markdown\n## version\n\nPrint the current Wave Terminal version.\n\n```sh\nwsh version\n```\n\nExamples:\n\n```sh\n# Print version\nwsh version\n```\n````\n\n### Example 2: Command with Flags and RPC\n\n**Use case:** A command to update block title\n\n#### Command File (`cmd/wsh/cmd/wshcmd-settitle.go`)\n\n```go\n// Copyright 2025, Command Line Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n    \"fmt\"\n\n    \"github.com/spf13/cobra\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient\"\n)\n\nvar setTitleCmd = &cobra.Command{\n    Use:   \"settitle [title]\",\n    Short: \"Set block title\",\n    Long:  `Set the title for the current or specified block.`,\n    Args:  cobra.ExactArgs(1),\n    RunE:  setTitleRun,\n    PreRunE: preRunSetupRpcClient,\n    DisableFlagsInUseLine: true,\n}\n\nvar setTitleIcon string\n\nfunc init() {\n    rootCmd.AddCommand(setTitleCmd)\n    setTitleCmd.Flags().StringVarP(&setTitleIcon, \"icon\", \"i\", \"\", \"set block icon\")\n}\n\nfunc setTitleRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"settitle\", rtnErr == nil)\n    }()\n    \n    title := args[0]\n    \n    // Resolve block\n    fullORef, err := resolveBlockArg()\n    if err != nil {\n        return err\n    }\n    \n    // Build metadata map\n    meta := make(map[string]interface{})\n    meta[\"title\"] = title\n    if setTitleIcon != \"\" {\n        meta[\"icon\"] = setTitleIcon\n    }\n    \n    // Make RPC call\n    err = wshclient.SetMetaCommand(RpcClient, wshrpc.CommandSetMetaData{\n        ORef: *fullORef,\n        Meta: meta,\n    }, &wshrpc.RpcOpts{Timeout: 2000})\n    if err != nil {\n        return fmt.Errorf(\"setting title: %w\", err)\n    }\n    \n    fmt.Printf(\"title updated\\n\")\n    return nil\n}\n```\n\n#### Documentation\n\n````markdown\n## settitle\n\nSet the title for a block.\n\n```sh\nwsh settitle [title]\n```\n\nUpdate the display title for the current or specified block. Optionally set an icon as well.\n\nFlags:\n- `-i, --icon <icon>` - set block icon along with title\n- `-b, --block <blockid>` - specify target block (default: current block)\n\nExamples:\n\n```sh\n# Set title for current block\nwsh settitle \"My Terminal\"\n\n# Set title and icon\nwsh settitle --icon \"terminal\" \"Development Shell\"\n\n# Set title for specific block\nwsh settitle -b 2 \"Build Output\"\n```\n````\n\n### Example 3: Subcommands\n\n**Use case:** Command with multiple subcommands (like `wsh conn`)\n\n#### Command File (`cmd/wsh/cmd/wshcmd-mygroup.go`)\n\n```go\n// Copyright 2025, Command Line Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n    \"fmt\"\n\n    \"github.com/spf13/cobra\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient\"\n)\n\nvar myGroupCmd = &cobra.Command{\n    Use:   \"mygroup\",\n    Short: \"Manage something\",\n}\n\nvar myGroupListCmd = &cobra.Command{\n    Use:   \"list\",\n    Short: \"List items\",\n    RunE:  myGroupListRun,\n    PreRunE: preRunSetupRpcClient,\n}\n\nvar myGroupAddCmd = &cobra.Command{\n    Use:   \"add [name]\",\n    Short: \"Add an item\",\n    Args:  cobra.ExactArgs(1),\n    RunE:  myGroupAddRun,\n    PreRunE: preRunSetupRpcClient,\n}\n\nfunc init() {\n    // Add parent command\n    rootCmd.AddCommand(myGroupCmd)\n    \n    // Add subcommands\n    myGroupCmd.AddCommand(myGroupListCmd)\n    myGroupCmd.AddCommand(myGroupAddCmd)\n}\n\nfunc myGroupListRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mygroup:list\", rtnErr == nil)\n    }()\n    \n    // Implementation\n    fmt.Printf(\"Listing items...\\n\")\n    return nil\n}\n\nfunc myGroupAddRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mygroup:add\", rtnErr == nil)\n    }()\n    \n    name := args[0]\n    fmt.Printf(\"Adding item: %s\\n\", name)\n    return nil\n}\n```\n\n#### Documentation\n\n````markdown\n## mygroup\n\nManage something with subcommands.\n\n### list\n\nList all items.\n\n```sh\nwsh mygroup list\n```\n\n### add\n\nAdd a new item.\n\n```sh\nwsh mygroup add [name]\n```\n\nExamples:\n\n```sh\n# List items\nwsh mygroup list\n\n# Add an item\nwsh mygroup add \"new-item\"\n```\n````\n\n## Common Patterns\n\n### Reading from Stdin\n\n```go\nimport \"io\"\n\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Check if reading from stdin (using \"-\" convention)\n    var data []byte\n    var err error\n    \n    if len(args) > 0 && args[0] == \"-\" {\n        data, err = io.ReadAll(os.Stdin)\n        if err != nil {\n            return fmt.Errorf(\"reading stdin: %w\", err)\n        }\n    } else {\n        // Read from file or other source\n        data, err = os.ReadFile(args[0])\n        if err != nil {\n            return fmt.Errorf(\"reading file: %w\", err)\n        }\n    }\n    \n    // Process data\n    fmt.Printf(\"Read %d bytes\\n\", len(data))\n    return nil\n}\n```\n\n### JSON File Input\n\n```go\nimport (\n    \"encoding/json\"\n    \"io\"\n)\n\nfunc loadJSONFile(filepath string) (map[string]interface{}, error) {\n    var data []byte\n    var err error\n    \n    if filepath == \"-\" {\n        data, err = io.ReadAll(os.Stdin)\n        if err != nil {\n            return nil, fmt.Errorf(\"reading stdin: %w\", err)\n        }\n    } else {\n        data, err = os.ReadFile(filepath)\n        if err != nil {\n            return nil, fmt.Errorf(\"reading file: %w\", err)\n        }\n    }\n    \n    var result map[string]interface{}\n    if err := json.Unmarshal(data, &result); err != nil {\n        return nil, fmt.Errorf(\"parsing JSON: %w\", err)\n    }\n    \n    return result, nil\n}\n```\n\n### Conditional Output (TTY Detection)\n\n```go\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    isTty := getIsTty()\n    \n    // Output value\n    fmt.Printf(\"%s\", value)\n    \n    // Add newline only if TTY (for better piping experience)\n    if isTty {\n        fmt.Printf(\"\\n\")\n    }\n    \n    return nil\n}\n```\n\n### Environment Variable Access\n\n```go\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Get block ID from environment\n    blockId := os.Getenv(\"WAVETERM_BLOCKID\")\n    if blockId == \"\" {\n        return fmt.Errorf(\"WAVETERM_BLOCKID not set\")\n    }\n    \n    // Get tab ID from environment\n    tabId := os.Getenv(\"WAVETERM_TABID\")\n    if tabId == \"\" {\n        return fmt.Errorf(\"WAVETERM_TABID not set\")\n    }\n    \n    fmt.Printf(\"Block: %s, Tab: %s\\n\", blockId, tabId)\n    return nil\n}\n```\n\n## Best Practices\n\n### Command Design\n\n1. **Single Responsibility**: Each command should do one thing well\n2. **Composable**: Design commands to work with pipes and other commands\n3. **Consistent**: Follow existing wsh command patterns and conventions\n4. **Documented**: Provide clear help text and examples\n\n### Error Handling\n\n1. **Context**: Wrap errors with context using `fmt.Errorf(\"context: %w\", err)`\n2. **User-Friendly**: Make error messages clear and actionable\n3. **No Panics**: Return errors instead of calling `os.Exit()` or `panic()`\n4. **Exit Codes**: Use `WshExitCode` for custom exit codes\n\n### Output\n\n1. **Structured**: Use consistent formatting for output\n2. **Quiet by Default**: Only output what's necessary\n3. **Verbose Flag**: Optionally provide `-v` for detailed output\n4. **Stderr for Errors**: Use `fmt.Fprintf(os.Stderr, ...)` for error messages\n\n### Flags\n\n1. **Short Versions**: Provide `-x` short versions for common flags\n2. **Sensible Defaults**: Choose defaults that work for most users\n3. **Boolean Flags**: Use for on/off options\n4. **String Flags**: Use for values that need user input\n\n### RPC Calls\n\n1. **Timeouts**: Always specify reasonable timeouts\n2. **Error Context**: Wrap RPC errors with operation context\n3. **Retries**: Don't retry automatically; let user retry command\n4. **Routes**: Use appropriate routes for different operations\n\n## Common Pitfalls\n\n### 1. Forgetting Activity Tracking\n\n**Problem**: Command usage not tracked in telemetry\n\n**Solution**: Always include deferred `sendActivity()` call:\n```go\ndefer func() {\n    sendActivity(\"commandname\", rtnErr == nil)\n}()\n```\n\n### 2. Using os.Exit() Instead of Returning Error\n\n**Problem**: Breaks defer statements and cleanup\n\n**Solution**: Return errors from RunE function:\n```go\n// Bad\nif err != nil {\n    fmt.Fprintf(os.Stderr, \"error: %v\\n\", err)\n    os.Exit(1)\n}\n\n// Good\nif err != nil {\n    return fmt.Errorf(\"operation failed: %w\", err)\n}\n```\n\n### 3. Not Validating Arguments\n\n**Problem**: Command crashes with nil pointer or index out of range\n\n**Solution**: Validate arguments early and show help:\n```go\nif len(args) == 0 {\n    OutputHelpMessage(cmd)\n    return fmt.Errorf(\"requires at least one argument\")\n}\n```\n\n### 4. Forgetting to Add to init()\n\n**Problem**: Command not available when running wsh\n\n**Solution**: Always add command in `init()` function:\n```go\nfunc init() {\n    rootCmd.AddCommand(myCommandCmd)\n}\n```\n\n### 5. Inconsistent Output\n\n**Problem**: Inconsistent use of output methods\n\n**Solution**: Use standard `fmt` package functions:\n```go\n// For stdout\nfmt.Printf(\"output\\n\")\n\n// For stderr\nfmt.Fprintf(os.Stderr, \"error message\\n\")\n```\n\n## Quick Reference Checklist\n\nWhen adding a new wsh command:\n\n- [ ] Create `cmd/wsh/cmd/wshcmd-[commandname].go`\n- [ ] Define command struct with Use, Short, Long descriptions\n- [ ] Add `PreRunE: preRunSetupRpcClient` if using RPC\n- [ ] Implement command function with activity tracking\n- [ ] Add command to `rootCmd` in `init()` function\n- [ ] Define flags in `init()` function if needed\n- [ ] Add documentation to `docs/docs/wsh-reference.mdx`\n- [ ] Build and test: `task build:wsh`\n- [ ] Test help: `wsh [commandname] --help`\n- [ ] Test all flag combinations\n- [ ] Test error cases\n\n## Related Files\n\n- **Root Command**: `cmd/wsh/cmd/wshcmd-root.go` - Main command setup and utilities\n- **RPC Client**: `pkg/wshrpc/wshclient/` - Client functions for RPC calls\n- **RPC Types**: `pkg/wshrpc/wshrpctypes.go` - RPC request/response data structures\n- **Documentation**: `docs/docs/wsh-reference.mdx` - User-facing command reference\n- **Examples**: `cmd/wsh/cmd/wshcmd-*.go` - Existing command implementations\n",".kilocode/rules/overview.md":"# Wave Terminal - High Level Architecture Overview\n\n## Project Description\n\nWave Terminal is an open-source AI-native terminal built for seamless workflows. It's an Electron application that serves as a command line terminal host (it hosts CLI applications rather than running inside a CLI). The application combines a React frontend with a Go backend server to provide a modern terminal experience with advanced features.\n\n## Top-Level Directory Structure\n\n```\nwaveterm/\n├── emain/              # Electron main process code\n├── frontend/           # React application (renderer process)\n├── cmd/                # Go command-line applications\n├── pkg/                # Go packages/modules\n├── db/                 # Database migrations\n├── docs/               # Documentation (Docusaurus)\n├── build/              # Build configuration and assets\n├── assets/             # Application assets (icons, images)\n├── public/             # Static public assets\n├── tests/              # Test files\n├── .github/            # GitHub workflows and configuration\n└── Configuration files (package.json, tsconfig.json, etc.)\n```\n\n## Architecture Components\n\n### 1. Electron Main Process (`emain/`)\n\nThe Electron main process handles the native desktop application layer:\n\n**Key Files:**\n\n- [`emain.ts`](emain/emain.ts) - Main entry point, application lifecycle management\n- [`emain-window.ts`](emain/emain-window.ts) - Window management (`WaveBrowserWindow` class)\n- [`emain-tabview.ts`](emain/emain-tabview.ts) - Tab view management (`WaveTabView` class)\n- [`emain-wavesrv.ts`](emain/emain-wavesrv.ts) - Go backend server integration\n- [`emain-wsh.ts`](emain/emain-wsh.ts) - WSH (Wave Shell) client integration\n- [`emain-ipc.ts`](emain/emain-ipc.ts) - IPC handlers for frontend ↔ main process communication\n- [`emain-menu.ts`](emain/emain-menu.ts) - Application menu system\n- [`updater.ts`](emain/updater.ts) - Auto-update functionality\n- [`preload.ts`](emain/preload.ts) - Preload script for renderer security\n- [`preload-webview.ts`](emain/preload-webview.ts) - Webview preload script\n\n### 2. Frontend React Application (`frontend/`)\n\nThe React application runs in the Electron renderer process:\n\n**Structure:**\n\n```\nfrontend/\n├── app/                # Main application code\n│   ├── app.tsx         # Root App component\n│   ├── aipanel/        # AI panel UI\n│   ├── block/          # Block-based UI components\n│   ├── element/        # Reusable UI elements\n│   ├── hook/           # Custom React hooks\n│   ├── modals/         # Modal components\n│   ├── store/          # State management (Jotai)\n│   ├── tab/            # Tab components\n│   ├── view/           # Different view types\n│   │   ├── codeeditor/ # Code editor (Monaco)\n│   │   ├── preview/    # File preview\n│   │   ├── sysinfo/    # System info view\n│   │   ├── term/       # Terminal view\n│   │   ├── tsunami/    # Tsunami builder view\n│   │   ├── vdom/       # Virtual DOM view\n│   │   ├── waveai/     # AI chat integration\n│   │   ├── waveconfig/ # Config editor view\n│   │   └── webview/    # Web view\n│   └── workspace/      # Workspace management\n├── builder/            # Builder app entry\n├── layout/             # Layout system\n├── preview/            # Standalone preview renderer\n├── types/              # TypeScript type definitions\n└── util/               # Utility functions\n```\n\n**Key Technologies:**\n\n- Electron (desktop application shell)\n- React 19 with TypeScript\n- Jotai for state management\n- Monaco Editor for code editing\n- XTerm.js for terminal emulation\n- Tailwind CSS v4 for styling\n- SCSS for additional styling (deprecated, new components should use Tailwind)\n- Vite / electron-vite for bundling\n- Task (Taskfile.yml) for build and code generation commands\n\n### 3. Go Backend Server (`cmd/server/`)\n\nThe Go backend server handles all heavy lifting operations:\n\n**Entry Point:** [`main-server.go`](cmd/server/main-server.go)\n\n### 4. Go Packages (`pkg/`)\n\nThe Go codebase is organized into modular packages:\n\n**Key Packages:**\n\n- `wstore/` - Database and storage layer\n- `wconfig/` - Configuration management\n- `wcore/` - Core business logic\n- `wshrpc/` - RPC communication system\n- `wshutil/` - WSH (Wave Shell) utilities\n- `blockcontroller/` - Block execution management\n- `remote/` - Remote connection handling\n- `filestore/` - File storage system\n- `web/` - Web server and WebSocket handling\n- `telemetry/` - Usage analytics and telemetry\n- `waveobj/` - Core data objects\n- `service/` - Service layer\n- `wps/` - Wave PubSub event system\n- `waveai/` - AI functionality\n- `shellexec/` - Shell execution\n- `util/` - Common utilities\n\n### 5. Command Line Tools (`cmd/`)\n\nKey Go command-line utilities:\n\n- `wsh/` - Wave Shell command-line tool\n- `server/` - Main backend server\n- `generatego/` - Code generation\n- `generateschema/` - Schema generation\n- `generatets/` - TypeScript generation\n\n## Communication Architecture\n\nThe core communication system is built around the **WSH RPC (Wave Shell RPC)** system, which provides a unified interface for all inter-process communication: frontend ↔ Go backend, Electron main process ↔ backend, and backend ↔ remote systems (SSH, WSL).\n\n### WSH RPC System (`pkg/wshrpc/`)\n\nThe WSH RPC system is the backbone of Wave Terminal's communication architecture:\n\n**Key Components:**\n\n- [`wshrpctypes.go`](pkg/wshrpc/wshrpctypes.go) - Core RPC interface and type definitions (source of truth for all RPC commands)\n- [`wshserver/`](pkg/wshrpc/wshserver/) - Server-side RPC implementation\n- [`wshremote/`](pkg/wshrpc/wshremote/) - Remote connection handling\n- [`wshclient.go`](pkg/wshrpc/wshclient.go) - Go client for making RPC calls\n- [`frontend/app/store/wshclientapi.ts`](frontend/app/store/wshclientapi.ts) - Generated TypeScript RPC client\n\n**Routing:** Callers address RPC calls using _routes_ (e.g. a block ID, connection name, or `\"waveapp\"`) rather than caring about the underlying transport. The RPC layer resolves the route to the correct transport (WebSocket, Unix socket, SSH tunnel, stdio) automatically. This means the same RPC interface works whether the target is local or a remote SSH connection.\n\n## Development Notes\n\n- **Build commands** - Use `task` (Taskfile.yml) for all build, generate, and packaging commands\n- **Code generation** - Run `task generate` after modifying Go types in `pkg/wshrpc/wshrpctypes.go`, `pkg/wconfig/settingsconfig.go`, or `pkg/waveobj/wtypemeta.go`\n- **Testing** - Vitest for frontend unit tests; standard `go test` for Go packages\n- **Database migrations** - SQL migration files in `db/migrations-wstore/` and `db/migrations-filestore/`\n- **Documentation** - Docusaurus site in `docs/`\n",".kilocode/rules/rules.md":"Wave Terminal is a modern terminal which provides graphical blocks, dynamic layout, workspaces, and SSH connection management. It is cross platform and built on electron.\n\n### Project Structure\n\nIt has a TypeScript/React frontend and a Go backend. They talk together over `wshrpc` a custom RPC protocol that is implemented over websocket (and domain sockets).\n\n### Coding Guidelines\n\n- **Go Conventions**:\n  - Don't use custom enum types in Go. Instead, use string constants (e.g., `const StatusRunning = \"running\"` rather than creating a custom type like `type Status string`).\n  - Use string constants for status values, packet types, and other string-based enumerations.\n  - in Go code, prefer using Printf() vs Println()\n  - use \"Make\" as opposed to \"New\" for struct initialization func names\n  - in general const decls go at the top of the file (before types and functions)\n  - NEVER run `go build` (especially in weird sub-package directories). we can tell if everything compiles by seeing there are no problems/errors.\n- **Synchronization**:\n  - Always prefer to use the `lock.Lock(); defer lock.Unlock()` pattern for synchronization if possible\n  - Avoid inline lock/unlock pairs - instead create helper functions that use the defer pattern\n  - When accessing shared data structures (maps, slices, etc.), ensure proper locking\n  - Example: Instead of `gc.lock.Lock(); gc.map[key]++; gc.lock.Unlock()`, create a helper function like `getNextValue(key string) int { gc.lock.Lock(); defer gc.lock.Unlock(); gc.map[key]++; return gc.map[key] }`\n- **TypeScript Imports**:\n  - Use `@/...` for imports from different parts of the project (configured in `tsconfig.json` as `\"@/*\": [\"frontend/*\"]`).\n  - Prefer relative imports (`\"./name\"`) only within the same directory.\n  - Use named exports exclusively; avoid default exports. It's acceptable to export functions directly (e.g., React Components).\n  - Our indent is 4 spaces\n- **JSON Field Naming**: All fields must be lowercase, without underscores.\n- **TypeScript Conventions**\n  - **Type Handling**:\n    - In TypeScript we have strict null checks off, so no need to add \"| null\" to all the types.\n    - In TypeScript for Jotai atoms, if we want to write, we need to type the atom as a PrimitiveAtom<Type>\n    - Jotai has a bug with strict null checks off where if you create a null atom, e.g. atom(null) it does not \"type\" correctly. That's no issue, just cast it to the proper PrimitiveAtom type (no \"| null\") and it will work fine.\n    - Generally never use \"=== undefined\" or \"!== undefined\". This is bad style. Just use a \"== null\" or \"!= null\" unless it is a very specific case where we need to distinguish undefined from null.\n  - **Coding Style**:\n    - Use all lowercase filenames (except where case is actually important like Taskfile.yml)\n    - Import the \"cn\" function from \"@/util/util\" to do classname / clsx class merge (it uses twMerge underneath)\n    - Do NOT create private fields in classes (they are impossible to inspect)\n    - Use PascalCase for global consts at the top of files\n  - **Component Practices**:\n    - Make sure to add cursor-pointer to buttons/links and clickable items\n    - NEVER use cursor-help (it looks terrible)\n    - useAtom() and useAtomValue() are react HOOKS, so they must be called at the component level not inline in JSX\n    - If you use React.memo(), make sure to add a displayName for the component\n  - Other\n    - never use atob() or btoa() (not UTF-8 safe). use functions in frontend/util/util.ts for base64 decoding and encoding\n- In general, when writing functions, we prefer _early returns_ rather than putting the majority of a function inside of an if block.\n\n### Styling\n\n- We use **Tailwind v4** to style. Custom stuff is defined in frontend/tailwindsetup.css\n- _never_ use cursor-help, or cursor-not-allowed (it looks terrible)\n- We have custom CSS setup as well, so it is a hybrid system. For new code we prefer tailwind, and are working to migrate code to all use tailwind.\n- For accent buttons, use \"bg-accent/80 text-primary rounded hover:bg-accent transition-colors cursor-pointer\" (if you do \"bg-accent hover:bg-accent/80\" it looks weird as on hover the button gets darker instead of lighter)\n\n### RPC System\n\nTo define a new RPC call, add the new definition to `pkg/wshrpc/wshrpctypes.go` including any input/output data that is required. After modifying wshrpctypes.go run `task generate` to generate the client APIs.\n\nFor normal \"server\" RPCs (where a frontend client is calling the main server) you should implement the RPC call in `pkg/wshrpc/wshserver.go`.\n\n### Electron API\n\nFrom within the FE to get the electron API (e.g. the preload functions):\n\n```ts\nimport { getApi } from \"@/store/global\";\n\ngetApi().getIsDev();\n```\n\nThe full API is defined in custom.d.ts as type ElectronApi.\n\n### Code Generation\n\n- **TypeScript Types**: TypeScript types are automatically generated from Go types. After modifying Go types in `pkg/wshrpc/wshrpctypes.go`, run `task generate` to update the TypeScript type definitions in `frontend/types/gotypes.d.ts`.\n- **Manual Edits**: Do not manually edit generated files like `frontend/types/gotypes.d.ts` or `frontend/app/store/wshclientapi.ts`. Instead, modify the source Go types and run `task generate`.\n\n### Frontend Architecture\n\n- The application uses Jotai for state management.\n- When working with Jotai atoms that need to be updated, define them as `PrimitiveAtom<Type>` rather than just `atom<Type>`.\n\n### Notes\n\n- **CRITICAL: Completion format MUST be: \"Done: [one-line description]\"**\n- **Keep your Task Completed summaries VERY short**\n- **No lengthy pre-completion summaries** - Do not provide detailed explanations of implementation before using attempt_completion\n- **No recaps of changes** - Skip explaining what was done before completion\n- **Go directly to completion** - After making changes, proceed directly to attempt_completion without summarizing\n- The project is currently an un-released POC / MVP. Do not worry about backward compatibility when making changes\n- With React hooks, always complete all hook calls at the top level before any conditional returns (including jotai hook calls useAtom and useAtomValue); when a user explicitly tells you a function handles null inputs, trust them and stop trying to \"protect\" it with unnecessary checks or workarounds.\n- **Match response length to question complexity** - For simple, direct questions in Ask mode (especially those that can be answered in 1-2 sentences), provide equally brief answers. Save detailed explanations for complex topics or when explicitly requested.\n- **CRITICAL** - useAtomValue and useAtom are React HOOKS. They cannot be used inline in JSX code, they must appear at the top of a component in the hooks area of the react code.\n- for simple functions, we prefer `if (!cond) { return }; functionality;` pattern over `if (cond) { functionality }` because it produces less indentation and is easier to follow.\n- It is now 2026, so if you write new files, or update files use 2026 for the copyright year\n- React.MutableRefObject is deprecated, just use React.RefObject now (in React 19 RefObject is always mutable)\n\n### Strict Comment Rules\n\n- **NEVER add comments that merely describe what code is doing**:\n  - ❌ `mutex.Lock() // Lock the mutex`\n  - ❌ `counter++ // Increment the counter`\n  - ❌ `buffer.Write(data) // Write data to buffer`\n  - ❌ `// Header component for app run list` (above AppRunListHeader)\n  - ❌ `// Updated function to include onClick parameter`\n  - ❌ `// Changed padding calculation`\n  - ❌ `// Removed unnecessary div`\n  - ❌ `// Using the model's width value here`\n- **Only use comments for**:\n  - Explaining WHY a particular approach was chosen\n  - Documenting non-obvious edge cases or side effects\n  - Warning about potential pitfalls in usage\n  - Explaining complex algorithms that can't be simplified\n- **When in doubt, leave it out**. No comment is better than a redundant comment.\n- **Never add comments explaining code changes** - The code should speak for itself, and version control tracks changes. The one exception to this rule is if it is a very unobvious implementation. Something that someone would typically implement in a different (wrong) way. Then the comment helps us remember WHY we changed it to a less obvious implementation.\n- **Never remove existing comments** unless specifically directed by the user. Comments that are already defined in existing code have been vetted by the user.\n\n### Jotai Model Pattern (our rules)\n\n- **Atoms live on the model.**\n- **Simple atoms:** define as **field initializers**.\n- **Atoms that depend on values/other atoms:** create in the **constructor**.\n- Models **never use React hooks**; they use `globalStore.get/set`.\n- It's fine to call model methods from **event handlers** or **`useEffect`**.\n- Models use the **singleton pattern** with a `private static instance` field, a `private constructor`, and a `static getInstance()` method.\n- The constructor is `private`; callers always use `getInstance()`.\n\n```ts\n// model/MyModel.ts\nimport * as jotai from \"jotai\";\nimport { globalStore } from \"@/app/store/jotaiStore\";\n\nexport class MyModel {\n  private static instance: MyModel | null = null;\n\n  // simple atoms (field init)\n  statusAtom = jotai.atom<\"idle\" | \"running\" | \"error\">(\"idle\");\n  outputAtom = jotai.atom(\"\");\n\n  // ctor-built atoms (need types)\n  lengthAtom!: jotai.Atom<number>;\n  thresholdedAtom!: jotai.Atom<boolean>;\n\n  private constructor(initialThreshold = 20) {\n    this.lengthAtom = jotai.atom((get) => get(this.outputAtom).length);\n    this.thresholdedAtom = jotai.atom((get) => get(this.lengthAtom) > initialThreshold);\n  }\n\n  static getInstance(): MyModel {\n    if (!MyModel.instance) {\n      MyModel.instance = new MyModel();\n    }\n    return MyModel.instance;\n  }\n\n  static resetInstance(): void {\n    MyModel.instance = null;\n  }\n\n  async doWork() {\n    globalStore.set(this.statusAtom, \"running\");\n    // ... do work ...\n    globalStore.set(this.statusAtom, \"idle\");\n  }\n}\n```\n\n```tsx\n// component usage (events & effects OK)\nimport { useAtomValue } from \"jotai\";\n\nfunction Panel() {\n  const model = MyModel.getInstance();\n  const status = useAtomValue(model.statusAtom);\n  const isBig = useAtomValue(model.thresholdedAtom);\n\n  const onClick = () => model.doWork();\n\n  return (\n    <div>\n      {status} • {String(isBig)}\n    </div>\n  );\n}\n```\n\n**Remember:** singleton pattern with `getInstance()`, `private constructor`, atoms on the model, simple-as-fields, ctor for dependent/derived, updates via `globalStore.set/get`.\n**Note** Older models may not use the singleton pattern\n\n### Tool Use\n\nDo NOT use write_to_file unless it is a new file or very short. Always prefer to use replace_in_file. Often your diffs fail when a file may be out of date in your cache vs the actual on-disk format. You should RE-READ the file and try to create diffs again if your diffs fail rather than fall back to write_to_file. If you feel like your ONLY option is to use write_to_file please ask first.\n\nAlso when adding content to the end of files prefer to use the new append_file tool rather than trying to create a diff (as your diffs are often not specific enough and end up inserting code in the middle of existing functions).\n\n### Directory Awareness\n\n- **ALWAYS verify the current working directory before executing commands**\n- Either run \"pwd\" first to verify the directory, or do a \"cd\" to the correct absolute directory before running commands\n- When running tests, do not \"cd\" to the pkg directory and then run the test. This screws up the cwd and you never recover. run the test from the project root instead.\n\n### Testing / Compiling Go Code\n\nNo need to run a `go build` or a `go run` to just check if the Go code compiles. VSCode's errors/problems cover this well.\nIf there are no Go errors in VSCode you can assume the code compiles fine.\n",".github/copilot-instructions.md":"# Wave Terminal — Copilot Instructions\n\n## Project Rules\n\n- See the overview of the project in `.kilocode/rules/overview.md`\n- Read and follow all guidelines in `.kilocode/rules/rules.md`\n\n---\n\n## Skill Guides\n\nThis project uses a set of \"skill\" guides — focused how-to documents for common implementation tasks. When your task matches one of the descriptions below, **read the linked SKILL.md file before proceeding** and follow its instructions precisely.\n\n| Skill        | File                                     | Description                                                                                                                                                                                                                                 |\n| ------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| add-config   | `.kilocode/skills/add-config/SKILL.md`   | Guide for adding new configuration settings to Wave Terminal. Use when adding a new setting to the configuration system, implementing a new config key, or adding user-customizable settings.                                               |\n| add-rpc      | `.kilocode/skills/add-rpc/SKILL.md`      | Guide for adding new RPC calls to Wave Terminal. Use when implementing new RPC commands, adding server-client communication methods, or extending the RPC interface with new functionality.                                                 |\n| add-wshcmd   | `.kilocode/skills/add-wshcmd/SKILL.md`   | Guide for adding new wsh commands to Wave Terminal. Use when implementing new CLI commands, adding command-line functionality, or extending the wsh command interface.                                                                      |\n| context-menu | `.kilocode/skills/context-menu/SKILL.md` | Guide for creating and displaying context menus in Wave Terminal. Use when implementing right-click menus, adding context menu items, creating submenus, or handling menu interactions with checkboxes and separators.                      |\n| create-view  | `.kilocode/skills/create-view/SKILL.md`  | Guide for implementing a new view type in Wave Terminal. Use when creating a new view component, implementing the ViewModel interface, registering a new view type in BlockRegistry, or adding a new content type to display within blocks. |\n| electron-api | `.kilocode/skills/electron-api/SKILL.md` | Guide for adding new Electron APIs to Wave Terminal. Use when implementing new frontend-to-electron communications via preload/IPC.                                                                                                         |\n| waveenv      | `.kilocode/skills/waveenv/SKILL.md`      | Guide for creating WaveEnv narrowings in Wave Terminal. Use when writing a named subset type of WaveEnv for a component tree, documenting environmental dependencies, or enabling mock environments for preview/test server usage.          |\n| wps-events   | `.kilocode/skills/wps-events/SKILL.md`   | Guide for working with Wave Terminal's WPS (Wave PubSub) event system. Use when implementing new event types, publishing events, subscribing to events, or adding asynchronous communication between components.                            |\n\n> **How skills work:** Each skill is a self-contained guide covering the exact files to edit, patterns to follow, and steps to take for a specific type of task in this codebase. If your task matches a skill's description, open that SKILL.md and treat it as your primary reference for the implementation.\n\n---\n\n## Preview Server\n\nTo run the standalone component preview (no Electron, no backend required):\n\n```\ntask preview\n```\n\nThis runs `cd frontend/preview && npx vite` and serves at **http://localhost:7007** (port configured in `frontend/preview/vite.config.ts`).\n\nTo build a static preview: `task build:preview`\n\n**Do NOT use any of the following to start the preview — they all launch the full Electron app or serve the wrong content:**\n\n- `npm run dev` — runs `electron-vite dev`, launches Electron\n- `npm run start` — also launches Electron\n- `npx vite` from the repo root — uses the Electron-Vite config, not the preview app\n- Serving the `dist/` directory — the preview app is never built there; it has its own build output\n",".kilocode/skills/add-config/SKILL.md":"---\nname: add-config\ndescription: Guide for adding new configuration settings to Wave Terminal. Use when adding a new setting to the configuration system, implementing a new config key, or adding user-customizable settings.\n---\n\n# Adding a New Configuration Setting to Wave Terminal\n\nThis guide explains how to add a new configuration setting to Wave Terminal's hierarchical configuration system.\n\n## Configuration System Overview\n\nWave Terminal uses a hierarchical configuration system with:\n\n1. **Go Struct Definitions** - Type-safe configuration structure in `pkg/wconfig/settingsconfig.go`\n2. **JSON Schema** - Auto-generated validation schema in `schema/settings.json`\n3. **Default Values** - Built-in defaults in `pkg/wconfig/defaultconfig/settings.json`\n4. **User Configuration** - User overrides in `~/.config/waveterm/settings.json`\n5. **Block Metadata** - Block-level overrides in `pkg/waveobj/wtypemeta.go`\n6. **Documentation** - User-facing docs in `docs/docs/config.mdx`\n\nSettings cascade from defaults → user settings → connection config → block overrides.\n\n## Step-by-Step Guide\n\n### Step 1: Add to Go Struct Definition\n\nEdit `pkg/wconfig/settingsconfig.go` and add your new field to the `SettingsType` struct:\n\n```go\ntype SettingsType struct {\n    // ... existing fields ...\n\n    // Add your new field with appropriate JSON tag\n    MyNewSetting string `json:\"mynew:setting,omitempty\"`\n\n    // For different types:\n    MyBoolSetting   bool    `json:\"mynew:boolsetting,omitempty\"`\n    MyNumberSetting float64 `json:\"mynew:numbersetting,omitempty\"`\n    MyIntSetting    *int64  `json:\"mynew:intsetting,omitempty\"`    // Use pointer for optional ints\n    MyArraySetting  []string `json:\"mynew:arraysetting,omitempty\"`\n}\n```\n\n**Naming Conventions:**\n\n- Use namespace prefixes (e.g., `term:`, `window:`, `ai:`, `web:`, `app:`)\n- Use lowercase with colons as separators\n- Field names should be descriptive and follow Go naming conventions\n- Use `omitempty` tag to exclude empty values from JSON\n\n**Type Guidelines:**\n\n- Use `*int64` and `*float64` for optional numeric values\n- Use `*bool` for optional boolean values (or `bool` if default is false)\n- Use `string` for text values\n- Use `[]string` for arrays\n- Use `float64` for numbers that can be decimals\n\n**Namespace Organization:**\n\n- `app:*` - Application-level settings\n- `term:*` - Terminal-specific settings\n- `window:*` - Window and UI settings\n- `ai:*` - AI-related settings\n- `web:*` - Web browser settings\n- `editor:*` - Code editor settings\n- `conn:*` - Connection settings\n\n### Step 1.5: Add to Block Metadata (Optional)\n\nIf your setting should support block-level overrides, also add it to `pkg/waveobj/wtypemeta.go`:\n\n```go\ntype MetaTSType struct {\n    // ... existing fields ...\n\n    // Add your new field with matching JSON tag and type\n    MyNewSetting *string `json:\"mynew:setting,omitempty\"`  // Use pointer for optional values\n\n    // For different types:\n    MyBoolSetting   *bool    `json:\"mynew:boolsetting,omitempty\"`\n    MyNumberSetting *float64 `json:\"mynew:numbersetting,omitempty\"`\n    MyIntSetting    *int     `json:\"mynew:intsetting,omitempty\"`\n    MyArraySetting  []string `json:\"mynew:arraysetting,omitempty\"`\n}\n```\n\n**Block Metadata Guidelines:**\n\n- Use pointer types (`*string`, `*bool`, `*int`, `*float64`) for optional overrides\n- JSON tags should exactly match the corresponding settings field\n- This enables the hierarchical config system: block metadata → connection config → global settings\n- Only add settings here that make sense to override per-block or per-connection\n\n### Step 2: Set Default Value (Optional)\n\nIf your setting should have a default value, add it to `pkg/wconfig/defaultconfig/settings.json`:\n\n```json\n{\n  \"ai:preset\": \"ai@global\",\n  \"ai:model\": \"gpt-5-mini\",\n  // ... existing defaults ...\n\n  \"mynew:setting\": \"default value\",\n  \"mynew:boolsetting\": true,\n  \"mynew:numbersetting\": 42.5,\n  \"mynew:intsetting\": 100\n}\n```\n\n**Default Value Guidelines:**\n\n- Only add defaults for settings that should have non-zero/non-empty initial values\n- Ensure defaults make sense for typical user experience\n- Keep defaults conservative and safe\n- Boolean settings often don't need defaults if `false` is the correct default\n\n### Step 3: Update Documentation\n\nAdd your new setting to the configuration table in `docs/docs/config.mdx`:\n\n```markdown\n| Key Name            | Type     | Function                                  |\n| ------------------- | -------- | ----------------------------------------- |\n| mynew:setting       | string   | Description of what this setting controls |\n| mynew:boolsetting   | bool     | Enable/disable some feature               |\n| mynew:numbersetting | float    | Numeric setting for some parameter        |\n| mynew:intsetting    | int      | Integer setting for some configuration    |\n| mynew:arraysetting  | string[] | Array of strings for multiple values      |\n```\n\n**Documentation Guidelines:**\n\n- Provide clear, concise descriptions\n- For new settings in upcoming releases, add `<VersionBadge version=\"v0.14\" />`\n- Update the default configuration example if you added defaults\n- Explain what values are valid and what they do\n\n### Step 4: Regenerate Schema and TypeScript Types\n\nRun the generate task to automatically regenerate the JSON schema and TypeScript types:\n\n```bash\ntask generate\n```\n\n**What this does:**\n\n- Runs `task build:schema` (automatically generates JSON schema from Go structs)\n- Generates TypeScript type definitions in `frontend/types/gotypes.d.ts`\n- Generates RPC client APIs\n- Generates metadata constants\n\n**Important:** The JSON schema in `schema/settings.json` is **automatically generated** from the Go struct definitions - you don't need to edit it manually.\n\n### Step 5: Use in Frontend Code\n\nAccess your new setting in React components:\n\n```typescript\nimport { getOverrideConfigAtom, getSettingsKeyAtom, useAtomValue } from \"@/store/global\";\n\n// In a React component\nconst MyComponent = ({ blockId }: { blockId: string }) => {\n    // Use override config atom for hierarchical resolution\n    // This automatically checks: block metadata → connection config → global settings → default\n    const mySettingAtom = getOverrideConfigAtom(blockId, \"mynew:setting\");\n    const mySetting = useAtomValue(mySettingAtom) ?? \"fallback value\";\n\n    // For global-only settings (no block overrides)\n    const globalOnlySetting = useAtomValue(getSettingsKeyAtom(\"mynew:globalsetting\")) ?? \"fallback\";\n\n    return <div>Setting value: {mySetting}</div>;\n};\n```\n\n**Frontend Configuration Patterns:**\n\n```typescript\n// 1. Settings with block-level overrides (recommended for most view/display settings)\nconst termFontSize = useAtomValue(getOverrideConfigAtom(blockId, \"term:fontsize\")) ?? 12;\n\n// 2. Global-only settings (app-wide settings that don't vary by block)\nconst appGlobalHotkey = useAtomValue(getSettingsKeyAtom(\"app:globalhotkey\")) ?? \"\";\n\n// 3. Connection-specific settings\nconst connStatus = useAtomValue(getConnStatusAtom(connectionName));\n```\n\n**When to use each pattern:**\n\n- Use `getOverrideConfigAtom()` for settings that can vary by block or connection (most UI/display settings)\n- Use `getSettingsKeyAtom()` for app-level settings that are always global\n- Always provide a fallback value with `??` operator\n\n### Step 6: Use in Backend Code\n\nAccess settings in Go code:\n\n```go\n// Get the full config\nfullConfig := wconfig.GetWatcher().GetFullConfig()\n\n// Access your setting\nmyValue := fullConfig.Settings.MyNewSetting\n\n// For optional values (pointers)\nif fullConfig.Settings.MyIntSetting != nil {\n    intValue := *fullConfig.Settings.MyIntSetting\n    // Use intValue\n}\n```\n\n## Complete Examples\n\n### Example 1: Simple Boolean Setting (No Block Override)\n\n**Use case:** Add a setting to hide the AI button globally\n\n#### 1. Go Struct (`pkg/wconfig/settingsconfig.go`)\n\n```go\ntype SettingsType struct {\n    // ... existing fields ...\n    AppHideAiButton bool `json:\"app:hideaibutton,omitempty\"`\n}\n```\n\n#### 2. Default Value (`pkg/wconfig/defaultconfig/settings.json`)\n\n```json\n{\n  \"app:hideaibutton\": false\n}\n```\n\n#### 3. Documentation (`docs/docs/config.mdx`)\n\n```markdown\n| app:hideaibutton <VersionBadge version=\"v0.14\" /> | bool | Hide the AI button in the tab bar (defaults to false) |\n```\n\n#### 4. Generate Types\n\n```bash\ntask generate\n```\n\n#### 5. Frontend Usage\n\n```typescript\nimport { getSettingsKeyAtom } from \"@/store/global\";\n\nconst TabBar = () => {\n    const hideAiButton = useAtomValue(getSettingsKeyAtom(\"app:hideaibutton\"));\n\n    if (hideAiButton) {\n        return null; // Don't render AI button\n    }\n\n    return <button>AI</button>;\n};\n```\n\n#### 6. Usage Examples\n\n```bash\n# Set in settings file\nwsh setconfig app:hideaibutton=true\n\n# Or edit ~/.config/waveterm/settings.json\n{\n  \"app:hideaibutton\": true\n}\n```\n\n### Example 2: Terminal Setting with Block Override\n\n**Use case:** Add a terminal bell sound setting that can be overridden per block\n\n#### 1. Go Struct (`pkg/wconfig/settingsconfig.go`)\n\n```go\ntype SettingsType struct {\n    // ... existing fields ...\n    TermBellSound string `json:\"term:bellsound,omitempty\"`\n}\n```\n\n#### 2. Block Metadata (`pkg/waveobj/wtypemeta.go`)\n\n```go\ntype MetaTSType struct {\n    // ... existing fields ...\n    TermBellSound *string `json:\"term:bellsound,omitempty\"`  // Pointer for optional override\n}\n```\n\n#### 3. Default Value (`pkg/wconfig/defaultconfig/settings.json`)\n\n```json\n{\n  \"term:bellsound\": \"default\"\n}\n```\n\n#### 4. Documentation (`docs/docs/config.mdx`)\n\n```markdown\n| term:bellsound <VersionBadge version=\"v0.14\" /> | string | Sound to play for terminal bell (\"default\", \"none\", or custom sound file path) |\n```\n\n#### 5. Generate Types\n\n```bash\ntask generate\n```\n\n#### 6. Frontend Usage\n\n```typescript\nimport { getOverrideConfigAtom } from \"@/store/global\";\n\nconst TerminalView = ({ blockId }: { blockId: string }) => {\n    // Use override config for hierarchical resolution\n    const bellSoundAtom = getOverrideConfigAtom(blockId, \"term:bellsound\");\n    const bellSound = useAtomValue(bellSoundAtom) ?? \"default\";\n\n    const playBellSound = () => {\n        if (bellSound === \"none\") return;\n        // Play the bell sound\n    };\n\n    return <div>Terminal with bell: {bellSound}</div>;\n};\n```\n\n#### 7. Usage Examples\n\n```bash\n# Set globally in settings file\nwsh setconfig term:bellsound=\"custom.wav\"\n\n# Set for current block only\nwsh setmeta term:bellsound=\"none\"\n\n# Set for specific block\nwsh setmeta --block BLOCK_ID term:bellsound=\"beep\"\n\n# Or edit ~/.config/waveterm/settings.json\n{\n  \"term:bellsound\": \"custom.wav\"\n}\n```\n\n## Configuration Patterns\n\n### Clear/Reset Pattern\n\nEach namespace can have a \"clear\" field for resetting all settings in that namespace:\n\n```go\nAppClear  bool `json:\"app:*,omitempty\"`\nTermClear bool `json:\"term:*,omitempty\"`\n```\n\n### Optional vs Required Settings\n\n- Use pointer types (`*bool`, `*int64`, `*float64`) for truly optional settings\n- Use regular types for settings that should always have a value\n- Provide sensible defaults for important settings\n\n### Block-Level Overrides via RPC\n\nSettings can be overridden at the block level using metadata:\n\n```typescript\nimport { RpcApi } from \"@/app/store/wshclientapi\";\nimport { TabRpcClient } from \"@/app/store/wshrpcutil\";\nimport { WOS } from \"@/store/global\";\n\n// Set block-specific override\nawait RpcApi.SetMetaCommand(TabRpcClient, {\n  oref: WOS.makeORef(\"block\", blockId),\n  meta: { \"mynew:setting\": \"block-specific value\" },\n});\n```\n\n## Common Pitfalls\n\n### 1. Forgetting to Run `task generate`\n\n**Problem:** TypeScript types not updated, schema out of sync\n\n**Solution:** Always run `task generate` after modifying Go structs\n\n### 2. Type Mismatch Between Settings and Metadata\n\n**Problem:** Settings uses `string`, metadata uses `*int`\n\n**Solution:** Ensure types match (except metadata uses pointers for optionals)\n\n### 3. Not Providing Fallback Values\n\n**Problem:** Component breaks if setting is undefined\n\n**Solution:** Always use `??` operator with fallback:\n\n```typescript\nconst value = useAtomValue(getSettingsKeyAtom(\"key\")) ?? \"default\";\n```\n\n### 4. Using Wrong Config Atom\n\n**Problem:** Using `getSettingsKeyAtom()` for settings that need block overrides\n\n**Solution:** Use `getOverrideConfigAtom()` for any setting in `MetaTSType`\n\n## Best Practices\n\n### Naming\n\n- **Use descriptive names**: `term:fontsize` not `term:fs`\n- **Follow namespace conventions**: Group related settings with common prefix\n- **Use consistent casing**: Always lowercase with colons\n\n### Types\n\n- **Use `bool`** for simple on/off settings (no pointer if false is default)\n- **Use `*bool`** only if you need to distinguish unset from false\n- **Use `*int64`/`*float64`** for optional numeric values\n- **Use `string`** for text, paths, or enum-like values\n- **Use `[]string`** for lists\n\n### Defaults\n\n- **Provide sensible defaults** for settings users will commonly change\n- **Omit defaults** for advanced/optional settings\n- **Keep defaults safe** - don't enable experimental features by default\n- **Document defaults** clearly in config.mdx\n\n### Block Overrides\n\n- **Enable for view/display settings**: Font sizes, colors, themes, etc.\n- **Don't enable for app-wide settings**: Global hotkeys, window behavior, etc.\n- **Consider the use case**: Would a user want different values per block or connection?\n\n### Documentation\n\n- **Be specific**: Explain what the setting does and what values are valid\n- **Provide examples**: Show common use cases\n- **Add version badges**: Mark new settings with `<VersionBadge version=\"v0.x\" />`\n- **Keep it current**: Update docs when behavior changes\n\n## Quick Reference\n\nWhen adding a new configuration setting:\n\n- [ ] Add field to `SettingsType` in `pkg/wconfig/settingsconfig.go`\n- [ ] Add field to `MetaTSType` in `pkg/waveobj/wtypemeta.go` (if block override needed)\n- [ ] Add default to `pkg/wconfig/defaultconfig/settings.json` (if needed)\n- [ ] Document in `docs/docs/config.mdx`\n- [ ] Run `task generate` to update TypeScript types\n- [ ] Use appropriate atom (`getOverrideConfigAtom` or `getSettingsKeyAtom`) in frontend\n\n## Related Documentation\n\n- **User Documentation**: `docs/docs/config.mdx` - User-facing configuration docs\n- **Type Definitions**: `pkg/wconfig/settingsconfig.go` - Go struct definitions\n- **Metadata Types**: `pkg/waveobj/wtypemeta.go` - Block metadata definitions\n","CLAUDE.md":"@.kilocode/rules/rules.md\n\n---\n\n## Skill Guides\n\nThis project uses a set of \"skill\" guides — focused how-to documents for common implementation tasks. When your task matches one of the descriptions below, **read the linked SKILL.md file before proceeding** and follow its instructions precisely.\n\n| Skill        | File                                     | Description                                                                                                                                                                                                                                 |\n| ------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| add-config   | `.kilocode/skills/add-config/SKILL.md`   | Guide for adding new configuration settings to Wave Terminal. Use when adding a new setting to the configuration system, implementing a new config key, or adding user-customizable settings.                                               |\n| add-rpc      | `.kilocode/skills/add-rpc/SKILL.md`      | Guide for adding new RPC calls to Wave Terminal. Use when implementing new RPC commands, adding server-client communication methods, or extending the RPC interface with new functionality.                                                 |\n| add-wshcmd   | `.kilocode/skills/add-wshcmd/SKILL.md`   | Guide for adding new wsh commands to Wave Terminal. Use when implementing new CLI commands, adding command-line functionality, or extending the wsh command interface.                                                                      |\n| context-menu | `.kilocode/skills/context-menu/SKILL.md` | Guide for creating and displaying context menus in Wave Terminal. Use when implementing right-click menus, adding context menu items, creating submenus, or handling menu interactions with checkboxes and separators.                      |\n| create-view  | `.kilocode/skills/create-view/SKILL.md`  | Guide for implementing a new view type in Wave Terminal. Use when creating a new view component, implementing the ViewModel interface, registering a new view type in BlockRegistry, or adding a new content type to display within blocks. |\n| electron-api | `.kilocode/skills/electron-api/SKILL.md` | Guide for adding new Electron APIs to Wave Terminal. Use when implementing new frontend-to-electron communications via preload/IPC.                                                                                                         |\n| waveenv      | `.kilocode/skills/waveenv/SKILL.md`      | Guide for creating WaveEnv narrowings in Wave Terminal. Use when writing a named subset type of WaveEnv for a component tree, documenting environmental dependencies, or enabling mock environments for preview/test server usage.          |\n| wps-events   | `.kilocode/skills/wps-events/SKILL.md`   | Guide for working with Wave Terminal's WPS (Wave PubSub) event system. Use when implementing new event types, publishing events, subscribing to events, or adding asynchronous communication between components.                            |\n",".roo/rules/overview.md":"# Rules Skill\n\nPath: `.roo/rules/overview.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.roo/rules/overview.md)",".roo/rules/rules.md":"# Rules Skill\n\nPath: `.roo/rules/rules.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.roo/rules/rules.md)"},"items":[{"name":"overview.md","path":".kilocode/rules/overview.md","rawUrl":"https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.kilocode/rules/overview.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Wave Terminal - High Level Architecture Overview\n\n## Project Description\n\nWave Terminal is an open-source AI-native terminal built for seamless workflows. It's an Electron application that serves as a command line terminal host (it hosts CLI applications rather than running inside a CLI). The application combines a React frontend with a Go backend server to provide a modern terminal experience with advanced features.\n\n## Top-Level Directory Structure\n\n```\nwaveterm/\n├── emain/              # Electron main process code\n├── frontend/           # React application (renderer process)\n├── cmd/                # Go command-line applications\n├── pkg/                # Go packages/modules\n├── db/                 # Database migrations\n├── docs/               # Documentation (Docusaurus)\n├── build/              # Build configuration and assets\n├── assets/             # Application assets (icons, images)\n├── public/             # Static public assets\n├── tests/              # Test files\n├── .github/            # GitHub workflows and configuration\n└── Configuration files (package.json, tsconfig.json, etc.)\n```\n\n## Architecture Components\n\n### 1. Electron Main Process (`emain/`)\n\nThe Electron main process handles the native desktop application layer:\n\n**Key Files:**\n\n- [`emain.ts`](emain/emain.ts) - Main entry point, application lifecycle management\n- [`emain-window.ts`](emain/emain-window.ts) - Window management (`WaveBrowserWindow` class)\n- [`emain-tabview.ts`](emain/emain-tabview.ts) - Tab view management (`WaveTabView` class)\n- [`emain-wavesrv.ts`](emain/emain-wavesrv.ts) - Go backend server integration\n- [`emain-wsh.ts`](emain/emain-wsh.ts) - WSH (Wave Shell) client integration\n- [`emain-ipc.ts`](emain/emain-ipc.ts) - IPC handlers for frontend ↔ main process communication\n- [`emain-menu.ts`](emain/emain-menu.ts) - Application menu system\n- [`updater.ts`](emain/updater.ts) - Auto-update functionality\n- [`preload.ts`](emain/preload.ts) - Preload script for renderer security\n- [`preload-webview.ts`](emain/preload-webview.ts) - Webview preload script\n\n### 2. Frontend React Application (`frontend/`)\n\nThe React application runs in the Electron renderer process:\n\n**Structure:**\n\n```\nfrontend/\n├── app/                # Main application code\n│   ├── app.tsx         # Root App component\n│   ├── aipanel/        # AI panel UI\n│   ├── block/          # Block-based UI components\n│   ├── element/        # Reusable UI elements\n│   ├── hook/           # Custom React hooks\n│   ├── modals/         # Modal components\n│   ├── store/          # State management (Jotai)\n│   ├── tab/            # Tab components\n│   ├── view/           # Different view types\n│   │   ├── codeeditor/ # Code editor (Monaco)\n│   │   ├── preview/    # File preview\n│   │   ├── sysinfo/    # System info view\n│   │   ├── term/       # Terminal view\n│   │   ├── tsunami/    # Tsunami builder view\n│   │   ├── vdom/       # Virtual DOM view\n│   │   ├── waveai/     # AI chat integration\n│   │   ├── waveconfig/ # Config editor view\n│   │   └── webview/    # Web view\n│   └── workspace/      # Workspace management\n├── builder/            # Builder app entry\n├── layout/             # Layout system\n├── preview/            # Standalone preview renderer\n├── types/              # TypeScript type definitions\n└── util/               # Utility functions\n```\n\n**Key Technologies:**\n\n- Electron (desktop application shell)\n- React 19 with TypeScript\n- Jotai for state management\n- Monaco Editor for code editing\n- XTerm.js for terminal emulation\n- Tailwind CSS v4 for styling\n- SCSS for additional styling (deprecated, new components should use Tailwind)\n- Vite / electron-vite for bundling\n- Task (Taskfile.yml) for build and code generation commands\n\n### 3. Go Backend Server (`cmd/server/`)\n\nThe Go backend server handles all heavy lifting operations:\n\n**Entry Point:** [`main-server.go`](cmd/server/main-server.go)\n\n### 4. Go Packages (`pkg/`)\n\nThe Go codebase is organized into modular packages:\n\n**Key Packages:**\n\n- `wstore/` - Database and storage layer\n- `wconfig/` - Configuration management\n- `wcore/` - Core business logic\n- `wshrpc/` - RPC communication system\n- `wshutil/` - WSH (Wave Shell) utilities\n- `blockcontroller/` - Block execution management\n- `remote/` - Remote connection handling\n- `filestore/` - File storage system\n- `web/` - Web server and WebSocket handling\n- `telemetry/` - Usage analytics and telemetry\n- `waveobj/` - Core data objects\n- `service/` - Service layer\n- `wps/` - Wave PubSub event system\n- `waveai/` - AI functionality\n- `shellexec/` - Shell execution\n- `util/` - Common utilities\n\n### 5. Command Line Tools (`cmd/`)\n\nKey Go command-line utilities:\n\n- `wsh/` - Wave Shell command-line tool\n- `server/` - Main backend server\n- `generatego/` - Code generation\n- `generateschema/` - Schema generation\n- `generatets/` - TypeScript generation\n\n## Communication Architecture\n\nThe core communication system is built around the **WSH RPC (Wave Shell RPC)** system, which provides a unified interface for all inter-process communication: frontend ↔ Go backend, Electron main process ↔ backend, and backend ↔ remote systems (SSH, WSL).\n\n### WSH RPC System (`pkg/wshrpc/`)\n\nThe WSH RPC system is the backbone of Wave Terminal's communication architecture:\n\n**Key Components:**\n\n- [`wshrpctypes.go`](pkg/wshrpc/wshrpctypes.go) - Core RPC interface and type definitions (source of truth for all RPC commands)\n- [`wshserver/`](pkg/wshrpc/wshserver/) - Server-side RPC implementation\n- [`wshremote/`](pkg/wshrpc/wshremote/) - Remote connection handling\n- [`wshclient.go`](pkg/wshrpc/wshclient.go) - Go client for making RPC calls\n- [`frontend/app/store/wshclientapi.ts`](frontend/app/store/wshclientapi.ts) - Generated TypeScript RPC client\n\n**Routing:** Callers address RPC calls using _routes_ (e.g. a block ID, connection name, or `\"waveapp\"`) rather than caring about the underlying transport. The RPC layer resolves the route to the correct transport (WebSocket, Unix socket, SSH tunnel, stdio) automatically. This means the same RPC interface works whether the target is local or a remote SSH connection.\n\n## Development Notes\n\n- **Build commands** - Use `task` (Taskfile.yml) for all build, generate, and packaging commands\n- **Code generation** - Run `task generate` after modifying Go types in `pkg/wshrpc/wshrpctypes.go`, `pkg/wconfig/settingsconfig.go`, or `pkg/waveobj/wtypemeta.go`\n- **Testing** - Vitest for frontend unit tests; standard `go test` for Go packages\n- **Database migrations** - SQL migration files in `db/migrations-wstore/` and `db/migrations-filestore/`\n- **Documentation** - Docusaurus site in `docs/`\n","isInternal":false,"tokens":1656,"sizeBytes":6935},{"name":"rules.md","path":".kilocode/rules/rules.md","rawUrl":"https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.kilocode/rules/rules.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"Wave Terminal is a modern terminal which provides graphical blocks, dynamic layout, workspaces, and SSH connection management. It is cross platform and built on electron.\n\n### Project Structure\n\nIt has a TypeScript/React frontend and a Go backend. They talk together over `wshrpc` a custom RPC protocol that is implemented over websocket (and domain sockets).\n\n### Coding Guidelines\n\n- **Go Conventions**:\n  - Don't use custom enum types in Go. Instead, use string constants (e.g., `const StatusRunning = \"running\"` rather than creating a custom type like `type Status string`).\n  - Use string constants for status values, packet types, and other string-based enumerations.\n  - in Go code, prefer using Printf() vs Println()\n  - use \"Make\" as opposed to \"New\" for struct initialization func names\n  - in general const decls go at the top of the file (before types and functions)\n  - NEVER run `go build` (especially in weird sub-package directories). we can tell if everything compiles by seeing there are no problems/errors.\n- **Synchronization**:\n  - Always prefer to use the `lock.Lock(); defer lock.Unlock()` pattern for synchronization if possible\n  - Avoid inline lock/unlock pairs - instead create helper functions that use the defer pattern\n  - When accessing shared data structures (maps, slices, etc.), ensure proper locking\n  - Example: Instead of `gc.lock.Lock(); gc.map[key]++; gc.lock.Unlock()`, create a helper function like `getNextValue(key string) int { gc.lock.Lock(); defer gc.lock.Unlock(); gc.map[key]++; return gc.map[key] }`\n- **TypeScript Imports**:\n  - Use `@/...` for imports from different parts of the project (configured in `tsconfig.json` as `\"@/*\": [\"frontend/*\"]`).\n  - Prefer relative imports (`\"./name\"`) only within the same directory.\n  - Use named exports exclusively; avoid default exports. It's acceptable to export functions directly (e.g., React Components).\n  - Our indent is 4 spaces\n- **JSON Field Naming**: All fields must be lowercase, without underscores.\n- **TypeScript Conventions**\n  - **Type Handling**:\n    - In TypeScript we have strict null checks off, so no need to add \"| null\" to all the types.\n    - In TypeScript for Jotai atoms, if we want to write, we need to type the atom as a PrimitiveAtom<Type>\n    - Jotai has a bug with strict null checks off where if you create a null atom, e.g. atom(null) it does not \"type\" correctly. That's no issue, just cast it to the proper PrimitiveAtom type (no \"| null\") and it will work fine.\n    - Generally never use \"=== undefined\" or \"!== undefined\". This is bad style. Just use a \"== null\" or \"!= null\" unless it is a very specific case where we need to distinguish undefined from null.\n  - **Coding Style**:\n    - Use all lowercase filenames (except where case is actually important like Taskfile.yml)\n    - Import the \"cn\" function from \"@/util/util\" to do classname / clsx class merge (it uses twMerge underneath)\n    - Do NOT create private fields in classes (they are impossible to inspect)\n    - Use PascalCase for global consts at the top of files\n  - **Component Practices**:\n    - Make sure to add cursor-pointer to buttons/links and clickable items\n    - NEVER use cursor-help (it looks terrible)\n    - useAtom() and useAtomValue() are react HOOKS, so they must be called at the component level not inline in JSX\n    - If you use React.memo(), make sure to add a displayName for the component\n  - Other\n    - never use atob() or btoa() (not UTF-8 safe). use functions in frontend/util/util.ts for base64 decoding and encoding\n- In general, when writing functions, we prefer _early returns_ rather than putting the majority of a function inside of an if block.\n\n### Styling\n\n- We use **Tailwind v4** to style. Custom stuff is defined in frontend/tailwindsetup.css\n- _never_ use cursor-help, or cursor-not-allowed (it looks terrible)\n- We have custom CSS setup as well, so it is a hybrid system. For new code we prefer tailwind, and are working to migrate code to all use tailwind.\n- For accent buttons, use \"bg-accent/80 text-primary rounded hover:bg-accent transition-colors cursor-pointer\" (if you do \"bg-accent hover:bg-accent/80\" it looks weird as on hover the button gets darker instead of lighter)\n\n### RPC System\n\nTo define a new RPC call, add the new definition to `pkg/wshrpc/wshrpctypes.go` including any input/output data that is required. After modifying wshrpctypes.go run `task generate` to generate the client APIs.\n\nFor normal \"server\" RPCs (where a frontend client is calling the main server) you should implement the RPC call in `pkg/wshrpc/wshserver.go`.\n\n### Electron API\n\nFrom within the FE to get the electron API (e.g. the preload functions):\n\n```ts\nimport { getApi } from \"@/store/global\";\n\ngetApi().getIsDev();\n```\n\nThe full API is defined in custom.d.ts as type ElectronApi.\n\n### Code Generation\n\n- **TypeScript Types**: TypeScript types are automatically generated from Go types. After modifying Go types in `pkg/wshrpc/wshrpctypes.go`, run `task generate` to update the TypeScript type definitions in `frontend/types/gotypes.d.ts`.\n- **Manual Edits**: Do not manually edit generated files like `frontend/types/gotypes.d.ts` or `frontend/app/store/wshclientapi.ts`. Instead, modify the source Go types and run `task generate`.\n\n### Frontend Architecture\n\n- The application uses Jotai for state management.\n- When working with Jotai atoms that need to be updated, define them as `PrimitiveAtom<Type>` rather than just `atom<Type>`.\n\n### Notes\n\n- **CRITICAL: Completion format MUST be: \"Done: [one-line description]\"**\n- **Keep your Task Completed summaries VERY short**\n- **No lengthy pre-completion summaries** - Do not provide detailed explanations of implementation before using attempt_completion\n- **No recaps of changes** - Skip explaining what was done before completion\n- **Go directly to completion** - After making changes, proceed directly to attempt_completion without summarizing\n- The project is currently an un-released POC / MVP. Do not worry about backward compatibility when making changes\n- With React hooks, always complete all hook calls at the top level before any conditional returns (including jotai hook calls useAtom and useAtomValue); when a user explicitly tells you a function handles null inputs, trust them and stop trying to \"protect\" it with unnecessary checks or workarounds.\n- **Match response length to question complexity** - For simple, direct questions in Ask mode (especially those that can be answered in 1-2 sentences), provide equally brief answers. Save detailed explanations for complex topics or when explicitly requested.\n- **CRITICAL** - useAtomValue and useAtom are React HOOKS. They cannot be used inline in JSX code, they must appear at the top of a component in the hooks area of the react code.\n- for simple functions, we prefer `if (!cond) { return }; functionality;` pattern over `if (cond) { functionality }` because it produces less indentation and is easier to follow.\n- It is now 2026, so if you write new files, or update files use 2026 for the copyright year\n- React.MutableRefObject is deprecated, just use React.RefObject now (in React 19 RefObject is always mutable)\n\n### Strict Comment Rules\n\n- **NEVER add comments that merely describe what code is doing**:\n  - ❌ `mutex.Lock() // Lock the mutex`\n  - ❌ `counter++ // Increment the counter`\n  - ❌ `buffer.Write(data) // Write data to buffer`\n  - ❌ `// Header component for app run list` (above AppRunListHeader)\n  - ❌ `// Updated function to include onClick parameter`\n  - ❌ `// Changed padding calculation`\n  - ❌ `// Removed unnecessary div`\n  - ❌ `// Using the model's width value here`\n- **Only use comments for**:\n  - Explaining WHY a particular approach was chosen\n  - Documenting non-obvious edge cases or side effects\n  - Warning about potential pitfalls in usage\n  - Explaining complex algorithms that can't be simplified\n- **When in doubt, leave it out**. No comment is better than a redundant comment.\n- **Never add comments explaining code changes** - The code should speak for itself, and version control tracks changes. The one exception to this rule is if it is a very unobvious implementation. Something that someone would typically implement in a different (wrong) way. Then the comment helps us remember WHY we changed it to a less obvious implementation.\n- **Never remove existing comments** unless specifically directed by the user. Comments that are already defined in existing code have been vetted by the user.\n\n### Jotai Model Pattern (our rules)\n\n- **Atoms live on the model.**\n- **Simple atoms:** define as **field initializers**.\n- **Atoms that depend on values/other atoms:** create in the **constructor**.\n- Models **never use React hooks**; they use `globalStore.get/set`.\n- It's fine to call model methods from **event handlers** or **`useEffect`**.\n- Models use the **singleton pattern** with a `private static instance` field, a `private constructor`, and a `static getInstance()` method.\n- The constructor is `private`; callers always use `getInstance()`.\n\n```ts\n// model/MyModel.ts\nimport * as jotai from \"jotai\";\nimport { globalStore } from \"@/app/store/jotaiStore\";\n\nexport class MyModel {\n  private static instance: MyModel | null = null;\n\n  // simple atoms (field init)\n  statusAtom = jotai.atom<\"idle\" | \"running\" | \"error\">(\"idle\");\n  outputAtom = jotai.atom(\"\");\n\n  // ctor-built atoms (need types)\n  lengthAtom!: jotai.Atom<number>;\n  thresholdedAtom!: jotai.Atom<boolean>;\n\n  private constructor(initialThreshold = 20) {\n    this.lengthAtom = jotai.atom((get) => get(this.outputAtom).length);\n    this.thresholdedAtom = jotai.atom((get) => get(this.lengthAtom) > initialThreshold);\n  }\n\n  static getInstance(): MyModel {\n    if (!MyModel.instance) {\n      MyModel.instance = new MyModel();\n    }\n    return MyModel.instance;\n  }\n\n  static resetInstance(): void {\n    MyModel.instance = null;\n  }\n\n  async doWork() {\n    globalStore.set(this.statusAtom, \"running\");\n    // ... do work ...\n    globalStore.set(this.statusAtom, \"idle\");\n  }\n}\n```\n\n```tsx\n// component usage (events & effects OK)\nimport { useAtomValue } from \"jotai\";\n\nfunction Panel() {\n  const model = MyModel.getInstance();\n  const status = useAtomValue(model.statusAtom);\n  const isBig = useAtomValue(model.thresholdedAtom);\n\n  const onClick = () => model.doWork();\n\n  return (\n    <div>\n      {status} • {String(isBig)}\n    </div>\n  );\n}\n```\n\n**Remember:** singleton pattern with `getInstance()`, `private constructor`, atoms on the model, simple-as-fields, ctor for dependent/derived, updates via `globalStore.set/get`.\n**Note** Older models may not use the singleton pattern\n\n### Tool Use\n\nDo NOT use write_to_file unless it is a new file or very short. Always prefer to use replace_in_file. Often your diffs fail when a file may be out of date in your cache vs the actual on-disk format. You should RE-READ the file and try to create diffs again if your diffs fail rather than fall back to write_to_file. If you feel like your ONLY option is to use write_to_file please ask first.\n\nAlso when adding content to the end of files prefer to use the new append_file tool rather than trying to create a diff (as your diffs are often not specific enough and end up inserting code in the middle of existing functions).\n\n### Directory Awareness\n\n- **ALWAYS verify the current working directory before executing commands**\n- Either run \"pwd\" first to verify the directory, or do a \"cd\" to the correct absolute directory before running commands\n- When running tests, do not \"cd\" to the pkg directory and then run the test. This screws up the cwd and you never recover. run the test from the project root instead.\n\n### Testing / Compiling Go Code\n\nNo need to run a `go build` or a `go run` to just check if the Go code compiles. VSCode's errors/problems cover this well.\nIf there are no Go errors in VSCode you can assume the code compiles fine.\n","isInternal":false,"tokens":2764,"sizeBytes":11918},{"name":"SKILL.md","path":".kilocode/skills/add-config/SKILL.md","rawUrl":"https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.kilocode/skills/add-config/SKILL.md","title":"add-config","category":"anthropic-skill","format":"markdown","content":"---\nname: add-config\ndescription: Guide for adding new configuration settings to Wave Terminal. Use when adding a new setting to the configuration system, implementing a new config key, or adding user-customizable settings.\n---\n\n# Adding a New Configuration Setting to Wave Terminal\n\nThis guide explains how to add a new configuration setting to Wave Terminal's hierarchical configuration system.\n\n## Configuration System Overview\n\nWave Terminal uses a hierarchical configuration system with:\n\n1. **Go Struct Definitions** - Type-safe configuration structure in `pkg/wconfig/settingsconfig.go`\n2. **JSON Schema** - Auto-generated validation schema in `schema/settings.json`\n3. **Default Values** - Built-in defaults in `pkg/wconfig/defaultconfig/settings.json`\n4. **User Configuration** - User overrides in `~/.config/waveterm/settings.json`\n5. **Block Metadata** - Block-level overrides in `pkg/waveobj/wtypemeta.go`\n6. **Documentation** - User-facing docs in `docs/docs/config.mdx`\n\nSettings cascade from defaults → user settings → connection config → block overrides.\n\n## Step-by-Step Guide\n\n### Step 1: Add to Go Struct Definition\n\nEdit `pkg/wconfig/settingsconfig.go` and add your new field to the `SettingsType` struct:\n\n```go\ntype SettingsType struct {\n    // ... existing fields ...\n\n    // Add your new field with appropriate JSON tag\n    MyNewSetting string `json:\"mynew:setting,omitempty\"`\n\n    // For different types:\n    MyBoolSetting   bool    `json:\"mynew:boolsetting,omitempty\"`\n    MyNumberSetting float64 `json:\"mynew:numbersetting,omitempty\"`\n    MyIntSetting    *int64  `json:\"mynew:intsetting,omitempty\"`    // Use pointer for optional ints\n    MyArraySetting  []string `json:\"mynew:arraysetting,omitempty\"`\n}\n```\n\n**Naming Conventions:**\n\n- Use namespace prefixes (e.g., `term:`, `window:`, `ai:`, `web:`, `app:`)\n- Use lowercase with colons as separators\n- Field names should be descriptive and follow Go naming conventions\n- Use `omitempty` tag to exclude empty values from JSON\n\n**Type Guidelines:**\n\n- Use `*int64` and `*float64` for optional numeric values\n- Use `*bool` for optional boolean values (or `bool` if default is false)\n- Use `string` for text values\n- Use `[]string` for arrays\n- Use `float64` for numbers that can be decimals\n\n**Namespace Organization:**\n\n- `app:*` - Application-level settings\n- `term:*` - Terminal-specific settings\n- `window:*` - Window and UI settings\n- `ai:*` - AI-related settings\n- `web:*` - Web browser settings\n- `editor:*` - Code editor settings\n- `conn:*` - Connection settings\n\n### Step 1.5: Add to Block Metadata (Optional)\n\nIf your setting should support block-level overrides, also add it to `pkg/waveobj/wtypemeta.go`:\n\n```go\ntype MetaTSType struct {\n    // ... existing fields ...\n\n    // Add your new field with matching JSON tag and type\n    MyNewSetting *string `json:\"mynew:setting,omitempty\"`  // Use pointer for optional values\n\n    // For different types:\n    MyBoolSetting   *bool    `json:\"mynew:boolsetting,omitempty\"`\n    MyNumberSetting *float64 `json:\"mynew:numbersetting,omitempty\"`\n    MyIntSetting    *int     `json:\"mynew:intsetting,omitempty\"`\n    MyArraySetting  []string `json:\"mynew:arraysetting,omitempty\"`\n}\n```\n\n**Block Metadata Guidelines:**\n\n- Use pointer types (`*string`, `*bool`, `*int`, `*float64`) for optional overrides\n- JSON tags should exactly match the corresponding settings field\n- This enables the hierarchical config system: block metadata → connection config → global settings\n- Only add settings here that make sense to override per-block or per-connection\n\n### Step 2: Set Default Value (Optional)\n\nIf your setting should have a default value, add it to `pkg/wconfig/defaultconfig/settings.json`:\n\n```json\n{\n  \"ai:preset\": \"ai@global\",\n  \"ai:model\": \"gpt-5-mini\",\n  // ... existing defaults ...\n\n  \"mynew:setting\": \"default value\",\n  \"mynew:boolsetting\": true,\n  \"mynew:numbersetting\": 42.5,\n  \"mynew:intsetting\": 100\n}\n```\n\n**Default Value Guidelines:**\n\n- Only add defaults for settings that should have non-zero/non-empty initial values\n- Ensure defaults make sense for typical user experience\n- Keep defaults conservative and safe\n- Boolean settings often don't need defaults if `false` is the correct default\n\n### Step 3: Update Documentation\n\nAdd your new setting to the configuration table in `docs/docs/config.mdx`:\n\n```markdown\n| Key Name            | Type     | Function                                  |\n| ------------------- | -------- | ----------------------------------------- |\n| mynew:setting       | string   | Description of what this setting controls |\n| mynew:boolsetting   | bool     | Enable/disable some feature               |\n| mynew:numbersetting | float    | Numeric setting for some parameter        |\n| mynew:intsetting    | int      | Integer setting for some configuration    |\n| mynew:arraysetting  | string[] | Array of strings for multiple values      |\n```\n\n**Documentation Guidelines:**\n\n- Provide clear, concise descriptions\n- For new settings in upcoming releases, add `<VersionBadge version=\"v0.14\" />`\n- Update the default configuration example if you added defaults\n- Explain what values are valid and what they do\n\n### Step 4: Regenerate Schema and TypeScript Types\n\nRun the generate task to automatically regenerate the JSON schema and TypeScript types:\n\n```bash\ntask generate\n```\n\n**What this does:**\n\n- Runs `task build:schema` (automatically generates JSON schema from Go structs)\n- Generates TypeScript type definitions in `frontend/types/gotypes.d.ts`\n- Generates RPC client APIs\n- Generates metadata constants\n\n**Important:** The JSON schema in `schema/settings.json` is **automatically generated** from the Go struct definitions - you don't need to edit it manually.\n\n### Step 5: Use in Frontend Code\n\nAccess your new setting in React components:\n\n```typescript\nimport { getOverrideConfigAtom, getSettingsKeyAtom, useAtomValue } from \"@/store/global\";\n\n// In a React component\nconst MyComponent = ({ blockId }: { blockId: string }) => {\n    // Use override config atom for hierarchical resolution\n    // This automatically checks: block metadata → connection config → global settings → default\n    const mySettingAtom = getOverrideConfigAtom(blockId, \"mynew:setting\");\n    const mySetting = useAtomValue(mySettingAtom) ?? \"fallback value\";\n\n    // For global-only settings (no block overrides)\n    const globalOnlySetting = useAtomValue(getSettingsKeyAtom(\"mynew:globalsetting\")) ?? \"fallback\";\n\n    return <div>Setting value: {mySetting}</div>;\n};\n```\n\n**Frontend Configuration Patterns:**\n\n```typescript\n// 1. Settings with block-level overrides (recommended for most view/display settings)\nconst termFontSize = useAtomValue(getOverrideConfigAtom(blockId, \"term:fontsize\")) ?? 12;\n\n// 2. Global-only settings (app-wide settings that don't vary by block)\nconst appGlobalHotkey = useAtomValue(getSettingsKeyAtom(\"app:globalhotkey\")) ?? \"\";\n\n// 3. Connection-specific settings\nconst connStatus = useAtomValue(getConnStatusAtom(connectionName));\n```\n\n**When to use each pattern:**\n\n- Use `getOverrideConfigAtom()` for settings that can vary by block or connection (most UI/display settings)\n- Use `getSettingsKeyAtom()` for app-level settings that are always global\n- Always provide a fallback value with `??` operator\n\n### Step 6: Use in Backend Code\n\nAccess settings in Go code:\n\n```go\n// Get the full config\nfullConfig := wconfig.GetWatcher().GetFullConfig()\n\n// Access your setting\nmyValue := fullConfig.Settings.MyNewSetting\n\n// For optional values (pointers)\nif fullConfig.Settings.MyIntSetting != nil {\n    intValue := *fullConfig.Settings.MyIntSetting\n    // Use intValue\n}\n```\n\n## Complete Examples\n\n### Example 1: Simple Boolean Setting (No Block Override)\n\n**Use case:** Add a setting to hide the AI button globally\n\n#### 1. Go Struct (`pkg/wconfig/settingsconfig.go`)\n\n```go\ntype SettingsType struct {\n    // ... existing fields ...\n    AppHideAiButton bool `json:\"app:hideaibutton,omitempty\"`\n}\n```\n\n#### 2. Default Value (`pkg/wconfig/defaultconfig/settings.json`)\n\n```json\n{\n  \"app:hideaibutton\": false\n}\n```\n\n#### 3. Documentation (`docs/docs/config.mdx`)\n\n```markdown\n| app:hideaibutton <VersionBadge version=\"v0.14\" /> | bool | Hide the AI button in the tab bar (defaults to false) |\n```\n\n#### 4. Generate Types\n\n```bash\ntask generate\n```\n\n#### 5. Frontend Usage\n\n```typescript\nimport { getSettingsKeyAtom } from \"@/store/global\";\n\nconst TabBar = () => {\n    const hideAiButton = useAtomValue(getSettingsKeyAtom(\"app:hideaibutton\"));\n\n    if (hideAiButton) {\n        return null; // Don't render AI button\n    }\n\n    return <button>AI</button>;\n};\n```\n\n#### 6. Usage Examples\n\n```bash\n# Set in settings file\nwsh setconfig app:hideaibutton=true\n\n# Or edit ~/.config/waveterm/settings.json\n{\n  \"app:hideaibutton\": true\n}\n```\n\n### Example 2: Terminal Setting with Block Override\n\n**Use case:** Add a terminal bell sound setting that can be overridden per block\n\n#### 1. Go Struct (`pkg/wconfig/settingsconfig.go`)\n\n```go\ntype SettingsType struct {\n    // ... existing fields ...\n    TermBellSound string `json:\"term:bellsound,omitempty\"`\n}\n```\n\n#### 2. Block Metadata (`pkg/waveobj/wtypemeta.go`)\n\n```go\ntype MetaTSType struct {\n    // ... existing fields ...\n    TermBellSound *string `json:\"term:bellsound,omitempty\"`  // Pointer for optional override\n}\n```\n\n#### 3. Default Value (`pkg/wconfig/defaultconfig/settings.json`)\n\n```json\n{\n  \"term:bellsound\": \"default\"\n}\n```\n\n#### 4. Documentation (`docs/docs/config.mdx`)\n\n```markdown\n| term:bellsound <VersionBadge version=\"v0.14\" /> | string | Sound to play for terminal bell (\"default\", \"none\", or custom sound file path) |\n```\n\n#### 5. Generate Types\n\n```bash\ntask generate\n```\n\n#### 6. Frontend Usage\n\n```typescript\nimport { getOverrideConfigAtom } from \"@/store/global\";\n\nconst TerminalView = ({ blockId }: { blockId: string }) => {\n    // Use override config for hierarchical resolution\n    const bellSoundAtom = getOverrideConfigAtom(blockId, \"term:bellsound\");\n    const bellSound = useAtomValue(bellSoundAtom) ?? \"default\";\n\n    const playBellSound = () => {\n        if (bellSound === \"none\") return;\n        // Play the bell sound\n    };\n\n    return <div>Terminal with bell: {bellSound}</div>;\n};\n```\n\n#### 7. Usage Examples\n\n```bash\n# Set globally in settings file\nwsh setconfig term:bellsound=\"custom.wav\"\n\n# Set for current block only\nwsh setmeta term:bellsound=\"none\"\n\n# Set for specific block\nwsh setmeta --block BLOCK_ID term:bellsound=\"beep\"\n\n# Or edit ~/.config/waveterm/settings.json\n{\n  \"term:bellsound\": \"custom.wav\"\n}\n```\n\n## Configuration Patterns\n\n### Clear/Reset Pattern\n\nEach namespace can have a \"clear\" field for resetting all settings in that namespace:\n\n```go\nAppClear  bool `json:\"app:*,omitempty\"`\nTermClear bool `json:\"term:*,omitempty\"`\n```\n\n### Optional vs Required Settings\n\n- Use pointer types (`*bool`, `*int64`, `*float64`) for truly optional settings\n- Use regular types for settings that should always have a value\n- Provide sensible defaults for important settings\n\n### Block-Level Overrides via RPC\n\nSettings can be overridden at the block level using metadata:\n\n```typescript\nimport { RpcApi } from \"@/app/store/wshclientapi\";\nimport { TabRpcClient } from \"@/app/store/wshrpcutil\";\nimport { WOS } from \"@/store/global\";\n\n// Set block-specific override\nawait RpcApi.SetMetaCommand(TabRpcClient, {\n  oref: WOS.makeORef(\"block\", blockId),\n  meta: { \"mynew:setting\": \"block-specific value\" },\n});\n```\n\n## Common Pitfalls\n\n### 1. Forgetting to Run `task generate`\n\n**Problem:** TypeScript types not updated, schema out of sync\n\n**Solution:** Always run `task generate` after modifying Go structs\n\n### 2. Type Mismatch Between Settings and Metadata\n\n**Problem:** Settings uses `string`, metadata uses `*int`\n\n**Solution:** Ensure types match (except metadata uses pointers for optionals)\n\n### 3. Not Providing Fallback Values\n\n**Problem:** Component breaks if setting is undefined\n\n**Solution:** Always use `??` operator with fallback:\n\n```typescript\nconst value = useAtomValue(getSettingsKeyAtom(\"key\")) ?? \"default\";\n```\n\n### 4. Using Wrong Config Atom\n\n**Problem:** Using `getSettingsKeyAtom()` for settings that need block overrides\n\n**Solution:** Use `getOverrideConfigAtom()` for any setting in `MetaTSType`\n\n## Best Practices\n\n### Naming\n\n- **Use descriptive names**: `term:fontsize` not `term:fs`\n- **Follow namespace conventions**: Group related settings with common prefix\n- **Use consistent casing**: Always lowercase with colons\n\n### Types\n\n- **Use `bool`** for simple on/off settings (no pointer if false is default)\n- **Use `*bool`** only if you need to distinguish unset from false\n- **Use `*int64`/`*float64`** for optional numeric values\n- **Use `string`** for text, paths, or enum-like values\n- **Use `[]string`** for lists\n\n### Defaults\n\n- **Provide sensible defaults** for settings users will commonly change\n- **Omit defaults** for advanced/optional settings\n- **Keep defaults safe** - don't enable experimental features by default\n- **Document defaults** clearly in config.mdx\n\n### Block Overrides\n\n- **Enable for view/display settings**: Font sizes, colors, themes, etc.\n- **Don't enable for app-wide settings**: Global hotkeys, window behavior, etc.\n- **Consider the use case**: Would a user want different values per block or connection?\n\n### Documentation\n\n- **Be specific**: Explain what the setting does and what values are valid\n- **Provide examples**: Show common use cases\n- **Add version badges**: Mark new settings with `<VersionBadge version=\"v0.x\" />`\n- **Keep it current**: Update docs when behavior changes\n\n## Quick Reference\n\nWhen adding a new configuration setting:\n\n- [ ] Add field to `SettingsType` in `pkg/wconfig/settingsconfig.go`\n- [ ] Add field to `MetaTSType` in `pkg/waveobj/wtypemeta.go` (if block override needed)\n- [ ] Add default to `pkg/wconfig/defaultconfig/settings.json` (if needed)\n- [ ] Document in `docs/docs/config.mdx`\n- [ ] Run `task generate` to update TypeScript types\n- [ ] Use appropriate atom (`getOverrideConfigAtom` or `getSettingsKeyAtom`) in frontend\n\n## Related Documentation\n\n- **User Documentation**: `docs/docs/config.mdx` - User-facing configuration docs\n- **Type Definitions**: `pkg/wconfig/settingsconfig.go` - Go struct definitions\n- **Metadata Types**: `pkg/waveobj/wtypemeta.go` - Block metadata definitions\n","frontmatter":{"name":"add-config","description":"Guide for adding new configuration settings to Wave Terminal. Use when adding a new setting to the configuration system, implementing a new config key, or adding user-customizable settings."},"isInternal":false,"tokens":3449,"sizeBytes":14377},{"name":"SKILL.md","path":".kilocode/skills/add-rpc/SKILL.md","rawUrl":"https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.kilocode/skills/add-rpc/SKILL.md","title":"add-rpc","category":"anthropic-skill","format":"markdown","content":"---\nname: add-rpc\ndescription: Guide for adding new RPC calls to Wave Terminal. Use when implementing new RPC commands, adding server-client communication methods, or extending the RPC interface with new functionality.\n---\n\n# Adding RPC Calls Guide\n\n## Overview\n\nWave Terminal uses a WebSocket-based RPC (Remote Procedure Call) system for communication between different components. The RPC system allows the frontend, backend, electron main process, remote servers, and terminal blocks to communicate with each other through well-defined commands.\n\nThis guide covers how to add a new RPC command to the system.\n\n## Key Files\n\n- `pkg/wshrpc/wshrpctypes.go` - RPC interface and type definitions\n- `pkg/wshrpc/wshserver/wshserver.go` - Main server implementation (most common)\n- `emain/emain-wsh.ts` - Electron main process implementation\n- `frontend/app/store/tabrpcclient.ts` - Frontend tab implementation\n- `pkg/wshrpc/wshremote/wshremote.go` - Remote server implementation\n- `frontend/app/view/term/term-wsh.tsx` - Terminal block implementation\n\n## RPC Command Structure\n\nRPC commands in Wave Terminal follow these conventions:\n\n- **Method names** must end with `Command`\n- **First parameter** must be `context.Context`\n- **Remaining parameters** are a regular Go parameter list (zero or more typed args)\n- **Return values** can be either just an error, or one return value plus an error\n- **Streaming commands** return a channel instead of a direct value\n\n## Adding a New RPC Call\n\n### Step 1: Define the Command in the Interface\n\nAdd your command to the `WshRpcInterface` in `pkg/wshrpc/wshrpctypes.go`:\n\n```go\ntype WshRpcInterface interface {\n    // ... existing commands ...\n    \n    // Add your new command\n    YourNewCommand(ctx context.Context, data CommandYourNewData) (*YourNewResponse, error)\n}\n```\n\n**Method Signature Rules:**\n\n- Method name must end with `Command`\n- First parameter must be `ctx context.Context`\n- Remaining parameters are a regular Go parameter list (zero or more)\n- Return either `error` or `(ReturnType, error)`\n- For streaming, return `chan RespOrErrorUnion[T]`\n\n### Step 2: Define Request and Response Types\n\nIf your command needs structured input or output, define types in the same file:\n\n```go\ntype CommandYourNewData struct {\n    FieldOne   string `json:\"fieldone\"`\n    FieldTwo   int    `json:\"fieldtwo\"`\n    SomeId     string `json:\"someid\"`\n}\n\ntype YourNewResponse struct {\n    ResultField string `json:\"resultfield\"`\n    Success     bool   `json:\"success\"`\n}\n```\n\n**Type Naming Conventions:**\n\n- Request types: `Command[Name]Data` (e.g., `CommandGetMetaData`)\n- Response types: `[Name]Response` or `Command[Name]RtnData` (e.g., `CommandResolveIdsRtnData`)\n- Use `json` struct tags with lowercase field names\n- Follow existing patterns in the file for consistency\n\n### Step 3: Generate Bindings\n\nAfter modifying `pkg/wshrpc/wshrpctypes.go`, run code generation to create TypeScript bindings and Go helper code:\n\n```bash\ntask generate\n```\n\nThis command will:\n- Generate TypeScript type definitions in `frontend/types/gotypes.d.ts`\n- Create RPC client bindings\n- Update routing code\n\n**Note:** If generation fails, check that your method signature follows all the rules above.\n\n### Step 4: Implement the Command\n\nChoose where to implement your command based on what it needs to do:\n\n#### A. Main Server Implementation (Most Common)\n\nImplement in `pkg/wshrpc/wshserver/wshserver.go`:\n\n```go\nfunc (ws *WshServer) YourNewCommand(ctx context.Context, data wshrpc.CommandYourNewData) (*wshrpc.YourNewResponse, error) {\n    // Validate input\n    if data.SomeId == \"\" {\n        return nil, fmt.Errorf(\"someid is required\")\n    }\n    \n    // Implement your logic\n    result := doSomething(data)\n    \n    // Return response\n    return &wshrpc.YourNewResponse{\n        ResultField: result,\n        Success:     true,\n    }, nil\n}\n```\n\n**Use main server when:**\n- Accessing the database\n- Managing blocks, tabs, or workspaces\n- Coordinating between components\n- Handling file operations on the main filesystem\n\n#### B. Electron Implementation\n\nImplement in `emain/emain-wsh.ts`:\n\n```typescript\nasync handle_yournew(rh: RpcResponseHelper, data: CommandYourNewData): Promise<YourNewResponse> {\n    // Electron-specific logic\n    const result = await electronAPI.doSomething(data);\n    \n    return {\n        resultfield: result,\n        success: true,\n    };\n}\n```\n\n**Use Electron when:**\n- Accessing native OS features\n- Managing application windows\n- Using Electron APIs (notifications, system tray, etc.)\n- Handling encryption/decryption with safeStorage\n\n#### C. Frontend Tab Implementation\n\nImplement in `frontend/app/store/tabrpcclient.ts`:\n\n```typescript\nasync handle_yournew(rh: RpcResponseHelper, data: CommandYourNewData): Promise<YourNewResponse> {\n    // Access frontend state/models\n    const layoutModel = getLayoutModelForStaticTab();\n    \n    // Implement tab-specific logic\n    const result = layoutModel.doSomething(data);\n    \n    return {\n        resultfield: result,\n        success: true,\n    };\n}\n```\n\n**Use tab client when:**\n- Accessing React state or Jotai atoms\n- Manipulating UI layout\n- Capturing screenshots\n- Reading frontend-only data\n\n#### D. Remote Server Implementation\n\nImplement in `pkg/wshrpc/wshremote/wshremote.go`:\n\n```go\nfunc (impl *ServerImpl) RemoteYourNewCommand(ctx context.Context, data wshrpc.CommandRemoteYourNewData) (*wshrpc.YourNewResponse, error) {\n    // Remote filesystem or process operations\n    result, err := performRemoteOperation(data)\n    if err != nil {\n        return nil, fmt.Errorf(\"remote operation failed: %w\", err)\n    }\n    \n    return &wshrpc.YourNewResponse{\n        ResultField: result,\n        Success:     true,\n    }, nil\n}\n```\n\n**Use remote server when:**\n- Operating on remote filesystems\n- Executing commands on remote hosts\n- Managing remote processes\n- Convention: prefix command name with `Remote` (e.g., `RemoteGetInfoCommand`)\n\n#### E. Terminal Block Implementation\n\nImplement in `frontend/app/view/term/term-wsh.tsx`:\n\n```typescript\nasync handle_yournew(rh: RpcResponseHelper, data: CommandYourNewData): Promise<YourNewResponse> {\n    // Access terminal-specific data\n    const termWrap = this.model.termRef.current;\n    \n    // Implement terminal logic\n    const result = termWrap.doSomething(data);\n    \n    return {\n        resultfield: result,\n        success: true,\n    };\n}\n```\n\n**Use terminal client when:**\n- Accessing terminal buffer/scrollback\n- Managing VDOM contexts\n- Reading terminal-specific state\n- Interacting with xterm.js\n\n## Complete Example: Adding GetWaveInfo Command\n\n### 1. Define Interface\n\nIn `pkg/wshrpc/wshrpctypes.go`:\n\n```go\ntype WshRpcInterface interface {\n    // ... other commands ...\n    WaveInfoCommand(ctx context.Context) (*WaveInfoData, error)\n}\n\ntype WaveInfoData struct {\n    Version      string            `json:\"version\"`\n    BuildTime    string            `json:\"buildtime\"`\n    ConfigPath   string            `json:\"configpath\"`\n    DataPath     string            `json:\"datapath\"`\n}\n```\n\n### 2. Generate Bindings\n\n```bash\ntask generate\n```\n\n### 3. Implement in Main Server\n\nIn `pkg/wshrpc/wshserver/wshserver.go`:\n\n```go\nfunc (ws *WshServer) WaveInfoCommand(ctx context.Context) (*wshrpc.WaveInfoData, error) {\n    return &wshrpc.WaveInfoData{\n        Version:    wavebase.WaveVersion,\n        BuildTime:  wavebase.BuildTime,\n        ConfigPath: wavebase.GetConfigDir(),\n        DataPath:   wavebase.GetWaveDataDir(),\n    }, nil\n}\n```\n\n### 4. Call from Frontend\n\n```typescript\nimport { RpcApi } from \"@/app/store/wshclientapi\";\n\n// Call the RPC\nconst info = await RpcApi.WaveInfoCommand(TabRpcClient);\nconsole.log(\"Wave Version:\", info.version);\n```\n\n## Streaming Commands\n\nFor commands that return data progressively, use channels:\n\n### Define Streaming Interface\n\n```go\ntype WshRpcInterface interface {\n    StreamYourDataCommand(ctx context.Context, request YourDataRequest) chan RespOrErrorUnion[YourDataType]\n}\n```\n\n### Implement Streaming Command\n\n```go\nfunc (ws *WshServer) StreamYourDataCommand(ctx context.Context, request wshrpc.YourDataRequest) chan wshrpc.RespOrErrorUnion[wshrpc.YourDataType] {\n    rtn := make(chan wshrpc.RespOrErrorUnion[wshrpc.YourDataType])\n    \n    go func() {\n        defer close(rtn)\n        defer func() {\n            panichandler.PanicHandler(\"StreamYourDataCommand\", recover())\n        }()\n        \n        // Stream data\n        for i := 0; i < 10; i++ {\n            select {\n            case <-ctx.Done():\n                return\n            default:\n                rtn <- wshrpc.RespOrErrorUnion[wshrpc.YourDataType]{\n                    Response: wshrpc.YourDataType{\n                        Value: i,\n                    },\n                }\n                time.Sleep(100 * time.Millisecond)\n            }\n        }\n    }()\n    \n    return rtn\n}\n```\n\n## Best Practices\n\n1. **Validation First**: Always validate input parameters at the start of your implementation\n\n2. **Descriptive Names**: Use clear, action-oriented command names (e.g., `GetFullConfigCommand`, not `ConfigCommand`)\n\n3. **Error Handling**: Return descriptive errors with context:\n   ```go\n   return nil, fmt.Errorf(\"error creating block: %w\", err)\n   ```\n\n4. **Context Awareness**: Respect context cancellation for long-running operations:\n   ```go\n   select {\n   case <-ctx.Done():\n       return ctx.Err()\n   default:\n       // continue\n   }\n   ```\n\n5. **Consistent Types**: Follow existing naming patterns for request/response types\n\n6. **JSON Tags**: Always use lowercase JSON tags matching frontend conventions\n\n7. **Documentation**: Add comments explaining complex commands or special behaviors\n\n8. **Type Safety**: Leverage TypeScript generation - your types will be checked on both ends\n\n9. **Panic Recovery**: Use `panichandler.PanicHandler` in goroutines to prevent crashes\n\n10. **Route Awareness**: For multi-route scenarios, use `wshutil.GetRpcSourceFromContext(ctx)` to identify callers\n\n## Common Command Patterns\n\n### Simple Query\n\n```go\nfunc (ws *WshServer) GetSomethingCommand(ctx context.Context, id string) (*Something, error) {\n    obj, err := wstore.DBGet[*Something](ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"error getting something: %w\", err)\n    }\n    return obj, nil\n}\n```\n\n### Mutation with Updates\n\n```go\nfunc (ws *WshServer) UpdateSomethingCommand(ctx context.Context, data wshrpc.CommandUpdateData) error {\n    ctx = waveobj.ContextWithUpdates(ctx)\n    \n    // Make changes\n    err := wstore.UpdateObject(ctx, data.ORef, data.Updates)\n    if err != nil {\n        return fmt.Errorf(\"error updating: %w\", err)\n    }\n    \n    // Broadcast updates\n    updates := waveobj.ContextGetUpdatesRtn(ctx)\n    wps.Broker.SendUpdateEvents(updates)\n    \n    return nil\n}\n```\n\n### Command with Side Effects\n\n```go\nfunc (ws *WshServer) DoActionCommand(ctx context.Context, data wshrpc.CommandActionData) error {\n    // Perform action\n    result, err := performAction(data)\n    if err != nil {\n        return err\n    }\n    \n    // Publish event about the action\n    go func() {\n        wps.Broker.Publish(wps.WaveEvent{\n            Event: wps.Event_ActionComplete,\n            Data:  result,\n        })\n    }()\n    \n    return nil\n}\n```\n\n## Troubleshooting\n\n### Command Not Found\n\n- Ensure method name ends with `Command`\n- Verify you ran `task generate`\n- Check that the interface is in `WshRpcInterface`\n\n### Type Mismatch Errors\n\n- Run `task generate` after changing types\n- Ensure JSON tags are lowercase\n- Verify TypeScript code is using generated types\n\n### Command Times Out\n\n- Check for blocking operations\n- Ensure context is passed through\n- Consider using a streaming command for long operations\n\n### Routing Issues\n\n- For remote commands, ensure they're implemented in correct location\n- Check route configuration in RpcContext\n- Verify authentication for secured routes\n\n## Quick Reference\n\nWhen adding a new RPC command:\n\n- [ ] Add method to `WshRpcInterface` in `pkg/wshrpc/wshrpctypes.go` (must end with `Command`)\n- [ ] Define request/response types with JSON tags (if needed)\n- [ ] Run `task generate` to create bindings\n- [ ] Implement in appropriate location:\n  - [ ] `wshserver.go` for main server (most common)\n  - [ ] `emain-wsh.ts` for Electron\n  - [ ] `tabrpcclient.ts` for frontend\n  - [ ] `wshremote.go` for remote (prefix with `Remote`)\n  - [ ] `term-wsh.tsx` for terminal\n- [ ] Add input validation\n- [ ] Handle errors with context\n- [ ] Test the command end-to-end\n\n## Related Documentation\n\n- **WPS Events**: See the `wps-events` skill - Publishing events from RPC commands\n","frontmatter":{"name":"add-rpc","description":"Guide for adding new RPC calls to Wave Terminal. Use when implementing new RPC commands, adding server-client communication methods, or extending the RPC interface with new functionality."},"isInternal":false,"tokens":3001,"sizeBytes":12604},{"name":"SKILL.md","path":".kilocode/skills/add-wshcmd/SKILL.md","rawUrl":"https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.kilocode/skills/add-wshcmd/SKILL.md","title":"add-wshcmd","category":"anthropic-skill","format":"markdown","content":"---\nname: add-wshcmd\ndescription: Guide for adding new wsh commands to Wave Terminal. Use when implementing new CLI commands, adding command-line functionality, or extending the wsh command interface.\n---\n\n# Adding a New wsh Command to Wave Terminal\n\nThis guide explains how to add a new command to the `wsh` CLI tool.\n\n## wsh Command System Overview\n\nWave Terminal's `wsh` command provides CLI access to Wave Terminal features. The system uses:\n\n1. **Cobra Framework** - CLI command structure and parsing\n2. **Command Files** - Individual command implementations in `cmd/wsh/cmd/wshcmd-*.go`\n3. **RPC Client** - Communication with Wave Terminal backend via `RpcClient`\n4. **Activity Tracking** - Telemetry for command usage analytics\n5. **Documentation** - User-facing docs in `docs/docs/wsh-reference.mdx`\n\nCommands are registered in their `init()` functions and execute through the Cobra framework.\n\n## Step-by-Step Guide\n\n### Step 1: Create Command File\n\nCreate a new file in `cmd/wsh/cmd/` named `wshcmd-[commandname].go`:\n\n```go\n// Copyright 2025, Command Line Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n    \"fmt\"\n\n    \"github.com/spf13/cobra\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient\"\n)\n\nvar myCommandCmd = &cobra.Command{\n    Use:   \"mycommand [args]\",\n    Short: \"Brief description of what this command does\",\n    Long: `Detailed description of the command.\nCan include multiple lines and examples of usage.`,\n    RunE:                  myCommandRun,\n    PreRunE:               preRunSetupRpcClient,  // Include if command needs RPC\n    DisableFlagsInUseLine: true,\n}\n\n// Flag variables\nvar (\n    myCommandFlagExample string\n    myCommandFlagVerbose bool\n)\n\nfunc init() {\n    // Add command to root\n    rootCmd.AddCommand(myCommandCmd)\n    \n    // Define flags\n    myCommandCmd.Flags().StringVarP(&myCommandFlagExample, \"example\", \"e\", \"\", \"example flag description\")\n    myCommandCmd.Flags().BoolVarP(&myCommandFlagVerbose, \"verbose\", \"v\", false, \"enable verbose output\")\n}\n\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    // Always track activity for telemetry\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Validate arguments\n    if len(args) == 0 {\n        OutputHelpMessage(cmd)\n        return fmt.Errorf(\"requires at least one argument\")\n    }\n    \n    // Command implementation\n    fmt.Printf(\"Command executed successfully\\n\")\n    return nil\n}\n```\n\n**File Naming Convention:**\n- Use `wshcmd-[commandname].go` format\n- Use lowercase, hyphenated names for multi-word commands\n- Examples: `wshcmd-getvar.go`, `wshcmd-setmeta.go`, `wshcmd-ai.go`\n\n### Step 2: Command Structure\n\n#### Basic Command Structure\n\n```go\nvar myCommandCmd = &cobra.Command{\n    Use:   \"mycommand [required] [optional...]\",\n    Short: \"One-line description (shown in help)\",\n    Long:  `Detailed multi-line description`,\n    \n    // Argument validation\n    Args:    cobra.MinimumNArgs(1),  // Or cobra.ExactArgs(1), cobra.NoArgs, etc.\n    \n    // Execution function\n    RunE:    myCommandRun,\n    \n    // Pre-execution setup (if needed)\n    PreRunE: preRunSetupRpcClient,  // Sets up RPC client for backend communication\n    \n    // Example usage (optional)\n    Example: \"  wsh mycommand foo\\n  wsh mycommand --flag bar\",\n    \n    // Disable flag notation in usage line\n    DisableFlagsInUseLine: true,\n}\n```\n\n**Key Fields:**\n- `Use`: Command name and argument pattern\n- `Short`: Brief description for command list\n- `Long`: Detailed description shown in help\n- `Args`: Argument validator (optional)\n- `RunE`: Main execution function (returns error)\n- `PreRunE`: Setup function that runs before `RunE`\n- `Example`: Usage examples (optional)\n- `DisableFlagsInUseLine`: Clean up help display\n\n#### When to Use PreRunE\n\nInclude `PreRunE: preRunSetupRpcClient` if your command:\n- Communicates with the Wave Terminal backend\n- Needs access to `RpcClient` \n- Requires JWT authentication (WAVETERM_JWT env var)\n- Makes RPC calls via `wshclient.*Command()` functions\n\n**Don't include PreRunE** for commands that:\n- Only manipulate local state\n- Don't need backend communication\n- Are purely informational/local operations\n\n### Step 3: Implement Command Logic\n\n#### Command Function Pattern\n\n```go\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    // Step 1: Always track activity (for telemetry)\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Step 2: Validate arguments and flags\n    if len(args) != 1 {\n        OutputHelpMessage(cmd)\n        return fmt.Errorf(\"requires exactly one argument\")\n    }\n    \n    // Step 3: Parse/prepare data\n    targetArg := args[0]\n    \n    // Step 4: Make RPC call if needed\n    result, err := wshclient.SomeCommand(RpcClient, wshrpc.CommandSomeData{\n        Field: targetArg,\n    }, &wshrpc.RpcOpts{Timeout: 2000})\n    if err != nil {\n        return fmt.Errorf(\"executing command: %w\", err)\n    }\n    \n    // Step 5: Output results\n    fmt.Printf(\"Result: %s\\n\", result)\n    return nil\n}\n```\n\n**Important Patterns:**\n\n1. **Activity Tracking**: Always include deferred `sendActivity()` call\n   ```go\n   defer func() {\n       sendActivity(\"commandname\", rtnErr == nil)\n   }()\n   ```\n\n2. **Error Handling**: Return errors, don't call `os.Exit()`\n   ```go\n   if err != nil {\n       return fmt.Errorf(\"context: %w\", err)\n   }\n   ```\n\n3. **Output**: Use standard `fmt` package for output\n   ```go\n   fmt.Printf(\"Success message\\n\")\n   fmt.Fprintf(os.Stderr, \"Error message\\n\")\n   ```\n\n4. **Help Messages**: Show help when arguments are invalid\n   ```go\n   if len(args) == 0 {\n       OutputHelpMessage(cmd)\n       return fmt.Errorf(\"requires arguments\")\n   }\n   ```\n\n5. **Exit Codes**: Set custom exit code via `WshExitCode`\n   ```go\n   if notFound {\n       WshExitCode = 1\n       return nil  // Don't return error, just set exit code\n   }\n   ```\n\n### Step 4: Define Flags\n\nAdd flags in the `init()` function:\n\n```go\nvar (\n    // Declare flag variables at package level\n    myCommandFlagString string\n    myCommandFlagBool   bool\n    myCommandFlagInt    int\n)\n\nfunc init() {\n    rootCmd.AddCommand(myCommandCmd)\n    \n    // String flag with short version\n    myCommandCmd.Flags().StringVarP(&myCommandFlagString, \"name\", \"n\", \"default\", \"description\")\n    \n    // Boolean flag\n    myCommandCmd.Flags().BoolVarP(&myCommandFlagBool, \"verbose\", \"v\", false, \"enable verbose\")\n    \n    // Integer flag\n    myCommandCmd.Flags().IntVar(&myCommandFlagInt, \"count\", 10, \"set count\")\n    \n    // Flag without short version\n    myCommandCmd.Flags().StringVar(&myCommandFlagString, \"longname\", \"\", \"description\")\n}\n```\n\n**Flag Types:**\n- `StringVar/StringVarP` - String values\n- `BoolVar/BoolVarP` - Boolean flags\n- `IntVar/IntVarP` - Integer values\n- The `P` suffix versions include a short flag name\n\n**Flag Naming:**\n- Use camelCase for variable names: `myCommandFlagName`\n- Use kebab-case for flag names: `--flag-name`\n- Prefix variable names with command name for clarity\n\n### Step 5: Working with Block Arguments\n\nMany commands operate on blocks. Use the standard block resolution pattern:\n\n```go\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Resolve block using the -b/--block flag\n    fullORef, err := resolveBlockArg()\n    if err != nil {\n        return err\n    }\n    \n    // Use the blockid in RPC call\n    err = wshclient.SomeCommand(RpcClient, wshrpc.CommandSomeData{\n        BlockId: fullORef.OID,\n    }, &wshrpc.RpcOpts{Timeout: 2000})\n    if err != nil {\n        return fmt.Errorf(\"command failed: %w\", err)\n    }\n    \n    return nil\n}\n```\n\n**Block Resolution:**\n- The `-b/--block` flag is defined globally in `wshcmd-root.go`\n- `resolveBlockArg()` resolves the block argument to a full ORef\n- Supports: `this`, `tab`, full UUIDs, 8-char prefixes, block numbers\n- Default is `\"this\"` (current block)\n\n**Alternative: Manual Block Resolution**\n\n```go\n// Get tab ID from environment\ntabId := os.Getenv(\"WAVETERM_TABID\")\nif tabId == \"\" {\n    return fmt.Errorf(\"WAVETERM_TABID not set\")\n}\n\n// Create route for tab-level operations\nroute := wshutil.MakeTabRouteId(tabId)\n\n// Use route in RPC call\nerr := wshclient.SomeCommand(RpcClient, commandData, &wshrpc.RpcOpts{\n    Route:   route,\n    Timeout: 2000,\n})\n```\n\n### Step 6: Making RPC Calls\n\nUse the `wshclient` package to make RPC calls:\n\n```go\nimport (\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient\"\n)\n\n// Simple RPC call\nresult, err := wshclient.GetMetaCommand(RpcClient, wshrpc.CommandGetMetaData{\n    ORef: *fullORef,\n}, &wshrpc.RpcOpts{Timeout: 2000})\nif err != nil {\n    return fmt.Errorf(\"getting metadata: %w\", err)\n}\n\n// RPC call with routing\nerr := wshclient.SetMetaCommand(RpcClient, wshrpc.CommandSetMetaData{\n    ORef: *fullORef,\n    Meta: metaMap,\n}, &wshrpc.RpcOpts{\n    Route:   route,\n    Timeout: 5000,\n})\nif err != nil {\n    return fmt.Errorf(\"setting metadata: %w\", err)\n}\n```\n\n**RPC Options:**\n- `Timeout`: Request timeout in milliseconds (typically 2000-5000)\n- `Route`: Route ID for targeting specific components\n- Available routes: `wshutil.ControlRoute`, `wshutil.MakeTabRouteId(tabId)`\n\n### Step 7: Add Documentation\n\nAdd your command to `docs/docs/wsh-reference.mdx`:\n\n````markdown\n## mycommand\n\nBrief description of what the command does.\n\n```sh\nwsh mycommand [args] [flags]\n```\n\nDetailed explanation of the command's purpose and behavior.\n\nFlags:\n- `-n, --name <value>` - description of this flag\n- `-v, --verbose` - enable verbose output\n- `-b, --block <blockid>` - specify target block (default: current block)\n\nExamples:\n\n```sh\n# Basic usage\nwsh mycommand arg1\n\n# With flags\nwsh mycommand --name value arg1\n\n# With block targeting\nwsh mycommand -b 2 arg1\n\n# Complex example\nwsh mycommand -v --name \"example\" arg1 arg2\n```\n\nAdditional notes, tips, or warnings about the command.\n\n---\n````\n\n**Documentation Guidelines:**\n- Place in alphabetical order with other commands\n- Include command signature with argument pattern\n- List all flags with short and long versions\n- Provide practical examples (at least 3-5)\n- Explain common use cases and patterns\n- Add tips or warnings if relevant\n- Use `---` separator between commands\n\n### Step 8: Test Your Command\n\nBuild and test the command:\n\n```bash\n# Build wsh\ntask build:wsh\n\n# Or build everything\ntask build\n\n# Test the command\n./bin/wsh/wsh mycommand --help\n./bin/wsh/wsh mycommand arg1 arg2\n```\n\n**Testing Checklist:**\n- [ ] Help message displays correctly\n- [ ] Required arguments validated\n- [ ] Flags work as expected\n- [ ] Error messages are clear\n- [ ] Success cases work correctly\n- [ ] RPC calls complete successfully\n- [ ] Output is formatted correctly\n\n## Complete Examples\n\n### Example 1: Simple Command with No RPC\n\n**Use case:** A command that prints Wave Terminal version info\n\n#### Command File (`cmd/wsh/cmd/wshcmd-version.go`)\n\n```go\n// Copyright 2025, Command Line Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n    \"github.com/spf13/cobra\"\n    \"github.com/wavetermdev/waveterm/pkg/wavebase\"\n)\n\nvar versionCmd = &cobra.Command{\n    Use:   \"version\",\n    Short: \"Print Wave Terminal version\",\n    RunE:  versionRun,\n}\n\nfunc init() {\n    rootCmd.AddCommand(versionCmd)\n}\n\nfunc versionRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"version\", rtnErr == nil)\n    }()\n    \n    fmt.Printf(\"Wave Terminal %s\\n\", wavebase.WaveVersion)\n    return nil\n}\n```\n\n#### Documentation\n\n````markdown\n## version\n\nPrint the current Wave Terminal version.\n\n```sh\nwsh version\n```\n\nExamples:\n\n```sh\n# Print version\nwsh version\n```\n````\n\n### Example 2: Command with Flags and RPC\n\n**Use case:** A command to update block title\n\n#### Command File (`cmd/wsh/cmd/wshcmd-settitle.go`)\n\n```go\n// Copyright 2025, Command Line Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n    \"fmt\"\n\n    \"github.com/spf13/cobra\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient\"\n)\n\nvar setTitleCmd = &cobra.Command{\n    Use:   \"settitle [title]\",\n    Short: \"Set block title\",\n    Long:  `Set the title for the current or specified block.`,\n    Args:  cobra.ExactArgs(1),\n    RunE:  setTitleRun,\n    PreRunE: preRunSetupRpcClient,\n    DisableFlagsInUseLine: true,\n}\n\nvar setTitleIcon string\n\nfunc init() {\n    rootCmd.AddCommand(setTitleCmd)\n    setTitleCmd.Flags().StringVarP(&setTitleIcon, \"icon\", \"i\", \"\", \"set block icon\")\n}\n\nfunc setTitleRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"settitle\", rtnErr == nil)\n    }()\n    \n    title := args[0]\n    \n    // Resolve block\n    fullORef, err := resolveBlockArg()\n    if err != nil {\n        return err\n    }\n    \n    // Build metadata map\n    meta := make(map[string]interface{})\n    meta[\"title\"] = title\n    if setTitleIcon != \"\" {\n        meta[\"icon\"] = setTitleIcon\n    }\n    \n    // Make RPC call\n    err = wshclient.SetMetaCommand(RpcClient, wshrpc.CommandSetMetaData{\n        ORef: *fullORef,\n        Meta: meta,\n    }, &wshrpc.RpcOpts{Timeout: 2000})\n    if err != nil {\n        return fmt.Errorf(\"setting title: %w\", err)\n    }\n    \n    fmt.Printf(\"title updated\\n\")\n    return nil\n}\n```\n\n#### Documentation\n\n````markdown\n## settitle\n\nSet the title for a block.\n\n```sh\nwsh settitle [title]\n```\n\nUpdate the display title for the current or specified block. Optionally set an icon as well.\n\nFlags:\n- `-i, --icon <icon>` - set block icon along with title\n- `-b, --block <blockid>` - specify target block (default: current block)\n\nExamples:\n\n```sh\n# Set title for current block\nwsh settitle \"My Terminal\"\n\n# Set title and icon\nwsh settitle --icon \"terminal\" \"Development Shell\"\n\n# Set title for specific block\nwsh settitle -b 2 \"Build Output\"\n```\n````\n\n### Example 3: Subcommands\n\n**Use case:** Command with multiple subcommands (like `wsh conn`)\n\n#### Command File (`cmd/wsh/cmd/wshcmd-mygroup.go`)\n\n```go\n// Copyright 2025, Command Line Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n    \"fmt\"\n\n    \"github.com/spf13/cobra\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient\"\n)\n\nvar myGroupCmd = &cobra.Command{\n    Use:   \"mygroup\",\n    Short: \"Manage something\",\n}\n\nvar myGroupListCmd = &cobra.Command{\n    Use:   \"list\",\n    Short: \"List items\",\n    RunE:  myGroupListRun,\n    PreRunE: preRunSetupRpcClient,\n}\n\nvar myGroupAddCmd = &cobra.Command{\n    Use:   \"add [name]\",\n    Short: \"Add an item\",\n    Args:  cobra.ExactArgs(1),\n    RunE:  myGroupAddRun,\n    PreRunE: preRunSetupRpcClient,\n}\n\nfunc init() {\n    // Add parent command\n    rootCmd.AddCommand(myGroupCmd)\n    \n    // Add subcommands\n    myGroupCmd.AddCommand(myGroupListCmd)\n    myGroupCmd.AddCommand(myGroupAddCmd)\n}\n\nfunc myGroupListRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mygroup:list\", rtnErr == nil)\n    }()\n    \n    // Implementation\n    fmt.Printf(\"Listing items...\\n\")\n    return nil\n}\n\nfunc myGroupAddRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mygroup:add\", rtnErr == nil)\n    }()\n    \n    name := args[0]\n    fmt.Printf(\"Adding item: %s\\n\", name)\n    return nil\n}\n```\n\n#### Documentation\n\n````markdown\n## mygroup\n\nManage something with subcommands.\n\n### list\n\nList all items.\n\n```sh\nwsh mygroup list\n```\n\n### add\n\nAdd a new item.\n\n```sh\nwsh mygroup add [name]\n```\n\nExamples:\n\n```sh\n# List items\nwsh mygroup list\n\n# Add an item\nwsh mygroup add \"new-item\"\n```\n````\n\n## Common Patterns\n\n### Reading from Stdin\n\n```go\nimport \"io\"\n\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Check if reading from stdin (using \"-\" convention)\n    var data []byte\n    var err error\n    \n    if len(args) > 0 && args[0] == \"-\" {\n        data, err = io.ReadAll(os.Stdin)\n        if err != nil {\n            return fmt.Errorf(\"reading stdin: %w\", err)\n        }\n    } else {\n        // Read from file or other source\n        data, err = os.ReadFile(args[0])\n        if err != nil {\n            return fmt.Errorf(\"reading file: %w\", err)\n        }\n    }\n    \n    // Process data\n    fmt.Printf(\"Read %d bytes\\n\", len(data))\n    return nil\n}\n```\n\n### JSON File Input\n\n```go\nimport (\n    \"encoding/json\"\n    \"io\"\n)\n\nfunc loadJSONFile(filepath string) (map[string]interface{}, error) {\n    var data []byte\n    var err error\n    \n    if filepath == \"-\" {\n        data, err = io.ReadAll(os.Stdin)\n        if err != nil {\n            return nil, fmt.Errorf(\"reading stdin: %w\", err)\n        }\n    } else {\n        data, err = os.ReadFile(filepath)\n        if err != nil {\n            return nil, fmt.Errorf(\"reading file: %w\", err)\n        }\n    }\n    \n    var result map[string]interface{}\n    if err := json.Unmarshal(data, &result); err != nil {\n        return nil, fmt.Errorf(\"parsing JSON: %w\", err)\n    }\n    \n    return result, nil\n}\n```\n\n### Conditional Output (TTY Detection)\n\n```go\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    isTty := getIsTty()\n    \n    // Output value\n    fmt.Printf(\"%s\", value)\n    \n    // Add newline only if TTY (for better piping experience)\n    if isTty {\n        fmt.Printf(\"\\n\")\n    }\n    \n    return nil\n}\n```\n\n### Environment Variable Access\n\n```go\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Get block ID from environment\n    blockId := os.Getenv(\"WAVETERM_BLOCKID\")\n    if blockId == \"\" {\n        return fmt.Errorf(\"WAVETERM_BLOCKID not set\")\n    }\n    \n    // Get tab ID from environment\n    tabId := os.Getenv(\"WAVETERM_TABID\")\n    if tabId == \"\" {\n        return fmt.Errorf(\"WAVETERM_TABID not set\")\n    }\n    \n    fmt.Printf(\"Block: %s, Tab: %s\\n\", blockId, tabId)\n    return nil\n}\n```\n\n## Best Practices\n\n### Command Design\n\n1. **Single Responsibility**: Each command should do one thing well\n2. **Composable**: Design commands to work with pipes and other commands\n3. **Consistent**: Follow existing wsh command patterns and conventions\n4. **Documented**: Provide clear help text and examples\n\n### Error Handling\n\n1. **Context**: Wrap errors with context using `fmt.Errorf(\"context: %w\", err)`\n2. **User-Friendly**: Make error messages clear and actionable\n3. **No Panics**: Return errors instead of calling `os.Exit()` or `panic()`\n4. **Exit Codes**: Use `WshExitCode` for custom exit codes\n\n### Output\n\n1. **Structured**: Use consistent formatting for output\n2. **Quiet by Default**: Only output what's necessary\n3. **Verbose Flag**: Optionally provide `-v` for detailed output\n4. **Stderr for Errors**: Use `fmt.Fprintf(os.Stderr, ...)` for error messages\n\n### Flags\n\n1. **Short Versions**: Provide `-x` short versions for common flags\n2. **Sensible Defaults**: Choose defaults that work for most users\n3. **Boolean Flags**: Use for on/off options\n4. **String Flags**: Use for values that need user input\n\n### RPC Calls\n\n1. **Timeouts**: Always specify reasonable timeouts\n2. **Error Context**: Wrap RPC errors with operation context\n3. **Retries**: Don't retry automatically; let user retry command\n4. **Routes**: Use appropriate routes for different operations\n\n## Common Pitfalls\n\n### 1. Forgetting Activity Tracking\n\n**Problem**: Command usage not tracked in telemetry\n\n**Solution**: Always include deferred `sendActivity()` call:\n```go\ndefer func() {\n    sendActivity(\"commandname\", rtnErr == nil)\n}()\n```\n\n### 2. Using os.Exit() Instead of Returning Error\n\n**Problem**: Breaks defer statements and cleanup\n\n**Solution**: Return errors from RunE function:\n```go\n// Bad\nif err != nil {\n    fmt.Fprintf(os.Stderr, \"error: %v\\n\", err)\n    os.Exit(1)\n}\n\n// Good\nif err != nil {\n    return fmt.Errorf(\"operation failed: %w\", err)\n}\n```\n\n### 3. Not Validating Arguments\n\n**Problem**: Command crashes with nil pointer or index out of range\n\n**Solution**: Validate arguments early and show help:\n```go\nif len(args) == 0 {\n    OutputHelpMessage(cmd)\n    return fmt.Errorf(\"requires at least one argument\")\n}\n```\n\n### 4. Forgetting to Add to init()\n\n**Problem**: Command not available when running wsh\n\n**Solution**: Always add command in `init()` function:\n```go\nfunc init() {\n    rootCmd.AddCommand(myCommandCmd)\n}\n```\n\n### 5. Inconsistent Output\n\n**Problem**: Inconsistent use of output methods\n\n**Solution**: Use standard `fmt` package functions:\n```go\n// For stdout\nfmt.Printf(\"output\\n\")\n\n// For stderr\nfmt.Fprintf(os.Stderr, \"error message\\n\")\n```\n\n## Quick Reference Checklist\n\nWhen adding a new wsh command:\n\n- [ ] Create `cmd/wsh/cmd/wshcmd-[commandname].go`\n- [ ] Define command struct with Use, Short, Long descriptions\n- [ ] Add `PreRunE: preRunSetupRpcClient` if using RPC\n- [ ] Implement command function with activity tracking\n- [ ] Add command to `rootCmd` in `init()` function\n- [ ] Define flags in `init()` function if needed\n- [ ] Add documentation to `docs/docs/wsh-reference.mdx`\n- [ ] Build and test: `task build:wsh`\n- [ ] Test help: `wsh [commandname] --help`\n- [ ] Test all flag combinations\n- [ ] Test error cases\n\n## Related Files\n\n- **Root Command**: `cmd/wsh/cmd/wshcmd-root.go` - Main command setup and utilities\n- **RPC Client**: `pkg/wshrpc/wshclient/` - Client functions for RPC calls\n- **RPC Types**: `pkg/wshrpc/wshrpctypes.go` - RPC request/response data structures\n- **Documentation**: `docs/docs/wsh-reference.mdx` - User-facing command reference\n- **Examples**: `cmd/wsh/cmd/wshcmd-*.go` - Existing command implementations\n","frontmatter":{"name":"add-wshcmd","description":"Guide for adding new wsh commands to Wave Terminal. Use when implementing new CLI commands, adding command-line functionality, or extending the wsh command interface."},"isInternal":false,"tokens":5829,"sizeBytes":21935},{"name":"SKILL.md","path":".kilocode/skills/context-menu/SKILL.md","rawUrl":"https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.kilocode/skills/context-menu/SKILL.md","title":"context-menu","category":"anthropic-skill","format":"markdown","content":"---\nname: context-menu\ndescription: Guide for creating and displaying context menus in Wave Terminal. Use when implementing right-click menus, adding context menu items, creating submenus, or handling menu interactions with checkboxes and separators.\n---\n\n# Context Menu Quick Reference\n\nThis guide provides a quick overview of how to create and display a context menu using our system.\n\n---\n\n## ContextMenuItem Type\n\nDefine each menu item using the `ContextMenuItem` type:\n\n```ts\ntype ContextMenuItem = {\n  label?: string;\n  type?: \"separator\" | \"normal\" | \"submenu\" | \"checkbox\" | \"radio\";\n  role?: string; // Electron role (optional)\n  click?: () => void; // Callback for item selection (not needed if role is set)\n  submenu?: ContextMenuItem[]; // For nested menus\n  checked?: boolean; // For checkbox or radio items\n  visible?: boolean;\n  enabled?: boolean;\n  sublabel?: string;\n};\n```\n\n---\n\n## Import and Show the Menu\n\nImport the context menu module:\n\n```ts\nimport { ContextMenuModel } from \"@/app/store/contextmenu\";\n```\n\nTo display the context menu, call:\n\n```ts\nContextMenuModel.getInstance().showContextMenu(menu, event);\n```\n\n- **menu**: An array of `ContextMenuItem`.\n- **event**: The mouse event that triggered the context menu (typically from an onContextMenu handler).\n\n---\n\n## Basic Example\n\nA simple context menu with a separator:\n\n```ts\nconst menu: ContextMenuItem[] = [\n  {\n    label: \"New File\",\n    click: () => {\n      /* create a new file */\n    },\n  },\n  {\n    label: \"New Folder\",\n    click: () => {\n      /* create a new folder */\n    },\n  },\n  { type: \"separator\" },\n  {\n    label: \"Rename\",\n    click: () => {\n      /* rename item */\n    },\n  },\n];\n\nContextMenuModel.getInstance().showContextMenu(menu, e);\n```\n\n---\n\n## Example with Submenu and Checkboxes\n\nToggle settings using a submenu with checkbox items:\n\n```ts\nconst isClearOnStart = true; // Example setting\n\nconst menu: ContextMenuItem[] = [\n  {\n    label: \"Clear Output On Restart\",\n    submenu: [\n      {\n        label: \"On\",\n        type: \"checkbox\",\n        checked: isClearOnStart,\n        click: () => {\n          // Set the config to enable clear on restart\n        },\n      },\n      {\n        label: \"Off\",\n        type: \"checkbox\",\n        checked: !isClearOnStart,\n        click: () => {\n          // Set the config to disable clear on restart\n        },\n      },\n    ],\n  },\n];\n\nContextMenuModel.getInstance().showContextMenu(menu, e);\n```\n\n---\n\n## Editing a Config File Example\n\nOpen a configuration file (e.g., `widgets.json`) in preview mode:\n\n```ts\n{\n    label: \"Edit widgets.json\",\n    click: () => {\n        fireAndForget(async () => {\n            const path = `${getApi().getConfigDir()}/widgets.json`;\n            const blockDef: BlockDef = {\n                meta: { view: \"preview\", file: path },\n            };\n            await createBlock(blockDef, false, true);\n        });\n    },\n}\n```\n\n---\n\n## Summary\n\n- **Menu Definition**: Use the `ContextMenuItem` type.\n- **Actions**: Use `click` for actions; use `submenu` for nested options.\n- **Separators**: Use `type: \"separator\"` to group items.\n- **Toggles**: Use `type: \"checkbox\"` or `\"radio\"` with the `checked` property.\n- **Displaying**: Use `ContextMenuModel.getInstance().showContextMenu(menu, event)` to render the menu.\n\n## Common Use Cases\n\n### File/Folder Operations\nContext menus are commonly used for file operations like creating, renaming, and deleting files or folders.\n\n### Settings Toggles\nUse checkbox menu items to toggle settings on and off, with the `checked` property reflecting the current state.\n\n### Nested Options\nUse `submenu` to organize related options hierarchically, keeping the top-level menu clean and organized.\n\n### Conditional Items\nUse the `visible` and `enabled` properties to dynamically show or disable menu items based on the current state.\n","frontmatter":{"name":"context-menu","description":"Guide for creating and displaying context menus in Wave Terminal. Use when implementing right-click menus, adding context menu items, creating submenus, or handling menu interactions with checkboxes and separators."},"isInternal":false,"tokens":877,"sizeBytes":3841},{"name":"SKILL.md","path":".kilocode/skills/create-view/SKILL.md","rawUrl":"https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.kilocode/skills/create-view/SKILL.md","title":"create-view","category":"anthropic-skill","format":"markdown","content":"---\nname: create-view\ndescription: Guide for implementing a new view type in Wave Terminal. Use when creating a new view component, implementing the ViewModel interface, registering a new view type in BlockRegistry, or adding a new content type to display within blocks.\n---\n\n# Creating a New View in Wave Terminal\n\nThis guide explains how to implement a new view type in Wave Terminal. Views are the core content components displayed within blocks in the terminal interface.\n\n## Architecture Overview\n\nWave Terminal uses a **Model-View architecture** where:\n\n- **ViewModel** - Contains all state, logic, and UI configuration as Jotai atoms\n- **ViewComponent** - Pure React component that renders the UI using the model\n- **BlockFrame** - Wraps views with a header, connection management, and standard controls\n\nThe separation between model and component ensures:\n\n- Models can update state without React hooks\n- Components remain pure and testable\n- State is centralized in Jotai atoms for easy access\n\n## ViewModel Interface\n\nEvery view must implement the `ViewModel` interface defined in `frontend/types/custom.d.ts`:\n\n```typescript\ninterface ViewModel {\n  // Required: The type identifier for this view (e.g., \"term\", \"web\", \"preview\")\n  viewType: string;\n\n  // Required: The React component that renders this view\n  viewComponent: ViewComponent<ViewModel>;\n\n  // Optional: Icon shown in block header (FontAwesome icon name or IconButtonDecl)\n  viewIcon?: jotai.Atom<string | IconButtonDecl>;\n\n  // Optional: Display name shown in block header (e.g., \"Terminal\", \"Web\", \"Preview\")\n  viewName?: jotai.Atom<string>;\n\n  // Optional: Additional header elements (text, buttons, inputs) shown after the name\n  viewText?: jotai.Atom<string | HeaderElem[]>;\n\n  // Optional: Icon button shown before the view name in header\n  preIconButton?: jotai.Atom<IconButtonDecl>;\n\n  // Optional: Icon buttons shown at the end of the header (before settings/close)\n  endIconButtons?: jotai.Atom<IconButtonDecl[]>;\n\n  // Optional: Custom background styling for the block\n  blockBg?: jotai.Atom<MetaType>;\n\n  // Optional: If true, completely hides the block header\n  noHeader?: jotai.Atom<boolean>;\n\n  // Optional: If true, shows connection picker in header for remote connections\n  manageConnection?: jotai.Atom<boolean>;\n\n  // Optional: If true, filters out 'nowsh' connections from connection picker\n  filterOutNowsh?: jotai.Atom<boolean>;\n\n  // Optional: If true, removes default padding from content area\n  noPadding?: jotai.Atom<boolean>;\n\n  // Optional: Atoms for managing in-block search functionality\n  searchAtoms?: SearchAtoms;\n\n  // Optional: Returns whether this is a basic terminal (for multi-input feature)\n  isBasicTerm?: (getFn: jotai.Getter) => boolean;\n\n  // Optional: Returns context menu items for the settings dropdown\n  getSettingsMenuItems?: () => ContextMenuItem[];\n\n  // Optional: Focuses the view when called, returns true if successful\n  giveFocus?: () => boolean;\n\n  // Optional: Handles keyboard events, returns true if handled\n  keyDownHandler?: (e: WaveKeyboardEvent) => boolean;\n\n  // Optional: Cleanup when block is closed\n  dispose?: () => void;\n}\n```\n\n### Key Concepts\n\n**Atoms**: All UI-related properties must be Jotai atoms. This enables:\n\n- Reactive updates when state changes\n- Access from anywhere via `globalStore.get()`/`globalStore.set()`\n- Derived atoms that compute values from other atoms\n\n**ViewComponent**: The React component receives these props:\n\n```typescript\ntype ViewComponentProps<T extends ViewModel> = {\n  blockId: string; // Unique ID for this block\n  blockRef: React.RefObject<HTMLDivElement>; // Ref to block container\n  contentRef: React.RefObject<HTMLDivElement>; // Ref to content area\n  model: T; // Your ViewModel instance\n};\n```\n\n## Step-by-Step Guide\n\n### 1. Create the View Model Class\n\nCreate a new file for your view model (e.g., `frontend/app/view/myview/myview-model.ts`):\n\n```typescript\nimport { BlockNodeModel } from \"@/app/block/blocktypes\";\nimport { globalStore } from \"@/app/store/jotaiStore\";\nimport { WOS, useBlockAtom } from \"@/store/global\";\nimport * as jotai from \"jotai\";\nimport { MyView } from \"./myview\";\n\nexport class MyViewModel implements ViewModel {\n  viewType: string;\n  blockId: string;\n  nodeModel: BlockNodeModel;\n  blockAtom: jotai.Atom<Block>;\n\n  // Define your atoms (simple field initializers)\n  viewIcon = jotai.atom<string>(\"circle\");\n  viewName = jotai.atom<string>(\"My View\");\n  noPadding = jotai.atom<boolean>(true);\n\n  // Derived atom (created in constructor)\n  viewText!: jotai.Atom<HeaderElem[]>;\n\n  constructor(blockId: string, nodeModel: BlockNodeModel) {\n    this.viewType = \"myview\";\n    this.blockId = blockId;\n    this.nodeModel = nodeModel;\n    this.blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);\n\n    // Create derived atoms that depend on block data or other atoms\n    this.viewText = jotai.atom((get) => {\n      const blockData = get(this.blockAtom);\n      const rtn: HeaderElem[] = [];\n\n      // Add header buttons/text based on state\n      rtn.push({\n        elemtype: \"iconbutton\",\n        icon: \"refresh\",\n        title: \"Refresh\",\n        click: () => this.refresh(),\n      });\n\n      return rtn;\n    });\n  }\n\n  get viewComponent(): ViewComponent {\n    return MyView;\n  }\n\n  refresh() {\n    // Update state using globalStore\n    // Never use React hooks in model methods\n    console.log(\"refreshing...\");\n  }\n\n  giveFocus(): boolean {\n    // Focus your view component\n    return true;\n  }\n\n  dispose() {\n    // Cleanup resources (unsubscribe from events, etc.)\n  }\n}\n```\n\n### 2. Create the View Component\n\nCreate your React component (e.g., `frontend/app/view/myview/myview.tsx`):\n\n```typescript\nimport { ViewComponentProps } from \"@/app/block/blocktypes\";\nimport { MyViewModel } from \"./myview-model\";\nimport { useAtomValue } from \"jotai\";\nimport \"./myview.scss\";\n\nexport const MyView: React.FC<ViewComponentProps<MyViewModel>> = ({\n    blockId,\n    model,\n    contentRef\n}) => {\n    // Use atoms from the model (these are React hooks - call at top level!)\n    const blockData = useAtomValue(model.blockAtom);\n\n    return (\n        <div className=\"myview-container\" ref={contentRef}>\n            <div>Block ID: {blockId}</div>\n            <div>View: {model.viewType}</div>\n            {/* Your view content here */}\n        </div>\n    );\n};\n```\n\n### 3. Register the View\n\nAdd your view to the `BlockRegistry` in `frontend/app/block/blockregistry.ts`:\n\n```typescript\nimport { MyViewModel } from \"@/app/view/myview/myview-model\";\n\nconst BlockRegistry: Map<string, ViewModelClass> = new Map();\nBlockRegistry.set(\"term\", TermViewModel);\nBlockRegistry.set(\"preview\", PreviewModel);\nBlockRegistry.set(\"web\", WebViewModel);\n// ... existing registrations ...\nBlockRegistry.set(\"myview\", MyViewModel); // Add your view here\n```\n\nThe registry key (e.g., `\"myview\"`) becomes the view type used in block metadata.\n\n### 4. Create Blocks with Your View\n\nUsers can create blocks with your view type:\n\n- Via CLI: `wsh view myview`\n- Via RPC: Use the block's `meta.view` field set to `\"myview\"`\n\n## Real-World Examples\n\n### Example 1: Terminal View (`term-model.ts`)\n\nThe terminal view demonstrates:\n\n- **Connection management** via `manageConnection` atom\n- **Dynamic header buttons** showing shell status (play/restart)\n- **Mode switching** between terminal and vdom views\n- **Custom keyboard handling** for terminal-specific shortcuts\n- **Focus management** to focus the xterm.js instance\n- **Shell integration status** showing AI capability indicators\n\nKey features:\n\n```typescript\nthis.manageConnection = jotai.atom((get) => {\n  const termMode = get(this.termMode);\n  if (termMode == \"vdom\") return false;\n  return true; // Show connection picker for regular terminal mode\n});\n\nthis.endIconButtons = jotai.atom((get) => {\n  const shellProcStatus = get(this.shellProcStatus);\n  const buttons: IconButtonDecl[] = [];\n\n  if (shellProcStatus == \"running\") {\n    buttons.push({\n      elemtype: \"iconbutton\",\n      icon: \"refresh\",\n      title: \"Restart Shell\",\n      click: this.forceRestartController.bind(this),\n    });\n  }\n  return buttons;\n});\n```\n\n### Example 2: Web View (`webview.tsx`)\n\nThe web view shows:\n\n- **Complex header controls** (back/forward/home/URL input)\n- **State management** for loading, URL, and navigation\n- **Event handling** for webview navigation events\n- **Custom styling** with `noPadding` for full-bleed content\n- **Media controls** showing play/pause/mute when media is active\n\nKey features:\n\n```typescript\nthis.viewText = jotai.atom((get) => {\n  const url = get(this.url);\n  const rtn: HeaderElem[] = [];\n\n  // Navigation buttons\n  rtn.push({\n    elemtype: \"iconbutton\",\n    icon: \"chevron-left\",\n    click: this.handleBack.bind(this),\n    disabled: this.shouldDisableBackButton(),\n  });\n\n  // URL input with nested controls\n  rtn.push({\n    elemtype: \"div\",\n    className: \"block-frame-div-url\",\n    children: [\n      {\n        elemtype: \"input\",\n        value: url,\n        onChange: this.handleUrlChange.bind(this),\n        onKeyDown: this.handleKeyDown.bind(this),\n      },\n      {\n        elemtype: \"iconbutton\",\n        icon: \"rotate-right\",\n        click: this.handleRefresh.bind(this),\n      },\n    ],\n  });\n\n  return rtn;\n});\n```\n\n## Header Elements (`HeaderElem`)\n\nThe `viewText` atom can return an array of these element types:\n\n```typescript\n// Icon button\n{\n    elemtype: \"iconbutton\",\n    icon: \"refresh\",\n    title: \"Tooltip text\",\n    click: () => { /* handler */ },\n    disabled?: boolean,\n    iconColor?: string,\n    iconSpin?: boolean,\n    noAction?: boolean,  // Shows icon but no click action\n}\n\n// Text element\n{\n    elemtype: \"text\",\n    text: \"Display text\",\n    className?: string,\n    noGrow?: boolean,\n    ref?: React.RefObject<HTMLElement>,\n    onClick?: (e: React.MouseEvent) => void,\n}\n\n// Text button\n{\n    elemtype: \"textbutton\",\n    text: \"Button text\",\n    className?: string,\n    title: \"Tooltip\",\n    onClick: (e: React.MouseEvent) => void,\n}\n\n// Input field\n{\n    elemtype: \"input\",\n    value: string,\n    className?: string,\n    onChange: (e: React.ChangeEvent<HTMLInputElement>) => void,\n    onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void,\n    onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void,\n    onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void,\n    ref?: React.RefObject<HTMLInputElement>,\n}\n\n// Container with children\n{\n    elemtype: \"div\",\n    className?: string,\n    children: HeaderElem[],\n    onMouseOver?: (e: React.MouseEvent) => void,\n    onMouseOut?: (e: React.MouseEvent) => void,\n}\n\n// Menu button (dropdown)\n{\n    elemtype: \"menubutton\",\n    // ... MenuButtonProps ...\n}\n```\n\n## Best Practices\n\n### Jotai Model Pattern\n\nFollow these rules for Jotai atoms in models:\n\n1. **Simple atoms as field initializers**:\n\n   ```typescript\n   viewIcon = jotai.atom<string>(\"circle\");\n   noPadding = jotai.atom<boolean>(true);\n   ```\n\n2. **Derived atoms in constructor** (need dependency on other atoms):\n\n   ```typescript\n   constructor(blockId: string, nodeModel: BlockNodeModel) {\n       this.viewText = jotai.atom((get) => {\n           const blockData = get(this.blockAtom);\n           return [/* computed based on blockData */];\n       });\n   }\n   ```\n\n3. **Models never use React hooks** - Use `globalStore.get()`/`set()`:\n\n   ```typescript\n   refresh() {\n       const currentData = globalStore.get(this.blockAtom);\n       globalStore.set(this.dataAtom, newData);\n   }\n   ```\n\n4. **Components use hooks for atoms**:\n   ```typescript\n   const data = useAtomValue(model.dataAtom);\n   const [value, setValue] = useAtom(model.valueAtom);\n   ```\n\n### State Management\n\n- All view state should live in atoms on the model\n- Use `useBlockAtom()` helper for block-scoped atoms that persist\n- Use `globalStore` for imperative access outside React components\n- Subscribe to Wave events using `waveEventSubscribe()`\n\n### Styling\n\n- Create a `.scss` file for your view styles\n- Use Tailwind utilities where possible (v4)\n- Add `noPadding: atom(true)` for full-bleed content\n- Use `blockBg` atom to customize block background\n\n### Focus Management\n\nImplement `giveFocus()` to focus your view when:\n\n- Block gains focus via keyboard navigation\n- User clicks the block\n- Return `true` if successfully focused, `false` otherwise\n\n### Keyboard Handling\n\nImplement `keyDownHandler(e: WaveKeyboardEvent)` for:\n\n- View-specific keyboard shortcuts\n- Return `true` if event was handled (prevents propagation)\n- Use `keyutil.checkKeyPressed(waveEvent, \"Cmd:K\")` for shortcut checks\n\n### Cleanup\n\nImplement `dispose()` to:\n\n- Unsubscribe from Wave events\n- Unregister routes/handlers\n- Clear timers/intervals\n- Release resources\n\n### Connection Management\n\nFor views that need remote connections:\n\n```typescript\nthis.manageConnection = jotai.atom(true); // Show connection picker\nthis.filterOutNowsh = jotai.atom(true); // Hide nowsh connections\n```\n\nAccess connection status:\n\n```typescript\nconst connStatus = jotai.atom((get) => {\n  const blockData = get(this.blockAtom);\n  const connName = blockData?.meta?.connection;\n  return get(getConnStatusAtom(connName));\n});\n```\n\n## Common Patterns\n\n### Reading Block Metadata\n\n```typescript\nimport { getBlockMetaKeyAtom } from \"@/store/global\";\n\n// In constructor:\nthis.someFlag = getBlockMetaKeyAtom(blockId, \"myview:flag\");\n\n// In component:\nconst flag = useAtomValue(model.someFlag);\n```\n\n### Configuration Overrides\n\nWave has a hierarchical config system (global → connection → block):\n\n```typescript\nimport { getOverrideConfigAtom } from \"@/store/global\";\n\nthis.settingAtom = jotai.atom((get) => {\n  // Checks block meta, then connection config, then global settings\n  return get(getOverrideConfigAtom(this.blockId, \"myview:setting\")) ?? defaultValue;\n});\n```\n\n### Updating Block Metadata\n\n```typescript\nimport { RpcApi } from \"@/app/store/wshclientapi\";\nimport { TabRpcClient } from \"@/app/store/wshrpcutil\";\nimport { WOS } from \"@/store/global\";\n\nawait RpcApi.SetMetaCommand(TabRpcClient, {\n  oref: WOS.makeORef(\"block\", this.blockId),\n  meta: { \"myview:key\": value },\n});\n```\n\n## Additional Resources\n\n- `frontend/app/block/blockframe-header.tsx` - Block header rendering\n- `frontend/app/view/term/term-model.ts` - Complex view example\n- `frontend/app/view/webview/webview.tsx` - Navigation UI example\n- `frontend/types/custom.d.ts` - Type definitions\n","frontmatter":{"name":"create-view","description":"Guide for implementing a new view type in Wave Terminal. Use when creating a new view component, implementing the ViewModel interface, registering a new view type in BlockRegistry, or adding a new content type to display within blocks."},"isInternal":false,"tokens":3424,"sizeBytes":14401},{"name":"SKILL.md","path":".kilocode/skills/electron-api/SKILL.md","rawUrl":"https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.kilocode/skills/electron-api/SKILL.md","title":"electron-api","category":"anthropic-skill","format":"markdown","content":"---\nname: electron-api\ndescription: Guide for adding new Electron APIs to Wave Terminal. Use when implementing new frontend-to-electron communications via preload/IPC.\n---\n\n# Adding Electron APIs\n\nElectron APIs allow the frontend to call Electron main process functionality directly via IPC.\n\n## Four Files to Edit\n\n1. [`frontend/types/custom.d.ts`](frontend/types/custom.d.ts) - TypeScript [`ElectronApi`](frontend/types/custom.d.ts:82) type\n2. [`emain/preload.ts`](emain/preload.ts) - Expose method via `contextBridge`\n3. [`emain/emain-ipc.ts`](emain/emain-ipc.ts) - Implement IPC handler\n4. [`frontend/preview/preview-electron-api.ts`](frontend/preview/preview-electron-api.ts) - Add a no-op stub to keep the `previewElectronApi` object in sync with the `ElectronApi` type\n\n## Three Communication Patterns\n\n1. **Sync** - `ipcRenderer.sendSync()` + `ipcMain.on()` + `event.returnValue = ...`\n2. **Async** - `ipcRenderer.invoke()` + `ipcMain.handle()`\n3. **Fire-and-forget** - `ipcRenderer.send()` + `ipcMain.on()`\n\n## Example: Async Method\n\n### 1. Define TypeScript Interface\n\nIn [`frontend/types/custom.d.ts`](frontend/types/custom.d.ts):\n\n```typescript\ntype ElectronApi = {\n    captureScreenshot: (rect: Electron.Rectangle) => Promise<string>; // capture-screenshot\n};\n```\n\n### 2. Expose in Preload\n\nIn [`emain/preload.ts`](emain/preload.ts):\n\n```typescript\ncontextBridge.exposeInMainWorld(\"api\", {\n    captureScreenshot: (rect: Rectangle) => ipcRenderer.invoke(\"capture-screenshot\", rect),\n});\n```\n\n### 3. Implement Handler\n\nIn [`emain/emain-ipc.ts`](emain/emain-ipc.ts):\n\n```typescript\nelectron.ipcMain.handle(\"capture-screenshot\", async (event, rect) => {\n    const tabView = getWaveTabViewByWebContentsId(event.sender.id);\n    if (!tabView) throw new Error(\"No tab view found\");\n    const image = await tabView.webContents.capturePage(rect);\n    return `data:image/png;base64,${image.toPNG().toString(\"base64\")}`;\n});\n```\n\n### 4. Add Preview Stub\n\nIn [`frontend/preview/preview-electron-api.ts`](frontend/preview/preview-electron-api.ts):\n\n```typescript\ncaptureScreenshot: (_rect: Electron.Rectangle) => Promise.resolve(\"\"),\n```\n\n### 5. Call from Frontend\n\n```typescript\nimport { getApi } from \"@/store/global\";\n\nconst dataUrl = await getApi().captureScreenshot({ x: 0, y: 0, width: 800, height: 600 });\n```\n\n## Example: Sync Method\n\n### 1. Define\n\n```typescript\ntype ElectronApi = {\n    getUserName: () => string; // get-user-name\n};\n```\n\n### 2. Preload\n\n```typescript\ngetUserName: () => ipcRenderer.sendSync(\"get-user-name\"),\n```\n\n### 3. Handler (⚠️ MUST set event.returnValue or browser hangs)\n\n```typescript\nelectron.ipcMain.on(\"get-user-name\", (event) => {\n    event.returnValue = process.env.USER || \"unknown\";\n});\n```\n\n### 4. Call\n\n```typescript\nimport { getApi } from \"@/store/global\";\n\nconst userName = getApi().getUserName(); // blocks until returns\n```\n\n## Example: Fire-and-Forget\n\n### 1. Define\n\n```typescript\ntype ElectronApi = {\n    openExternal: (url: string) => void; // open-external\n};\n```\n\n### 2. Preload\n\n```typescript\nopenExternal: (url) => ipcRenderer.send(\"open-external\", url),\n```\n\n### 3. Handler\n\n```typescript\nelectron.ipcMain.on(\"open-external\", (event, url) => {\n    electron.shell.openExternal(url);\n});\n```\n\n## Example: Event Listener\n\n### 1. Define\n\n```typescript\ntype ElectronApi = {\n    onZoomFactorChange: (callback: (zoomFactor: number) => void) => void; // zoom-factor-change\n};\n```\n\n### 2. Preload\n\n```typescript\nonZoomFactorChange: (callback) => \n    ipcRenderer.on(\"zoom-factor-change\", (_event, zoomFactor) => callback(zoomFactor)),\n```\n\n### 3. Send from Main\n\n```typescript\nwebContents.send(\"zoom-factor-change\", newZoomFactor);\n```\n\n## Quick Reference\n\n**Use Sync when:**\n- Getting config/env vars\n- Quick lookups, no I/O\n- ⚠️ **CRITICAL**: Always set `event.returnValue` or browser hangs\n\n**Use Async when:**\n- File operations\n- Network requests\n- Can fail or take time\n\n**Use Fire-and-forget when:**\n- No return value needed\n- Triggering actions\n\n**Electron API vs RPC:**\n- Electron API: Native OS features, window management, Electron APIs\n- RPC: Database, backend logic, remote servers\n\n## Checklist\n\n- [ ] Add to [`ElectronApi`](frontend/types/custom.d.ts:82) in [`custom.d.ts`](frontend/types/custom.d.ts)\n- [ ] Include IPC channel name in comment\n- [ ] Expose in [`preload.ts`](emain/preload.ts)\n- [ ] Implement in [`emain-ipc.ts`](emain/emain-ipc.ts)\n- [ ] Add no-op stub to [`preview-electron-api.ts`](frontend/preview/preview-electron-api.ts)\n- [ ] IPC channel names match exactly\n- [ ] **For sync**: Set `event.returnValue` (or browser hangs!)\n- [ ] Test end-to-end\n","frontmatter":{"name":"electron-api","description":"Guide for adding new Electron APIs to Wave Terminal. Use when implementing new frontend-to-electron communications via preload/IPC."},"isInternal":false,"tokens":1212,"sizeBytes":4637},{"name":"SKILL.md","path":".kilocode/skills/waveenv/SKILL.md","rawUrl":"https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.kilocode/skills/waveenv/SKILL.md","title":"waveenv","category":"anthropic-skill","format":"markdown","content":"---\nname: waveenv\ndescription: Guide for creating WaveEnv narrowings in Wave Terminal. Use when writing a named subset type of WaveEnv for a component tree, documenting environmental dependencies, or enabling mock environments for preview/test server usage.\n---\n\n# WaveEnv Narrowing Skill\n\n## Purpose\n\nA WaveEnv narrowing creates a _named subset type_ of `WaveEnv` that:\n\n1. Documents exactly which parts of the environment a component tree actually uses.\n2. Forms a type contract so callers and tests know what to provide.\n3. Enables mocking in the preview/test server — you only need to implement what's listed.\n\n## When To Create One\n\nCreate a narrowing whenever you are writing a component (or group of components) that you want to test in the preview server, or when you want to make the environmental dependencies of a component tree explicit.\n\n## Core Principle: Only Include What You Use\n\n**Only list the fields, methods, atoms, and keys that the component tree actually accesses.** If you don't call `wos`, don't include `wos`. If you only call one RPC command, only list that one command. The narrowing is a precise dependency declaration — not a copy of `WaveEnv`.\n\n## File Location\n\n- **Separate file** (preferred for shared/complex envs): name it `<feature>env.ts` next to the component, e.g. `frontend/app/block/blockenv.ts`.\n- **Inline** (acceptable for small, single-file components): export the type directly from the component file, e.g. `WidgetsEnv` in `frontend/app/workspace/widgets.tsx`.\n\n## Imports Required\n\n```ts\nimport {\n  MetaKeyAtomFnType, // only if you use getBlockMetaKeyAtom or getTabMetaKeyAtom\n  ConnConfigKeyAtomFnType, // only if you use getConnConfigKeyAtom\n  SettingsKeyAtomFnType, // only if you use getSettingsKeyAtom\n  WaveEnv,\n  WaveEnvSubset,\n} from \"@/app/waveenv/waveenv\";\n```\n\n## The Shape\n\n```ts\nexport type MyEnv = WaveEnvSubset<{\n  // --- Simple WaveEnv properties ---\n  // Copy the type verbatim from WaveEnv with WaveEnv[\"key\"] syntax.\n  isDev: WaveEnv[\"isDev\"];\n  createBlock: WaveEnv[\"createBlock\"];\n  showContextMenu: WaveEnv[\"showContextMenu\"];\n  platform: WaveEnv[\"platform\"];\n\n  // --- electron: list only the methods you call ---\n  electron: {\n    openExternal: WaveEnv[\"electron\"][\"openExternal\"];\n  };\n\n  // --- rpc: list only the commands you call ---\n  rpc: {\n    ActivityCommand: WaveEnv[\"rpc\"][\"ActivityCommand\"];\n    ConnEnsureCommand: WaveEnv[\"rpc\"][\"ConnEnsureCommand\"];\n  };\n\n  // --- atoms: list only the atoms you read ---\n  atoms: {\n    modalOpen: WaveEnv[\"atoms\"][\"modalOpen\"];\n    fullConfigAtom: WaveEnv[\"atoms\"][\"fullConfigAtom\"];\n  };\n\n  // --- wos: always take the whole thing, no sub-typing needed ---\n  wos: WaveEnv[\"wos\"];\n\n  // --- services: list only the services you call; no method-level narrowing ---\n  services: {\n    block: WaveEnv[\"services\"][\"block\"];\n    workspace: WaveEnv[\"services\"][\"workspace\"];\n  };\n\n  // --- key-parameterized atom factories: enumerate the keys you use ---\n  getSettingsKeyAtom: SettingsKeyAtomFnType<\"app:focusfollowscursor\" | \"window:magnifiedblockopacity\">;\n  getBlockMetaKeyAtom: MetaKeyAtomFnType<\"view\" | \"frame:title\" | \"connection\">;\n  getTabMetaKeyAtom: MetaKeyAtomFnType<\"tabid\" | \"name\">;\n  getConnConfigKeyAtom: ConnConfigKeyAtomFnType<\"conn:wshenabled\">;\n\n  // --- other atom helpers: copy verbatim ---\n  getConnStatusAtom: WaveEnv[\"getConnStatusAtom\"];\n  getLocalHostDisplayNameAtom: WaveEnv[\"getLocalHostDisplayNameAtom\"];\n  getConfigBackgroundAtom: WaveEnv[\"getConfigBackgroundAtom\"];\n}>;\n```\n\n### Automatically Included Fields\n\nEvery `WaveEnvSubset<T>` automatically includes the mock fields — you never need to declare them:\n\n- `isMock: boolean`\n- `mockSetWaveObj: <T extends WaveObj>(oref: string, obj: T) => void`\n- `mockModels?: Map<any, any>`\n\n### Rules for Each Section\n\n| Section                    | Pattern                                                | Notes                                                                                              |\n| -------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |\n| `electron`                 | `electron: { method: WaveEnv[\"electron\"][\"method\"]; }` | List every method called; omit the rest.                                                           |\n| `rpc`                      | `rpc: { Cmd: WaveEnv[\"rpc\"][\"Cmd\"]; }`                 | List every RPC command called; omit the rest.                                                      |\n| `atoms`                    | `atoms: { atom: WaveEnv[\"atoms\"][\"atom\"]; }`           | List every atom read; omit the rest.                                                               |\n| `wos`                      | `wos: WaveEnv[\"wos\"]`                                  | Take the whole `wos` object (no sub-typing needed), but **only add it if `wos` is actually used**. |\n| `services`                 | `services: { svc: WaveEnv[\"services\"][\"svc\"]; }`       | List each service used; take the whole service object (no method-level narrowing).                 |\n| `getSettingsKeyAtom`       | `SettingsKeyAtomFnType<\"key1\" \\| \"key2\">`              | Union all settings keys accessed.                                                                  |\n| `getBlockMetaKeyAtom`      | `MetaKeyAtomFnType<\"key1\" \\| \"key2\">`                  | Union all block meta keys accessed.                                                                |\n| `getTabMetaKeyAtom`        | `MetaKeyAtomFnType<\"key1\" \\| \"key2\">`                  | Union all tab meta keys accessed.                                                                  |\n| `getConnConfigKeyAtom`     | `ConnConfigKeyAtomFnType<\"key1\">`                      | Union all conn config keys accessed.                                                               |\n| All other `WaveEnv` fields | `WaveEnv[\"fieldName\"]`                                 | Copy type verbatim.                                                                                |\n\n## Using the Narrowed Type in Components\n\n```ts\nimport { useWaveEnv } from \"@/app/waveenv/waveenv\";\nimport { MyEnv } from \"./myenv\";\n\nconst MyComponent = memo(() => {\n    const env = useWaveEnv<MyEnv>();\n    // TypeScript now enforces you only access what's in MyEnv.\n    const val = useAtomValue(env.getSettingsKeyAtom(\"app:focusfollowscursor\"));\n    ...\n});\n```\n\nThe generic parameter on `useWaveEnv<MyEnv>()` casts the context to your narrowed type. The real production `WaveEnv` satisfies every narrowing; mock envs only need to implement the listed subset.\n\n## Real Examples\n\n- `BlockEnv` in `frontend/app/block/blockenv.ts` — complex narrowing with all section types, in a separate file.\n- `WidgetsEnv` in `frontend/app/workspace/widgets.tsx` — smaller narrowing defined inline in the component file.\n","frontmatter":{"name":"waveenv","description":"Guide for creating WaveEnv narrowings in Wave Terminal. Use when writing a named subset type of WaveEnv for a component tree, documenting environmental dependencies, or enabling mock environments for preview/test server usage."},"isInternal":false,"tokens":1503,"sizeBytes":6893},{"name":"SKILL.md","path":".kilocode/skills/wps-events/SKILL.md","rawUrl":"https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.kilocode/skills/wps-events/SKILL.md","title":"wps-events","category":"anthropic-skill","format":"markdown","content":"---\nname: wps-events\ndescription: Guide for working with Wave Terminal's WPS (Wave PubSub) event system. Use when implementing new event types, publishing events, subscribing to events, or adding asynchronous communication between components.\n---\n\n# WPS Events Guide\n\n## Overview\n\nWPS (Wave PubSub) is Wave Terminal's publish-subscribe event system that enables different parts of the application to communicate asynchronously. The system uses a broker pattern to route events from publishers to subscribers based on event types and scopes.\n\n## Key Files\n\n- `pkg/wps/wpstypes.go` - Event type constants and data structures\n- `pkg/wps/wps.go` - Broker implementation and core logic\n- `pkg/wcore/wcore.go` - Example usage patterns\n\n## Event Structure\n\nEvents in WPS have the following structure:\n\n```go\ntype WaveEvent struct {\n    Event   string   `json:\"event\"`      // Event type constant\n    Scopes  []string `json:\"scopes,omitempty\"` // Optional scopes for targeted delivery\n    Sender  string   `json:\"sender,omitempty\"` // Optional sender identifier\n    Persist int      `json:\"persist,omitempty\"` // Number of events to persist in history\n    Data    any      `json:\"data,omitempty\"`    // Event payload\n}\n```\n\n## Adding a New Event Type\n\n### Step 1: Define the Event Constant\n\nAdd your event type constant to `pkg/wps/wpstypes.go`:\n\n```go\nconst (\n    Event_BlockClose       = \"blockclose\"\n    Event_ConnChange       = \"connchange\"\n    // ... other events ...\n    Event_YourNewEvent     = \"your:newevent\"  // type: YourEventData (or \"none\" if no data)\n)\n```\n\n**Naming Convention:**\n\n- Use descriptive PascalCase for the constant name with `Event_` prefix\n- Use lowercase with colons for the string value (e.g., \"namespace:eventname\")\n- Group related events with the same namespace prefix\n- Always add a `// type: <TypeName>` comment; use `// type: none` if no data is sent\n\n### Step 2: Add to AllEvents\n\nAdd your new constant to the `AllEvents` slice in `pkg/wps/wpstypes.go`:\n\n```go\nvar AllEvents []string = []string{\n    // ... existing events ...\n    Event_YourNewEvent,\n}\n```\n\n### Step 3: Register in WaveEventDataTypes (REQUIRED)\n\nYou **must** add an entry to `WaveEventDataTypes` in `pkg/tsgen/tsgenevent.go`. This drives TypeScript type generation for the event's `data` field:\n\n```go\nvar WaveEventDataTypes = map[string]reflect.Type{\n    // ... existing entries ...\n    wps.Event_YourNewEvent: reflect.TypeOf(YourEventData{}),        // value type\n    // wps.Event_YourNewEvent: reflect.TypeOf((*YourEventData)(nil)), // pointer type\n    // wps.Event_YourNewEvent: nil,                                   // no data (type: none)\n}\n```\n\n- Use `reflect.TypeOf(YourType{})` for value types\n- Use `reflect.TypeOf((*YourType)(nil))` for pointer types\n- Use `nil` if no data is sent for the event\n\n### Step 4: Define Event Data Structure (Optional)\n\nIf your event carries structured data, define a type for it:\n\n```go\ntype YourEventData struct {\n    Field1 string `json:\"field1\"`\n    Field2 int    `json:\"field2\"`\n}\n```\n\n### Step 5: Expose Type to Frontend (If Needed)\n\nIf your event data type isn't already exposed via an RPC call, you need to add it to `pkg/tsgen/tsgen.go` so TypeScript types are generated:\n\n```go\n// add extra types to generate here\nvar ExtraTypes = []any{\n    waveobj.ORef{},\n    // ... other types ...\n    uctypes.RateLimitInfo{},  // Example: already added\n    YourEventData{},          // Add your new type here\n}\n```\n\nThen run code generation:\n\n```bash\ntask generate\n```\n\nThis will update `frontend/types/gotypes.d.ts` with TypeScript definitions for your type, ensuring type safety in the frontend when handling these events.\n\n## Publishing Events\n\n### Basic Publishing\n\nTo publish an event, use the global broker:\n\n```go\nimport \"github.com/wavetermdev/waveterm/pkg/wps\"\n\nwps.Broker.Publish(wps.WaveEvent{\n    Event: wps.Event_YourNewEvent,\n    Data:  yourData,\n})\n```\n\n### Publishing with Scopes\n\nScopes allow targeted event delivery. Subscribers can filter events by scope:\n\n```go\nwps.Broker.Publish(wps.WaveEvent{\n    Event:  wps.Event_WaveObjUpdate,\n    Scopes: []string{oref.String()},  // Target specific object\n    Data:   updateData,\n})\n```\n\n### Publishing in a Goroutine\n\nTo avoid blocking the caller, publish events asynchronously:\n\n```go\ngo func() {\n    wps.Broker.Publish(wps.WaveEvent{\n        Event: wps.Event_YourNewEvent,\n        Data:  data,\n    })\n}()\n```\n\n**When to use goroutines:**\n\n- When publishing from performance-critical code paths\n- When the event is informational and doesn't need immediate delivery\n- When publishing from code that holds locks (to prevent deadlocks)\n\n### Event Persistence\n\nEvents can be persisted in memory for late subscribers:\n\n```go\nwps.Broker.Publish(wps.WaveEvent{\n    Event:   wps.Event_YourNewEvent,\n    Persist: 100,  // Keep last 100 events\n    Data:    data,\n})\n```\n\n## Complete Example: Rate Limit Updates\n\nThis example shows how rate limit information is published when AI chat responses include rate limit headers.\n\n### 1. Define the Event Type\n\nIn `pkg/wps/wpstypes.go`:\n\n```go\nconst (\n    // ... other events ...\n    Event_WaveAIRateLimit  = \"waveai:ratelimit\"\n)\n```\n\n### 2. Publish the Event\n\nIn `pkg/aiusechat/usechat.go`:\n\n```go\nimport \"github.com/wavetermdev/waveterm/pkg/wps\"\n\nfunc updateRateLimit(info *uctypes.RateLimitInfo) {\n    if info == nil {\n        return\n    }\n    rateLimitLock.Lock()\n    defer rateLimitLock.Unlock()\n    globalRateLimitInfo = info\n\n    // Publish event in goroutine to avoid blocking\n    go func() {\n        wps.Broker.Publish(wps.WaveEvent{\n            Event: wps.Event_WaveAIRateLimit,\n            Data:  info,  // RateLimitInfo struct\n        })\n    }()\n}\n```\n\n### 3. Subscribe to the Event (Frontend)\n\nIn the frontend, subscribe to events via WebSocket:\n\n```typescript\n// Subscribe to rate limit updates\nconst subscription = {\n  event: \"waveai:ratelimit\",\n  allscopes: true, // Receive all rate limit events\n};\n```\n\n## Subscribing to Events\n\n### From Go Code\n\n```go\n// Subscribe to all events of a type\nwps.Broker.Subscribe(routeId, wps.SubscriptionRequest{\n    Event:     wps.Event_YourNewEvent,\n    AllScopes: true,\n})\n\n// Subscribe to specific scopes\nwps.Broker.Subscribe(routeId, wps.SubscriptionRequest{\n    Event:  wps.Event_WaveObjUpdate,\n    Scopes: []string{\"workspace:123\"},\n})\n\n// Unsubscribe\nwps.Broker.Unsubscribe(routeId, wps.Event_YourNewEvent)\n```\n\n### Scope Matching\n\nScopes support wildcard matching:\n\n- `*` matches a single scope segment\n- `**` matches multiple scope segments\n\n```go\n// Subscribe to all workspace events\nwps.Broker.Subscribe(routeId, wps.SubscriptionRequest{\n    Event:  wps.Event_WaveObjUpdate,\n    Scopes: []string{\"workspace:*\"},\n})\n```\n\n## Best Practices\n\n1. **Use Namespaces**: Prefix event names with a namespace (e.g., `waveai:`, `workspace:`, `block:`)\n\n2. **Don't Block**: Use goroutines when publishing from performance-critical code or while holding locks\n\n3. **Type-Safe Data**: Define struct types for event data rather than using maps\n\n4. **Scope Wisely**: Use scopes to limit event delivery and reduce unnecessary processing\n\n5. **Document Events**: Add comments explaining when events are fired and what data they carry\n\n6. **Consider Persistence**: Use `Persist` for events that late subscribers might need (like status updates). This is normally not used. We normally do a live RPC call to get the current value and then subscribe for updates.\n\n## Common Event Patterns\n\n### Status Updates\n\n```go\nwps.Broker.Publish(wps.WaveEvent{\n    Event:   wps.Event_ControllerStatus,\n    Scopes:  []string{blockId},\n    Persist: 1,  // Keep only latest status\n    Data:    statusData,\n})\n```\n\n### Object Updates\n\n```go\nwps.Broker.Publish(wps.WaveEvent{\n    Event:  wps.Event_WaveObjUpdate,\n    Scopes: []string{oref.String()},\n    Data: waveobj.WaveObjUpdate{\n        UpdateType: waveobj.UpdateType_Update,\n        OType:      obj.GetOType(),\n        OID:        waveobj.GetOID(obj),\n        Obj:        obj,\n    },\n})\n```\n\n### Batch Updates\n\n```go\n// Helper function for multiple updates\nfunc (b *BrokerType) SendUpdateEvents(updates waveobj.UpdatesRtnType) {\n    for _, update := range updates {\n        b.Publish(WaveEvent{\n            Event:  Event_WaveObjUpdate,\n            Scopes: []string{waveobj.MakeORef(update.OType, update.OID).String()},\n            Data:   update,\n        })\n    }\n}\n```\n\n## Debugging\n\nTo debug event flow:\n\n1. Check broker subscription map: `wps.Broker.SubMap`\n2. View persisted events: `wps.Broker.ReadEventHistory(eventType, scope, maxItems)`\n3. Add logging in publish/subscribe methods\n4. Monitor WebSocket traffic in browser dev tools\n\n## Quick Reference\n\nWhen adding a new event:\n\n- [ ] Add event constant to [`pkg/wps/wpstypes.go`](pkg/wps/wpstypes.go) with a `// type: <TypeName>` comment (use `none` if no data)\n- [ ] Add the constant to `AllEvents` in [`pkg/wps/wpstypes.go`](pkg/wps/wpstypes.go)\n- [ ] **REQUIRED**: Add an entry to `WaveEventDataTypes` in [`pkg/tsgen/tsgenevent.go`](pkg/tsgen/tsgenevent.go) — use `nil` for events with no data\n- [ ] Define event data structure (if needed)\n- [ ] Add data type to `pkg/tsgen/tsgen.go` for frontend use (if not already exposed via RPC)\n- [ ] Run `task generate` to update TypeScript types\n- [ ] Publish events using `wps.Broker.Publish()`\n- [ ] Use goroutines for non-blocking publish when appropriate\n- [ ] Subscribe to events in relevant components\n","frontmatter":{"name":"wps-events","description":"Guide for working with Wave Terminal's WPS (Wave PubSub) event system. Use when implementing new event types, publishing events, subscribing to events, or adding asynchronous communication between components."},"isInternal":false,"tokens":2386,"sizeBytes":9420},{"name":"overview.md","path":".roo/rules/overview.md","rawUrl":"https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.roo/rules/overview.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.roo/rules/overview.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.roo/rules/overview.md)","tokens":300,"sizeBytes":159},{"name":"rules.md","path":".roo/rules/rules.md","rawUrl":"https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.roo/rules/rules.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.roo/rules/rules.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.roo/rules/rules.md)","tokens":300,"sizeBytes":153},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"@.kilocode/rules/rules.md\n\n---\n\n## Skill Guides\n\nThis project uses a set of \"skill\" guides — focused how-to documents for common implementation tasks. When your task matches one of the descriptions below, **read the linked SKILL.md file before proceeding** and follow its instructions precisely.\n\n| Skill        | File                                     | Description                                                                                                                                                                                                                                 |\n| ------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| add-config   | `.kilocode/skills/add-config/SKILL.md`   | Guide for adding new configuration settings to Wave Terminal. Use when adding a new setting to the configuration system, implementing a new config key, or adding user-customizable settings.                                               |\n| add-rpc      | `.kilocode/skills/add-rpc/SKILL.md`      | Guide for adding new RPC calls to Wave Terminal. Use when implementing new RPC commands, adding server-client communication methods, or extending the RPC interface with new functionality.                                                 |\n| add-wshcmd   | `.kilocode/skills/add-wshcmd/SKILL.md`   | Guide for adding new wsh commands to Wave Terminal. Use when implementing new CLI commands, adding command-line functionality, or extending the wsh command interface.                                                                      |\n| context-menu | `.kilocode/skills/context-menu/SKILL.md` | Guide for creating and displaying context menus in Wave Terminal. Use when implementing right-click menus, adding context menu items, creating submenus, or handling menu interactions with checkboxes and separators.                      |\n| create-view  | `.kilocode/skills/create-view/SKILL.md`  | Guide for implementing a new view type in Wave Terminal. Use when creating a new view component, implementing the ViewModel interface, registering a new view type in BlockRegistry, or adding a new content type to display within blocks. |\n| electron-api | `.kilocode/skills/electron-api/SKILL.md` | Guide for adding new Electron APIs to Wave Terminal. Use when implementing new frontend-to-electron communications via preload/IPC.                                                                                                         |\n| waveenv      | `.kilocode/skills/waveenv/SKILL.md`      | Guide for creating WaveEnv narrowings in Wave Terminal. Use when writing a named subset type of WaveEnv for a component tree, documenting environmental dependencies, or enabling mock environments for preview/test server usage.          |\n| wps-events   | `.kilocode/skills/wps-events/SKILL.md`   | Guide for working with Wave Terminal's WPS (Wave PubSub) event system. Use when implementing new event types, publishing events, subscribing to events, or adding asynchronous communication between components.                            |\n","isInternal":false,"tokens":545,"sizeBytes":3279},{"name":"copilot-instructions.md","path":".github/copilot-instructions.md","rawUrl":"https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.github/copilot-instructions.md","title":"GitHub Copilot Instructions","category":"copilot-instructions","format":"markdown","content":"# Wave Terminal — Copilot Instructions\n\n## Project Rules\n\n- See the overview of the project in `.kilocode/rules/overview.md`\n- Read and follow all guidelines in `.kilocode/rules/rules.md`\n\n---\n\n## Skill Guides\n\nThis project uses a set of \"skill\" guides — focused how-to documents for common implementation tasks. When your task matches one of the descriptions below, **read the linked SKILL.md file before proceeding** and follow its instructions precisely.\n\n| Skill        | File                                     | Description                                                                                                                                                                                                                                 |\n| ------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| add-config   | `.kilocode/skills/add-config/SKILL.md`   | Guide for adding new configuration settings to Wave Terminal. Use when adding a new setting to the configuration system, implementing a new config key, or adding user-customizable settings.                                               |\n| add-rpc      | `.kilocode/skills/add-rpc/SKILL.md`      | Guide for adding new RPC calls to Wave Terminal. Use when implementing new RPC commands, adding server-client communication methods, or extending the RPC interface with new functionality.                                                 |\n| add-wshcmd   | `.kilocode/skills/add-wshcmd/SKILL.md`   | Guide for adding new wsh commands to Wave Terminal. Use when implementing new CLI commands, adding command-line functionality, or extending the wsh command interface.                                                                      |\n| context-menu | `.kilocode/skills/context-menu/SKILL.md` | Guide for creating and displaying context menus in Wave Terminal. Use when implementing right-click menus, adding context menu items, creating submenus, or handling menu interactions with checkboxes and separators.                      |\n| create-view  | `.kilocode/skills/create-view/SKILL.md`  | Guide for implementing a new view type in Wave Terminal. Use when creating a new view component, implementing the ViewModel interface, registering a new view type in BlockRegistry, or adding a new content type to display within blocks. |\n| electron-api | `.kilocode/skills/electron-api/SKILL.md` | Guide for adding new Electron APIs to Wave Terminal. Use when implementing new frontend-to-electron communications via preload/IPC.                                                                                                         |\n| waveenv      | `.kilocode/skills/waveenv/SKILL.md`      | Guide for creating WaveEnv narrowings in Wave Terminal. Use when writing a named subset type of WaveEnv for a component tree, documenting environmental dependencies, or enabling mock environments for preview/test server usage.          |\n| wps-events   | `.kilocode/skills/wps-events/SKILL.md`   | Guide for working with Wave Terminal's WPS (Wave PubSub) event system. Use when implementing new event types, publishing events, subscribing to events, or adding asynchronous communication between components.                            |\n\n> **How skills work:** Each skill is a self-contained guide covering the exact files to edit, patterns to follow, and steps to take for a specific type of task in this codebase. If your task matches a skill's description, open that SKILL.md and treat it as your primary reference for the implementation.\n\n---\n\n## Preview Server\n\nTo run the standalone component preview (no Electron, no backend required):\n\n```\ntask preview\n```\n\nThis runs `cd frontend/preview && npx vite` and serves at **http://localhost:7007** (port configured in `frontend/preview/vite.config.ts`).\n\nTo build a static preview: `task build:preview`\n\n**Do NOT use any of the following to start the preview — they all launch the full Electron app or serve the wrong content:**\n\n- `npm run dev` — runs `electron-vite dev`, launches Electron\n- `npm run start` — also launches Electron\n- `npx vite` from the repo root — uses the Electron-Vite config, not the preview app\n- Serving the `dist/` directory — the preview app is never built there; it has its own build output\n","isInternal":false,"tokens":821,"sizeBytes":4488}],"systemPromptSnippet":"<yakaai_skills repo=\"wavetermdev/waveterm\">\n<!-- File: .kilocode/rules/overview.md (Tokens: ~1656 | Category: anthropic-skill) -->\n# Wave Terminal - High Level Architecture Overview\n\n## Project Description\n\nWave Terminal is an open-source AI-native terminal built for seamless workflows. It's an Electron application that serves as a command line terminal host (it hosts CLI applications rather than running inside a CLI). The application combines a React frontend with a Go backend server to provide a modern terminal experience with advanced features.\n\n## Top-Level Directory Structure\n\n```\nwaveterm/\n├── emain/              # Electron main process code\n├── frontend/           # React application (renderer process)\n├── cmd/                # Go command-line applications\n├── pkg/                # Go packages/modules\n├── db/                 # Database migrations\n├── docs/               # Documentation (Docusaurus)\n├── build/              # Build configuration and assets\n├── assets/             # Application assets (icons, images)\n├── public/             # Static public assets\n├── tests/              # Test files\n├── .github/            # GitHub workflows and configuration\n└── Configuration files (package.json, tsconfig.json, etc.)\n```\n\n## Architecture Components\n\n### 1. Electron Main Process (`emain/`)\n\nThe Electron main process handles the native desktop application layer:\n\n**Key Files:**\n\n- [`emain.ts`](emain/emain.ts) - Main entry point, application lifecycle management\n- [`emain-window.ts`](emain/emain-window.ts) - Window management (`WaveBrowserWindow` class)\n- [`emain-tabview.ts`](emain/emain-tabview.ts) - Tab view management (`WaveTabView` class)\n- [`emain-wavesrv.ts`](emain/emain-wavesrv.ts) - Go backend server integration\n- [`emain-wsh.ts`](emain/emain-wsh.ts) - WSH (Wave Shell) client integration\n- [`emain-ipc.ts`](emain/emain-ipc.ts) - IPC handlers for frontend ↔ main process communication\n- [`emain-menu.ts`](emain/emain-menu.ts) - Application menu system\n- [`updater.ts`](emain/updater.ts) - Auto-update functionality\n- [`preload.ts`](emain/preload.ts) - Preload script for renderer security\n- [`preload-webview.ts`](emain/preload-webview.ts) - Webview preload script\n\n### 2. Frontend React Application (`frontend/`)\n\nThe React application runs in the Electron renderer process:\n\n**Structure:**\n\n```\nfrontend/\n├── app/                # Main application code\n│   ├── app.tsx         # Root App component\n│   ├── aipanel/        # AI panel UI\n│   ├── block/          # Block-based UI components\n│   ├── element/        # Reusable UI elements\n│   ├── hook/           # Custom React hooks\n│   ├── modals/         # Modal components\n│   ├── store/          # State management (Jotai)\n│   ├── tab/            # Tab components\n│   ├── view/           # Different view types\n│   │   ├── codeeditor/ # Code editor (Monaco)\n│   │   ├── preview/    # File preview\n│   │   ├── sysinfo/    # System info view\n│   │   ├── term/       # Terminal view\n│   │   ├── tsunami/    # Tsunami builder view\n│   │   ├── vdom/       # Virtual DOM view\n│   │   ├── waveai/     # AI chat integration\n│   │   ├── waveconfig/ # Config editor view\n│   │   └── webview/    # Web view\n│   └── workspace/      # Workspace management\n├── builder/            # Builder app entry\n├── layout/             # Layout system\n├── preview/            # Standalone preview renderer\n├── types/              # TypeScript type definitions\n└── util/               # Utility functions\n```\n\n**Key Technologies:**\n\n- Electron (desktop application shell)\n- React 19 with TypeScript\n- Jotai for state management\n- Monaco Editor for code editing\n- XTerm.js for terminal emulation\n- Tailwind CSS v4 for styling\n- SCSS for additional styling (deprecated, new components should use Tailwind)\n- Vite / electron-vite for bundling\n- Task (Taskfile.yml) for build and code generation commands\n\n### 3. Go Backend Server (`cmd/server/`)\n\nThe Go backend server handles all heavy lifting operations:\n\n**Entry Point:** [`main-server.go`](cmd/server/main-server.go)\n\n### 4. Go Packages (`pkg/`)\n\nThe Go codebase is organized into modular packages:\n\n**Key Packages:**\n\n- `wstore/` - Database and storage layer\n- `wconfig/` - Configuration management\n- `wcore/` - Core business logic\n- `wshrpc/` - RPC communication system\n- `wshutil/` - WSH (Wave Shell) utilities\n- `blockcontroller/` - Block execution management\n- `remote/` - Remote connection handling\n- `filestore/` - File storage system\n- `web/` - Web server and WebSocket handling\n- `telemetry/` - Usage analytics and telemetry\n- `waveobj/` - Core data objects\n- `service/` - Service layer\n- `wps/` - Wave PubSub event system\n- `waveai/` - AI functionality\n- `shellexec/` - Shell execution\n- `util/` - Common utilities\n\n### 5. Command Line Tools (`cmd/`)\n\nKey Go command-line utilities:\n\n- `wsh/` - Wave Shell command-line tool\n- `server/` - Main backend server\n- `generatego/` - Code generation\n- `generateschema/` - Schema generation\n- `generatets/` - TypeScript generation\n\n## Communication Architecture\n\nThe core communication system is built around the **WSH RPC (Wave Shell RPC)** system, which provides a unified interface for all inter-process communication: frontend ↔ Go backend, Electron main process ↔ backend, and backend ↔ remote systems (SSH, WSL).\n\n### WSH RPC System (`pkg/wshrpc/`)\n\nThe WSH RPC system is the backbone of Wave Terminal's communication architecture:\n\n**Key Components:**\n\n- [`wshrpctypes.go`](pkg/wshrpc/wshrpctypes.go) - Core RPC interface and type definitions (source of truth for all RPC commands)\n- [`wshserver/`](pkg/wshrpc/wshserver/) - Server-side RPC implementation\n- [`wshremote/`](pkg/wshrpc/wshremote/) - Remote connection handling\n- [`wshclient.go`](pkg/wshrpc/wshclient.go) - Go client for making RPC calls\n- [`frontend/app/store/wshclientapi.ts`](frontend/app/store/wshclientapi.ts) - Generated TypeScript RPC client\n\n**Routing:** Callers address RPC calls using _routes_ (e.g. a block ID, connection name, or `\"waveapp\"`) rather than caring about the underlying transport. The RPC layer resolves the route to the correct transport (WebSocket, Unix socket, SSH tunnel, stdio) automatically. This means the same RPC interface works whether the target is local or a remote SSH connection.\n\n## Development Notes\n\n- **Build commands** - Use `task` (Taskfile.yml) for all build, generate, and packaging commands\n- **Code generation** - Run `task generate` after modifying Go types in `pkg/wshrpc/wshrpctypes.go`, `pkg/wconfig/settingsconfig.go`, or `pkg/waveobj/wtypemeta.go`\n- **Testing** - Vitest for frontend unit tests; standard `go test` for Go packages\n- **Database migrations** - SQL migration files in `db/migrations-wstore/` and `db/migrations-filestore/`\n- **Documentation** - Docusaurus site in `docs/`\n\n<!-- File: .kilocode/rules/rules.md (Tokens: ~2764 | Category: anthropic-skill) -->\nWave Terminal is a modern terminal which provides graphical blocks, dynamic layout, workspaces, and SSH connection management. It is cross platform and built on electron.\n\n### Project Structure\n\nIt has a TypeScript/React frontend and a Go backend. They talk together over `wshrpc` a custom RPC protocol that is implemented over websocket (and domain sockets).\n\n### Coding Guidelines\n\n- **Go Conventions**:\n  - Don't use custom enum types in Go. Instead, use string constants (e.g., `const StatusRunning = \"running\"` rather than creating a custom type like `type Status string`).\n  - Use string constants for status values, packet types, and other string-based enumerations.\n  - in Go code, prefer using Printf() vs Println()\n  - use \"Make\" as opposed to \"New\" for struct initialization func names\n  - in general const decls go at the top of the file (before types and functions)\n  - NEVER run `go build` (especially in weird sub-package directories). we can tell if everything compiles by seeing there are no problems/errors.\n- **Synchronization**:\n  - Always prefer to use the `lock.Lock(); defer lock.Unlock()` pattern for synchronization if possible\n  - Avoid inline lock/unlock pairs - instead create helper functions that use the defer pattern\n  - When accessing shared data structures (maps, slices, etc.), ensure proper locking\n  - Example: Instead of `gc.lock.Lock(); gc.map[key]++; gc.lock.Unlock()`, create a helper function like `getNextValue(key string) int { gc.lock.Lock(); defer gc.lock.Unlock(); gc.map[key]++; return gc.map[key] }`\n- **TypeScript Imports**:\n  - Use `@/...` for imports from different parts of the project (configured in `tsconfig.json` as `\"@/*\": [\"frontend/*\"]`).\n  - Prefer relative imports (`\"./name\"`) only within the same directory.\n  - Use named exports exclusively; avoid default exports. It's acceptable to export functions directly (e.g., React Components).\n  - Our indent is 4 spaces\n- **JSON Field Naming**: All fields must be lowercase, without underscores.\n- **TypeScript Conventions**\n  - **Type Handling**:\n    - In TypeScript we have strict null checks off, so no need to add \"| null\" to all the types.\n    - In TypeScript for Jotai atoms, if we want to write, we need to type the atom as a PrimitiveAtom<Type>\n    - Jotai has a bug with strict null checks off where if you create a null atom, e.g. atom(null) it does not \"type\" correctly. That's no issue, just cast it to the proper PrimitiveAtom type (no \"| null\") and it will work fine.\n    - Generally never use \"=== undefined\" or \"!== undefined\". This is bad style. Just use a \"== null\" or \"!= null\" unless it is a very specific case where we need to distinguish undefined from null.\n  - **Coding Style**:\n    - Use all lowercase filenames (except where case is actually important like Taskfile.yml)\n    - Import the \"cn\" function from \"@/util/util\" to do classname / clsx class merge (it uses twMerge underneath)\n    - Do NOT create private fields in classes (they are impossible to inspect)\n    - Use PascalCase for global consts at the top of files\n  - **Component Practices**:\n    - Make sure to add cursor-pointer to buttons/links and clickable items\n    - NEVER use cursor-help (it looks terrible)\n    - useAtom() and useAtomValue() are react HOOKS, so they must be called at the component level not inline in JSX\n    - If you use React.memo(), make sure to add a displayName for the component\n  - Other\n    - never use atob() or btoa() (not UTF-8 safe). use functions in frontend/util/util.ts for base64 decoding and encoding\n- In general, when writing functions, we prefer _early returns_ rather than putting the majority of a function inside of an if block.\n\n### Styling\n\n- We use **Tailwind v4** to style. Custom stuff is defined in frontend/tailwindsetup.css\n- _never_ use cursor-help, or cursor-not-allowed (it looks terrible)\n- We have custom CSS setup as well, so it is a hybrid system. For new code we prefer tailwind, and are working to migrate code to all use tailwind.\n- For accent buttons, use \"bg-accent/80 text-primary rounded hover:bg-accent transition-colors cursor-pointer\" (if you do \"bg-accent hover:bg-accent/80\" it looks weird as on hover the button gets darker instead of lighter)\n\n### RPC System\n\nTo define a new RPC call, add the new definition to `pkg/wshrpc/wshrpctypes.go` including any input/output data that is required. After modifying wshrpctypes.go run `task generate` to generate the client APIs.\n\nFor normal \"server\" RPCs (where a frontend client is calling the main server) you should implement the RPC call in `pkg/wshrpc/wshserver.go`.\n\n### Electron API\n\nFrom within the FE to get the electron API (e.g. the preload functions):\n\n```ts\nimport { getApi } from \"@/store/global\";\n\ngetApi().getIsDev();\n```\n\nThe full API is defined in custom.d.ts as type ElectronApi.\n\n### Code Generation\n\n- **TypeScript Types**: TypeScript types are automatically generated from Go types. After modifying Go types in `pkg/wshrpc/wshrpctypes.go`, run `task generate` to update the TypeScript type definitions in `frontend/types/gotypes.d.ts`.\n- **Manual Edits**: Do not manually edit generated files like `frontend/types/gotypes.d.ts` or `frontend/app/store/wshclientapi.ts`. Instead, modify the source Go types and run `task generate`.\n\n### Frontend Architecture\n\n- The application uses Jotai for state management.\n- When working with Jotai atoms that need to be updated, define them as `PrimitiveAtom<Type>` rather than just `atom<Type>`.\n\n### Notes\n\n- **CRITICAL: Completion format MUST be: \"Done: [one-line description]\"**\n- **Keep your Task Completed summaries VERY short**\n- **No lengthy pre-completion summaries** - Do not provide detailed explanations of implementation before using attempt_completion\n- **No recaps of changes** - Skip explaining what was done before completion\n- **Go directly to completion** - After making changes, proceed directly to attempt_completion without summarizing\n- The project is currently an un-released POC / MVP. Do not worry about backward compatibility when making changes\n- With React hooks, always complete all hook calls at the top level before any conditional returns (including jotai hook calls useAtom and useAtomValue); when a user explicitly tells you a function handles null inputs, trust them and stop trying to \"protect\" it with unnecessary checks or workarounds.\n- **Match response length to question complexity** - For simple, direct questions in Ask mode (especially those that can be answered in 1-2 sentences), provide equally brief answers. Save detailed explanations for complex topics or when explicitly requested.\n- **CRITICAL** - useAtomValue and useAtom are React HOOKS. They cannot be used inline in JSX code, they must appear at the top of a component in the hooks area of the react code.\n- for simple functions, we prefer `if (!cond) { return }; functionality;` pattern over `if (cond) { functionality }` because it produces less indentation and is easier to follow.\n- It is now 2026, so if you write new files, or update files use 2026 for the copyright year\n- React.MutableRefObject is deprecated, just use React.RefObject now (in React 19 RefObject is always mutable)\n\n### Strict Comment Rules\n\n- **NEVER add comments that merely describe what code is doing**:\n  - ❌ `mutex.Lock() // Lock the mutex`\n  - ❌ `counter++ // Increment the counter`\n  - ❌ `buffer.Write(data) // Write data to buffer`\n  - ❌ `// Header component for app run list` (above AppRunListHeader)\n  - ❌ `// Updated function to include onClick parameter`\n  - ❌ `// Changed padding calculation`\n  - ❌ `// Removed unnecessary div`\n  - ❌ `// Using the model's width value here`\n- **Only use comments for**:\n  - Explaining WHY a particular approach was chosen\n  - Documenting non-obvious edge cases or side effects\n  - Warning about potential pitfalls in usage\n  - Explaining complex algorithms that can't be simplified\n- **When in doubt, leave it out**. No comment is better than a redundant comment.\n- **Never add comments explaining code changes** - The code should speak for itself, and version control tracks changes. The one exception to this rule is if it is a very unobvious implementation. Something that someone would typically implement in a different (wrong) way. Then the comment helps us remember WHY we changed it to a less obvious implementation.\n- **Never remove existing comments** unless specifically directed by the user. Comments that are already defined in existing code have been vetted by the user.\n\n### Jotai Model Pattern (our rules)\n\n- **Atoms live on the model.**\n- **Simple atoms:** define as **field initializers**.\n- **Atoms that depend on values/other atoms:** create in the **constructor**.\n- Models **never use React hooks**; they use `globalStore.get/set`.\n- It's fine to call model methods from **event handlers** or **`useEffect`**.\n- Models use the **singleton pattern** with a `private static instance` field, a `private constructor`, and a `static getInstance()` method.\n- The constructor is `private`; callers always use `getInstance()`.\n\n```ts\n// model/MyModel.ts\nimport * as jotai from \"jotai\";\nimport { globalStore } from \"@/app/store/jotaiStore\";\n\nexport class MyModel {\n  private static instance: MyModel | null = null;\n\n  // simple atoms (field init)\n  statusAtom = jotai.atom<\"idle\" | \"running\" | \"error\">(\"idle\");\n  outputAtom = jotai.atom(\"\");\n\n  // ctor-built atoms (need types)\n  lengthAtom!: jotai.Atom<number>;\n  thresholdedAtom!: jotai.Atom<boolean>;\n\n  private constructor(initialThreshold = 20) {\n    this.lengthAtom = jotai.atom((get) => get(this.outputAtom).length);\n    this.thresholdedAtom = jotai.atom((get) => get(this.lengthAtom) > initialThreshold);\n  }\n\n  static getInstance(): MyModel {\n    if (!MyModel.instance) {\n      MyModel.instance = new MyModel();\n    }\n    return MyModel.instance;\n  }\n\n  static resetInstance(): void {\n    MyModel.instance = null;\n  }\n\n  async doWork() {\n    globalStore.set(this.statusAtom, \"running\");\n    // ... do work ...\n    globalStore.set(this.statusAtom, \"idle\");\n  }\n}\n```\n\n```tsx\n// component usage (events & effects OK)\nimport { useAtomValue } from \"jotai\";\n\nfunction Panel() {\n  const model = MyModel.getInstance();\n  const status = useAtomValue(model.statusAtom);\n  const isBig = useAtomValue(model.thresholdedAtom);\n\n  const onClick = () => model.doWork();\n\n  return (\n    <div>\n      {status} • {String(isBig)}\n    </div>\n  );\n}\n```\n\n**Remember:** singleton pattern with `getInstance()`, `private constructor`, atoms on the model, simple-as-fields, ctor for dependent/derived, updates via `globalStore.set/get`.\n**Note** Older models may not use the singleton pattern\n\n### Tool Use\n\nDo NOT use write_to_file unless it is a new file or very short. Always prefer to use replace_in_file. Often your diffs fail when a file may be out of date in your cache vs the actual on-disk format. You should RE-READ the file and try to create diffs again if your diffs fail rather than fall back to write_to_file. If you feel like your ONLY option is to use write_to_file please ask first.\n\nAlso when adding content to the end of files prefer to use the new append_file tool rather than trying to create a diff (as your diffs are often not specific enough and end up inserting code in the middle of existing functions).\n\n### Directory Awareness\n\n- **ALWAYS verify the current working directory before executing commands**\n- Either run \"pwd\" first to verify the directory, or do a \"cd\" to the correct absolute directory before running commands\n- When running tests, do not \"cd\" to the pkg directory and then run the test. This screws up the cwd and you never recover. run the test from the project root instead.\n\n### Testing / Compiling Go Code\n\nNo need to run a `go build` or a `go run` to just check if the Go code compiles. VSCode's errors/problems cover this well.\nIf there are no Go errors in VSCode you can assume the code compiles fine.\n\n<!-- File: .kilocode/skills/add-config/SKILL.md (Tokens: ~3449 | Category: anthropic-skill) -->\n---\nname: add-config\ndescription: Guide for adding new configuration settings to Wave Terminal. Use when adding a new setting to the configuration system, implementing a new config key, or adding user-customizable settings.\n---\n\n# Adding a New Configuration Setting to Wave Terminal\n\nThis guide explains how to add a new configuration setting to Wave Terminal's hierarchical configuration system.\n\n## Configuration System Overview\n\nWave Terminal uses a hierarchical configuration system with:\n\n1. **Go Struct Definitions** - Type-safe configuration structure in `pkg/wconfig/settingsconfig.go`\n2. **JSON Schema** - Auto-generated validation schema in `schema/settings.json`\n3. **Default Values** - Built-in defaults in `pkg/wconfig/defaultconfig/settings.json`\n4. **User Configuration** - User overrides in `~/.config/waveterm/settings.json`\n5. **Block Metadata** - Block-level overrides in `pkg/waveobj/wtypemeta.go`\n6. **Documentation** - User-facing docs in `docs/docs/config.mdx`\n\nSettings cascade from defaults → user settings → connection config → block overrides.\n\n## Step-by-Step Guide\n\n### Step 1: Add to Go Struct Definition\n\nEdit `pkg/wconfig/settingsconfig.go` and add your new field to the `SettingsType` struct:\n\n```go\ntype SettingsType struct {\n    // ... existing fields ...\n\n    // Add your new field with appropriate JSON tag\n    MyNewSetting string `json:\"mynew:setting,omitempty\"`\n\n    // For different types:\n    MyBoolSetting   bool    `json:\"mynew:boolsetting,omitempty\"`\n    MyNumberSetting float64 `json:\"mynew:numbersetting,omitempty\"`\n    MyIntSetting    *int64  `json:\"mynew:intsetting,omitempty\"`    // Use pointer for optional ints\n    MyArraySetting  []string `json:\"mynew:arraysetting,omitempty\"`\n}\n```\n\n**Naming Conventions:**\n\n- Use namespace prefixes (e.g., `term:`, `window:`, `ai:`, `web:`, `app:`)\n- Use lowercase with colons as separators\n- Field names should be descriptive and follow Go naming conventions\n- Use `omitempty` tag to exclude empty values from JSON\n\n**Type Guidelines:**\n\n- Use `*int64` and `*float64` for optional numeric values\n- Use `*bool` for optional boolean values (or `bool` if default is false)\n- Use `string` for text values\n- Use `[]string` for arrays\n- Use `float64` for numbers that can be decimals\n\n**Namespace Organization:**\n\n- `app:*` - Application-level settings\n- `term:*` - Terminal-specific settings\n- `window:*` - Window and UI settings\n- `ai:*` - AI-related settings\n- `web:*` - Web browser settings\n- `editor:*` - Code editor settings\n- `conn:*` - Connection settings\n\n### Step 1.5: Add to Block Metadata (Optional)\n\nIf your setting should support block-level overrides, also add it to `pkg/waveobj/wtypemeta.go`:\n\n```go\ntype MetaTSType struct {\n    // ... existing fields ...\n\n    // Add your new field with matching JSON tag and type\n    MyNewSetting *string `json:\"mynew:setting,omitempty\"`  // Use pointer for optional values\n\n    // For different types:\n    MyBoolSetting   *bool    `json:\"mynew:boolsetting,omitempty\"`\n    MyNumberSetting *float64 `json:\"mynew:numbersetting,omitempty\"`\n    MyIntSetting    *int     `json:\"mynew:intsetting,omitempty\"`\n    MyArraySetting  []string `json:\"mynew:arraysetting,omitempty\"`\n}\n```\n\n**Block Metadata Guidelines:**\n\n- Use pointer types (`*string`, `*bool`, `*int`, `*float64`) for optional overrides\n- JSON tags should exactly match the corresponding settings field\n- This enables the hierarchical config system: block metadata → connection config → global settings\n- Only add settings here that make sense to override per-block or per-connection\n\n### Step 2: Set Default Value (Optional)\n\nIf your setting should have a default value, add it to `pkg/wconfig/defaultconfig/settings.json`:\n\n```json\n{\n  \"ai:preset\": \"ai@global\",\n  \"ai:model\": \"gpt-5-mini\",\n  // ... existing defaults ...\n\n  \"mynew:setting\": \"default value\",\n  \"mynew:boolsetting\": true,\n  \"mynew:numbersetting\": 42.5,\n  \"mynew:intsetting\": 100\n}\n```\n\n**Default Value Guidelines:**\n\n- Only add defaults for settings that should have non-zero/non-empty initial values\n- Ensure defaults make sense for typical user experience\n- Keep defaults conservative and safe\n- Boolean settings often don't need defaults if `false` is the correct default\n\n### Step 3: Update Documentation\n\nAdd your new setting to the configuration table in `docs/docs/config.mdx`:\n\n```markdown\n| Key Name            | Type     | Function                                  |\n| ------------------- | -------- | ----------------------------------------- |\n| mynew:setting       | string   | Description of what this setting controls |\n| mynew:boolsetting   | bool     | Enable/disable some feature               |\n| mynew:numbersetting | float    | Numeric setting for some parameter        |\n| mynew:intsetting    | int      | Integer setting for some configuration    |\n| mynew:arraysetting  | string[] | Array of strings for multiple values      |\n```\n\n**Documentation Guidelines:**\n\n- Provide clear, concise descriptions\n- For new settings in upcoming releases, add `<VersionBadge version=\"v0.14\" />`\n- Update the default configuration example if you added defaults\n- Explain what values are valid and what they do\n\n### Step 4: Regenerate Schema and TypeScript Types\n\nRun the generate task to automatically regenerate the JSON schema and TypeScript types:\n\n```bash\ntask generate\n```\n\n**What this does:**\n\n- Runs `task build:schema` (automatically generates JSON schema from Go structs)\n- Generates TypeScript type definitions in `frontend/types/gotypes.d.ts`\n- Generates RPC client APIs\n- Generates metadata constants\n\n**Important:** The JSON schema in `schema/settings.json` is **automatically generated** from the Go struct definitions - you don't need to edit it manually.\n\n### Step 5: Use in Frontend Code\n\nAccess your new setting in React components:\n\n```typescript\nimport { getOverrideConfigAtom, getSettingsKeyAtom, useAtomValue } from \"@/store/global\";\n\n// In a React component\nconst MyComponent = ({ blockId }: { blockId: string }) => {\n    // Use override config atom for hierarchical resolution\n    // This automatically checks: block metadata → connection config → global settings → default\n    const mySettingAtom = getOverrideConfigAtom(blockId, \"mynew:setting\");\n    const mySetting = useAtomValue(mySettingAtom) ?? \"fallback value\";\n\n    // For global-only settings (no block overrides)\n    const globalOnlySetting = useAtomValue(getSettingsKeyAtom(\"mynew:globalsetting\")) ?? \"fallback\";\n\n    return <div>Setting value: {mySetting}</div>;\n};\n```\n\n**Frontend Configuration Patterns:**\n\n```typescript\n// 1. Settings with block-level overrides (recommended for most view/display settings)\nconst termFontSize = useAtomValue(getOverrideConfigAtom(blockId, \"term:fontsize\")) ?? 12;\n\n// 2. Global-only settings (app-wide settings that don't vary by block)\nconst appGlobalHotkey = useAtomValue(getSettingsKeyAtom(\"app:globalhotkey\")) ?? \"\";\n\n// 3. Connection-specific settings\nconst connStatus = useAtomValue(getConnStatusAtom(connectionName));\n```\n\n**When to use each pattern:**\n\n- Use `getOverrideConfigAtom()` for settings that can vary by block or connection (most UI/display settings)\n- Use `getSettingsKeyAtom()` for app-level settings that are always global\n- Always provide a fallback value with `??` operator\n\n### Step 6: Use in Backend Code\n\nAccess settings in Go code:\n\n```go\n// Get the full config\nfullConfig := wconfig.GetWatcher().GetFullConfig()\n\n// Access your setting\nmyValue := fullConfig.Settings.MyNewSetting\n\n// For optional values (pointers)\nif fullConfig.Settings.MyIntSetting != nil {\n    intValue := *fullConfig.Settings.MyIntSetting\n    // Use intValue\n}\n```\n\n## Complete Examples\n\n### Example 1: Simple Boolean Setting (No Block Override)\n\n**Use case:** Add a setting to hide the AI button globally\n\n#### 1. Go Struct (`pkg/wconfig/settingsconfig.go`)\n\n```go\ntype SettingsType struct {\n    // ... existing fields ...\n    AppHideAiButton bool `json:\"app:hideaibutton,omitempty\"`\n}\n```\n\n#### 2. Default Value (`pkg/wconfig/defaultconfig/settings.json`)\n\n```json\n{\n  \"app:hideaibutton\": false\n}\n```\n\n#### 3. Documentation (`docs/docs/config.mdx`)\n\n```markdown\n| app:hideaibutton <VersionBadge version=\"v0.14\" /> | bool | Hide the AI button in the tab bar (defaults to false) |\n```\n\n#### 4. Generate Types\n\n```bash\ntask generate\n```\n\n#### 5. Frontend Usage\n\n```typescript\nimport { getSettingsKeyAtom } from \"@/store/global\";\n\nconst TabBar = () => {\n    const hideAiButton = useAtomValue(getSettingsKeyAtom(\"app:hideaibutton\"));\n\n    if (hideAiButton) {\n        return null; // Don't render AI button\n    }\n\n    return <button>AI</button>;\n};\n```\n\n#### 6. Usage Examples\n\n```bash\n# Set in settings file\nwsh setconfig app:hideaibutton=true\n\n# Or edit ~/.config/waveterm/settings.json\n{\n  \"app:hideaibutton\": true\n}\n```\n\n### Example 2: Terminal Setting with Block Override\n\n**Use case:** Add a terminal bell sound setting that can be overridden per block\n\n#### 1. Go Struct (`pkg/wconfig/settingsconfig.go`)\n\n```go\ntype SettingsType struct {\n    // ... existing fields ...\n    TermBellSound string `json:\"term:bellsound,omitempty\"`\n}\n```\n\n#### 2. Block Metadata (`pkg/waveobj/wtypemeta.go`)\n\n```go\ntype MetaTSType struct {\n    // ... existing fields ...\n    TermBellSound *string `json:\"term:bellsound,omitempty\"`  // Pointer for optional override\n}\n```\n\n#### 3. Default Value (`pkg/wconfig/defaultconfig/settings.json`)\n\n```json\n{\n  \"term:bellsound\": \"default\"\n}\n```\n\n#### 4. Documentation (`docs/docs/config.mdx`)\n\n```markdown\n| term:bellsound <VersionBadge version=\"v0.14\" /> | string | Sound to play for terminal bell (\"default\", \"none\", or custom sound file path) |\n```\n\n#### 5. Generate Types\n\n```bash\ntask generate\n```\n\n#### 6. Frontend Usage\n\n```typescript\nimport { getOverrideConfigAtom } from \"@/store/global\";\n\nconst TerminalView = ({ blockId }: { blockId: string }) => {\n    // Use override config for hierarchical resolution\n    const bellSoundAtom = getOverrideConfigAtom(blockId, \"term:bellsound\");\n    const bellSound = useAtomValue(bellSoundAtom) ?? \"default\";\n\n    const playBellSound = () => {\n        if (bellSound === \"none\") return;\n        // Play the bell sound\n    };\n\n    return <div>Terminal with bell: {bellSound}</div>;\n};\n```\n\n#### 7. Usage Examples\n\n```bash\n# Set globally in settings file\nwsh setconfig term:bellsound=\"custom.wav\"\n\n# Set for current block only\nwsh setmeta term:bellsound=\"none\"\n\n# Set for specific block\nwsh setmeta --block BLOCK_ID term:bellsound=\"beep\"\n\n# Or edit ~/.config/waveterm/settings.json\n{\n  \"term:bellsound\": \"custom.wav\"\n}\n```\n\n## Configuration Patterns\n\n### Clear/Reset Pattern\n\nEach namespace can have a \"clear\" field for resetting all settings in that namespace:\n\n```go\nAppClear  bool `json:\"app:*,omitempty\"`\nTermClear bool `json:\"term:*,omitempty\"`\n```\n\n### Optional vs Required Settings\n\n- Use pointer types (`*bool`, `*int64`, `*float64`) for truly optional settings\n- Use regular types for settings that should always have a value\n- Provide sensible defaults for important settings\n\n### Block-Level Overrides via RPC\n\nSettings can be overridden at the block level using metadata:\n\n```typescript\nimport { RpcApi } from \"@/app/store/wshclientapi\";\nimport { TabRpcClient } from \"@/app/store/wshrpcutil\";\nimport { WOS } from \"@/store/global\";\n\n// Set block-specific override\nawait RpcApi.SetMetaCommand(TabRpcClient, {\n  oref: WOS.makeORef(\"block\", blockId),\n  meta: { \"mynew:setting\": \"block-specific value\" },\n});\n```\n\n## Common Pitfalls\n\n### 1. Forgetting to Run `task generate`\n\n**Problem:** TypeScript types not updated, schema out of sync\n\n**Solution:** Always run `task generate` after modifying Go structs\n\n### 2. Type Mismatch Between Settings and Metadata\n\n**Problem:** Settings uses `string`, metadata uses `*int`\n\n**Solution:** Ensure types match (except metadata uses pointers for optionals)\n\n### 3. Not Providing Fallback Values\n\n**Problem:** Component breaks if setting is undefined\n\n**Solution:** Always use `??` operator with fallback:\n\n```typescript\nconst value = useAtomValue(getSettingsKeyAtom(\"key\")) ?? \"default\";\n```\n\n### 4. Using Wrong Config Atom\n\n**Problem:** Using `getSettingsKeyAtom()` for settings that need block overrides\n\n**Solution:** Use `getOverrideConfigAtom()` for any setting in `MetaTSType`\n\n## Best Practices\n\n### Naming\n\n- **Use descriptive names**: `term:fontsize` not `term:fs`\n- **Follow namespace conventions**: Group related settings with common prefix\n- **Use consistent casing**: Always lowercase with colons\n\n### Types\n\n- **Use `bool`** for simple on/off settings (no pointer if false is default)\n- **Use `*bool`** only if you need to distinguish unset from false\n- **Use `*int64`/`*float64`** for optional numeric values\n- **Use `string`** for text, paths, or enum-like values\n- **Use `[]string`** for lists\n\n### Defaults\n\n- **Provide sensible defaults** for settings users will commonly change\n- **Omit defaults** for advanced/optional settings\n- **Keep defaults safe** - don't enable experimental features by default\n- **Document defaults** clearly in config.mdx\n\n### Block Overrides\n\n- **Enable for view/display settings**: Font sizes, colors, themes, etc.\n- **Don't enable for app-wide settings**: Global hotkeys, window behavior, etc.\n- **Consider the use case**: Would a user want different values per block or connection?\n\n### Documentation\n\n- **Be specific**: Explain what the setting does and what values are valid\n- **Provide examples**: Show common use cases\n- **Add version badges**: Mark new settings with `<VersionBadge version=\"v0.x\" />`\n- **Keep it current**: Update docs when behavior changes\n\n## Quick Reference\n\nWhen adding a new configuration setting:\n\n- [ ] Add field to `SettingsType` in `pkg/wconfig/settingsconfig.go`\n- [ ] Add field to `MetaTSType` in `pkg/waveobj/wtypemeta.go` (if block override needed)\n- [ ] Add default to `pkg/wconfig/defaultconfig/settings.json` (if needed)\n- [ ] Document in `docs/docs/config.mdx`\n- [ ] Run `task generate` to update TypeScript types\n- [ ] Use appropriate atom (`getOverrideConfigAtom` or `getSettingsKeyAtom`) in frontend\n\n## Related Documentation\n\n- **User Documentation**: `docs/docs/config.mdx` - User-facing configuration docs\n- **Type Definitions**: `pkg/wconfig/settingsconfig.go` - Go struct definitions\n- **Metadata Types**: `pkg/waveobj/wtypemeta.go` - Block metadata definitions\n\n<!-- File: .kilocode/skills/add-rpc/SKILL.md (Tokens: ~3001 | Category: anthropic-skill) -->\n---\nname: add-rpc\ndescription: Guide for adding new RPC calls to Wave Terminal. Use when implementing new RPC commands, adding server-client communication methods, or extending the RPC interface with new functionality.\n---\n\n# Adding RPC Calls Guide\n\n## Overview\n\nWave Terminal uses a WebSocket-based RPC (Remote Procedure Call) system for communication between different components. The RPC system allows the frontend, backend, electron main process, remote servers, and terminal blocks to communicate with each other through well-defined commands.\n\nThis guide covers how to add a new RPC command to the system.\n\n## Key Files\n\n- `pkg/wshrpc/wshrpctypes.go` - RPC interface and type definitions\n- `pkg/wshrpc/wshserver/wshserver.go` - Main server implementation (most common)\n- `emain/emain-wsh.ts` - Electron main process implementation\n- `frontend/app/store/tabrpcclient.ts` - Frontend tab implementation\n- `pkg/wshrpc/wshremote/wshremote.go` - Remote server implementation\n- `frontend/app/view/term/term-wsh.tsx` - Terminal block implementation\n\n## RPC Command Structure\n\nRPC commands in Wave Terminal follow these conventions:\n\n- **Method names** must end with `Command`\n- **First parameter** must be `context.Context`\n- **Remaining parameters** are a regular Go parameter list (zero or more typed args)\n- **Return values** can be either just an error, or one return value plus an error\n- **Streaming commands** return a channel instead of a direct value\n\n## Adding a New RPC Call\n\n### Step 1: Define the Command in the Interface\n\nAdd your command to the `WshRpcInterface` in `pkg/wshrpc/wshrpctypes.go`:\n\n```go\ntype WshRpcInterface interface {\n    // ... existing commands ...\n    \n    // Add your new command\n    YourNewCommand(ctx context.Context, data CommandYourNewData) (*YourNewResponse, error)\n}\n```\n\n**Method Signature Rules:**\n\n- Method name must end with `Command`\n- First parameter must be `ctx context.Context`\n- Remaining parameters are a regular Go parameter list (zero or more)\n- Return either `error` or `(ReturnType, error)`\n- For streaming, return `chan RespOrErrorUnion[T]`\n\n### Step 2: Define Request and Response Types\n\nIf your command needs structured input or output, define types in the same file:\n\n```go\ntype CommandYourNewData struct {\n    FieldOne   string `json:\"fieldone\"`\n    FieldTwo   int    `json:\"fieldtwo\"`\n    SomeId     string `json:\"someid\"`\n}\n\ntype YourNewResponse struct {\n    ResultField string `json:\"resultfield\"`\n    Success     bool   `json:\"success\"`\n}\n```\n\n**Type Naming Conventions:**\n\n- Request types: `Command[Name]Data` (e.g., `CommandGetMetaData`)\n- Response types: `[Name]Response` or `Command[Name]RtnData` (e.g., `CommandResolveIdsRtnData`)\n- Use `json` struct tags with lowercase field names\n- Follow existing patterns in the file for consistency\n\n### Step 3: Generate Bindings\n\nAfter modifying `pkg/wshrpc/wshrpctypes.go`, run code generation to create TypeScript bindings and Go helper code:\n\n```bash\ntask generate\n```\n\nThis command will:\n- Generate TypeScript type definitions in `frontend/types/gotypes.d.ts`\n- Create RPC client bindings\n- Update routing code\n\n**Note:** If generation fails, check that your method signature follows all the rules above.\n\n### Step 4: Implement the Command\n\nChoose where to implement your command based on what it needs to do:\n\n#### A. Main Server Implementation (Most Common)\n\nImplement in `pkg/wshrpc/wshserver/wshserver.go`:\n\n```go\nfunc (ws *WshServer) YourNewCommand(ctx context.Context, data wshrpc.CommandYourNewData) (*wshrpc.YourNewResponse, error) {\n    // Validate input\n    if data.SomeId == \"\" {\n        return nil, fmt.Errorf(\"someid is required\")\n    }\n    \n    // Implement your logic\n    result := doSomething(data)\n    \n    // Return response\n    return &wshrpc.YourNewResponse{\n        ResultField: result,\n        Success:     true,\n    }, nil\n}\n```\n\n**Use main server when:**\n- Accessing the database\n- Managing blocks, tabs, or workspaces\n- Coordinating between components\n- Handling file operations on the main filesystem\n\n#### B. Electron Implementation\n\nImplement in `emain/emain-wsh.ts`:\n\n```typescript\nasync handle_yournew(rh: RpcResponseHelper, data: CommandYourNewData): Promise<YourNewResponse> {\n    // Electron-specific logic\n    const result = await electronAPI.doSomething(data);\n    \n    return {\n        resultfield: result,\n        success: true,\n    };\n}\n```\n\n**Use Electron when:**\n- Accessing native OS features\n- Managing application windows\n- Using Electron APIs (notifications, system tray, etc.)\n- Handling encryption/decryption with safeStorage\n\n#### C. Frontend Tab Implementation\n\nImplement in `frontend/app/store/tabrpcclient.ts`:\n\n```typescript\nasync handle_yournew(rh: RpcResponseHelper, data: CommandYourNewData): Promise<YourNewResponse> {\n    // Access frontend state/models\n    const layoutModel = getLayoutModelForStaticTab();\n    \n    // Implement tab-specific logic\n    const result = layoutModel.doSomething(data);\n    \n    return {\n        resultfield: result,\n        success: true,\n    };\n}\n```\n\n**Use tab client when:**\n- Accessing React state or Jotai atoms\n- Manipulating UI layout\n- Capturing screenshots\n- Reading frontend-only data\n\n#### D. Remote Server Implementation\n\nImplement in `pkg/wshrpc/wshremote/wshremote.go`:\n\n```go\nfunc (impl *ServerImpl) RemoteYourNewCommand(ctx context.Context, data wshrpc.CommandRemoteYourNewData) (*wshrpc.YourNewResponse, error) {\n    // Remote filesystem or process operations\n    result, err := performRemoteOperation(data)\n    if err != nil {\n        return nil, fmt.Errorf(\"remote operation failed: %w\", err)\n    }\n    \n    return &wshrpc.YourNewResponse{\n        ResultField: result,\n        Success:     true,\n    }, nil\n}\n```\n\n**Use remote server when:**\n- Operating on remote filesystems\n- Executing commands on remote hosts\n- Managing remote processes\n- Convention: prefix command name with `Remote` (e.g., `RemoteGetInfoCommand`)\n\n#### E. Terminal Block Implementation\n\nImplement in `frontend/app/view/term/term-wsh.tsx`:\n\n```typescript\nasync handle_yournew(rh: RpcResponseHelper, data: CommandYourNewData): Promise<YourNewResponse> {\n    // Access terminal-specific data\n    const termWrap = this.model.termRef.current;\n    \n    // Implement terminal logic\n    const result = termWrap.doSomething(data);\n    \n    return {\n        resultfield: result,\n        success: true,\n    };\n}\n```\n\n**Use terminal client when:**\n- Accessing terminal buffer/scrollback\n- Managing VDOM contexts\n- Reading terminal-specific state\n- Interacting with xterm.js\n\n## Complete Example: Adding GetWaveInfo Command\n\n### 1. Define Interface\n\nIn `pkg/wshrpc/wshrpctypes.go`:\n\n```go\ntype WshRpcInterface interface {\n    // ... other commands ...\n    WaveInfoCommand(ctx context.Context) (*WaveInfoData, error)\n}\n\ntype WaveInfoData struct {\n    Version      string            `json:\"version\"`\n    BuildTime    string            `json:\"buildtime\"`\n    ConfigPath   string            `json:\"configpath\"`\n    DataPath     string            `json:\"datapath\"`\n}\n```\n\n### 2. Generate Bindings\n\n```bash\ntask generate\n```\n\n### 3. Implement in Main Server\n\nIn `pkg/wshrpc/wshserver/wshserver.go`:\n\n```go\nfunc (ws *WshServer) WaveInfoCommand(ctx context.Context) (*wshrpc.WaveInfoData, error) {\n    return &wshrpc.WaveInfoData{\n        Version:    wavebase.WaveVersion,\n        BuildTime:  wavebase.BuildTime,\n        ConfigPath: wavebase.GetConfigDir(),\n        DataPath:   wavebase.GetWaveDataDir(),\n    }, nil\n}\n```\n\n### 4. Call from Frontend\n\n```typescript\nimport { RpcApi } from \"@/app/store/wshclientapi\";\n\n// Call the RPC\nconst info = await RpcApi.WaveInfoCommand(TabRpcClient);\nconsole.log(\"Wave Version:\", info.version);\n```\n\n## Streaming Commands\n\nFor commands that return data progressively, use channels:\n\n### Define Streaming Interface\n\n```go\ntype WshRpcInterface interface {\n    StreamYourDataCommand(ctx context.Context, request YourDataRequest) chan RespOrErrorUnion[YourDataType]\n}\n```\n\n### Implement Streaming Command\n\n```go\nfunc (ws *WshServer) StreamYourDataCommand(ctx context.Context, request wshrpc.YourDataRequest) chan wshrpc.RespOrErrorUnion[wshrpc.YourDataType] {\n    rtn := make(chan wshrpc.RespOrErrorUnion[wshrpc.YourDataType])\n    \n    go func() {\n        defer close(rtn)\n        defer func() {\n            panichandler.PanicHandler(\"StreamYourDataCommand\", recover())\n        }()\n        \n        // Stream data\n        for i := 0; i < 10; i++ {\n            select {\n            case <-ctx.Done():\n                return\n            default:\n                rtn <- wshrpc.RespOrErrorUnion[wshrpc.YourDataType]{\n                    Response: wshrpc.YourDataType{\n                        Value: i,\n                    },\n                }\n                time.Sleep(100 * time.Millisecond)\n            }\n        }\n    }()\n    \n    return rtn\n}\n```\n\n## Best Practices\n\n1. **Validation First**: Always validate input parameters at the start of your implementation\n\n2. **Descriptive Names**: Use clear, action-oriented command names (e.g., `GetFullConfigCommand`, not `ConfigCommand`)\n\n3. **Error Handling**: Return descriptive errors with context:\n   ```go\n   return nil, fmt.Errorf(\"error creating block: %w\", err)\n   ```\n\n4. **Context Awareness**: Respect context cancellation for long-running operations:\n   ```go\n   select {\n   case <-ctx.Done():\n       return ctx.Err()\n   default:\n       // continue\n   }\n   ```\n\n5. **Consistent Types**: Follow existing naming patterns for request/response types\n\n6. **JSON Tags**: Always use lowercase JSON tags matching frontend conventions\n\n7. **Documentation**: Add comments explaining complex commands or special behaviors\n\n8. **Type Safety**: Leverage TypeScript generation - your types will be checked on both ends\n\n9. **Panic Recovery**: Use `panichandler.PanicHandler` in goroutines to prevent crashes\n\n10. **Route Awareness**: For multi-route scenarios, use `wshutil.GetRpcSourceFromContext(ctx)` to identify callers\n\n## Common Command Patterns\n\n### Simple Query\n\n```go\nfunc (ws *WshServer) GetSomethingCommand(ctx context.Context, id string) (*Something, error) {\n    obj, err := wstore.DBGet[*Something](ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"error getting something: %w\", err)\n    }\n    return obj, nil\n}\n```\n\n### Mutation with Updates\n\n```go\nfunc (ws *WshServer) UpdateSomethingCommand(ctx context.Context, data wshrpc.CommandUpdateData) error {\n    ctx = waveobj.ContextWithUpdates(ctx)\n    \n    // Make changes\n    err := wstore.UpdateObject(ctx, data.ORef, data.Updates)\n    if err != nil {\n        return fmt.Errorf(\"error updating: %w\", err)\n    }\n    \n    // Broadcast updates\n    updates := waveobj.ContextGetUpdatesRtn(ctx)\n    wps.Broker.SendUpdateEvents(updates)\n    \n    return nil\n}\n```\n\n### Command with Side Effects\n\n```go\nfunc (ws *WshServer) DoActionCommand(ctx context.Context, data wshrpc.CommandActionData) error {\n    // Perform action\n    result, err := performAction(data)\n    if err != nil {\n        return err\n    }\n    \n    // Publish event about the action\n    go func() {\n        wps.Broker.Publish(wps.WaveEvent{\n            Event: wps.Event_ActionComplete,\n            Data:  result,\n        })\n    }()\n    \n    return nil\n}\n```\n\n## Troubleshooting\n\n### Command Not Found\n\n- Ensure method name ends with `Command`\n- Verify you ran `task generate`\n- Check that the interface is in `WshRpcInterface`\n\n### Type Mismatch Errors\n\n- Run `task generate` after changing types\n- Ensure JSON tags are lowercase\n- Verify TypeScript code is using generated types\n\n### Command Times Out\n\n- Check for blocking operations\n- Ensure context is passed through\n- Consider using a streaming command for long operations\n\n### Routing Issues\n\n- For remote commands, ensure they're implemented in correct location\n- Check route configuration in RpcContext\n- Verify authentication for secured routes\n\n## Quick Reference\n\nWhen adding a new RPC command:\n\n- [ ] Add method to `WshRpcInterface` in `pkg/wshrpc/wshrpctypes.go` (must end with `Command`)\n- [ ] Define request/response types with JSON tags (if needed)\n- [ ] Run `task generate` to create bindings\n- [ ] Implement in appropriate location:\n  - [ ] `wshserver.go` for main server (most common)\n  - [ ] `emain-wsh.ts` for Electron\n  - [ ] `tabrpcclient.ts` for frontend\n  - [ ] `wshremote.go` for remote (prefix with `Remote`)\n  - [ ] `term-wsh.tsx` for terminal\n- [ ] Add input validation\n- [ ] Handle errors with context\n- [ ] Test the command end-to-end\n\n## Related Documentation\n\n- **WPS Events**: See the `wps-events` skill - Publishing events from RPC commands\n\n<!-- File: .kilocode/skills/add-wshcmd/SKILL.md (Tokens: ~5829 | Category: anthropic-skill) -->\n---\nname: add-wshcmd\ndescription: Guide for adding new wsh commands to Wave Terminal. Use when implementing new CLI commands, adding command-line functionality, or extending the wsh command interface.\n---\n\n# Adding a New wsh Command to Wave Terminal\n\nThis guide explains how to add a new command to the `wsh` CLI tool.\n\n## wsh Command System Overview\n\nWave Terminal's `wsh` command provides CLI access to Wave Terminal features. The system uses:\n\n1. **Cobra Framework** - CLI command structure and parsing\n2. **Command Files** - Individual command implementations in `cmd/wsh/cmd/wshcmd-*.go`\n3. **RPC Client** - Communication with Wave Terminal backend via `RpcClient`\n4. **Activity Tracking** - Telemetry for command usage analytics\n5. **Documentation** - User-facing docs in `docs/docs/wsh-reference.mdx`\n\nCommands are registered in their `init()` functions and execute through the Cobra framework.\n\n## Step-by-Step Guide\n\n### Step 1: Create Command File\n\nCreate a new file in `cmd/wsh/cmd/` named `wshcmd-[commandname].go`:\n\n```go\n// Copyright 2025, Command Line Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n    \"fmt\"\n\n    \"github.com/spf13/cobra\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient\"\n)\n\nvar myCommandCmd = &cobra.Command{\n    Use:   \"mycommand [args]\",\n    Short: \"Brief description of what this command does\",\n    Long: `Detailed description of the command.\nCan include multiple lines and examples of usage.`,\n    RunE:                  myCommandRun,\n    PreRunE:               preRunSetupRpcClient,  // Include if command needs RPC\n    DisableFlagsInUseLine: true,\n}\n\n// Flag variables\nvar (\n    myCommandFlagExample string\n    myCommandFlagVerbose bool\n)\n\nfunc init() {\n    // Add command to root\n    rootCmd.AddCommand(myCommandCmd)\n    \n    // Define flags\n    myCommandCmd.Flags().StringVarP(&myCommandFlagExample, \"example\", \"e\", \"\", \"example flag description\")\n    myCommandCmd.Flags().BoolVarP(&myCommandFlagVerbose, \"verbose\", \"v\", false, \"enable verbose output\")\n}\n\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    // Always track activity for telemetry\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Validate arguments\n    if len(args) == 0 {\n        OutputHelpMessage(cmd)\n        return fmt.Errorf(\"requires at least one argument\")\n    }\n    \n    // Command implementation\n    fmt.Printf(\"Command executed successfully\\n\")\n    return nil\n}\n```\n\n**File Naming Convention:**\n- Use `wshcmd-[commandname].go` format\n- Use lowercase, hyphenated names for multi-word commands\n- Examples: `wshcmd-getvar.go`, `wshcmd-setmeta.go`, `wshcmd-ai.go`\n\n### Step 2: Command Structure\n\n#### Basic Command Structure\n\n```go\nvar myCommandCmd = &cobra.Command{\n    Use:   \"mycommand [required] [optional...]\",\n    Short: \"One-line description (shown in help)\",\n    Long:  `Detailed multi-line description`,\n    \n    // Argument validation\n    Args:    cobra.MinimumNArgs(1),  // Or cobra.ExactArgs(1), cobra.NoArgs, etc.\n    \n    // Execution function\n    RunE:    myCommandRun,\n    \n    // Pre-execution setup (if needed)\n    PreRunE: preRunSetupRpcClient,  // Sets up RPC client for backend communication\n    \n    // Example usage (optional)\n    Example: \"  wsh mycommand foo\\n  wsh mycommand --flag bar\",\n    \n    // Disable flag notation in usage line\n    DisableFlagsInUseLine: true,\n}\n```\n\n**Key Fields:**\n- `Use`: Command name and argument pattern\n- `Short`: Brief description for command list\n- `Long`: Detailed description shown in help\n- `Args`: Argument validator (optional)\n- `RunE`: Main execution function (returns error)\n- `PreRunE`: Setup function that runs before `RunE`\n- `Example`: Usage examples (optional)\n- `DisableFlagsInUseLine`: Clean up help display\n\n#### When to Use PreRunE\n\nInclude `PreRunE: preRunSetupRpcClient` if your command:\n- Communicates with the Wave Terminal backend\n- Needs access to `RpcClient` \n- Requires JWT authentication (WAVETERM_JWT env var)\n- Makes RPC calls via `wshclient.*Command()` functions\n\n**Don't include PreRunE** for commands that:\n- Only manipulate local state\n- Don't need backend communication\n- Are purely informational/local operations\n\n### Step 3: Implement Command Logic\n\n#### Command Function Pattern\n\n```go\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    // Step 1: Always track activity (for telemetry)\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Step 2: Validate arguments and flags\n    if len(args) != 1 {\n        OutputHelpMessage(cmd)\n        return fmt.Errorf(\"requires exactly one argument\")\n    }\n    \n    // Step 3: Parse/prepare data\n    targetArg := args[0]\n    \n    // Step 4: Make RPC call if needed\n    result, err := wshclient.SomeCommand(RpcClient, wshrpc.CommandSomeData{\n        Field: targetArg,\n    }, &wshrpc.RpcOpts{Timeout: 2000})\n    if err != nil {\n        return fmt.Errorf(\"executing command: %w\", err)\n    }\n    \n    // Step 5: Output results\n    fmt.Printf(\"Result: %s\\n\", result)\n    return nil\n}\n```\n\n**Important Patterns:**\n\n1. **Activity Tracking**: Always include deferred `sendActivity()` call\n   ```go\n   defer func() {\n       sendActivity(\"commandname\", rtnErr == nil)\n   }()\n   ```\n\n2. **Error Handling**: Return errors, don't call `os.Exit()`\n   ```go\n   if err != nil {\n       return fmt.Errorf(\"context: %w\", err)\n   }\n   ```\n\n3. **Output**: Use standard `fmt` package for output\n   ```go\n   fmt.Printf(\"Success message\\n\")\n   fmt.Fprintf(os.Stderr, \"Error message\\n\")\n   ```\n\n4. **Help Messages**: Show help when arguments are invalid\n   ```go\n   if len(args) == 0 {\n       OutputHelpMessage(cmd)\n       return fmt.Errorf(\"requires arguments\")\n   }\n   ```\n\n5. **Exit Codes**: Set custom exit code via `WshExitCode`\n   ```go\n   if notFound {\n       WshExitCode = 1\n       return nil  // Don't return error, just set exit code\n   }\n   ```\n\n### Step 4: Define Flags\n\nAdd flags in the `init()` function:\n\n```go\nvar (\n    // Declare flag variables at package level\n    myCommandFlagString string\n    myCommandFlagBool   bool\n    myCommandFlagInt    int\n)\n\nfunc init() {\n    rootCmd.AddCommand(myCommandCmd)\n    \n    // String flag with short version\n    myCommandCmd.Flags().StringVarP(&myCommandFlagString, \"name\", \"n\", \"default\", \"description\")\n    \n    // Boolean flag\n    myCommandCmd.Flags().BoolVarP(&myCommandFlagBool, \"verbose\", \"v\", false, \"enable verbose\")\n    \n    // Integer flag\n    myCommandCmd.Flags().IntVar(&myCommandFlagInt, \"count\", 10, \"set count\")\n    \n    // Flag without short version\n    myCommandCmd.Flags().StringVar(&myCommandFlagString, \"longname\", \"\", \"description\")\n}\n```\n\n**Flag Types:**\n- `StringVar/StringVarP` - String values\n- `BoolVar/BoolVarP` - Boolean flags\n- `IntVar/IntVarP` - Integer values\n- The `P` suffix versions include a short flag name\n\n**Flag Naming:**\n- Use camelCase for variable names: `myCommandFlagName`\n- Use kebab-case for flag names: `--flag-name`\n- Prefix variable names with command name for clarity\n\n### Step 5: Working with Block Arguments\n\nMany commands operate on blocks. Use the standard block resolution pattern:\n\n```go\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Resolve block using the -b/--block flag\n    fullORef, err := resolveBlockArg()\n    if err != nil {\n        return err\n    }\n    \n    // Use the blockid in RPC call\n    err = wshclient.SomeCommand(RpcClient, wshrpc.CommandSomeData{\n        BlockId: fullORef.OID,\n    }, &wshrpc.RpcOpts{Timeout: 2000})\n    if err != nil {\n        return fmt.Errorf(\"command failed: %w\", err)\n    }\n    \n    return nil\n}\n```\n\n**Block Resolution:**\n- The `-b/--block` flag is defined globally in `wshcmd-root.go`\n- `resolveBlockArg()` resolves the block argument to a full ORef\n- Supports: `this`, `tab`, full UUIDs, 8-char prefixes, block numbers\n- Default is `\"this\"` (current block)\n\n**Alternative: Manual Block Resolution**\n\n```go\n// Get tab ID from environment\ntabId := os.Getenv(\"WAVETERM_TABID\")\nif tabId == \"\" {\n    return fmt.Errorf(\"WAVETERM_TABID not set\")\n}\n\n// Create route for tab-level operations\nroute := wshutil.MakeTabRouteId(tabId)\n\n// Use route in RPC call\nerr := wshclient.SomeCommand(RpcClient, commandData, &wshrpc.RpcOpts{\n    Route:   route,\n    Timeout: 2000,\n})\n```\n\n### Step 6: Making RPC Calls\n\nUse the `wshclient` package to make RPC calls:\n\n```go\nimport (\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient\"\n)\n\n// Simple RPC call\nresult, err := wshclient.GetMetaCommand(RpcClient, wshrpc.CommandGetMetaData{\n    ORef: *fullORef,\n}, &wshrpc.RpcOpts{Timeout: 2000})\nif err != nil {\n    return fmt.Errorf(\"getting metadata: %w\", err)\n}\n\n// RPC call with routing\nerr := wshclient.SetMetaCommand(RpcClient, wshrpc.CommandSetMetaData{\n    ORef: *fullORef,\n    Meta: metaMap,\n}, &wshrpc.RpcOpts{\n    Route:   route,\n    Timeout: 5000,\n})\nif err != nil {\n    return fmt.Errorf(\"setting metadata: %w\", err)\n}\n```\n\n**RPC Options:**\n- `Timeout`: Request timeout in milliseconds (typically 2000-5000)\n- `Route`: Route ID for targeting specific components\n- Available routes: `wshutil.ControlRoute`, `wshutil.MakeTabRouteId(tabId)`\n\n### Step 7: Add Documentation\n\nAdd your command to `docs/docs/wsh-reference.mdx`:\n\n````markdown\n## mycommand\n\nBrief description of what the command does.\n\n```sh\nwsh mycommand [args] [flags]\n```\n\nDetailed explanation of the command's purpose and behavior.\n\nFlags:\n- `-n, --name <value>` - description of this flag\n- `-v, --verbose` - enable verbose output\n- `-b, --block <blockid>` - specify target block (default: current block)\n\nExamples:\n\n```sh\n# Basic usage\nwsh mycommand arg1\n\n# With flags\nwsh mycommand --name value arg1\n\n# With block targeting\nwsh mycommand -b 2 arg1\n\n# Complex example\nwsh mycommand -v --name \"example\" arg1 arg2\n```\n\nAdditional notes, tips, or warnings about the command.\n\n---\n````\n\n**Documentation Guidelines:**\n- Place in alphabetical order with other commands\n- Include command signature with argument pattern\n- List all flags with short and long versions\n- Provide practical examples (at least 3-5)\n- Explain common use cases and patterns\n- Add tips or warnings if relevant\n- Use `---` separator between commands\n\n### Step 8: Test Your Command\n\nBuild and test the command:\n\n```bash\n# Build wsh\ntask build:wsh\n\n# Or build everything\ntask build\n\n# Test the command\n./bin/wsh/wsh mycommand --help\n./bin/wsh/wsh mycommand arg1 arg2\n```\n\n**Testing Checklist:**\n- [ ] Help message displays correctly\n- [ ] Required arguments validated\n- [ ] Flags work as expected\n- [ ] Error messages are clear\n- [ ] Success cases work correctly\n- [ ] RPC calls complete successfully\n- [ ] Output is formatted correctly\n\n## Complete Examples\n\n### Example 1: Simple Command with No RPC\n\n**Use case:** A command that prints Wave Terminal version info\n\n#### Command File (`cmd/wsh/cmd/wshcmd-version.go`)\n\n```go\n// Copyright 2025, Command Line Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n    \"github.com/spf13/cobra\"\n    \"github.com/wavetermdev/waveterm/pkg/wavebase\"\n)\n\nvar versionCmd = &cobra.Command{\n    Use:   \"version\",\n    Short: \"Print Wave Terminal version\",\n    RunE:  versionRun,\n}\n\nfunc init() {\n    rootCmd.AddCommand(versionCmd)\n}\n\nfunc versionRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"version\", rtnErr == nil)\n    }()\n    \n    fmt.Printf(\"Wave Terminal %s\\n\", wavebase.WaveVersion)\n    return nil\n}\n```\n\n#### Documentation\n\n````markdown\n## version\n\nPrint the current Wave Terminal version.\n\n```sh\nwsh version\n```\n\nExamples:\n\n```sh\n# Print version\nwsh version\n```\n````\n\n### Example 2: Command with Flags and RPC\n\n**Use case:** A command to update block title\n\n#### Command File (`cmd/wsh/cmd/wshcmd-settitle.go`)\n\n```go\n// Copyright 2025, Command Line Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n    \"fmt\"\n\n    \"github.com/spf13/cobra\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient\"\n)\n\nvar setTitleCmd = &cobra.Command{\n    Use:   \"settitle [title]\",\n    Short: \"Set block title\",\n    Long:  `Set the title for the current or specified block.`,\n    Args:  cobra.ExactArgs(1),\n    RunE:  setTitleRun,\n    PreRunE: preRunSetupRpcClient,\n    DisableFlagsInUseLine: true,\n}\n\nvar setTitleIcon string\n\nfunc init() {\n    rootCmd.AddCommand(setTitleCmd)\n    setTitleCmd.Flags().StringVarP(&setTitleIcon, \"icon\", \"i\", \"\", \"set block icon\")\n}\n\nfunc setTitleRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"settitle\", rtnErr == nil)\n    }()\n    \n    title := args[0]\n    \n    // Resolve block\n    fullORef, err := resolveBlockArg()\n    if err != nil {\n        return err\n    }\n    \n    // Build metadata map\n    meta := make(map[string]interface{})\n    meta[\"title\"] = title\n    if setTitleIcon != \"\" {\n        meta[\"icon\"] = setTitleIcon\n    }\n    \n    // Make RPC call\n    err = wshclient.SetMetaCommand(RpcClient, wshrpc.CommandSetMetaData{\n        ORef: *fullORef,\n        Meta: meta,\n    }, &wshrpc.RpcOpts{Timeout: 2000})\n    if err != nil {\n        return fmt.Errorf(\"setting title: %w\", err)\n    }\n    \n    fmt.Printf(\"title updated\\n\")\n    return nil\n}\n```\n\n#### Documentation\n\n````markdown\n## settitle\n\nSet the title for a block.\n\n```sh\nwsh settitle [title]\n```\n\nUpdate the display title for the current or specified block. Optionally set an icon as well.\n\nFlags:\n- `-i, --icon <icon>` - set block icon along with title\n- `-b, --block <blockid>` - specify target block (default: current block)\n\nExamples:\n\n```sh\n# Set title for current block\nwsh settitle \"My Terminal\"\n\n# Set title and icon\nwsh settitle --icon \"terminal\" \"Development Shell\"\n\n# Set title for specific block\nwsh settitle -b 2 \"Build Output\"\n```\n````\n\n### Example 3: Subcommands\n\n**Use case:** Command with multiple subcommands (like `wsh conn`)\n\n#### Command File (`cmd/wsh/cmd/wshcmd-mygroup.go`)\n\n```go\n// Copyright 2025, Command Line Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n    \"fmt\"\n\n    \"github.com/spf13/cobra\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc\"\n    \"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient\"\n)\n\nvar myGroupCmd = &cobra.Command{\n    Use:   \"mygroup\",\n    Short: \"Manage something\",\n}\n\nvar myGroupListCmd = &cobra.Command{\n    Use:   \"list\",\n    Short: \"List items\",\n    RunE:  myGroupListRun,\n    PreRunE: preRunSetupRpcClient,\n}\n\nvar myGroupAddCmd = &cobra.Command{\n    Use:   \"add [name]\",\n    Short: \"Add an item\",\n    Args:  cobra.ExactArgs(1),\n    RunE:  myGroupAddRun,\n    PreRunE: preRunSetupRpcClient,\n}\n\nfunc init() {\n    // Add parent command\n    rootCmd.AddCommand(myGroupCmd)\n    \n    // Add subcommands\n    myGroupCmd.AddCommand(myGroupListCmd)\n    myGroupCmd.AddCommand(myGroupAddCmd)\n}\n\nfunc myGroupListRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mygroup:list\", rtnErr == nil)\n    }()\n    \n    // Implementation\n    fmt.Printf(\"Listing items...\\n\")\n    return nil\n}\n\nfunc myGroupAddRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mygroup:add\", rtnErr == nil)\n    }()\n    \n    name := args[0]\n    fmt.Printf(\"Adding item: %s\\n\", name)\n    return nil\n}\n```\n\n#### Documentation\n\n````markdown\n## mygroup\n\nManage something with subcommands.\n\n### list\n\nList all items.\n\n```sh\nwsh mygroup list\n```\n\n### add\n\nAdd a new item.\n\n```sh\nwsh mygroup add [name]\n```\n\nExamples:\n\n```sh\n# List items\nwsh mygroup list\n\n# Add an item\nwsh mygroup add \"new-item\"\n```\n````\n\n## Common Patterns\n\n### Reading from Stdin\n\n```go\nimport \"io\"\n\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Check if reading from stdin (using \"-\" convention)\n    var data []byte\n    var err error\n    \n    if len(args) > 0 && args[0] == \"-\" {\n        data, err = io.ReadAll(os.Stdin)\n        if err != nil {\n            return fmt.Errorf(\"reading stdin: %w\", err)\n        }\n    } else {\n        // Read from file or other source\n        data, err = os.ReadFile(args[0])\n        if err != nil {\n            return fmt.Errorf(\"reading file: %w\", err)\n        }\n    }\n    \n    // Process data\n    fmt.Printf(\"Read %d bytes\\n\", len(data))\n    return nil\n}\n```\n\n### JSON File Input\n\n```go\nimport (\n    \"encoding/json\"\n    \"io\"\n)\n\nfunc loadJSONFile(filepath string) (map[string]interface{}, error) {\n    var data []byte\n    var err error\n    \n    if filepath == \"-\" {\n        data, err = io.ReadAll(os.Stdin)\n        if err != nil {\n            return nil, fmt.Errorf(\"reading stdin: %w\", err)\n        }\n    } else {\n        data, err = os.ReadFile(filepath)\n        if err != nil {\n            return nil, fmt.Errorf(\"reading file: %w\", err)\n        }\n    }\n    \n    var result map[string]interface{}\n    if err := json.Unmarshal(data, &result); err != nil {\n        return nil, fmt.Errorf(\"parsing JSON: %w\", err)\n    }\n    \n    return result, nil\n}\n```\n\n### Conditional Output (TTY Detection)\n\n```go\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    isTty := getIsTty()\n    \n    // Output value\n    fmt.Printf(\"%s\", value)\n    \n    // Add newline only if TTY (for better piping experience)\n    if isTty {\n        fmt.Printf(\"\\n\")\n    }\n    \n    return nil\n}\n```\n\n### Environment Variable Access\n\n```go\nfunc myCommandRun(cmd *cobra.Command, args []string) (rtnErr error) {\n    defer func() {\n        sendActivity(\"mycommand\", rtnErr == nil)\n    }()\n    \n    // Get block ID from environment\n    blockId := os.Getenv(\"WAVETERM_BLOCKID\")\n    if blockId == \"\" {\n        return fmt.Errorf(\"WAVETERM_BLOCKID not set\")\n    }\n    \n    // Get tab ID from environment\n    tabId := os.Getenv(\"WAVETERM_TABID\")\n    if tabId == \"\" {\n        return fmt.Errorf(\"WAVETERM_TABID not set\")\n    }\n    \n    fmt.Printf(\"Block: %s, Tab: %s\\n\", blockId, tabId)\n    return nil\n}\n```\n\n## Best Practices\n\n### Command Design\n\n1. **Single Responsibility**: Each command should do one thing well\n2. **Composable**: Design commands to work with pipes and other commands\n3. **Consistent**: Follow existing wsh command patterns and conventions\n4. **Documented**: Provide clear help text and examples\n\n### Error Handling\n\n1. **Context**: Wrap errors with context using `fmt.Errorf(\"context: %w\", err)`\n2. **User-Friendly**: Make error messages clear and actionable\n3. **No Panics**: Return errors instead of calling `os.Exit()` or `panic()`\n4. **Exit Codes**: Use `WshExitCode` for custom exit codes\n\n### Output\n\n1. **Structured**: Use consistent formatting for output\n2. **Quiet by Default**: Only output what's necessary\n3. **Verbose Flag**: Optionally provide `-v` for detailed output\n4. **Stderr for Errors**: Use `fmt.Fprintf(os.Stderr, ...)` for error messages\n\n### Flags\n\n1. **Short Versions**: Provide `-x` short versions for common flags\n2. **Sensible Defaults**: Choose defaults that work for most users\n3. **Boolean Flags**: Use for on/off options\n4. **String Flags**: Use for values that need user input\n\n### RPC Calls\n\n1. **Timeouts**: Always specify reasonable timeouts\n2. **Error Context**: Wrap RPC errors with operation context\n3. **Retries**: Don't retry automatically; let user retry command\n4. **Routes**: Use appropriate routes for different operations\n\n## Common Pitfalls\n\n### 1. Forgetting Activity Tracking\n\n**Problem**: Command usage not tracked in telemetry\n\n**Solution**: Always include deferred `sendActivity()` call:\n```go\ndefer func() {\n    sendActivity(\"commandname\", rtnErr == nil)\n}()\n```\n\n### 2. Using os.Exit() Instead of Returning Error\n\n**Problem**: Breaks defer statements and cleanup\n\n**Solution**: Return errors from RunE function:\n```go\n// Bad\nif err != nil {\n    fmt.Fprintf(os.Stderr, \"error: %v\\n\", err)\n    os.Exit(1)\n}\n\n// Good\nif err != nil {\n    return fmt.Errorf(\"operation failed: %w\", err)\n}\n```\n\n### 3. Not Validating Arguments\n\n**Problem**: Command crashes with nil pointer or index out of range\n\n**Solution**: Validate arguments early and show help:\n```go\nif len(args) == 0 {\n    OutputHelpMessage(cmd)\n    return fmt.Errorf(\"requires at least one argument\")\n}\n```\n\n### 4. Forgetting to Add to init()\n\n**Problem**: Command not available when running wsh\n\n**Solution**: Always add command in `init()` function:\n```go\nfunc init() {\n    rootCmd.AddCommand(myCommandCmd)\n}\n```\n\n### 5. Inconsistent Output\n\n**Problem**: Inconsistent use of output methods\n\n**Solution**: Use standard `fmt` package functions:\n```go\n// For stdout\nfmt.Printf(\"output\\n\")\n\n// For stderr\nfmt.Fprintf(os.Stderr, \"error message\\n\")\n```\n\n## Quick Reference Checklist\n\nWhen adding a new wsh command:\n\n- [ ] Create `cmd/wsh/cmd/wshcmd-[commandname].go`\n- [ ] Define command struct with Use, Short, Long descriptions\n- [ ] Add `PreRunE: preRunSetupRpcClient` if using RPC\n- [ ] Implement command function with activity tracking\n- [ ] Add command to `rootCmd` in `init()` function\n- [ ] Define flags in `init()` function if needed\n- [ ] Add documentation to `docs/docs/wsh-reference.mdx`\n- [ ] Build and test: `task build:wsh`\n- [ ] Test help: `wsh [commandname] --help`\n- [ ] Test all flag combinations\n- [ ] Test error cases\n\n## Related Files\n\n- **Root Command**: `cmd/wsh/cmd/wshcmd-root.go` - Main command setup and utilities\n- **RPC Client**: `pkg/wshrpc/wshclient/` - Client functions for RPC calls\n- **RPC Types**: `pkg/wshrpc/wshrpctypes.go` - RPC request/response data structures\n- **Documentation**: `docs/docs/wsh-reference.mdx` - User-facing command reference\n- **Examples**: `cmd/wsh/cmd/wshcmd-*.go` - Existing command implementations\n\n<!-- File: .kilocode/skills/context-menu/SKILL.md (Tokens: ~877 | Category: anthropic-skill) -->\n---\nname: context-menu\ndescription: Guide for creating and displaying context menus in Wave Terminal. Use when implementing right-click menus, adding context menu items, creating submenus, or handling menu interactions with checkboxes and separators.\n---\n\n# Context Menu Quick Reference\n\nThis guide provides a quick overview of how to create and display a context menu using our system.\n\n---\n\n## ContextMenuItem Type\n\nDefine each menu item using the `ContextMenuItem` type:\n\n```ts\ntype ContextMenuItem = {\n  label?: string;\n  type?: \"separator\" | \"normal\" | \"submenu\" | \"checkbox\" | \"radio\";\n  role?: string; // Electron role (optional)\n  click?: () => void; // Callback for item selection (not needed if role is set)\n  submenu?: ContextMenuItem[]; // For nested menus\n  checked?: boolean; // For checkbox or radio items\n  visible?: boolean;\n  enabled?: boolean;\n  sublabel?: string;\n};\n```\n\n---\n\n## Import and Show the Menu\n\nImport the context menu module:\n\n```ts\nimport { ContextMenuModel } from \"@/app/store/contextmenu\";\n```\n\nTo display the context menu, call:\n\n```ts\nContextMenuModel.getInstance().showContextMenu(menu, event);\n```\n\n- **menu**: An array of `ContextMenuItem`.\n- **event**: The mouse event that triggered the context menu (typically from an onContextMenu handler).\n\n---\n\n## Basic Example\n\nA simple context menu with a separator:\n\n```ts\nconst menu: ContextMenuItem[] = [\n  {\n    label: \"New File\",\n    click: () => {\n      /* create a new file */\n    },\n  },\n  {\n    label: \"New Folder\",\n    click: () => {\n      /* create a new folder */\n    },\n  },\n  { type: \"separator\" },\n  {\n    label: \"Rename\",\n    click: () => {\n      /* rename item */\n    },\n  },\n];\n\nContextMenuModel.getInstance().showContextMenu(menu, e);\n```\n\n---\n\n## Example with Submenu and Checkboxes\n\nToggle settings using a submenu with checkbox items:\n\n```ts\nconst isClearOnStart = true; // Example setting\n\nconst menu: ContextMenuItem[] = [\n  {\n    label: \"Clear Output On Restart\",\n    submenu: [\n      {\n        label: \"On\",\n        type: \"checkbox\",\n        checked: isClearOnStart,\n        click: () => {\n          // Set the config to enable clear on restart\n        },\n      },\n      {\n        label: \"Off\",\n        type: \"checkbox\",\n        checked: !isClearOnStart,\n        click: () => {\n          // Set the config to disable clear on restart\n        },\n      },\n    ],\n  },\n];\n\nContextMenuModel.getInstance().showContextMenu(menu, e);\n```\n\n---\n\n## Editing a Config File Example\n\nOpen a configuration file (e.g., `widgets.json`) in preview mode:\n\n```ts\n{\n    label: \"Edit widgets.json\",\n    click: () => {\n        fireAndForget(async () => {\n            const path = `${getApi().getConfigDir()}/widgets.json`;\n            const blockDef: BlockDef = {\n                meta: { view: \"preview\", file: path },\n            };\n            await createBlock(blockDef, false, true);\n        });\n    },\n}\n```\n\n---\n\n## Summary\n\n- **Menu Definition**: Use the `ContextMenuItem` type.\n- **Actions**: Use `click` for actions; use `submenu` for nested options.\n- **Separators**: Use `type: \"separator\"` to group items.\n- **Toggles**: Use `type: \"checkbox\"` or `\"radio\"` with the `checked` property.\n- **Displaying**: Use `ContextMenuModel.getInstance().showContextMenu(menu, event)` to render the menu.\n\n## Common Use Cases\n\n### File/Folder Operations\nContext menus are commonly used for file operations like creating, renaming, and deleting files or folders.\n\n### Settings Toggles\nUse checkbox menu items to toggle settings on and off, with the `checked` property reflecting the current state.\n\n### Nested Options\nUse `submenu` to organize related options hierarchically, keeping the top-level menu clean and organized.\n\n### Conditional Items\nUse the `visible` and `enabled` properties to dynamically show or disable menu items based on the current state.\n\n<!-- File: .kilocode/skills/create-view/SKILL.md (Tokens: ~3424 | Category: anthropic-skill) -->\n---\nname: create-view\ndescription: Guide for implementing a new view type in Wave Terminal. Use when creating a new view component, implementing the ViewModel interface, registering a new view type in BlockRegistry, or adding a new content type to display within blocks.\n---\n\n# Creating a New View in Wave Terminal\n\nThis guide explains how to implement a new view type in Wave Terminal. Views are the core content components displayed within blocks in the terminal interface.\n\n## Architecture Overview\n\nWave Terminal uses a **Model-View architecture** where:\n\n- **ViewModel** - Contains all state, logic, and UI configuration as Jotai atoms\n- **ViewComponent** - Pure React component that renders the UI using the model\n- **BlockFrame** - Wraps views with a header, connection management, and standard controls\n\nThe separation between model and component ensures:\n\n- Models can update state without React hooks\n- Components remain pure and testable\n- State is centralized in Jotai atoms for easy access\n\n## ViewModel Interface\n\nEvery view must implement the `ViewModel` interface defined in `frontend/types/custom.d.ts`:\n\n```typescript\ninterface ViewModel {\n  // Required: The type identifier for this view (e.g., \"term\", \"web\", \"preview\")\n  viewType: string;\n\n  // Required: The React component that renders this view\n  viewComponent: ViewComponent<ViewModel>;\n\n  // Optional: Icon shown in block header (FontAwesome icon name or IconButtonDecl)\n  viewIcon?: jotai.Atom<string | IconButtonDecl>;\n\n  // Optional: Display name shown in block header (e.g., \"Terminal\", \"Web\", \"Preview\")\n  viewName?: jotai.Atom<string>;\n\n  // Optional: Additional header elements (text, buttons, inputs) shown after the name\n  viewText?: jotai.Atom<string | HeaderElem[]>;\n\n  // Optional: Icon button shown before the view name in header\n  preIconButton?: jotai.Atom<IconButtonDecl>;\n\n  // Optional: Icon buttons shown at the end of the header (before settings/close)\n  endIconButtons?: jotai.Atom<IconButtonDecl[]>;\n\n  // Optional: Custom background styling for the block\n  blockBg?: jotai.Atom<MetaType>;\n\n  // Optional: If true, completely hides the block header\n  noHeader?: jotai.Atom<boolean>;\n\n  // Optional: If true, shows connection picker in header for remote connections\n  manageConnection?: jotai.Atom<boolean>;\n\n  // Optional: If true, filters out 'nowsh' connections from connection picker\n  filterOutNowsh?: jotai.Atom<boolean>;\n\n  // Optional: If true, removes default padding from content area\n  noPadding?: jotai.Atom<boolean>;\n\n  // Optional: Atoms for managing in-block search functionality\n  searchAtoms?: SearchAtoms;\n\n  // Optional: Returns whether this is a basic terminal (for multi-input feature)\n  isBasicTerm?: (getFn: jotai.Getter) => boolean;\n\n  // Optional: Returns context menu items for the settings dropdown\n  getSettingsMenuItems?: () => ContextMenuItem[];\n\n  // Optional: Focuses the view when called, returns true if successful\n  giveFocus?: () => boolean;\n\n  // Optional: Handles keyboard events, returns true if handled\n  keyDownHandler?: (e: WaveKeyboardEvent) => boolean;\n\n  // Optional: Cleanup when block is closed\n  dispose?: () => void;\n}\n```\n\n### Key Concepts\n\n**Atoms**: All UI-related properties must be Jotai atoms. This enables:\n\n- Reactive updates when state changes\n- Access from anywhere via `globalStore.get()`/`globalStore.set()`\n- Derived atoms that compute values from other atoms\n\n**ViewComponent**: The React component receives these props:\n\n```typescript\ntype ViewComponentProps<T extends ViewModel> = {\n  blockId: string; // Unique ID for this block\n  blockRef: React.RefObject<HTMLDivElement>; // Ref to block container\n  contentRef: React.RefObject<HTMLDivElement>; // Ref to content area\n  model: T; // Your ViewModel instance\n};\n```\n\n## Step-by-Step Guide\n\n### 1. Create the View Model Class\n\nCreate a new file for your view model (e.g., `frontend/app/view/myview/myview-model.ts`):\n\n```typescript\nimport { BlockNodeModel } from \"@/app/block/blocktypes\";\nimport { globalStore } from \"@/app/store/jotaiStore\";\nimport { WOS, useBlockAtom } from \"@/store/global\";\nimport * as jotai from \"jotai\";\nimport { MyView } from \"./myview\";\n\nexport class MyViewModel implements ViewModel {\n  viewType: string;\n  blockId: string;\n  nodeModel: BlockNodeModel;\n  blockAtom: jotai.Atom<Block>;\n\n  // Define your atoms (simple field initializers)\n  viewIcon = jotai.atom<string>(\"circle\");\n  viewName = jotai.atom<string>(\"My View\");\n  noPadding = jotai.atom<boolean>(true);\n\n  // Derived atom (created in constructor)\n  viewText!: jotai.Atom<HeaderElem[]>;\n\n  constructor(blockId: string, nodeModel: BlockNodeModel) {\n    this.viewType = \"myview\";\n    this.blockId = blockId;\n    this.nodeModel = nodeModel;\n    this.blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);\n\n    // Create derived atoms that depend on block data or other atoms\n    this.viewText = jotai.atom((get) => {\n      const blockData = get(this.blockAtom);\n      const rtn: HeaderElem[] = [];\n\n      // Add header buttons/text based on state\n      rtn.push({\n        elemtype: \"iconbutton\",\n        icon: \"refresh\",\n        title: \"Refresh\",\n        click: () => this.refresh(),\n      });\n\n      return rtn;\n    });\n  }\n\n  get viewComponent(): ViewComponent {\n    return MyView;\n  }\n\n  refresh() {\n    // Update state using globalStore\n    // Never use React hooks in model methods\n    console.log(\"refreshing...\");\n  }\n\n  giveFocus(): boolean {\n    // Focus your view component\n    return true;\n  }\n\n  dispose() {\n    // Cleanup resources (unsubscribe from events, etc.)\n  }\n}\n```\n\n### 2. Create the View Component\n\nCreate your React component (e.g., `frontend/app/view/myview/myview.tsx`):\n\n```typescript\nimport { ViewComponentProps } from \"@/app/block/blocktypes\";\nimport { MyViewModel } from \"./myview-model\";\nimport { useAtomValue } from \"jotai\";\nimport \"./myview.scss\";\n\nexport const MyView: React.FC<ViewComponentProps<MyViewModel>> = ({\n    blockId,\n    model,\n    contentRef\n}) => {\n    // Use atoms from the model (these are React hooks - call at top level!)\n    const blockData = useAtomValue(model.blockAtom);\n\n    return (\n        <div className=\"myview-container\" ref={contentRef}>\n            <div>Block ID: {blockId}</div>\n            <div>View: {model.viewType}</div>\n            {/* Your view content here */}\n        </div>\n    );\n};\n```\n\n### 3. Register the View\n\nAdd your view to the `BlockRegistry` in `frontend/app/block/blockregistry.ts`:\n\n```typescript\nimport { MyViewModel } from \"@/app/view/myview/myview-model\";\n\nconst BlockRegistry: Map<string, ViewModelClass> = new Map();\nBlockRegistry.set(\"term\", TermViewModel);\nBlockRegistry.set(\"preview\", PreviewModel);\nBlockRegistry.set(\"web\", WebViewModel);\n// ... existing registrations ...\nBlockRegistry.set(\"myview\", MyViewModel); // Add your view here\n```\n\nThe registry key (e.g., `\"myview\"`) becomes the view type used in block metadata.\n\n### 4. Create Blocks with Your View\n\nUsers can create blocks with your view type:\n\n- Via CLI: `wsh view myview`\n- Via RPC: Use the block's `meta.view` field set to `\"myview\"`\n\n## Real-World Examples\n\n### Example 1: Terminal View (`term-model.ts`)\n\nThe terminal view demonstrates:\n\n- **Connection management** via `manageConnection` atom\n- **Dynamic header buttons** showing shell status (play/restart)\n- **Mode switching** between terminal and vdom views\n- **Custom keyboard handling** for terminal-specific shortcuts\n- **Focus management** to focus the xterm.js instance\n- **Shell integration status** showing AI capability indicators\n\nKey features:\n\n```typescript\nthis.manageConnection = jotai.atom((get) => {\n  const termMode = get(this.termMode);\n  if (termMode == \"vdom\") return false;\n  return true; // Show connection picker for regular terminal mode\n});\n\nthis.endIconButtons = jotai.atom((get) => {\n  const shellProcStatus = get(this.shellProcStatus);\n  const buttons: IconButtonDecl[] = [];\n\n  if (shellProcStatus == \"running\") {\n    buttons.push({\n      elemtype: \"iconbutton\",\n      icon: \"refresh\",\n      title: \"Restart Shell\",\n      click: this.forceRestartController.bind(this),\n    });\n  }\n  return buttons;\n});\n```\n\n### Example 2: Web View (`webview.tsx`)\n\nThe web view shows:\n\n- **Complex header controls** (back/forward/home/URL input)\n- **State management** for loading, URL, and navigation\n- **Event handling** for webview navigation events\n- **Custom styling** with `noPadding` for full-bleed content\n- **Media controls** showing play/pause/mute when media is active\n\nKey features:\n\n```typescript\nthis.viewText = jotai.atom((get) => {\n  const url = get(this.url);\n  const rtn: HeaderElem[] = [];\n\n  // Navigation buttons\n  rtn.push({\n    elemtype: \"iconbutton\",\n    icon: \"chevron-left\",\n    click: this.handleBack.bind(this),\n    disabled: this.shouldDisableBackButton(),\n  });\n\n  // URL input with nested controls\n  rtn.push({\n    elemtype: \"div\",\n    className: \"block-frame-div-url\",\n    children: [\n      {\n        elemtype: \"input\",\n        value: url,\n        onChange: this.handleUrlChange.bind(this),\n        onKeyDown: this.handleKeyDown.bind(this),\n      },\n      {\n        elemtype: \"iconbutton\",\n        icon: \"rotate-right\",\n        click: this.handleRefresh.bind(this),\n      },\n    ],\n  });\n\n  return rtn;\n});\n```\n\n## Header Elements (`HeaderElem`)\n\nThe `viewText` atom can return an array of these element types:\n\n```typescript\n// Icon button\n{\n    elemtype: \"iconbutton\",\n    icon: \"refresh\",\n    title: \"Tooltip text\",\n    click: () => { /* handler */ },\n    disabled?: boolean,\n    iconColor?: string,\n    iconSpin?: boolean,\n    noAction?: boolean,  // Shows icon but no click action\n}\n\n// Text element\n{\n    elemtype: \"text\",\n    text: \"Display text\",\n    className?: string,\n    noGrow?: boolean,\n    ref?: React.RefObject<HTMLElement>,\n    onClick?: (e: React.MouseEvent) => void,\n}\n\n// Text button\n{\n    elemtype: \"textbutton\",\n    text: \"Button text\",\n    className?: string,\n    title: \"Tooltip\",\n    onClick: (e: React.MouseEvent) => void,\n}\n\n// Input field\n{\n    elemtype: \"input\",\n    value: string,\n    className?: string,\n    onChange: (e: React.ChangeEvent<HTMLInputElement>) => void,\n    onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void,\n    onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void,\n    onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void,\n    ref?: React.RefObject<HTMLInputElement>,\n}\n\n// Container with children\n{\n    elemtype: \"div\",\n    className?: string,\n    children: HeaderElem[],\n    onMouseOver?: (e: React.MouseEvent) => void,\n    onMouseOut?: (e: React.MouseEvent) => void,\n}\n\n// Menu button (dropdown)\n{\n    elemtype: \"menubutton\",\n    // ... MenuButtonProps ...\n}\n```\n\n## Best Practices\n\n### Jotai Model Pattern\n\nFollow these rules for Jotai atoms in models:\n\n1. **Simple atoms as field initializers**:\n\n   ```typescript\n   viewIcon = jotai.atom<string>(\"circle\");\n   noPadding = jotai.atom<boolean>(true);\n   ```\n\n2. **Derived atoms in constructor** (need dependency on other atoms):\n\n   ```typescript\n   constructor(blockId: string, nodeModel: BlockNodeModel) {\n       this.viewText = jotai.atom((get) => {\n           const blockData = get(this.blockAtom);\n           return [/* computed based on blockData */];\n       });\n   }\n   ```\n\n3. **Models never use React hooks** - Use `globalStore.get()`/`set()`:\n\n   ```typescript\n   refresh() {\n       const currentData = globalStore.get(this.blockAtom);\n       globalStore.set(this.dataAtom, newData);\n   }\n   ```\n\n4. **Components use hooks for atoms**:\n   ```typescript\n   const data = useAtomValue(model.dataAtom);\n   const [value, setValue] = useAtom(model.valueAtom);\n   ```\n\n### State Management\n\n- All view state should live in atoms on the model\n- Use `useBlockAtom()` helper for block-scoped atoms that persist\n- Use `globalStore` for imperative access outside React components\n- Subscribe to Wave events using `waveEventSubscribe()`\n\n### Styling\n\n- Create a `.scss` file for your view styles\n- Use Tailwind utilities where possible (v4)\n- Add `noPadding: atom(true)` for full-bleed content\n- Use `blockBg` atom to customize block background\n\n### Focus Management\n\nImplement `giveFocus()` to focus your view when:\n\n- Block gains focus via keyboard navigation\n- User clicks the block\n- Return `true` if successfully focused, `false` otherwise\n\n### Keyboard Handling\n\nImplement `keyDownHandler(e: WaveKeyboardEvent)` for:\n\n- View-specific keyboard shortcuts\n- Return `true` if event was handled (prevents propagation)\n- Use `keyutil.checkKeyPressed(waveEvent, \"Cmd:K\")` for shortcut checks\n\n### Cleanup\n\nImplement `dispose()` to:\n\n- Unsubscribe from Wave events\n- Unregister routes/handlers\n- Clear timers/intervals\n- Release resources\n\n### Connection Management\n\nFor views that need remote connections:\n\n```typescript\nthis.manageConnection = jotai.atom(true); // Show connection picker\nthis.filterOutNowsh = jotai.atom(true); // Hide nowsh connections\n```\n\nAccess connection status:\n\n```typescript\nconst connStatus = jotai.atom((get) => {\n  const blockData = get(this.blockAtom);\n  const connName = blockData?.meta?.connection;\n  return get(getConnStatusAtom(connName));\n});\n```\n\n## Common Patterns\n\n### Reading Block Metadata\n\n```typescript\nimport { getBlockMetaKeyAtom } from \"@/store/global\";\n\n// In constructor:\nthis.someFlag = getBlockMetaKeyAtom(blockId, \"myview:flag\");\n\n// In component:\nconst flag = useAtomValue(model.someFlag);\n```\n\n### Configuration Overrides\n\nWave has a hierarchical config system (global → connection → block):\n\n```typescript\nimport { getOverrideConfigAtom } from \"@/store/global\";\n\nthis.settingAtom = jotai.atom((get) => {\n  // Checks block meta, then connection config, then global settings\n  return get(getOverrideConfigAtom(this.blockId, \"myview:setting\")) ?? defaultValue;\n});\n```\n\n### Updating Block Metadata\n\n```typescript\nimport { RpcApi } from \"@/app/store/wshclientapi\";\nimport { TabRpcClient } from \"@/app/store/wshrpcutil\";\nimport { WOS } from \"@/store/global\";\n\nawait RpcApi.SetMetaCommand(TabRpcClient, {\n  oref: WOS.makeORef(\"block\", this.blockId),\n  meta: { \"myview:key\": value },\n});\n```\n\n## Additional Resources\n\n- `frontend/app/block/blockframe-header.tsx` - Block header rendering\n- `frontend/app/view/term/term-model.ts` - Complex view example\n- `frontend/app/view/webview/webview.tsx` - Navigation UI example\n- `frontend/types/custom.d.ts` - Type definitions\n\n<!-- File: .kilocode/skills/electron-api/SKILL.md (Tokens: ~1212 | Category: anthropic-skill) -->\n---\nname: electron-api\ndescription: Guide for adding new Electron APIs to Wave Terminal. Use when implementing new frontend-to-electron communications via preload/IPC.\n---\n\n# Adding Electron APIs\n\nElectron APIs allow the frontend to call Electron main process functionality directly via IPC.\n\n## Four Files to Edit\n\n1. [`frontend/types/custom.d.ts`](frontend/types/custom.d.ts) - TypeScript [`ElectronApi`](frontend/types/custom.d.ts:82) type\n2. [`emain/preload.ts`](emain/preload.ts) - Expose method via `contextBridge`\n3. [`emain/emain-ipc.ts`](emain/emain-ipc.ts) - Implement IPC handler\n4. [`frontend/preview/preview-electron-api.ts`](frontend/preview/preview-electron-api.ts) - Add a no-op stub to keep the `previewElectronApi` object in sync with the `ElectronApi` type\n\n## Three Communication Patterns\n\n1. **Sync** - `ipcRenderer.sendSync()` + `ipcMain.on()` + `event.returnValue = ...`\n2. **Async** - `ipcRenderer.invoke()` + `ipcMain.handle()`\n3. **Fire-and-forget** - `ipcRenderer.send()` + `ipcMain.on()`\n\n## Example: Async Method\n\n### 1. Define TypeScript Interface\n\nIn [`frontend/types/custom.d.ts`](frontend/types/custom.d.ts):\n\n```typescript\ntype ElectronApi = {\n    captureScreenshot: (rect: Electron.Rectangle) => Promise<string>; // capture-screenshot\n};\n```\n\n### 2. Expose in Preload\n\nIn [`emain/preload.ts`](emain/preload.ts):\n\n```typescript\ncontextBridge.exposeInMainWorld(\"api\", {\n    captureScreenshot: (rect: Rectangle) => ipcRenderer.invoke(\"capture-screenshot\", rect),\n});\n```\n\n### 3. Implement Handler\n\nIn [`emain/emain-ipc.ts`](emain/emain-ipc.ts):\n\n```typescript\nelectron.ipcMain.handle(\"capture-screenshot\", async (event, rect) => {\n    const tabView = getWaveTabViewByWebContentsId(event.sender.id);\n    if (!tabView) throw new Error(\"No tab view found\");\n    const image = await tabView.webContents.capturePage(rect);\n    return `data:image/png;base64,${image.toPNG().toString(\"base64\")}`;\n});\n```\n\n### 4. Add Preview Stub\n\nIn [`frontend/preview/preview-electron-api.ts`](frontend/preview/preview-electron-api.ts):\n\n```typescript\ncaptureScreenshot: (_rect: Electron.Rectangle) => Promise.resolve(\"\"),\n```\n\n### 5. Call from Frontend\n\n```typescript\nimport { getApi } from \"@/store/global\";\n\nconst dataUrl = await getApi().captureScreenshot({ x: 0, y: 0, width: 800, height: 600 });\n```\n\n## Example: Sync Method\n\n### 1. Define\n\n```typescript\ntype ElectronApi = {\n    getUserName: () => string; // get-user-name\n};\n```\n\n### 2. Preload\n\n```typescript\ngetUserName: () => ipcRenderer.sendSync(\"get-user-name\"),\n```\n\n### 3. Handler (⚠️ MUST set event.returnValue or browser hangs)\n\n```typescript\nelectron.ipcMain.on(\"get-user-name\", (event) => {\n    event.returnValue = process.env.USER || \"unknown\";\n});\n```\n\n### 4. Call\n\n```typescript\nimport { getApi } from \"@/store/global\";\n\nconst userName = getApi().getUserName(); // blocks until returns\n```\n\n## Example: Fire-and-Forget\n\n### 1. Define\n\n```typescript\ntype ElectronApi = {\n    openExternal: (url: string) => void; // open-external\n};\n```\n\n### 2. Preload\n\n```typescript\nopenExternal: (url) => ipcRenderer.send(\"open-external\", url),\n```\n\n### 3. Handler\n\n```typescript\nelectron.ipcMain.on(\"open-external\", (event, url) => {\n    electron.shell.openExternal(url);\n});\n```\n\n## Example: Event Listener\n\n### 1. Define\n\n```typescript\ntype ElectronApi = {\n    onZoomFactorChange: (callback: (zoomFactor: number) => void) => void; // zoom-factor-change\n};\n```\n\n### 2. Preload\n\n```typescript\nonZoomFactorChange: (callback) => \n    ipcRenderer.on(\"zoom-factor-change\", (_event, zoomFactor) => callback(zoomFactor)),\n```\n\n### 3. Send from Main\n\n```typescript\nwebContents.send(\"zoom-factor-change\", newZoomFactor);\n```\n\n## Quick Reference\n\n**Use Sync when:**\n- Getting config/env vars\n- Quick lookups, no I/O\n- ⚠️ **CRITICAL**: Always set `event.returnValue` or browser hangs\n\n**Use Async when:**\n- File operations\n- Network requests\n- Can fail or take time\n\n**Use Fire-and-forget when:**\n- No return value needed\n- Triggering actions\n\n**Electron API vs RPC:**\n- Electron API: Native OS features, window management, Electron APIs\n- RPC: Database, backend logic, remote servers\n\n## Checklist\n\n- [ ] Add to [`ElectronApi`](frontend/types/custom.d.ts:82) in [`custom.d.ts`](frontend/types/custom.d.ts)\n- [ ] Include IPC channel name in comment\n- [ ] Expose in [`preload.ts`](emain/preload.ts)\n- [ ] Implement in [`emain-ipc.ts`](emain/emain-ipc.ts)\n- [ ] Add no-op stub to [`preview-electron-api.ts`](frontend/preview/preview-electron-api.ts)\n- [ ] IPC channel names match exactly\n- [ ] **For sync**: Set `event.returnValue` (or browser hangs!)\n- [ ] Test end-to-end\n\n<!-- File: .kilocode/skills/waveenv/SKILL.md (Tokens: ~1503 | Category: anthropic-skill) -->\n---\nname: waveenv\ndescription: Guide for creating WaveEnv narrowings in Wave Terminal. Use when writing a named subset type of WaveEnv for a component tree, documenting environmental dependencies, or enabling mock environments for preview/test server usage.\n---\n\n# WaveEnv Narrowing Skill\n\n## Purpose\n\nA WaveEnv narrowing creates a _named subset type_ of `WaveEnv` that:\n\n1. Documents exactly which parts of the environment a component tree actually uses.\n2. Forms a type contract so callers and tests know what to provide.\n3. Enables mocking in the preview/test server — you only need to implement what's listed.\n\n## When To Create One\n\nCreate a narrowing whenever you are writing a component (or group of components) that you want to test in the preview server, or when you want to make the environmental dependencies of a component tree explicit.\n\n## Core Principle: Only Include What You Use\n\n**Only list the fields, methods, atoms, and keys that the component tree actually accesses.** If you don't call `wos`, don't include `wos`. If you only call one RPC command, only list that one command. The narrowing is a precise dependency declaration — not a copy of `WaveEnv`.\n\n## File Location\n\n- **Separate file** (preferred for shared/complex envs): name it `<feature>env.ts` next to the component, e.g. `frontend/app/block/blockenv.ts`.\n- **Inline** (acceptable for small, single-file components): export the type directly from the component file, e.g. `WidgetsEnv` in `frontend/app/workspace/widgets.tsx`.\n\n## Imports Required\n\n```ts\nimport {\n  MetaKeyAtomFnType, // only if you use getBlockMetaKeyAtom or getTabMetaKeyAtom\n  ConnConfigKeyAtomFnType, // only if you use getConnConfigKeyAtom\n  SettingsKeyAtomFnType, // only if you use getSettingsKeyAtom\n  WaveEnv,\n  WaveEnvSubset,\n} from \"@/app/waveenv/waveenv\";\n```\n\n## The Shape\n\n```ts\nexport type MyEnv = WaveEnvSubset<{\n  // --- Simple WaveEnv properties ---\n  // Copy the type verbatim from WaveEnv with WaveEnv[\"key\"] syntax.\n  isDev: WaveEnv[\"isDev\"];\n  createBlock: WaveEnv[\"createBlock\"];\n  showContextMenu: WaveEnv[\"showContextMenu\"];\n  platform: WaveEnv[\"platform\"];\n\n  // --- electron: list only the methods you call ---\n  electron: {\n    openExternal: WaveEnv[\"electron\"][\"openExternal\"];\n  };\n\n  // --- rpc: list only the commands you call ---\n  rpc: {\n    ActivityCommand: WaveEnv[\"rpc\"][\"ActivityCommand\"];\n    ConnEnsureCommand: WaveEnv[\"rpc\"][\"ConnEnsureCommand\"];\n  };\n\n  // --- atoms: list only the atoms you read ---\n  atoms: {\n    modalOpen: WaveEnv[\"atoms\"][\"modalOpen\"];\n    fullConfigAtom: WaveEnv[\"atoms\"][\"fullConfigAtom\"];\n  };\n\n  // --- wos: always take the whole thing, no sub-typing needed ---\n  wos: WaveEnv[\"wos\"];\n\n  // --- services: list only the services you call; no method-level narrowing ---\n  services: {\n    block: WaveEnv[\"services\"][\"block\"];\n    workspace: WaveEnv[\"services\"][\"workspace\"];\n  };\n\n  // --- key-parameterized atom factories: enumerate the keys you use ---\n  getSettingsKeyAtom: SettingsKeyAtomFnType<\"app:focusfollowscursor\" | \"window:magnifiedblockopacity\">;\n  getBlockMetaKeyAtom: MetaKeyAtomFnType<\"view\" | \"frame:title\" | \"connection\">;\n  getTabMetaKeyAtom: MetaKeyAtomFnType<\"tabid\" | \"name\">;\n  getConnConfigKeyAtom: ConnConfigKeyAtomFnType<\"conn:wshenabled\">;\n\n  // --- other atom helpers: copy verbatim ---\n  getConnStatusAtom: WaveEnv[\"getConnStatusAtom\"];\n  getLocalHostDisplayNameAtom: WaveEnv[\"getLocalHostDisplayNameAtom\"];\n  getConfigBackgroundAtom: WaveEnv[\"getConfigBackgroundAtom\"];\n}>;\n```\n\n### Automatically Included Fields\n\nEvery `WaveEnvSubset<T>` automatically includes the mock fields — you never need to declare them:\n\n- `isMock: boolean`\n- `mockSetWaveObj: <T extends WaveObj>(oref: string, obj: T) => void`\n- `mockModels?: Map<any, any>`\n\n### Rules for Each Section\n\n| Section                    | Pattern                                                | Notes                                                                                              |\n| -------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |\n| `electron`                 | `electron: { method: WaveEnv[\"electron\"][\"method\"]; }` | List every method called; omit the rest.                                                           |\n| `rpc`                      | `rpc: { Cmd: WaveEnv[\"rpc\"][\"Cmd\"]; }`                 | List every RPC command called; omit the rest.                                                      |\n| `atoms`                    | `atoms: { atom: WaveEnv[\"atoms\"][\"atom\"]; }`           | List every atom read; omit the rest.                                                               |\n| `wos`                      | `wos: WaveEnv[\"wos\"]`                                  | Take the whole `wos` object (no sub-typing needed), but **only add it if `wos` is actually used**. |\n| `services`                 | `services: { svc: WaveEnv[\"services\"][\"svc\"]; }`       | List each service used; take the whole service object (no method-level narrowing).                 |\n| `getSettingsKeyAtom`       | `SettingsKeyAtomFnType<\"key1\" \\| \"key2\">`              | Union all settings keys accessed.                                                                  |\n| `getBlockMetaKeyAtom`      | `MetaKeyAtomFnType<\"key1\" \\| \"key2\">`                  | Union all block meta keys accessed.                                                                |\n| `getTabMetaKeyAtom`        | `MetaKeyAtomFnType<\"key1\" \\| \"key2\">`                  | Union all tab meta keys accessed.                                                                  |\n| `getConnConfigKeyAtom`     | `ConnConfigKeyAtomFnType<\"key1\">`                      | Union all conn config keys accessed.                                                               |\n| All other `WaveEnv` fields | `WaveEnv[\"fieldName\"]`                                 | Copy type verbatim.                                                                                |\n\n## Using the Narrowed Type in Components\n\n```ts\nimport { useWaveEnv } from \"@/app/waveenv/waveenv\";\nimport { MyEnv } from \"./myenv\";\n\nconst MyComponent = memo(() => {\n    const env = useWaveEnv<MyEnv>();\n    // TypeScript now enforces you only access what's in MyEnv.\n    const val = useAtomValue(env.getSettingsKeyAtom(\"app:focusfollowscursor\"));\n    ...\n});\n```\n\nThe generic parameter on `useWaveEnv<MyEnv>()` casts the context to your narrowed type. The real production `WaveEnv` satisfies every narrowing; mock envs only need to implement the listed subset.\n\n## Real Examples\n\n- `BlockEnv` in `frontend/app/block/blockenv.ts` — complex narrowing with all section types, in a separate file.\n- `WidgetsEnv` in `frontend/app/workspace/widgets.tsx` — smaller narrowing defined inline in the component file.\n\n<!-- File: .kilocode/skills/wps-events/SKILL.md (Tokens: ~2386 | Category: anthropic-skill) -->\n---\nname: wps-events\ndescription: Guide for working with Wave Terminal's WPS (Wave PubSub) event system. Use when implementing new event types, publishing events, subscribing to events, or adding asynchronous communication between components.\n---\n\n# WPS Events Guide\n\n## Overview\n\nWPS (Wave PubSub) is Wave Terminal's publish-subscribe event system that enables different parts of the application to communicate asynchronously. The system uses a broker pattern to route events from publishers to subscribers based on event types and scopes.\n\n## Key Files\n\n- `pkg/wps/wpstypes.go` - Event type constants and data structures\n- `pkg/wps/wps.go` - Broker implementation and core logic\n- `pkg/wcore/wcore.go` - Example usage patterns\n\n## Event Structure\n\nEvents in WPS have the following structure:\n\n```go\ntype WaveEvent struct {\n    Event   string   `json:\"event\"`      // Event type constant\n    Scopes  []string `json:\"scopes,omitempty\"` // Optional scopes for targeted delivery\n    Sender  string   `json:\"sender,omitempty\"` // Optional sender identifier\n    Persist int      `json:\"persist,omitempty\"` // Number of events to persist in history\n    Data    any      `json:\"data,omitempty\"`    // Event payload\n}\n```\n\n## Adding a New Event Type\n\n### Step 1: Define the Event Constant\n\nAdd your event type constant to `pkg/wps/wpstypes.go`:\n\n```go\nconst (\n    Event_BlockClose       = \"blockclose\"\n    Event_ConnChange       = \"connchange\"\n    // ... other events ...\n    Event_YourNewEvent     = \"your:newevent\"  // type: YourEventData (or \"none\" if no data)\n)\n```\n\n**Naming Convention:**\n\n- Use descriptive PascalCase for the constant name with `Event_` prefix\n- Use lowercase with colons for the string value (e.g., \"namespace:eventname\")\n- Group related events with the same namespace prefix\n- Always add a `// type: <TypeName>` comment; use `// type: none` if no data is sent\n\n### Step 2: Add to AllEvents\n\nAdd your new constant to the `AllEvents` slice in `pkg/wps/wpstypes.go`:\n\n```go\nvar AllEvents []string = []string{\n    // ... existing events ...\n    Event_YourNewEvent,\n}\n```\n\n### Step 3: Register in WaveEventDataTypes (REQUIRED)\n\nYou **must** add an entry to `WaveEventDataTypes` in `pkg/tsgen/tsgenevent.go`. This drives TypeScript type generation for the event's `data` field:\n\n```go\nvar WaveEventDataTypes = map[string]reflect.Type{\n    // ... existing entries ...\n    wps.Event_YourNewEvent: reflect.TypeOf(YourEventData{}),        // value type\n    // wps.Event_YourNewEvent: reflect.TypeOf((*YourEventData)(nil)), // pointer type\n    // wps.Event_YourNewEvent: nil,                                   // no data (type: none)\n}\n```\n\n- Use `reflect.TypeOf(YourType{})` for value types\n- Use `reflect.TypeOf((*YourType)(nil))` for pointer types\n- Use `nil` if no data is sent for the event\n\n### Step 4: Define Event Data Structure (Optional)\n\nIf your event carries structured data, define a type for it:\n\n```go\ntype YourEventData struct {\n    Field1 string `json:\"field1\"`\n    Field2 int    `json:\"field2\"`\n}\n```\n\n### Step 5: Expose Type to Frontend (If Needed)\n\nIf your event data type isn't already exposed via an RPC call, you need to add it to `pkg/tsgen/tsgen.go` so TypeScript types are generated:\n\n```go\n// add extra types to generate here\nvar ExtraTypes = []any{\n    waveobj.ORef{},\n    // ... other types ...\n    uctypes.RateLimitInfo{},  // Example: already added\n    YourEventData{},          // Add your new type here\n}\n```\n\nThen run code generation:\n\n```bash\ntask generate\n```\n\nThis will update `frontend/types/gotypes.d.ts` with TypeScript definitions for your type, ensuring type safety in the frontend when handling these events.\n\n## Publishing Events\n\n### Basic Publishing\n\nTo publish an event, use the global broker:\n\n```go\nimport \"github.com/wavetermdev/waveterm/pkg/wps\"\n\nwps.Broker.Publish(wps.WaveEvent{\n    Event: wps.Event_YourNewEvent,\n    Data:  yourData,\n})\n```\n\n### Publishing with Scopes\n\nScopes allow targeted event delivery. Subscribers can filter events by scope:\n\n```go\nwps.Broker.Publish(wps.WaveEvent{\n    Event:  wps.Event_WaveObjUpdate,\n    Scopes: []string{oref.String()},  // Target specific object\n    Data:   updateData,\n})\n```\n\n### Publishing in a Goroutine\n\nTo avoid blocking the caller, publish events asynchronously:\n\n```go\ngo func() {\n    wps.Broker.Publish(wps.WaveEvent{\n        Event: wps.Event_YourNewEvent,\n        Data:  data,\n    })\n}()\n```\n\n**When to use goroutines:**\n\n- When publishing from performance-critical code paths\n- When the event is informational and doesn't need immediate delivery\n- When publishing from code that holds locks (to prevent deadlocks)\n\n### Event Persistence\n\nEvents can be persisted in memory for late subscribers:\n\n```go\nwps.Broker.Publish(wps.WaveEvent{\n    Event:   wps.Event_YourNewEvent,\n    Persist: 100,  // Keep last 100 events\n    Data:    data,\n})\n```\n\n## Complete Example: Rate Limit Updates\n\nThis example shows how rate limit information is published when AI chat responses include rate limit headers.\n\n### 1. Define the Event Type\n\nIn `pkg/wps/wpstypes.go`:\n\n```go\nconst (\n    // ... other events ...\n    Event_WaveAIRateLimit  = \"waveai:ratelimit\"\n)\n```\n\n### 2. Publish the Event\n\nIn `pkg/aiusechat/usechat.go`:\n\n```go\nimport \"github.com/wavetermdev/waveterm/pkg/wps\"\n\nfunc updateRateLimit(info *uctypes.RateLimitInfo) {\n    if info == nil {\n        return\n    }\n    rateLimitLock.Lock()\n    defer rateLimitLock.Unlock()\n    globalRateLimitInfo = info\n\n    // Publish event in goroutine to avoid blocking\n    go func() {\n        wps.Broker.Publish(wps.WaveEvent{\n            Event: wps.Event_WaveAIRateLimit,\n            Data:  info,  // RateLimitInfo struct\n        })\n    }()\n}\n```\n\n### 3. Subscribe to the Event (Frontend)\n\nIn the frontend, subscribe to events via WebSocket:\n\n```typescript\n// Subscribe to rate limit updates\nconst subscription = {\n  event: \"waveai:ratelimit\",\n  allscopes: true, // Receive all rate limit events\n};\n```\n\n## Subscribing to Events\n\n### From Go Code\n\n```go\n// Subscribe to all events of a type\nwps.Broker.Subscribe(routeId, wps.SubscriptionRequest{\n    Event:     wps.Event_YourNewEvent,\n    AllScopes: true,\n})\n\n// Subscribe to specific scopes\nwps.Broker.Subscribe(routeId, wps.SubscriptionRequest{\n    Event:  wps.Event_WaveObjUpdate,\n    Scopes: []string{\"workspace:123\"},\n})\n\n// Unsubscribe\nwps.Broker.Unsubscribe(routeId, wps.Event_YourNewEvent)\n```\n\n### Scope Matching\n\nScopes support wildcard matching:\n\n- `*` matches a single scope segment\n- `**` matches multiple scope segments\n\n```go\n// Subscribe to all workspace events\nwps.Broker.Subscribe(routeId, wps.SubscriptionRequest{\n    Event:  wps.Event_WaveObjUpdate,\n    Scopes: []string{\"workspace:*\"},\n})\n```\n\n## Best Practices\n\n1. **Use Namespaces**: Prefix event names with a namespace (e.g., `waveai:`, `workspace:`, `block:`)\n\n2. **Don't Block**: Use goroutines when publishing from performance-critical code or while holding locks\n\n3. **Type-Safe Data**: Define struct types for event data rather than using maps\n\n4. **Scope Wisely**: Use scopes to limit event delivery and reduce unnecessary processing\n\n5. **Document Events**: Add comments explaining when events are fired and what data they carry\n\n6. **Consider Persistence**: Use `Persist` for events that late subscribers might need (like status updates). This is normally not used. We normally do a live RPC call to get the current value and then subscribe for updates.\n\n## Common Event Patterns\n\n### Status Updates\n\n```go\nwps.Broker.Publish(wps.WaveEvent{\n    Event:   wps.Event_ControllerStatus,\n    Scopes:  []string{blockId},\n    Persist: 1,  // Keep only latest status\n    Data:    statusData,\n})\n```\n\n### Object Updates\n\n```go\nwps.Broker.Publish(wps.WaveEvent{\n    Event:  wps.Event_WaveObjUpdate,\n    Scopes: []string{oref.String()},\n    Data: waveobj.WaveObjUpdate{\n        UpdateType: waveobj.UpdateType_Update,\n        OType:      obj.GetOType(),\n        OID:        waveobj.GetOID(obj),\n        Obj:        obj,\n    },\n})\n```\n\n### Batch Updates\n\n```go\n// Helper function for multiple updates\nfunc (b *BrokerType) SendUpdateEvents(updates waveobj.UpdatesRtnType) {\n    for _, update := range updates {\n        b.Publish(WaveEvent{\n            Event:  Event_WaveObjUpdate,\n            Scopes: []string{waveobj.MakeORef(update.OType, update.OID).String()},\n            Data:   update,\n        })\n    }\n}\n```\n\n## Debugging\n\nTo debug event flow:\n\n1. Check broker subscription map: `wps.Broker.SubMap`\n2. View persisted events: `wps.Broker.ReadEventHistory(eventType, scope, maxItems)`\n3. Add logging in publish/subscribe methods\n4. Monitor WebSocket traffic in browser dev tools\n\n## Quick Reference\n\nWhen adding a new event:\n\n- [ ] Add event constant to [`pkg/wps/wpstypes.go`](pkg/wps/wpstypes.go) with a `// type: <TypeName>` comment (use `none` if no data)\n- [ ] Add the constant to `AllEvents` in [`pkg/wps/wpstypes.go`](pkg/wps/wpstypes.go)\n- [ ] **REQUIRED**: Add an entry to `WaveEventDataTypes` in [`pkg/tsgen/tsgenevent.go`](pkg/tsgen/tsgenevent.go) — use `nil` for events with no data\n- [ ] Define event data structure (if needed)\n- [ ] Add data type to `pkg/tsgen/tsgen.go` for frontend use (if not already exposed via RPC)\n- [ ] Run `task generate` to update TypeScript types\n- [ ] Publish events using `wps.Broker.Publish()`\n- [ ] Use goroutines for non-blocking publish when appropriate\n- [ ] Subscribe to events in relevant components\n\n<!-- File: .roo/rules/overview.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Rules Skill\n\nPath: `.roo/rules/overview.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.roo/rules/overview.md)\n\n<!-- File: .roo/rules/rules.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Rules Skill\n\nPath: `.roo/rules/rules.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/wavetermdev/waveterm/HEAD/.roo/rules/rules.md)\n\n<!-- File: CLAUDE.md (Tokens: ~545 | Category: claude-rule) -->\n@.kilocode/rules/rules.md\n\n---\n\n## Skill Guides\n\nThis project uses a set of \"skill\" guides — focused how-to documents for common implementation tasks. When your task matches one of the descriptions below, **read the linked SKILL.md file before proceeding** and follow its instructions precisely.\n\n| Skill        | File                                     | Description                                                                                                                                                                                                                                 |\n| ------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| add-config   | `.kilocode/skills/add-config/SKILL.md`   | Guide for adding new configuration settings to Wave Terminal. Use when adding a new setting to the configuration system, implementing a new config key, or adding user-customizable settings.                                               |\n| add-rpc      | `.kilocode/skills/add-rpc/SKILL.md`      | Guide for adding new RPC calls to Wave Terminal. Use when implementing new RPC commands, adding server-client communication methods, or extending the RPC interface with new functionality.                                                 |\n| add-wshcmd   | `.kilocode/skills/add-wshcmd/SKILL.md`   | Guide for adding new wsh commands to Wave Terminal. Use when implementing new CLI commands, adding command-line functionality, or extending the wsh command interface.                                                                      |\n| context-menu | `.kilocode/skills/context-menu/SKILL.md` | Guide for creating and displaying context menus in Wave Terminal. Use when implementing right-click menus, adding context menu items, creating submenus, or handling menu interactions with checkboxes and separators.                      |\n| create-view  | `.kilocode/skills/create-view/SKILL.md`  | Guide for implementing a new view type in Wave Terminal. Use when creating a new view component, implementing the ViewModel interface, registering a new view type in BlockRegistry, or adding a new content type to display within blocks. |\n| electron-api | `.kilocode/skills/electron-api/SKILL.md` | Guide for adding new Electron APIs to Wave Terminal. Use when implementing new frontend-to-electron communications via preload/IPC.                                                                                                         |\n| waveenv      | `.kilocode/skills/waveenv/SKILL.md`      | Guide for creating WaveEnv narrowings in Wave Terminal. Use when writing a named subset type of WaveEnv for a component tree, documenting environmental dependencies, or enabling mock environments for preview/test server usage.          |\n| wps-events   | `.kilocode/skills/wps-events/SKILL.md`   | Guide for working with Wave Terminal's WPS (Wave PubSub) event system. Use when implementing new event types, publishing events, subscribing to events, or adding asynchronous communication between components.                            |\n\n<!-- File: .github/copilot-instructions.md (Tokens: ~821 | Category: copilot-instructions) -->\n# Wave Terminal — Copilot Instructions\n\n## Project Rules\n\n- See the overview of the project in `.kilocode/rules/overview.md`\n- Read and follow all guidelines in `.kilocode/rules/rules.md`\n\n---\n\n## Skill Guides\n\nThis project uses a set of \"skill\" guides — focused how-to documents for common implementation tasks. When your task matches one of the descriptions below, **read the linked SKILL.md file before proceeding** and follow its instructions precisely.\n\n| Skill        | File                                     | Description                                                                                                                                                                                                                                 |\n| ------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| add-config   | `.kilocode/skills/add-config/SKILL.md`   | Guide for adding new configuration settings to Wave Terminal. Use when adding a new setting to the configuration system, implementing a new config key, or adding user-customizable settings.                                               |\n| add-rpc      | `.kilocode/skills/add-rpc/SKILL.md`      | Guide for adding new RPC calls to Wave Terminal. Use when implementing new RPC commands, adding server-client communication methods, or extending the RPC interface with new functionality.                                                 |\n| add-wshcmd   | `.kilocode/skills/add-wshcmd/SKILL.md`   | Guide for adding new wsh commands to Wave Terminal. Use when implementing new CLI commands, adding command-line functionality, or extending the wsh command interface.                                                                      |\n| context-menu | `.kilocode/skills/context-menu/SKILL.md` | Guide for creating and displaying context menus in Wave Terminal. Use when implementing right-click menus, adding context menu items, creating submenus, or handling menu interactions with checkboxes and separators.                      |\n| create-view  | `.kilocode/skills/create-view/SKILL.md`  | Guide for implementing a new view type in Wave Terminal. Use when creating a new view component, implementing the ViewModel interface, registering a new view type in BlockRegistry, or adding a new content type to display within blocks. |\n| electron-api | `.kilocode/skills/electron-api/SKILL.md` | Guide for adding new Electron APIs to Wave Terminal. Use when implementing new frontend-to-electron communications via preload/IPC.                                                                                                         |\n| waveenv      | `.kilocode/skills/waveenv/SKILL.md`      | Guide for creating WaveEnv narrowings in Wave Terminal. Use when writing a named subset type of WaveEnv for a component tree, documenting environmental dependencies, or enabling mock environments for preview/test server usage.          |\n| wps-events   | `.kilocode/skills/wps-events/SKILL.md`   | Guide for working with Wave Terminal's WPS (Wave PubSub) event system. Use when implementing new event types, publishing events, subscribing to events, or adding asynchronous communication between components.                            |\n\n> **How skills work:** Each skill is a self-contained guide covering the exact files to edit, patterns to follow, and steps to take for a specific type of task in this codebase. If your task matches a skill's description, open that SKILL.md and treat it as your primary reference for the implementation.\n\n---\n\n## Preview Server\n\nTo run the standalone component preview (no Electron, no backend required):\n\n```\ntask preview\n```\n\nThis runs `cd frontend/preview && npx vite` and serves at **http://localhost:7007** (port configured in `frontend/preview/vite.config.ts`).\n\nTo build a static preview: `task build:preview`\n\n**Do NOT use any of the following to start the preview — they all launch the full Electron app or serve the wrong content:**\n\n- `npm run dev` — runs `electron-vite dev`, launches Electron\n- `npm run start` — also launches Electron\n- `npx vite` from the repo root — uses the Electron-Vite config, not the preview app\n- Serving the `dist/` directory — the preview app is never built there; it has its own build output\n</yakaai_skills>"}