@protoboardai/cli 0.0.0-stage → 0.2.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 +202 -0
- package/NOTICE +13 -0
- package/bin/protoboard.mjs +5 -0
- package/build-info.json +8 -0
- package/package.json +40 -4
- package/src/agents.ts +235 -0
- package/src/bom.ts +145 -0
- package/src/dev/build-worker.ts +63 -0
- package/src/dev/layout.ts +199 -0
- package/src/dev/page.ts +284 -0
- package/src/dev/parts.ts +356 -0
- package/src/dev/relay.d.ts +60 -0
- package/src/dev/relay.js +147 -0
- package/src/dev/server.ts +727 -0
- package/src/dev/sources.ts +120 -0
- package/src/gate.ts +102 -0
- package/src/index.ts +2024 -0
- package/src/layout.ts +166 -0
- package/src/mcp/instructions.ts +60 -0
- package/src/mcp/server.ts +215 -0
- package/src/mcp/tools.ts +392 -0
- package/src/openUrl.ts +71 -0
- package/src/setup.ts +298 -0
- package/src/stack-messages.ts +53 -0
- package/src/views-check.ts +267 -0
- package/README.md +0 -3
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** Types for relay.js (the dev page's relay between the viewer and a framing host). */
|
|
2
|
+
|
|
3
|
+
/** Where a module is written down, as the dev server resolves it (./sources.ts). */
|
|
4
|
+
export interface RelayPlace {
|
|
5
|
+
file: string;
|
|
6
|
+
line?: number;
|
|
7
|
+
path: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** The parts of `window` the relay uses (a test passes its own). */
|
|
11
|
+
export interface RelayWindow {
|
|
12
|
+
parent: { postMessage(message: unknown, targetOrigin: string): void } | RelayWindow;
|
|
13
|
+
location: { origin: string; ancestorOrigins?: ArrayLike<string> };
|
|
14
|
+
document?: { referrer?: string };
|
|
15
|
+
addEventListener(type: "message", listener: (e: { data: unknown; origin: string; source: unknown }) => void): void;
|
|
16
|
+
removeEventListener(type: "message", listener: (e: { data: unknown; origin: string; source: unknown }) => void): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface OpenSourceEvent {
|
|
20
|
+
type: "uhd-viewer:openSource";
|
|
21
|
+
defId: string;
|
|
22
|
+
instancePath?: string;
|
|
23
|
+
/** Where the definition is declared. */
|
|
24
|
+
source?: RelayPlace;
|
|
25
|
+
/** Where the instance is declared in its parent's definition. */
|
|
26
|
+
instanceSource?: RelayPlace;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface RelayOptions {
|
|
30
|
+
window: RelayWindow;
|
|
31
|
+
/** Parent origins taken: exact origins, `scheme://*`, `*`, or `self` (default: `self` and `vscode-webview://*`). */
|
|
32
|
+
origins?: string[];
|
|
33
|
+
/** Asks the dev server where a definition and an instance are written. */
|
|
34
|
+
resolveSource(request: { defId: string; instancePath?: string }): Promise<{ source?: RelayPlace; instanceSource?: RelayPlace } | undefined>;
|
|
35
|
+
/** openSource in a tab of its own (no host to send it to). */
|
|
36
|
+
onLocal?(event: OpenSourceEvent): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface Relay {
|
|
40
|
+
/** The page is in a frame (a host may be listening). */
|
|
41
|
+
framed: boolean;
|
|
42
|
+
/** The viewer's callbacks (mountViewer options). `onOpenArtifact` and `onOpenPair` only in a frame. */
|
|
43
|
+
callbacks: {
|
|
44
|
+
onReady(): void;
|
|
45
|
+
onSelectionChanged(selection: unknown): void;
|
|
46
|
+
onOpenSource(request: { defId: string; instancePath?: string }): void;
|
|
47
|
+
onOpenArtifact?(request: { defId: string; artifactId: string }): void;
|
|
48
|
+
/** A link's pair view asked for (`uhd-viewer:openPair`); only in a frame. */
|
|
49
|
+
onOpenPair?(request: { linkId: string; a: string; b: string }): void;
|
|
50
|
+
};
|
|
51
|
+
/** Hand host messages to the viewer (those that came before are delivered now). */
|
|
52
|
+
attach(viewer: { post(message: unknown): boolean }): void;
|
|
53
|
+
/** Resolve and send (or show) an openSource; resolves with the event. */
|
|
54
|
+
openSource(request: { defId: string; instancePath?: string }): Promise<OpenSourceEvent>;
|
|
55
|
+
close(): void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export declare const DEFAULT_HOST_ORIGINS: string[];
|
|
59
|
+
export declare function originAllowed(origin: string, allowed: readonly string[], self?: string): boolean;
|
|
60
|
+
export declare function createRelay(options: RelayOptions): Relay;
|
package/src/dev/relay.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dev page's relay (docs/specs/vscode-extension.md §3), served as
|
|
3
|
+
* /__protoboard/relay.js and run in the browser as it is (no build step).
|
|
4
|
+
*
|
|
5
|
+
* `protoboard dev` mounts the viewer in its own page. When that page is
|
|
6
|
+
* itself in a frame (the VS Code extension's webview frames it), the relay
|
|
7
|
+
* carries the viewer's host message channel through it:
|
|
8
|
+
*
|
|
9
|
+
* - host → viewer: messages from the parent window, at an allowed origin,
|
|
10
|
+
* go to the viewer's handle (`post`), which checks them;
|
|
11
|
+
* - viewer → host: the viewer's events are posted to the parent at its
|
|
12
|
+
* origin; `openSource` first asks the dev server where the definition and
|
|
13
|
+
* the instance are written (file, line, absolute path) and carries the
|
|
14
|
+
* answer as `source` and `instanceSource`.
|
|
15
|
+
*
|
|
16
|
+
* In a tab of its own the page has no host: `openSource` goes to `onLocal`
|
|
17
|
+
* (the page shows the place), and artifact files open as links.
|
|
18
|
+
*
|
|
19
|
+
* Plain JavaScript on purpose: the page loads it without a bundler. Its
|
|
20
|
+
* types are in relay.d.ts.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** Origins a page takes host messages from unless the server is told otherwise: its own, and VS Code webviews. */
|
|
24
|
+
export const DEFAULT_HOST_ORIGINS = ["self", "vscode-webview://*"];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* `origin` is one of `allowed`: an exact origin, `scheme://*`, `*`, or
|
|
28
|
+
* `self` (the page's own origin, given as `self`). An opaque origin ("null")
|
|
29
|
+
* matches only `*`.
|
|
30
|
+
* @param {string} origin
|
|
31
|
+
* @param {readonly string[]} allowed
|
|
32
|
+
* @param {string} [self]
|
|
33
|
+
*/
|
|
34
|
+
export function originAllowed(origin, allowed, self) {
|
|
35
|
+
return allowed.some((a) => {
|
|
36
|
+
if (a === "*") return true;
|
|
37
|
+
if (origin === "null") return false;
|
|
38
|
+
if (a === "self") return origin === self;
|
|
39
|
+
const wild = /^([a-z][a-z0-9+.-]*):\/\/\*$/i.exec(a);
|
|
40
|
+
if (wild) return origin.toLowerCase().startsWith(`${wild[1].toLowerCase()}://`);
|
|
41
|
+
return a.replace(/\/+$/, "") === origin;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const EVENTS = new Set(["uhd-viewer:ready", "uhd-viewer:selectionChanged", "uhd-viewer:openSource", "uhd-viewer:openArtifact", "uhd-viewer:openPair"]);
|
|
46
|
+
/** Events held while the parent's origin is unknown; past this only the latest are kept. */
|
|
47
|
+
const HELD = 64;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {import("./relay.js").RelayOptions} options
|
|
51
|
+
* @returns {import("./relay.js").Relay}
|
|
52
|
+
*/
|
|
53
|
+
export function createRelay(options) {
|
|
54
|
+
const win = options.window;
|
|
55
|
+
const self = win.location.origin;
|
|
56
|
+
const framed = Boolean(win.parent) && win.parent !== win;
|
|
57
|
+
const allowed = options.origins && options.origins.length ? options.origins : DEFAULT_HOST_ORIGINS;
|
|
58
|
+
/** @type {{ post(message: unknown): boolean } | undefined} */
|
|
59
|
+
let viewer;
|
|
60
|
+
/** @type {unknown[]} */
|
|
61
|
+
let waiting = [];
|
|
62
|
+
/** @type {unknown[]} */
|
|
63
|
+
let held = [];
|
|
64
|
+
let target = framed ? initialTarget() : undefined;
|
|
65
|
+
|
|
66
|
+
function initialTarget() {
|
|
67
|
+
const ancestors = win.location.ancestorOrigins;
|
|
68
|
+
let guess = ancestors && ancestors.length ? ancestors[0] : undefined;
|
|
69
|
+
if (!guess && win.document && win.document.referrer) {
|
|
70
|
+
try {
|
|
71
|
+
guess = new URL(win.document.referrer).origin;
|
|
72
|
+
} catch {
|
|
73
|
+
guess = undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (guess && guess !== "null" && originAllowed(guess, allowed, self)) return guess;
|
|
77
|
+
const exact = allowed.filter((a) => a !== "*" && !a.includes("*"));
|
|
78
|
+
if (allowed.length === 1 && exact.length === 1) return exact[0] === "self" ? self : exact[0];
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function flush() {
|
|
83
|
+
if (!target) return;
|
|
84
|
+
const out = held;
|
|
85
|
+
held = [];
|
|
86
|
+
for (const e of out) win.parent.postMessage(e, target);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** @param {unknown} event */
|
|
90
|
+
function post(event) {
|
|
91
|
+
if (!framed) return;
|
|
92
|
+
held.push(event);
|
|
93
|
+
if (held.length > HELD) held = held.slice(-HELD);
|
|
94
|
+
flush();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** @param {{ data: unknown, origin: string, source: unknown }} e */
|
|
98
|
+
function onMessage(e) {
|
|
99
|
+
if (e.source !== win.parent || !originAllowed(e.origin, allowed, self)) return;
|
|
100
|
+
const data = /** @type {{ type?: unknown }} */ (e.data);
|
|
101
|
+
if (!data || typeof data !== "object" || typeof data.type !== "string" || !data.type.startsWith("uhd-viewer:") || EVENTS.has(data.type)) return;
|
|
102
|
+
if (!target) {
|
|
103
|
+
target = e.origin === "null" ? "*" : e.origin;
|
|
104
|
+
flush();
|
|
105
|
+
}
|
|
106
|
+
if (viewer) viewer.post(data);
|
|
107
|
+
else waiting.push(data);
|
|
108
|
+
}
|
|
109
|
+
if (framed) win.addEventListener("message", onMessage);
|
|
110
|
+
|
|
111
|
+
/** @param {{ defId: string, instancePath?: string }} request */
|
|
112
|
+
async function openSource(request) {
|
|
113
|
+
let resolved;
|
|
114
|
+
try {
|
|
115
|
+
resolved = await options.resolveSource(request);
|
|
116
|
+
} catch {
|
|
117
|
+
resolved = undefined;
|
|
118
|
+
}
|
|
119
|
+
const event = { type: "uhd-viewer:openSource", defId: request.defId, ...(request.instancePath ? { instancePath: request.instancePath } : {}), ...(resolved && resolved.source ? { source: resolved.source } : {}), ...(resolved && resolved.instanceSource ? { instanceSource: resolved.instanceSource } : {}) };
|
|
120
|
+
if (framed) post(event);
|
|
121
|
+
else if (options.onLocal) options.onLocal(event);
|
|
122
|
+
return event;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
framed,
|
|
127
|
+
callbacks: {
|
|
128
|
+
onReady: () => post({ type: "uhd-viewer:ready" }),
|
|
129
|
+
onSelectionChanged: (selection) => post({ type: "uhd-viewer:selectionChanged", selection }),
|
|
130
|
+
onOpenSource: (request) => void openSource(request),
|
|
131
|
+
// without a host, artifact rows stay links
|
|
132
|
+
...(framed ? { onOpenArtifact: (request) => post({ type: "uhd-viewer:openArtifact", defId: request.defId, artifactId: request.artifactId }) } : {}),
|
|
133
|
+
// a link's pair view: the page opens its own tab, a host may open one of its own as well
|
|
134
|
+
...(framed ? { onOpenPair: (request) => post({ type: "uhd-viewer:openPair", linkId: request.linkId, a: request.a, b: request.b }) } : {}),
|
|
135
|
+
},
|
|
136
|
+
attach(handle) {
|
|
137
|
+
viewer = handle;
|
|
138
|
+
const queued = waiting;
|
|
139
|
+
waiting = [];
|
|
140
|
+
for (const m of queued) handle.post(m);
|
|
141
|
+
},
|
|
142
|
+
openSource,
|
|
143
|
+
close() {
|
|
144
|
+
if (framed) win.removeEventListener("message", onMessage);
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|