restty 0.1.21 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # restty
2
2
 
3
3
  [![Version](https://img.shields.io/npm/v/restty?style=flat-square)](https://www.npmjs.com/package/restty)
4
+ [![Downloads](https://img.shields.io/npm/dm/restty)](https://www.npmjs.com/package/restty)
4
5
  [![Package Size](https://img.shields.io/npm/unpacked-size/restty?style=flat-square)](https://www.npmjs.com/package/restty)
5
6
  [![CI](https://img.shields.io/github/actions/workflow/status/wiedymi/restty/ci.yml?branch=main&style=flat-square)](https://github.com/wiedymi/restty/actions/workflows/ci.yml)
6
7
  [![Publish](https://img.shields.io/github/actions/workflow/status/wiedymi/restty/publish.yml?style=flat-square&label=publish)](https://github.com/wiedymi/restty/actions/workflows/publish.yml)
@@ -154,6 +155,78 @@ const restty = new Restty({
154
155
  });
155
156
  ```
156
157
 
158
+ ### Plugin system (native)
159
+
160
+ Use plugins when you want to extend restty behavior without patching core.
161
+
162
+ ```ts
163
+ import type { ResttyPlugin } from "restty";
164
+
165
+ const metricsPlugin: ResttyPlugin = {
166
+ id: "example/metrics",
167
+ apiVersion: 1,
168
+ activate(ctx) {
169
+ const paneCreated = ctx.on("pane:created", ({ paneId }) => {
170
+ console.log("pane created", paneId);
171
+ });
172
+ const outgoing = ctx.addInputInterceptor(({ text }) => text.replace(/\t/g, " "));
173
+ const lifecycle = ctx.addLifecycleHook(({ phase, action }) => {
174
+ console.log("lifecycle", phase, action);
175
+ });
176
+ return () => {
177
+ paneCreated.dispose();
178
+ outgoing.dispose();
179
+ lifecycle.dispose();
180
+ };
181
+ },
182
+ };
183
+
184
+ await restty.use(metricsPlugin, { sampleRate: 1 });
185
+ console.log(restty.pluginInfo("example/metrics"));
186
+ restty.unuse("example/metrics");
187
+ ```
188
+
189
+ Declarative loading (manifest + registry):
190
+
191
+ ```ts
192
+ await restty.loadPlugins(
193
+ [{ id: "example/metrics", options: { sampleRate: 1 } }],
194
+ {
195
+ "example/metrics": () => metricsPlugin,
196
+ },
197
+ );
198
+ ```
199
+
200
+ See `docs/plugins.md` for full plugin authoring details.
201
+
202
+ ### xterm compatibility layer
203
+
204
+ For migration from xterm.js-style app code, use `restty/xterm`:
205
+
206
+ ```ts
207
+ import { Terminal } from "restty/xterm";
208
+
209
+ const term = new Terminal({ cols: 100, rows: 30 });
210
+ term.open(document.getElementById("terminal") as HTMLElement);
211
+
212
+ term.onData((data) => console.log("input", data));
213
+ term.onResize(({ cols, rows }) => console.log("resize", cols, rows));
214
+
215
+ term.write("hello");
216
+ term.writeln(" world");
217
+ term.resize(120, 40);
218
+ term.loadAddon({
219
+ activate() {},
220
+ dispose() {},
221
+ });
222
+ ```
223
+
224
+ Compatibility scope:
225
+
226
+ - Good for common embed/migration flows.
227
+ - Not full xterm internals parity (buffer/parser/marker ecosystem APIs are not all implemented).
228
+ - Prefer native `Restty` API for long-term integrations.
229
+
157
230
  ## API Snapshot
158
231
 
159
232
  Primary class:
@@ -161,6 +234,11 @@ Primary class:
161
234
  - `new Restty({ root, ...options })`
162
235
  - `createRestty(options)`
163
236
 
237
+ Xterm compatibility:
238
+
239
+ - `import { Terminal } from "restty/xterm"`
240
+ - Supports `open`, `write`, `writeln`, `resize`, `focus`, `blur`, `clear`, `reset`, `onData`, `onResize`, `options`, `loadAddon`, `dispose`
241
+
164
242
  Pane access:
165
243
 
166
244
  - `panes()` / `pane(id)` / `activePane()` / `focusedPane()` / `forEachPane(visitor)`
@@ -175,9 +253,15 @@ Active-pane convenience:
175
253
  - `setMouseMode("auto" | "on" | "off")`
176
254
  - `sendInput(text)` / `sendKeyInput(text)`
177
255
  - `copySelectionToClipboard()` / `pasteFromClipboard()`
256
+ - `resize(cols, rows)` / `focus()` / `blur()`
178
257
  - `updateSize(force?)`
179
258
  - `destroy()`
180
259
 
260
+ Plugin host:
261
+
262
+ - `use(plugin, options?)` / `loadPlugins(manifest, registry)` / `unuse(pluginId)` / `plugins()` / `pluginInfo(pluginId?)`
263
+ - plugin context supports `on(...)`, `addInputInterceptor(...)`, `addOutputInterceptor(...)`, `addLifecycleHook(...)`, `addRenderHook(...)`
264
+
181
265
  ## Advanced / Internal Modules
182
266
 
183
267
  Use these only when you need lower-level control:
@@ -214,6 +298,7 @@ bun run pty # PTY websocket server only
214
298
 
215
299
  - `docs/README.md` - docs index
216
300
  - `docs/usage.md` - practical integration guide
301
+ - `docs/xterm-compat.md` - xterm migration shim
217
302
  - `docs/how-it-works.md` - runtime flow
218
303
  - `docs/internals/` - implementation notes and architecture
219
304
  - `THIRD_PARTY_NOTICES.md` - third-party credits and notices
@@ -0,0 +1,5 @@
1
+ export type ResttyPastePayload = {
2
+ kind: "text";
3
+ text: string;
4
+ };
5
+ export declare function readPastePayloadFromDataTransfer(dataTransfer: DataTransfer | null | undefined): ResttyPastePayload | null;
@@ -2,7 +2,8 @@ import type { ResttyApp, ResttyAppOptions } from "./types";
2
2
  export { createResttyAppSession, getDefaultResttyAppSession } from "./session";
3
3
  export { createResttyPaneManager, createDefaultResttyPaneContextMenuItems, getResttyShortcutModifierLabel, } from "./panes";
4
4
  export { Restty } from "./restty";
5
- export type { ResttyAppElements, ResttyAppCallbacks, FontSource, ResttyFontSource, ResttyTouchSelectionMode, ResttyUrlFontSource, ResttyBufferFontSource, ResttyLocalFontSource, ResttyWasmLogListener, ResttyAppSession, ResttyAppOptions, ResttyApp, } from "./types";
5
+ export { RESTTY_PLUGIN_API_VERSION } from "./restty";
6
+ export type { ResttyAppElements, ResttyAppCallbacks, FontSource, ResttyFontSource, ResttyTouchSelectionMode, ResttyUrlFontSource, ResttyBufferFontSource, ResttyLocalFontSource, ResttyWasmLogListener, ResttyAppSession, ResttyAppInputPayload, ResttyAppOptions, ResttyApp, } from "./types";
6
7
  export type { ResttyPaneSplitDirection, ResttyPaneContextMenuItem, ResttyPaneDefinition, ResttyPaneStyleOptions, ResttyPaneStylesOptions, ResttyPaneShortcutsOptions, ResttyPaneContextMenuOptions, CreateResttyPaneManagerOptions, ResttyPaneManager, ResttyPaneWithApp, CreateDefaultResttyPaneContextMenuItemsOptions, } from "./panes";
7
- export type { ResttyOptions } from "./restty";
8
+ export type { ResttyOptions, ResttyPluginApiRange, ResttyPlugin, ResttyPluginCleanup, ResttyPluginContext, ResttyPluginDisposable, ResttyPluginEvents, ResttyPluginInfo, ResttyPluginRequires, ResttyInputInterceptor, ResttyInputInterceptorPayload, ResttyInterceptorOptions, ResttyLifecycleHook, ResttyLifecycleHookPayload, ResttyPluginLoadResult, ResttyPluginLoadStatus, ResttyPluginManifestEntry, ResttyPluginRegistry, ResttyPluginRegistryEntry, ResttyRenderHook, ResttyRenderHookPayload, ResttyOutputInterceptor, ResttyOutputInterceptorPayload, } from "./restty";
8
9
  export declare function createResttyApp(options: ResttyAppOptions): ResttyApp;
@@ -16,6 +16,158 @@ export type ResttyOptions = Omit<CreateResttyAppPaneManagerOptions, "appOptions"
16
16
  focus?: boolean;
17
17
  };
18
18
  };
19
+ /** Current Restty plugin API version. */
20
+ export declare const RESTTY_PLUGIN_API_VERSION = 1;
21
+ /** Plugin API version requirements. */
22
+ export type ResttyPluginApiRange = {
23
+ min: number;
24
+ max?: number;
25
+ };
26
+ /** Optional compatibility requirements declared by plugins. */
27
+ export type ResttyPluginRequires = {
28
+ pluginApi?: number | ResttyPluginApiRange;
29
+ };
30
+ /** Diagnostics snapshot for a plugin. */
31
+ export type ResttyPluginInfo = {
32
+ id: string;
33
+ version: string | null;
34
+ apiVersion: number | null;
35
+ requires: ResttyPluginRequires | null;
36
+ active: boolean;
37
+ activatedAt: number | null;
38
+ lastError: string | null;
39
+ listeners: number;
40
+ inputInterceptors: number;
41
+ outputInterceptors: number;
42
+ lifecycleHooks: number;
43
+ renderHooks: number;
44
+ };
45
+ /** Declarative plugin manifest entry for registry-based loading. */
46
+ export type ResttyPluginManifestEntry = {
47
+ id: string;
48
+ enabled?: boolean;
49
+ options?: unknown;
50
+ };
51
+ /** Provider entry for plugin registry lookups. */
52
+ export type ResttyPluginRegistryEntry = ResttyPlugin | (() => ResttyPlugin | Promise<ResttyPlugin>);
53
+ /** Registry shape accepted by loadPlugins. */
54
+ export type ResttyPluginRegistry = ReadonlyMap<string, ResttyPluginRegistryEntry> | Record<string, ResttyPluginRegistryEntry>;
55
+ /** Status for manifest-driven plugin load attempts. */
56
+ export type ResttyPluginLoadStatus = "loaded" | "skipped" | "missing" | "failed";
57
+ /** Result row returned by loadPlugins. */
58
+ export type ResttyPluginLoadResult = {
59
+ id: string;
60
+ status: ResttyPluginLoadStatus;
61
+ error: string | null;
62
+ };
63
+ /** Event payloads emitted by the Restty plugin host. */
64
+ export type ResttyPluginEvents = {
65
+ "plugin:activated": {
66
+ pluginId: string;
67
+ };
68
+ "plugin:deactivated": {
69
+ pluginId: string;
70
+ };
71
+ "pane:created": {
72
+ paneId: number;
73
+ };
74
+ "pane:closed": {
75
+ paneId: number;
76
+ };
77
+ "pane:split": {
78
+ sourcePaneId: number;
79
+ createdPaneId: number;
80
+ direction: ResttyPaneSplitDirection;
81
+ };
82
+ "pane:active-changed": {
83
+ paneId: number | null;
84
+ };
85
+ "layout:changed": {};
86
+ "pane:resized": {
87
+ paneId: number;
88
+ cols: number;
89
+ rows: number;
90
+ };
91
+ "pane:focused": {
92
+ paneId: number;
93
+ };
94
+ "pane:blurred": {
95
+ paneId: number;
96
+ };
97
+ };
98
+ /** A disposable resource returned by plugin APIs. */
99
+ export type ResttyPluginDisposable = {
100
+ dispose: () => void;
101
+ };
102
+ /** Optional cleanup return supported by plugin activation. */
103
+ export type ResttyPluginCleanup = void | (() => void) | ResttyPluginDisposable;
104
+ /** Payload passed to input interceptors before terminal/program input is written. */
105
+ export type ResttyInputInterceptorPayload = {
106
+ paneId: number;
107
+ text: string;
108
+ source: string;
109
+ };
110
+ /** Payload passed to output interceptors before PTY data is rendered. */
111
+ export type ResttyOutputInterceptorPayload = {
112
+ paneId: number;
113
+ text: string;
114
+ source: string;
115
+ };
116
+ /** Input interceptor contract. */
117
+ export type ResttyInputInterceptor = (payload: ResttyInputInterceptorPayload) => string | null | void;
118
+ /** Output interceptor contract. */
119
+ export type ResttyOutputInterceptor = (payload: ResttyOutputInterceptorPayload) => string | null | void;
120
+ /** Payload passed to lifecycle hooks registered by plugins. */
121
+ export type ResttyLifecycleHookPayload = {
122
+ phase: "before" | "after";
123
+ action: "create-initial-pane" | "split-active-pane" | "split-pane" | "close-pane" | "set-active-pane" | "mark-pane-focused" | "connect-pty" | "disconnect-pty" | "resize" | "focus" | "blur";
124
+ paneId?: number | null;
125
+ sourcePaneId?: number;
126
+ createdPaneId?: number | null;
127
+ direction?: ResttyPaneSplitDirection;
128
+ cols?: number;
129
+ rows?: number;
130
+ ok?: boolean;
131
+ error?: string | null;
132
+ };
133
+ /** Lifecycle hook contract. */
134
+ export type ResttyLifecycleHook = (payload: ResttyLifecycleHookPayload) => void;
135
+ /** Payload passed to render hooks registered by plugins. */
136
+ export type ResttyRenderHookPayload = {
137
+ phase: "before" | "after";
138
+ paneId: number;
139
+ text: string;
140
+ source: string;
141
+ dropped: boolean;
142
+ };
143
+ /** Render hook contract. */
144
+ export type ResttyRenderHook = (payload: ResttyRenderHookPayload) => void;
145
+ /** Shared options for interceptor ordering. */
146
+ export type ResttyInterceptorOptions = {
147
+ priority?: number;
148
+ };
149
+ /** Context object provided to each plugin on activation. */
150
+ export type ResttyPluginContext = {
151
+ restty: Restty;
152
+ options: unknown;
153
+ panes: () => ResttyPaneHandle[];
154
+ pane: (id: number) => ResttyPaneHandle | null;
155
+ activePane: () => ResttyPaneHandle | null;
156
+ focusedPane: () => ResttyPaneHandle | null;
157
+ on: <E extends keyof ResttyPluginEvents>(event: E, listener: (payload: ResttyPluginEvents[E]) => void) => ResttyPluginDisposable;
158
+ addInputInterceptor: (interceptor: ResttyInputInterceptor, options?: ResttyInterceptorOptions) => ResttyPluginDisposable;
159
+ addOutputInterceptor: (interceptor: ResttyOutputInterceptor, options?: ResttyInterceptorOptions) => ResttyPluginDisposable;
160
+ addLifecycleHook: (hook: ResttyLifecycleHook, options?: ResttyInterceptorOptions) => ResttyPluginDisposable;
161
+ addRenderHook: (hook: ResttyRenderHook, options?: ResttyInterceptorOptions) => ResttyPluginDisposable;
162
+ };
163
+ /** Plugin contract for extending Restty behavior. */
164
+ export type ResttyPlugin = {
165
+ id: string;
166
+ version?: string;
167
+ apiVersion?: number;
168
+ requires?: ResttyPluginRequires;
169
+ activate: (context: ResttyPluginContext, options?: unknown) => ResttyPluginCleanup | Promise<ResttyPluginCleanup>;
170
+ };
19
171
  /**
20
172
  * Public API surface exposed by each pane handle.
21
173
  */
@@ -38,6 +190,9 @@ export type ResttyPaneApi = {
38
190
  copySelectionToClipboard: () => Promise<boolean>;
39
191
  pasteFromClipboard: () => Promise<boolean>;
40
192
  dumpAtlasForCodepoint: (cp: number) => void;
193
+ resize: (cols: number, rows: number) => void;
194
+ focus: () => void;
195
+ blur: () => void;
41
196
  updateSize: (force?: boolean) => void;
42
197
  getBackend: () => string;
43
198
  getRawPane: () => ResttyManagedAppPane;
@@ -68,6 +223,9 @@ export declare class ResttyPaneHandle implements ResttyPaneApi {
68
223
  copySelectionToClipboard(): Promise<boolean>;
69
224
  pasteFromClipboard(): Promise<boolean>;
70
225
  dumpAtlasForCodepoint(cp: number): void;
226
+ resize(cols: number, rows: number): void;
227
+ focus(): void;
228
+ blur(): void;
71
229
  updateSize(force?: boolean): void;
72
230
  getBackend(): string;
73
231
  getRawPane(): ResttyManagedAppPane;
@@ -80,6 +238,15 @@ export declare class ResttyPaneHandle implements ResttyPaneApi {
80
238
  export declare class Restty {
81
239
  readonly paneManager: ResttyPaneManager<ResttyManagedAppPane>;
82
240
  private fontSources;
241
+ private readonly pluginListeners;
242
+ private readonly pluginRuntimes;
243
+ private readonly pluginDiagnostics;
244
+ private readonly inputInterceptors;
245
+ private readonly outputInterceptors;
246
+ private readonly lifecycleHooks;
247
+ private readonly renderHooks;
248
+ private nextInterceptorId;
249
+ private nextInterceptorOrder;
83
250
  constructor(options: ResttyOptions);
84
251
  getPanes(): ResttyManagedAppPane[];
85
252
  getPaneById(id: number): ResttyManagedAppPane | null;
@@ -107,6 +274,12 @@ export declare class Restty {
107
274
  }): void;
108
275
  requestLayoutSync(): void;
109
276
  hideContextMenu(): void;
277
+ use(plugin: ResttyPlugin, options?: unknown): Promise<void>;
278
+ loadPlugins(manifest: ReadonlyArray<ResttyPluginManifestEntry>, registry: ResttyPluginRegistry): Promise<ResttyPluginLoadResult[]>;
279
+ unuse(pluginId: string): boolean;
280
+ plugins(): string[];
281
+ pluginInfo(pluginId: string): ResttyPluginInfo | null;
282
+ pluginInfo(): ResttyPluginInfo[];
110
283
  destroy(): void;
111
284
  connectPty(url?: string): void;
112
285
  disconnectPty(): void;
@@ -125,11 +298,39 @@ export declare class Restty {
125
298
  copySelectionToClipboard(): Promise<boolean>;
126
299
  pasteFromClipboard(): Promise<boolean>;
127
300
  dumpAtlasForCodepoint(cp: number): void;
301
+ resize(cols: number, rows: number): void;
302
+ focus(): void;
303
+ blur(): void;
128
304
  updateSize(force?: boolean): void;
129
305
  getBackend(): string;
130
306
  private makePaneHandle;
131
307
  private requirePaneById;
132
308
  private requireActivePaneHandle;
309
+ private createPluginContext;
310
+ private attachRuntimeDisposer;
311
+ private addInputInterceptor;
312
+ private addOutputInterceptor;
313
+ private addLifecycleHook;
314
+ private addRenderHook;
315
+ private registerInterceptor;
316
+ private applyInputInterceptors;
317
+ private applyOutputInterceptors;
318
+ private runLifecycleHooks;
319
+ private runRenderHooks;
320
+ private applyInterceptors;
321
+ private runHooks;
322
+ private normalizePluginMetadata;
323
+ private assertPluginCompatibility;
324
+ private lookupPluginRegistryEntry;
325
+ private resolvePluginRegistryEntry;
326
+ private setPluginLoadError;
327
+ private updatePluginDiagnostic;
328
+ private buildPluginInfo;
329
+ private errorToMessage;
330
+ private onPluginEvent;
331
+ private emitPluginEvent;
332
+ private teardownPluginRuntime;
333
+ private normalizePluginCleanup;
133
334
  }
134
335
  /** Create a new Restty instance with the given options. */
135
336
  export declare function createRestty(options: ResttyOptions): Restty;
@@ -136,6 +136,11 @@ export type ResttyFontPreset = "default-cdn" | "none";
136
136
  * - off: disable touch selection entirely
137
137
  */
138
138
  export type ResttyTouchSelectionMode = "drag" | "long-press" | "off";
139
+ /** Input payload passed to ResttyApp before-input hooks. */
140
+ export type ResttyAppInputPayload = {
141
+ text: string;
142
+ source: string;
143
+ };
139
144
  /**
140
145
  * Options for creating a ResttyApp instance.
141
146
  */
@@ -207,6 +212,16 @@ export type ResttyAppOptions = {
207
212
  debugExpose?: boolean;
208
213
  /** PTY transport layer for terminal I/O. */
209
214
  ptyTransport?: PtyTransport;
215
+ /**
216
+ * Optional hook to transform or suppress terminal/program input
217
+ * before it is written to the terminal core.
218
+ */
219
+ beforeInput?: (payload: ResttyAppInputPayload) => string | null | void;
220
+ /**
221
+ * Optional hook to transform or suppress PTY output before it is
222
+ * queued for rendering.
223
+ */
224
+ beforeRenderOutput?: (payload: ResttyAppInputPayload) => string | null | void;
210
225
  };
211
226
  /**
212
227
  * Public API for a terminal app instance.
@@ -252,6 +267,12 @@ export type ResttyApp = {
252
267
  pasteFromClipboard: () => Promise<boolean>;
253
268
  /** Dump the glyph atlas entry for a given Unicode codepoint. */
254
269
  dumpAtlasForCodepoint: (cp: number) => void;
270
+ /** Resize terminal grid to explicit columns/rows. */
271
+ resize: (cols: number, rows: number) => void;
272
+ /** Focus terminal input targets. */
273
+ focus: () => void;
274
+ /** Blur terminal input targets. */
275
+ blur: () => void;
255
276
  /** Recalculate terminal dimensions from the canvas size. */
256
277
  updateSize: (force?: boolean) => void;
257
278
  /** Return the name of the active renderer backend. */