Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/ephemlets/guidance/authoring.md

Raw
Rendered preview

Ephemlet authoring guidance

Build one small, self-contained Pi UI component, not an extension.

Persisted source is loaded by jiti evalModule with a virtual filename in the owning extension; do not assume a project-root-relative import path.

Export a default factory(context) that returns a native Pi Component, synchronously or asynchronously. Keep module initialization side-effect-free: validation can evaluate the module before mounting it.

The component may expose focused and dispose(); dispose every timer, listener, subscription, or other resource it starts.

The host owns Close and Save chrome, a right/top overlay, and the one-visible-ephemlet rule; do not recreate host chrome.

Mount context exposes pi, a live ctx getter, tui, theme, keybindings, state, setState, viewport(), and signal.

Use context.pi and native Pi TUI APIs; read installed Pi SDK documentation rather than inventing APIs.

Choose local behavior for self-contained UI, agent flow for user intent requiring Pi conversation, and external flow only when request facts provide a concrete integration.

Actions must expose progress/status/errors and support cancellation through context.signal or explicit local controls.

Submit user intents with pi.sendUserMessage(text, { deliverAs: "followUp", expandPromptTemplates: false }) when they should continue after the current run.

Keep state JSON-serializable; guard asynchronous updates after cancellation or disposal.

Use only request facts and supplied existing source as authority; preserve useful behavior and avoid invented claims or unrelated features. Components run as trusted native code with Pi's permissions, not in a browser or sandbox. Shell, filesystem, API, and web side effects may use native Node/Pi APIs when the interaction brief calls for them; never embed credentials in session source or state. Expose operation progress, results, errors, and cancellation; report task-relevant side effects to the main agent through normal message flow.

Source must be self-contained and at most 128 KiB UTF-8, with state at most 64 KiB JSON. The builder returns complete source through submit_ephemlet; the main agent can also supply source directly through the ephemlet tool.

Native component contract

import { Text } from "@earendil-works/pi-tui";

export default function mount(context) {
  // context: pi, live ctx getter, tui, theme, keybindings,
  // live state getter, setState(JSON), viewport(), signal.
  const component = new Text("Hello", 0, 0);
  return component;
}

Return a native component with render(width): string[] and invalidate(): void. Optional handleInput(data) and handleMouse(event) receive native Pi input events in the content area. Implement focused and propagate it to nested editors for cursor/IME support. Host reserves Escape, Alt+Left/Right, Alt+Tab, Ctrl+S, Ctrl+W, and Shift+PageUp/PageDown; ordinary Tab belongs to your component. Use viewport() to size/scroll content; fit every line to supplied width using Pi's helpers. Return at most 8192 string lines per render and never write diagnostics to stdout/stderr; use context.ctx.ui.notify() for actionable errors. Keep rendering side-effect-free and route nested keyboard/mouse interactions within the returned root component. Host provides scrolling for overlong reports, plus chrome/navigation and responsive right/top positioning. All components may remain mounted while hidden, but only one is visible; avoid unnecessary background work.

Read context.state for current durable state, including main-agent updates; call context.setState(next) after meaningful interactions. Uncheckpointed local variables do not survive session reopen. Read context.ctx when performing operations rather than retaining the context of the originating tool call. Do not register permanent tools/commands in a component: Pi has no general unregistration API. Track pi.on() unsubscribe callbacks and release them in dispose(). Guard all asynchronous results with context.signal; host aborts it before disposal. Source/state updates use stable ID and expected revision; query ephemlet list/get before adapting existing work. get returns bounded metadata by default; request include: ["source"], ["state"], or both only when those fields are needed. The main agent cannot see the panel and receives no automatic notification of local state changes; it reads current state through get. Submit user intents that need conversation work through the explicit follow-up flow above rather than treating local state updates as agent messages. Background builds return an ID/job ID immediately; ready/failed notices do not trigger a new turn, so query get before relying on completion. Do not confuse build completion with human interaction completion.

# Ephemlet authoring guidance

Build one small, self-contained Pi UI component, not an extension.

Persisted source is loaded by jiti `evalModule` with a virtual filename in the owning extension; do not assume a project-root-relative import path.

Export a default `factory(context)` that returns a native Pi `Component`, synchronously or asynchronously.
Keep module initialization side-effect-free: validation can evaluate the module before mounting it.

The component may expose `focused` and `dispose()`; dispose every timer, listener, subscription, or other resource it starts.

The host owns Close and Save chrome, a right/top overlay, and the one-visible-ephemlet rule; do not recreate host chrome.

Mount context exposes `pi`, a live `ctx` getter, `tui`, `theme`, `keybindings`, `state`, `setState`, `viewport()`, and `signal`.

Use `context.pi` and native Pi TUI APIs; read installed Pi SDK documentation rather than inventing APIs.

Choose local behavior for self-contained UI, agent flow for user intent requiring Pi conversation, and external flow only when request facts provide a concrete integration.

Actions must expose progress/status/errors and support cancellation through `context.signal` or explicit local controls.

Submit user intents with `pi.sendUserMessage(text, { deliverAs: "followUp", expandPromptTemplates: false })` when they should continue after the current run.

Keep state JSON-serializable; guard asynchronous updates after cancellation or disposal.

Use only request facts and supplied existing source as authority; preserve useful behavior and avoid invented claims or unrelated features.
Components run as trusted native code with Pi's permissions, not in a browser or sandbox.
Shell, filesystem, API, and web side effects may use native Node/Pi APIs when the interaction brief calls for them; never embed credentials in session source or state.
Expose operation progress, results, errors, and cancellation; report task-relevant side effects to the main agent through normal message flow.

Source must be self-contained and at most 128 KiB UTF-8, with state at most 64 KiB JSON.
The builder returns complete source through `submit_ephemlet`; the main agent can also supply source directly through the `ephemlet` tool.

## Native component contract

```ts
import { Text } from "@earendil-works/pi-tui";

export default function mount(context) {
  // context: pi, live ctx getter, tui, theme, keybindings,
  // live state getter, setState(JSON), viewport(), signal.
  const component = new Text("Hello", 0, 0);
  return component;
}
```

Return a native component with `render(width): string[]` and `invalidate(): void`.
Optional `handleInput(data)` and `handleMouse(event)` receive native Pi input events in the content area.
Implement `focused` and propagate it to nested editors for cursor/IME support.
Host reserves Escape, Alt+Left/Right, Alt+Tab, Ctrl+S, Ctrl+W, and Shift+PageUp/PageDown; ordinary Tab belongs to your component.
Use `viewport()` to size/scroll content; fit every line to supplied width using Pi's helpers.
Return at most 8192 string lines per render and never write diagnostics to stdout/stderr; use `context.ctx.ui.notify()` for actionable errors.
Keep rendering side-effect-free and route nested keyboard/mouse interactions within the returned root component.
Host provides scrolling for overlong reports, plus chrome/navigation and responsive right/top positioning.
All components may remain mounted while hidden, but only one is visible; avoid unnecessary background work.

Read `context.state` for current durable state, including main-agent updates; call `context.setState(next)` after meaningful interactions.
Uncheckpointed local variables do not survive session reopen.
Read `context.ctx` when performing operations rather than retaining the context of the originating tool call.
Do not register permanent tools/commands in a component: Pi has no general unregistration API.
Track `pi.on()` unsubscribe callbacks and release them in `dispose()`.
Guard all asynchronous results with `context.signal`; host aborts it before disposal.
Source/state updates use stable ID and expected revision; query `ephemlet list/get` before adapting existing work.
`get` returns bounded metadata by default; request `include: ["source"]`, `["state"]`, or both only when those fields are needed.
The main agent cannot see the panel and receives no automatic notification of local state changes; it reads current state through `get`.
Submit user intents that need conversation work through the explicit follow-up flow above rather than treating local state updates as agent messages.
Background builds return an ID/job ID immediately; ready/failed notices do not trigger a new turn, so query `get` before relying on completion.
Do not confuse build completion with human interaction completion.