pi-extension-utils 0.1.0
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/LICENSE +21 -0
- package/README.md +31 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +143 -0
- package/dist/src/client.d.ts +25 -0
- package/dist/src/client.js +132 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +3 -0
- package/dist/src/logger.d.ts +12 -0
- package/dist/src/logger.js +45 -0
- package/dist/src/protocol.d.ts +49 -0
- package/dist/src/protocol.js +19 -0
- package/package.json +40 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# pi-extension-utils
|
|
2
|
+
|
|
3
|
+
Shared utilities for Pi extensions. This package has two faces:
|
|
4
|
+
|
|
5
|
+
- `index.ts` is the Pi host extension registered through `package.json` `pi.extensions`.
|
|
6
|
+
- `src/index.ts` is the client library surface that other extensions can vendor and import.
|
|
7
|
+
|
|
8
|
+
## Architecture
|
|
9
|
+
|
|
10
|
+
The host and clients communicate over Pi's shared in-process `pi.events` bus. Protocol events are namespaced as `pi-extension-utils:*` and every payload carries `protocolVersion` and `clientId`. Payloads may carry live function references because they never leave the process.
|
|
11
|
+
|
|
12
|
+
Clients emit `hello` and listen for `ready`. Before a host is ready, widget calls fall back to the consumer extension's own `ctx.ui.setWidget`. If a host arrives later, fallback widgets are cleared and re-registered through the coordinator.
|
|
13
|
+
|
|
14
|
+
The host owns one real widget per placement and composes registered sub-widgets sorted by `(order, insertion)`. Fullscreen leases blank coordinated widgets until all leases are released or the owning client disposes.
|
|
15
|
+
|
|
16
|
+
## API sketch
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { connect, createLogger } from "pi-extension-utils";
|
|
20
|
+
|
|
21
|
+
const client = connect(pi, { ctx, clientId: "my-extension" });
|
|
22
|
+
client.widgets.set("belowEditor", "status", (tui, theme) => component, { order: 10 });
|
|
23
|
+
client.widgets.remove("belowEditor", "status");
|
|
24
|
+
|
|
25
|
+
const lease = client.fullscreen.acquire();
|
|
26
|
+
lease.release();
|
|
27
|
+
client.dispose();
|
|
28
|
+
|
|
29
|
+
const log = createLogger("my-extension");
|
|
30
|
+
log.info("started");
|
|
31
|
+
```
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { basePayload, EVENTS, PROTOCOL_VERSION, } from "./src/protocol.js";
|
|
2
|
+
const HOST_CLIENT_ID = "pi-extension-utils-host";
|
|
3
|
+
const HOST_WIDGET_PREFIX = "pi-extension-utils";
|
|
4
|
+
const PLACEMENTS = ["aboveEditor", "belowEditor"];
|
|
5
|
+
export default function (pi) {
|
|
6
|
+
const widgets = new Map();
|
|
7
|
+
const fullscreenStack = [];
|
|
8
|
+
let seq = 0;
|
|
9
|
+
let currentCtx;
|
|
10
|
+
for (const placement of PLACEMENTS)
|
|
11
|
+
widgets.set(placement, []);
|
|
12
|
+
function setHostWidget(placement) {
|
|
13
|
+
if (!currentCtx)
|
|
14
|
+
return;
|
|
15
|
+
const records = sortedWidgets(placement);
|
|
16
|
+
const hidden = fullscreenStack.length > 0;
|
|
17
|
+
if (records.length === 0 && !hidden) {
|
|
18
|
+
currentCtx.ui.setWidget(hostKey(placement), undefined, { placement });
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
currentCtx.ui.setWidget(hostKey(placement), (tui, theme) => createHostComponent(tui, theme, records, hidden), { placement });
|
|
22
|
+
}
|
|
23
|
+
function rerenderAll() {
|
|
24
|
+
for (const placement of PLACEMENTS)
|
|
25
|
+
setHostWidget(placement);
|
|
26
|
+
}
|
|
27
|
+
pi.events.on(EVENTS.hello, (data) => {
|
|
28
|
+
const payload = readPayload(data, "hello");
|
|
29
|
+
if (!payload)
|
|
30
|
+
return;
|
|
31
|
+
pi.events.emit(EVENTS.ready, basePayload(HOST_CLIENT_ID));
|
|
32
|
+
});
|
|
33
|
+
pi.events.on(EVENTS.registerWidget, (data) => {
|
|
34
|
+
const payload = readPayload(data, "register-widget");
|
|
35
|
+
if (!payload || !isPlacement(payload.placement) || typeof payload.key !== "string" || typeof payload.order !== "number" || typeof payload.factory !== "function") {
|
|
36
|
+
warnDrop("register-widget", data);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const records = widgets.get(payload.placement);
|
|
40
|
+
const existing = records.find((record) => record.clientId === payload.clientId && record.key === payload.key);
|
|
41
|
+
if (existing) {
|
|
42
|
+
existing.order = payload.order;
|
|
43
|
+
existing.factory = payload.factory;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
records.push({ clientId: payload.clientId, key: payload.key, order: payload.order, factory: payload.factory, seq: seq++ });
|
|
47
|
+
}
|
|
48
|
+
setHostWidget(payload.placement);
|
|
49
|
+
});
|
|
50
|
+
pi.events.on(EVENTS.unregisterWidget, (data) => {
|
|
51
|
+
const payload = readPayload(data, "unregister-widget");
|
|
52
|
+
if (!payload)
|
|
53
|
+
return;
|
|
54
|
+
if (payload.all) {
|
|
55
|
+
removeClient(payload.clientId);
|
|
56
|
+
rerenderAll();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!isPlacement(payload.placement) || typeof payload.key !== "string") {
|
|
60
|
+
warnDrop("unregister-widget", data);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const records = widgets.get(payload.placement);
|
|
64
|
+
widgets.set(payload.placement, records.filter((record) => record.clientId !== payload.clientId || record.key !== payload.key));
|
|
65
|
+
setHostWidget(payload.placement);
|
|
66
|
+
});
|
|
67
|
+
pi.events.on(EVENTS.fullscreenAcquire, (data) => {
|
|
68
|
+
const payload = readPayload(data, "fullscreen-acquire");
|
|
69
|
+
if (!payload || typeof payload.token !== "string") {
|
|
70
|
+
warnDrop("fullscreen-acquire", data);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!fullscreenStack.some((lease) => lease.token === payload.token)) {
|
|
74
|
+
fullscreenStack.push({ clientId: payload.clientId, token: payload.token });
|
|
75
|
+
rerenderAll();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
pi.events.on(EVENTS.fullscreenRelease, (data) => {
|
|
79
|
+
const payload = readPayload(data, "fullscreen-release");
|
|
80
|
+
if (!payload || typeof payload.token !== "string") {
|
|
81
|
+
warnDrop("fullscreen-release", data);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const before = fullscreenStack.length;
|
|
85
|
+
for (let index = fullscreenStack.length - 1; index >= 0; index--) {
|
|
86
|
+
if (fullscreenStack[index].token === payload.token)
|
|
87
|
+
fullscreenStack.splice(index, 1);
|
|
88
|
+
}
|
|
89
|
+
if (fullscreenStack.length !== before)
|
|
90
|
+
rerenderAll();
|
|
91
|
+
});
|
|
92
|
+
pi.on("session_start", (_event, ctx) => {
|
|
93
|
+
currentCtx = ctx;
|
|
94
|
+
rerenderAll();
|
|
95
|
+
pi.events.emit(EVENTS.ready, basePayload(HOST_CLIENT_ID));
|
|
96
|
+
});
|
|
97
|
+
pi.events.emit(EVENTS.ready, basePayload(HOST_CLIENT_ID));
|
|
98
|
+
function removeClient(clientId) {
|
|
99
|
+
for (const placement of PLACEMENTS) {
|
|
100
|
+
widgets.set(placement, widgets.get(placement).filter((record) => record.clientId !== clientId));
|
|
101
|
+
}
|
|
102
|
+
for (let index = fullscreenStack.length - 1; index >= 0; index--) {
|
|
103
|
+
if (fullscreenStack[index].clientId === clientId)
|
|
104
|
+
fullscreenStack.splice(index, 1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function sortedWidgets(placement) {
|
|
108
|
+
return [...widgets.get(placement)].sort((a, b) => a.order - b.order || a.seq - b.seq);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function createHostComponent(tui, theme, records, hidden) {
|
|
112
|
+
const components = hidden ? [] : records.map((record) => record.factory(tui, theme));
|
|
113
|
+
return {
|
|
114
|
+
render(width) {
|
|
115
|
+
return components.flatMap((component) => component.render(width));
|
|
116
|
+
},
|
|
117
|
+
invalidate() {
|
|
118
|
+
for (const component of components)
|
|
119
|
+
component.invalidate();
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function hostKey(placement) {
|
|
124
|
+
return `${HOST_WIDGET_PREFIX}-${placement}`;
|
|
125
|
+
}
|
|
126
|
+
function readPayload(data, eventName) {
|
|
127
|
+
if (!data || typeof data !== "object") {
|
|
128
|
+
warnDrop(eventName, data);
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
const payload = data;
|
|
132
|
+
if (typeof payload.protocolVersion !== "number" || payload.protocolVersion > PROTOCOL_VERSION || typeof payload.clientId !== "string") {
|
|
133
|
+
warnDrop(eventName, data);
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
return payload;
|
|
137
|
+
}
|
|
138
|
+
function isPlacement(value) {
|
|
139
|
+
return value === "aboveEditor" || value === "belowEditor";
|
|
140
|
+
}
|
|
141
|
+
function warnDrop(eventName, data) {
|
|
142
|
+
console.warn(`pi-extension-utils: dropping invalid ${eventName} payload`, data);
|
|
143
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { WidgetFactory, WidgetPlacement } from "./protocol.ts";
|
|
3
|
+
export interface UtilsClientOptions {
|
|
4
|
+
ctx: ExtensionContext;
|
|
5
|
+
clientId?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface WidgetSetOptions {
|
|
8
|
+
order?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface FullscreenLease {
|
|
11
|
+
release(): void;
|
|
12
|
+
}
|
|
13
|
+
export interface UtilsClient {
|
|
14
|
+
readonly clientId: string;
|
|
15
|
+
readonly mode: "fallback" | "coordinated";
|
|
16
|
+
widgets: {
|
|
17
|
+
set(placement: WidgetPlacement, key: string, factory: WidgetFactory, opts?: WidgetSetOptions): void;
|
|
18
|
+
remove(placement: WidgetPlacement, key: string): void;
|
|
19
|
+
};
|
|
20
|
+
fullscreen: {
|
|
21
|
+
acquire(): FullscreenLease;
|
|
22
|
+
};
|
|
23
|
+
dispose(): void;
|
|
24
|
+
}
|
|
25
|
+
export declare function connect(pi: ExtensionAPI, opts: UtilsClientOptions): UtilsClient;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { basePayload, EVENTS, PROTOCOL_VERSION, } from "./protocol.js";
|
|
2
|
+
let nextClientId = 1;
|
|
3
|
+
let nextLeaseId = 1;
|
|
4
|
+
export function connect(pi, opts) {
|
|
5
|
+
const clientId = opts.clientId ?? `client-${process.pid}-${Date.now()}-${nextClientId++}`;
|
|
6
|
+
const widgets = new Map();
|
|
7
|
+
const leases = new Set();
|
|
8
|
+
let disposed = false;
|
|
9
|
+
let coordinated = false;
|
|
10
|
+
const widgetId = (placement, key) => `${placement}:${key}`;
|
|
11
|
+
function emitRegister(record) {
|
|
12
|
+
pi.events.emit(EVENTS.registerWidget, {
|
|
13
|
+
...basePayload(clientId),
|
|
14
|
+
placement: record.placement,
|
|
15
|
+
key: record.key,
|
|
16
|
+
order: record.order,
|
|
17
|
+
factory: record.factory,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function clearFallback(record) {
|
|
21
|
+
opts.ctx.ui.setWidget(record.key, undefined, { placement: record.placement });
|
|
22
|
+
}
|
|
23
|
+
function restoreFallback(record) {
|
|
24
|
+
opts.ctx.ui.setWidget(record.key, record.factory, { placement: record.placement });
|
|
25
|
+
}
|
|
26
|
+
function attach() {
|
|
27
|
+
if (disposed || coordinated)
|
|
28
|
+
return;
|
|
29
|
+
coordinated = true;
|
|
30
|
+
for (const record of widgets.values()) {
|
|
31
|
+
clearFallback(record);
|
|
32
|
+
emitRegister(record);
|
|
33
|
+
}
|
|
34
|
+
for (const token of leases) {
|
|
35
|
+
pi.events.emit(EVENTS.fullscreenAcquire, { ...basePayload(clientId), token });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const offReady = pi.events.on(EVENTS.ready, (data) => {
|
|
39
|
+
if (!isReadyPayload(data))
|
|
40
|
+
return;
|
|
41
|
+
attach();
|
|
42
|
+
});
|
|
43
|
+
pi.events.emit(EVENTS.hello, basePayload(clientId));
|
|
44
|
+
const client = {
|
|
45
|
+
clientId,
|
|
46
|
+
get mode() {
|
|
47
|
+
return coordinated ? "coordinated" : "fallback";
|
|
48
|
+
},
|
|
49
|
+
widgets: {
|
|
50
|
+
set(placement, key, factory, setOpts = {}) {
|
|
51
|
+
if (disposed)
|
|
52
|
+
return;
|
|
53
|
+
const record = { placement, key, factory, order: setOpts.order ?? 0 };
|
|
54
|
+
widgets.set(widgetId(placement, key), record);
|
|
55
|
+
if (coordinated) {
|
|
56
|
+
emitRegister(record);
|
|
57
|
+
}
|
|
58
|
+
else if (leases.size === 0) {
|
|
59
|
+
opts.ctx.ui.setWidget(key, factory, { placement });
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
remove(placement, key) {
|
|
63
|
+
const id = widgetId(placement, key);
|
|
64
|
+
const record = widgets.get(id);
|
|
65
|
+
if (!record)
|
|
66
|
+
return;
|
|
67
|
+
widgets.delete(id);
|
|
68
|
+
if (coordinated) {
|
|
69
|
+
pi.events.emit(EVENTS.unregisterWidget, { ...basePayload(clientId), placement, key });
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
clearFallback(record);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
fullscreen: {
|
|
77
|
+
acquire() {
|
|
78
|
+
const token = `${clientId}-lease-${nextLeaseId++}`;
|
|
79
|
+
let released = false;
|
|
80
|
+
const wasFallbackVisible = !coordinated && leases.size === 0;
|
|
81
|
+
leases.add(token);
|
|
82
|
+
if (coordinated) {
|
|
83
|
+
pi.events.emit(EVENTS.fullscreenAcquire, { ...basePayload(clientId), token });
|
|
84
|
+
}
|
|
85
|
+
else if (wasFallbackVisible) {
|
|
86
|
+
for (const record of widgets.values())
|
|
87
|
+
clearFallback(record);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
release() {
|
|
91
|
+
if (released)
|
|
92
|
+
return;
|
|
93
|
+
released = true;
|
|
94
|
+
leases.delete(token);
|
|
95
|
+
if (coordinated && !disposed) {
|
|
96
|
+
pi.events.emit(EVENTS.fullscreenRelease, { ...basePayload(clientId), token });
|
|
97
|
+
}
|
|
98
|
+
else if (!disposed && leases.size === 0) {
|
|
99
|
+
for (const record of widgets.values())
|
|
100
|
+
restoreFallback(record);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
dispose() {
|
|
107
|
+
if (disposed)
|
|
108
|
+
return;
|
|
109
|
+
disposed = true;
|
|
110
|
+
offReady();
|
|
111
|
+
if (coordinated) {
|
|
112
|
+
pi.events.emit(EVENTS.unregisterWidget, { ...basePayload(clientId), all: true });
|
|
113
|
+
for (const token of leases) {
|
|
114
|
+
pi.events.emit(EVENTS.fullscreenRelease, { ...basePayload(clientId), token });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
for (const record of widgets.values())
|
|
119
|
+
clearFallback(record);
|
|
120
|
+
}
|
|
121
|
+
leases.clear();
|
|
122
|
+
widgets.clear();
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
return client;
|
|
126
|
+
}
|
|
127
|
+
function isReadyPayload(data) {
|
|
128
|
+
if (!data || typeof data !== "object")
|
|
129
|
+
return false;
|
|
130
|
+
const payload = data;
|
|
131
|
+
return typeof payload.protocolVersion === "number" && payload.protocolVersion <= PROTOCOL_VERSION && typeof payload.clientId === "string";
|
|
132
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface LoggerOptions {
|
|
2
|
+
dir?: string;
|
|
3
|
+
maxBytes?: number;
|
|
4
|
+
}
|
|
5
|
+
export interface Logger {
|
|
6
|
+
debug(message: string): void;
|
|
7
|
+
info(message: string): void;
|
|
8
|
+
warn(message: string): void;
|
|
9
|
+
error(message: string): void;
|
|
10
|
+
log(level: string, message: string): void;
|
|
11
|
+
}
|
|
12
|
+
export declare function createLogger(ns: string, opts?: LoggerOptions): Logger;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, renameSync, rmSync, statSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
const DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
5
|
+
export function createLogger(ns, opts = {}) {
|
|
6
|
+
if (!ns || ns.includes("/") || ns.includes("\\")) {
|
|
7
|
+
throw new Error("Logger namespace must not contain path separators");
|
|
8
|
+
}
|
|
9
|
+
const dir = opts.dir ?? join(homedir(), ".pi", "logs");
|
|
10
|
+
const maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
11
|
+
const file = join(dir, `${ns}.log`);
|
|
12
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
13
|
+
function rotateIfNeeded(nextBytes) {
|
|
14
|
+
if (maxBytes <= 0 || !existsSync(file))
|
|
15
|
+
return;
|
|
16
|
+
const size = statSync(file).size;
|
|
17
|
+
if (size + nextBytes <= maxBytes)
|
|
18
|
+
return;
|
|
19
|
+
const rotated = `${file}.1`;
|
|
20
|
+
if (existsSync(rotated))
|
|
21
|
+
rmSync(rotated);
|
|
22
|
+
renameSync(file, rotated);
|
|
23
|
+
}
|
|
24
|
+
function log(level, message) {
|
|
25
|
+
const line = `${new Date().toISOString()} ${level} ${message}\n`;
|
|
26
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
27
|
+
rotateIfNeeded(Buffer.byteLength(line));
|
|
28
|
+
appendFileSync(file, line);
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
debug(message) {
|
|
32
|
+
log("debug", message);
|
|
33
|
+
},
|
|
34
|
+
info(message) {
|
|
35
|
+
log("info", message);
|
|
36
|
+
},
|
|
37
|
+
warn(message) {
|
|
38
|
+
log("warn", message);
|
|
39
|
+
},
|
|
40
|
+
error(message) {
|
|
41
|
+
log("error", message);
|
|
42
|
+
},
|
|
43
|
+
log,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export declare const PROTOCOL_VERSION = 1;
|
|
2
|
+
export declare const EVENT_PREFIX = "pi-extension-utils";
|
|
3
|
+
export declare const HELLO_EVENT = "pi-extension-utils:hello";
|
|
4
|
+
export declare const READY_EVENT = "pi-extension-utils:ready";
|
|
5
|
+
export declare const REGISTER_WIDGET_EVENT = "pi-extension-utils:register-widget";
|
|
6
|
+
export declare const UNREGISTER_WIDGET_EVENT = "pi-extension-utils:unregister-widget";
|
|
7
|
+
export declare const FULLSCREEN_ACQUIRE_EVENT = "pi-extension-utils:fullscreen-acquire";
|
|
8
|
+
export declare const FULLSCREEN_RELEASE_EVENT = "pi-extension-utils:fullscreen-release";
|
|
9
|
+
export declare const EVENTS: {
|
|
10
|
+
readonly hello: "pi-extension-utils:hello";
|
|
11
|
+
readonly ready: "pi-extension-utils:ready";
|
|
12
|
+
readonly registerWidget: "pi-extension-utils:register-widget";
|
|
13
|
+
readonly unregisterWidget: "pi-extension-utils:unregister-widget";
|
|
14
|
+
readonly fullscreenAcquire: "pi-extension-utils:fullscreen-acquire";
|
|
15
|
+
readonly fullscreenRelease: "pi-extension-utils:fullscreen-release";
|
|
16
|
+
};
|
|
17
|
+
export type WidgetPlacement = "aboveEditor" | "belowEditor";
|
|
18
|
+
export interface WidgetComponent {
|
|
19
|
+
render(width: number): string[];
|
|
20
|
+
invalidate(): void;
|
|
21
|
+
dispose?(): void;
|
|
22
|
+
}
|
|
23
|
+
export type WidgetFactory = (tui: unknown, theme: unknown) => WidgetComponent;
|
|
24
|
+
export interface ProtocolPayload {
|
|
25
|
+
protocolVersion: number;
|
|
26
|
+
clientId: string;
|
|
27
|
+
}
|
|
28
|
+
export interface HelloPayload extends ProtocolPayload {
|
|
29
|
+
}
|
|
30
|
+
export interface ReadyPayload extends ProtocolPayload {
|
|
31
|
+
}
|
|
32
|
+
export interface RegisterWidgetPayload extends ProtocolPayload {
|
|
33
|
+
placement: WidgetPlacement;
|
|
34
|
+
key: string;
|
|
35
|
+
order: number;
|
|
36
|
+
factory: WidgetFactory;
|
|
37
|
+
}
|
|
38
|
+
export interface UnregisterWidgetPayload extends ProtocolPayload {
|
|
39
|
+
placement?: WidgetPlacement;
|
|
40
|
+
key?: string;
|
|
41
|
+
all?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface FullscreenAcquirePayload extends ProtocolPayload {
|
|
44
|
+
token: string;
|
|
45
|
+
}
|
|
46
|
+
export interface FullscreenReleasePayload extends ProtocolPayload {
|
|
47
|
+
token: string;
|
|
48
|
+
}
|
|
49
|
+
export declare function basePayload(clientId: string): ProtocolPayload;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const PROTOCOL_VERSION = 1;
|
|
2
|
+
export const EVENT_PREFIX = "pi-extension-utils";
|
|
3
|
+
export const HELLO_EVENT = `${EVENT_PREFIX}:hello`;
|
|
4
|
+
export const READY_EVENT = `${EVENT_PREFIX}:ready`;
|
|
5
|
+
export const REGISTER_WIDGET_EVENT = `${EVENT_PREFIX}:register-widget`;
|
|
6
|
+
export const UNREGISTER_WIDGET_EVENT = `${EVENT_PREFIX}:unregister-widget`;
|
|
7
|
+
export const FULLSCREEN_ACQUIRE_EVENT = `${EVENT_PREFIX}:fullscreen-acquire`;
|
|
8
|
+
export const FULLSCREEN_RELEASE_EVENT = `${EVENT_PREFIX}:fullscreen-release`;
|
|
9
|
+
export const EVENTS = {
|
|
10
|
+
hello: HELLO_EVENT,
|
|
11
|
+
ready: READY_EVENT,
|
|
12
|
+
registerWidget: REGISTER_WIDGET_EVENT,
|
|
13
|
+
unregisterWidget: UNREGISTER_WIDGET_EVENT,
|
|
14
|
+
fullscreenAcquire: FULLSCREEN_ACQUIRE_EVENT,
|
|
15
|
+
fullscreenRelease: FULLSCREEN_RELEASE_EVENT,
|
|
16
|
+
};
|
|
17
|
+
export function basePayload(clientId) {
|
|
18
|
+
return { protocolVersion: PROTOCOL_VERSION, clientId };
|
|
19
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-extension-utils",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared Pi extension utilities for coordinated widgets, fullscreen leases, and logging.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/src/index.js",
|
|
7
|
+
"types": "./dist/src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/src/index.d.ts",
|
|
11
|
+
"default": "./dist/src/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
15
|
+
"pi": {
|
|
16
|
+
"extensions": ["./dist/index.js"]
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
20
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build",
|
|
21
|
+
"test": "node --test --experimental-strip-types test/**/*.test.ts",
|
|
22
|
+
"typecheck": "tsc --noEmit"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [],
|
|
25
|
+
"author": "",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@earendil-works/pi-agent-core": "*",
|
|
29
|
+
"@earendil-works/pi-ai": "*",
|
|
30
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
31
|
+
"@earendil-works/pi-tui": "*"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@earendil-works/pi-agent-core": "^0.75.4",
|
|
35
|
+
"@earendil-works/pi-ai": "^0.75.4",
|
|
36
|
+
"@earendil-works/pi-coding-agent": "^0.75.4",
|
|
37
|
+
"@types/node": "^24.0.0",
|
|
38
|
+
"typescript": "^6.0.3"
|
|
39
|
+
}
|
|
40
|
+
}
|