mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 20:01:24 +00:00
Add ctx.ui.theme getter for styling status text with theme colors
- Add theme property to HookUIContext interface - Implement in interactive, RPC, and no-op contexts - Add status-line.ts example hook - Document styling with theme colors in hooks.md
This commit is contained in:
parent
48ca55ab3c
commit
dccdf91b8c
11 changed files with 96 additions and 2 deletions
|
|
@ -36,6 +36,7 @@ The hooks API has been restructured with more granular events and better session
|
|||
- New `pi.registerMessageRenderer(customType, renderer)` for custom TUI rendering
|
||||
- New `ctx.ui.custom(component)` for full TUI component rendering with keyboard focus
|
||||
- New `ctx.ui.setStatus(key, text)` for persistent status text in footer (multiple hooks can set their own)
|
||||
- New `ctx.ui.theme` getter for styling text with theme colors
|
||||
- `ctx.exec()` moved to `pi.exec()`
|
||||
- `ctx.sessionFile` → `ctx.sessionManager.getSessionFile()`
|
||||
- New `ctx.modelRegistry` and `ctx.model` for API key resolution
|
||||
|
|
@ -191,6 +192,7 @@ Total color count increased from 46 to 50. See [docs/theme.md](docs/theme.md) fo
|
|||
### Added
|
||||
|
||||
- `ctx.ui.setStatus(key, text)` for hooks to display persistent status text in the footer ([#385](https://github.com/badlogic/pi-mono/pull/385) by [@prateekmedia](https://github.com/prateekmedia))
|
||||
- `ctx.ui.theme` getter for styling status text and other output with theme colors
|
||||
- `/share` command to upload session as a secret GitHub gist and get a shareable URL via shittycodingagent.ai ([#380](https://github.com/badlogic/pi-mono/issues/380))
|
||||
- HTML export now includes a tree visualization sidebar for navigating session branches ([#375](https://github.com/badlogic/pi-mono/issues/375))
|
||||
- HTML export supports keyboard shortcuts: Ctrl+T to toggle thinking blocks, Ctrl+O to toggle tool outputs
|
||||
|
|
|
|||
|
|
@ -438,7 +438,25 @@ const currentText = ctx.ui.getEditorText();
|
|||
- Multiple hooks can set their own status using unique keys
|
||||
- Statuses are displayed on a single line in the footer, sorted alphabetically by key
|
||||
- Text is sanitized (newlines/tabs replaced with spaces) and truncated to terminal width
|
||||
- ANSI escape codes for styling are preserved
|
||||
- Use `ctx.ui.theme` to style status text with theme colors (see below)
|
||||
|
||||
**Styling with theme colors:**
|
||||
|
||||
Use `ctx.ui.theme` to apply consistent colors that respect the user's theme:
|
||||
|
||||
```typescript
|
||||
const theme = ctx.ui.theme;
|
||||
|
||||
// Foreground colors
|
||||
ctx.ui.setStatus("my-hook", theme.fg("success", "✓") + theme.fg("dim", " Ready"));
|
||||
ctx.ui.setStatus("my-hook", theme.fg("error", "✗") + theme.fg("dim", " Failed"));
|
||||
ctx.ui.setStatus("my-hook", theme.fg("accent", "●") + theme.fg("dim", " Working..."));
|
||||
|
||||
// Available fg colors: accent, success, error, warning, muted, dim, text, and more
|
||||
// See docs/theme.md for the full list of theme colors
|
||||
```
|
||||
|
||||
See [examples/hooks/status-line.ts](../examples/hooks/status-line.ts) for a complete example.
|
||||
|
||||
**Custom components:**
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ cp permission-gate.ts ~/.pi/agent/hooks/
|
|||
| `custom-compaction.ts` | Custom compaction that summarizes entire conversation |
|
||||
| `qna.ts` | Extracts questions from last response into editor via `ctx.ui.setEditorText()` |
|
||||
| `snake.ts` | Snake game with custom UI, keyboard handling, and session persistence |
|
||||
| `status-line.ts` | Shows turn progress in footer via `ctx.ui.setStatus()` with themed colors |
|
||||
|
||||
## Writing Hooks
|
||||
|
||||
|
|
|
|||
38
packages/coding-agent/examples/hooks/status-line.ts
Normal file
38
packages/coding-agent/examples/hooks/status-line.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Status Line Hook
|
||||
*
|
||||
* Demonstrates ctx.ui.setStatus() for displaying persistent status text in the footer.
|
||||
* Shows turn progress with themed colors.
|
||||
*/
|
||||
|
||||
import type { HookAPI } from "@mariozechner/pi-coding-agent";
|
||||
|
||||
export default function (pi: HookAPI) {
|
||||
let turnCount = 0;
|
||||
|
||||
pi.on("session_start", async (_event, ctx) => {
|
||||
const theme = ctx.ui.theme;
|
||||
ctx.ui.setStatus("status-demo", theme.fg("dim", "Ready"));
|
||||
});
|
||||
|
||||
pi.on("turn_start", async (_event, ctx) => {
|
||||
turnCount++;
|
||||
const theme = ctx.ui.theme;
|
||||
const spinner = theme.fg("accent", "●");
|
||||
const text = theme.fg("dim", ` Turn ${turnCount}...`);
|
||||
ctx.ui.setStatus("status-demo", spinner + text);
|
||||
});
|
||||
|
||||
pi.on("turn_end", async (_event, ctx) => {
|
||||
const theme = ctx.ui.theme;
|
||||
const check = theme.fg("success", "✓");
|
||||
const text = theme.fg("dim", ` Turn ${turnCount} complete`);
|
||||
ctx.ui.setStatus("status-demo", check + text);
|
||||
});
|
||||
|
||||
pi.on("session_new", async (_event, ctx) => {
|
||||
turnCount = 0;
|
||||
const theme = ctx.ui.theme;
|
||||
ctx.ui.setStatus("status-demo", theme.fg("dim", "Ready"));
|
||||
});
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import * as path from "node:path";
|
|||
import { fileURLToPath } from "node:url";
|
||||
import { createJiti } from "jiti";
|
||||
import { getAgentDir, isBunBinary } from "../../config.js";
|
||||
import { theme } from "../../modes/interactive/theme/theme.js";
|
||||
import type { ExecOptions } from "../exec.js";
|
||||
import { execCommand } from "../exec.js";
|
||||
import type { HookUIContext } from "../hooks/types.js";
|
||||
|
|
@ -94,6 +95,9 @@ function createNoOpUIContext(): HookUIContext {
|
|||
custom: async () => undefined as never,
|
||||
setEditorText: () => {},
|
||||
getEditorText: () => "",
|
||||
get theme() {
|
||||
return theme;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1074,7 +1074,12 @@
|
|||
highlighted = escapeHtml(code);
|
||||
}
|
||||
} else {
|
||||
highlighted = escapeHtml(code);
|
||||
// Auto-detect language if not specified
|
||||
try {
|
||||
highlighted = hljs.highlightAuto(code).value;
|
||||
} catch {
|
||||
highlighted = escapeHtml(code);
|
||||
}
|
||||
}
|
||||
return `<pre><code class="hljs">${highlighted}</code></pre>`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import type { Model } from "@mariozechner/pi-ai";
|
||||
import { theme } from "../../modes/interactive/theme/theme.js";
|
||||
import type { ModelRegistry } from "../model-registry.js";
|
||||
import type { SessionManager } from "../session-manager.js";
|
||||
import type { AppendEntryHandler, LoadedHook, SendMessageHandler } from "./loader.js";
|
||||
|
|
@ -43,6 +44,9 @@ const noOpUIContext: HookUIContext = {
|
|||
custom: async () => undefined as never,
|
||||
setEditorText: () => {},
|
||||
getEditorText: () => "",
|
||||
get theme() {
|
||||
return theme;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -112,6 +112,16 @@ export interface HookUIContext {
|
|||
* @returns Current editor text
|
||||
*/
|
||||
getEditorText(): string;
|
||||
|
||||
/**
|
||||
* Get the current theme for styling text with ANSI codes.
|
||||
* Use theme.fg() and theme.bg() to style status text.
|
||||
*
|
||||
* @example
|
||||
* const theme = ctx.ui.theme;
|
||||
* ctx.ui.setStatus("my-hook", theme.fg("success", "✓") + " Ready");
|
||||
*/
|
||||
readonly theme: Theme;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -375,6 +375,9 @@ export class InteractiveMode {
|
|||
custom: (factory) => this.showHookCustom(factory),
|
||||
setEditorText: (text) => this.editor.setText(text),
|
||||
getEditorText: () => this.editor.getText(),
|
||||
get theme() {
|
||||
return theme;
|
||||
},
|
||||
};
|
||||
this.setToolUIContext(uiContext, true);
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import * as crypto from "node:crypto";
|
|||
import * as readline from "readline";
|
||||
import type { AgentSession } from "../../core/agent-session.js";
|
||||
import type { HookUIContext } from "../../core/hooks/index.js";
|
||||
import { theme } from "../interactive/theme/theme.js";
|
||||
import type { RpcCommand, RpcHookUIRequest, RpcHookUIResponse, RpcResponse, RpcSessionState } from "./rpc-types.js";
|
||||
|
||||
// Re-export types for consumers
|
||||
|
|
@ -150,6 +151,10 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|||
// Host should track editor state locally if needed
|
||||
return "";
|
||||
},
|
||||
|
||||
get theme() {
|
||||
return theme;
|
||||
},
|
||||
});
|
||||
|
||||
// Set up hooks with RPC-based UI context
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { ModelRegistry } from "../src/core/model-registry.js";
|
|||
import { SessionManager } from "../src/core/session-manager.js";
|
||||
import { SettingsManager } from "../src/core/settings-manager.js";
|
||||
import { codingTools } from "../src/core/tools/index.js";
|
||||
import { theme } from "../src/modes/interactive/theme/theme.js";
|
||||
|
||||
const API_KEY = process.env.ANTHROPIC_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY;
|
||||
|
||||
|
|
@ -112,6 +113,9 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => {
|
|||
custom: async () => undefined as never,
|
||||
setEditorText: () => {},
|
||||
getEditorText: () => "",
|
||||
get theme() {
|
||||
return theme;
|
||||
},
|
||||
},
|
||||
hasUI: false,
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue