@omniaura/solid-pulse 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/dist/cli.js ADDED
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_PATH
4
+ } from "./chunk-4QA2G6S3.js";
5
+
6
+ // src/bridge/cli.ts
7
+ import { readFileSync, existsSync } from "fs";
8
+ import { dirname, join } from "path";
9
+ var HELP = `solid-pulse \u2014 agent-controllable devtools for SolidJS apps (every panel control is a command here)
10
+
11
+ solid-pulse status bridge + connected pages
12
+ solid-pulse commands the page's command contract (name, args, panel equivalent)
13
+ solid-pulse events [since=N] [kinds=dom,query] [limit=50]
14
+ solid-pulse tail [kinds=...] follow events live (SSE)
15
+ solid-pulse <command> [key=value ...] run any controller command, e.g.
16
+ solid-pulse features.set name=flash on=false
17
+ solid-pulse inspect.element selector='[data-testid=composer]'
18
+ solid-pulse scenario.select name=chat-stream-drop
19
+ solid-pulse bridge [--port 4567] standalone bridge server (non-Vite apps)
20
+
21
+ Options: --url <http://host:port[/__pulse]> (or SOLID_PULSE_URL; default auto-discovery via
22
+ node_modules/.vite/solid-pulse.json, then http://localhost:3000)
23
+ --client <id> --json --path </__pulse>
24
+ Values that parse as JSON (true, 3, [..], {..}, "..") are sent typed; others as strings.`;
25
+ function parseArgs(argv) {
26
+ const flags = {};
27
+ const positional = [];
28
+ const kv = {};
29
+ for (let i = 0; i < argv.length; i++) {
30
+ const a = argv[i];
31
+ if (a.startsWith("--")) {
32
+ const [k, inline] = a.slice(2).split("=", 2);
33
+ if (inline !== void 0) flags[k] = inline;
34
+ else if (argv[i + 1] !== void 0 && !argv[i + 1].startsWith("--") && !argv[i + 1].includes("=")) flags[k] = argv[++i];
35
+ else flags[k] = true;
36
+ } else if (a.includes("=") && positional.length > 0) {
37
+ const idx = a.indexOf("=");
38
+ kv[a.slice(0, idx)] = coerce(a.slice(idx + 1));
39
+ } else positional.push(a);
40
+ }
41
+ return { flags, positional, kv };
42
+ }
43
+ function coerce(v) {
44
+ if (/^(true|false|null|-?\d+(\.\d+)?|\[.*\]|\{.*\}|".*")$/s.test(v)) {
45
+ try {
46
+ return JSON.parse(v);
47
+ } catch {
48
+ return v;
49
+ }
50
+ }
51
+ return v;
52
+ }
53
+ function discoverUrl(flag, path) {
54
+ if (typeof flag === "string") return flag.replace(/\/$/, "").endsWith(path) ? flag.replace(/\/$/, "") : `${flag.replace(/\/$/, "")}${path}`;
55
+ if (process.env.SOLID_PULSE_URL) return process.env.SOLID_PULSE_URL.replace(/\/$/, "");
56
+ let dir = process.cwd();
57
+ for (let i = 0; i < 6; i++) {
58
+ const f = join(dir, "node_modules", ".vite", "solid-pulse.json");
59
+ if (existsSync(f)) {
60
+ try {
61
+ const data = JSON.parse(readFileSync(f, "utf8"));
62
+ if (data.url) return data.url.replace(/\/$/, "");
63
+ } catch {
64
+ }
65
+ }
66
+ const parent = dirname(dir);
67
+ if (parent === dir) break;
68
+ dir = parent;
69
+ }
70
+ return `http://localhost:3000${path}`;
71
+ }
72
+ async function api(base, route, init) {
73
+ let res;
74
+ try {
75
+ res = await fetch(`${base}/api${route}`, init);
76
+ } catch (err) {
77
+ throw new Error(`cannot reach bridge at ${base} \u2014 is the dev server (with the solid-pulse Vite plugin) or 'solid-pulse bridge' running? (${err.message})`);
78
+ }
79
+ const text = await res.text();
80
+ let body;
81
+ try {
82
+ body = JSON.parse(text);
83
+ } catch {
84
+ body = { error: text };
85
+ }
86
+ if (!res.ok) {
87
+ const msg = body.error ?? `${res.status} ${res.statusText}`;
88
+ throw new Error(msg);
89
+ }
90
+ return body;
91
+ }
92
+ function fmtEvent(e) {
93
+ const d = e.data;
94
+ const comp = e.component?.name ? ` <${e.component.name}>` : "";
95
+ let summary = "";
96
+ switch (true) {
97
+ case e.kind === "solid.flush":
98
+ summary = `${d.computations} computations ${JSON.stringify(d.byKind)} in ${JSON.stringify(d.components)}`;
99
+ break;
100
+ case e.kind.startsWith("solid.component"):
101
+ summary = `${d.name}${d.hydrated ? " (hydrated)" : ""}${d.gapMs !== void 0 ? ` gap=${d.gapMs}ms` : ""}${d.lifetimeMs !== void 0 ? ` lived=${d.lifetimeMs}ms` : ""}`;
102
+ break;
103
+ case e.kind === "dom.mutation": {
104
+ const s = d.summary ?? [];
105
+ summary = `${d.targets} targets: ${s.slice(0, 4).map((x) => `${x.tag}${x.id ? "#" + x.id : ""}${x.testId ? `[${x.testId}]` : ""}(${x.types.join("+")})`).join(" ")}${s.length > 4 ? " \u2026" : ""} [${d.attributedTo}]`;
106
+ break;
107
+ }
108
+ case e.kind === "dom.reattach": {
109
+ const el = d.element;
110
+ const resets = d.scrollReset.filter((r) => r.reset);
111
+ summary = `${el.tag}${el.testId ? `[${el.testId}]` : el.classes ? "." + el.classes.split(" ")[0] : ""} gap=${d.gapMs}ms${resets.length ? ` SCROLL RESET ${resets.map((r) => `${r.before}\u2192${r.after}`).join(",")}` : ""}${d.focusLost ? " FOCUS LOST" : ""}${d.suspenseInChain ? " (Suspense in chain)" : ""}`;
112
+ break;
113
+ }
114
+ case e.kind === "dom.detach": {
115
+ const el = d.element;
116
+ summary = `${el.tag}${el.testId ? `[${el.testId}]` : ""} scrollers=${d.scrollers.length}${d.hadFocus ? " had focus" : ""}`;
117
+ break;
118
+ }
119
+ case e.kind.startsWith("net.fetch"):
120
+ summary = `${d.method} ${d.url}${d.status !== void 0 ? ` \u2192 ${d.status} ${d.ms}ms${d.sse ? " (SSE)" : ""}` : ""}${d.message ? ` \u2717 ${d.message}` : ""}`;
121
+ break;
122
+ case e.kind.startsWith("net.ws"):
123
+ summary = `${d.url} ${d.state ?? d.dir ?? ""}${d.type ? ` type=${d.type}` : ""}${d.code !== void 0 ? ` code=${d.code}` : ""}`;
124
+ break;
125
+ case e.kind.startsWith("net.sse"):
126
+ summary = `${d.url} ${d.event ?? ""}${d.messages !== void 0 ? ` messages=${d.messages}` : ""}`;
127
+ break;
128
+ case (e.kind.startsWith("query") || e.kind.startsWith("mutation")):
129
+ summary = `${d.label ?? ""} ${d.role ?? d.trigger ?? d.action ?? ""}${d.ms !== void 0 && d.ms !== null ? ` ${d.ms}ms` : ""}${d.message ? ` \u2717 ${d.message}` : ""}`;
130
+ break;
131
+ case e.kind === "focus.lost":
132
+ summary = `${d.element.tag} \u2014 ${d.cause}`;
133
+ break;
134
+ default:
135
+ summary = JSON.stringify(d).slice(0, 160);
136
+ }
137
+ return `${String(e.seq).padStart(6)} ${(e.t / 1e3).toFixed(3).padStart(9)}s ${e.kind.padEnd(24)}${comp} ${summary}`;
138
+ }
139
+ async function main() {
140
+ const { flags, positional, kv } = parseArgs(process.argv.slice(2));
141
+ const path = typeof flags.path === "string" ? flags.path : DEFAULT_PATH;
142
+ const asJson = flags.json === true;
143
+ const client = typeof flags.client === "string" ? flags.client : void 0;
144
+ const cmd = positional[0];
145
+ if (!cmd || flags.help === true || cmd === "help") {
146
+ process.stdout.write(HELP + "\n");
147
+ return;
148
+ }
149
+ if (cmd === "bridge") {
150
+ const { startBridgeServer } = await import("./bridge.js");
151
+ const port = flags.port ? Number(flags.port) : 4567;
152
+ const host = typeof flags.host === "string" ? flags.host : "127.0.0.1";
153
+ const running = startBridgeServer({ port, host, path, allowRemote: flags["allow-remote"] === true, log: (m) => console.error(`[solid-pulse] ${m}`) });
154
+ const { url } = await running.ready;
155
+ console.error(`[solid-pulse] bridge listening at ${url}/api/status \u2014 page: initPulse({ bridge: "${url.replace(/^http/, "ws")}/ws" })`);
156
+ await new Promise(() => {
157
+ });
158
+ return;
159
+ }
160
+ const base = discoverUrl(flags.url, path);
161
+ const q = (extra) => {
162
+ const p = new URLSearchParams();
163
+ if (client) p.set("client", client);
164
+ for (const [k, v] of Object.entries(extra)) if (v !== void 0) p.set(k, String(v));
165
+ const s = p.toString();
166
+ return s ? `?${s}` : "";
167
+ };
168
+ const out = (v) => process.stdout.write((asJson ? JSON.stringify(v) : JSON.stringify(v, null, 2)) + "\n");
169
+ switch (cmd) {
170
+ case "status":
171
+ return out(await api(base, "/status"));
172
+ case "clients":
173
+ return out(await api(base, "/clients"));
174
+ case "commands": {
175
+ const r = await api(base, `/commands${q({})}`);
176
+ if (asJson) return out(r);
177
+ for (const c of r.commands) {
178
+ process.stdout.write(`${c.name.padEnd(22)} ${c.summary}
179
+ `);
180
+ if (c.args) for (const [k, v] of Object.entries(c.args)) process.stdout.write(`${"".padEnd(24)}${k}=\u2026 ${v}
181
+ `);
182
+ if (c.ui) process.stdout.write(`${"".padEnd(24)}panel: ${c.ui}
183
+ `);
184
+ }
185
+ return;
186
+ }
187
+ case "events": {
188
+ const r = await api(base, `/events${q({ since: kv.since, kinds: kv.kinds, limit: kv.limit ?? 50 })}`);
189
+ if (asJson) return out(r);
190
+ for (const e of r.events) process.stdout.write(fmtEvent(e) + "\n");
191
+ return;
192
+ }
193
+ case "tail": {
194
+ const res = await fetch(`${base}/api/events/stream${q({ kinds: kv.kinds })}`);
195
+ if (!res.ok || !res.body) throw new Error(`stream failed: ${res.status}`);
196
+ const reader = res.body.getReader();
197
+ const dec = new TextDecoder();
198
+ let buf = "";
199
+ for (; ; ) {
200
+ const { value, done } = await reader.read();
201
+ if (done) break;
202
+ buf += dec.decode(value, { stream: true });
203
+ let idx;
204
+ while ((idx = buf.indexOf("\n\n")) >= 0) {
205
+ const frame = buf.slice(0, idx);
206
+ buf = buf.slice(idx + 2);
207
+ const data = frame.split("\n").find((l) => l.startsWith("data: "));
208
+ if (!data) continue;
209
+ const e = JSON.parse(data.slice(6));
210
+ process.stdout.write((asJson ? JSON.stringify(e) : fmtEvent(e)) + "\n");
211
+ }
212
+ }
213
+ return;
214
+ }
215
+ case "run":
216
+ default: {
217
+ const name = cmd === "run" ? positional[1] : cmd;
218
+ if (!name) throw new Error("run needs a command name");
219
+ const r = await api(base, "/command", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ client, name, args: kv }) });
220
+ return out(r);
221
+ }
222
+ }
223
+ }
224
+ main().catch((err) => {
225
+ process.stderr.write(`solid-pulse: ${err instanceof Error ? err.message : String(err)}
226
+ `);
227
+ process.exit(1);
228
+ });
229
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bridge/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * solid-pulse CLI — the agent's hands. Every panel control is a command here.\n *\n * solid-pulse status bridge + connected pages\n * solid-pulse commands the page's command contract (name, args, panel equivalent)\n * solid-pulse events [since=N] [kinds=dom,query] [limit=50]\n * solid-pulse tail [kinds=...] follow events live (SSE)\n * solid-pulse <command> [key=value ...] run any controller command, e.g.\n * solid-pulse features.set name=flash on=false\n * solid-pulse inspect.element selector='[data-testid=composer]'\n * solid-pulse scenario.select name=chat-stream-drop\n * solid-pulse bridge [--port 4567] standalone bridge server (non-Vite apps)\n *\n * Options: --url <http://host:port[/__pulse]> (or SOLID_PULSE_URL; default auto-discovery\n * via node_modules/.vite/solid-pulse.json, then http://localhost:3000)\n * --client <id> --json --path </__pulse>\n * Values that parse as JSON (true, 3, [..], {..}, \"..\") are sent typed; others as strings.\n */\n\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { DEFAULT_PATH } from \"../core/protocol.js\";\n\nconst HELP = `solid-pulse — agent-controllable devtools for SolidJS apps (every panel control is a command here)\n\n solid-pulse status bridge + connected pages\n solid-pulse commands the page's command contract (name, args, panel equivalent)\n solid-pulse events [since=N] [kinds=dom,query] [limit=50]\n solid-pulse tail [kinds=...] follow events live (SSE)\n solid-pulse <command> [key=value ...] run any controller command, e.g.\n solid-pulse features.set name=flash on=false\n solid-pulse inspect.element selector='[data-testid=composer]'\n solid-pulse scenario.select name=chat-stream-drop\n solid-pulse bridge [--port 4567] standalone bridge server (non-Vite apps)\n\nOptions: --url <http://host:port[/__pulse]> (or SOLID_PULSE_URL; default auto-discovery via\n node_modules/.vite/solid-pulse.json, then http://localhost:3000)\n --client <id> --json --path </__pulse>\nValues that parse as JSON (true, 3, [..], {..}, \"..\") are sent typed; others as strings.`;\n\ninterface Parsed {\n flags: Record<string, string | boolean>;\n positional: string[];\n kv: Record<string, unknown>;\n}\n\nfunction parseArgs(argv: string[]): Parsed {\n const flags: Record<string, string | boolean> = {};\n const positional: string[] = [];\n const kv: Record<string, unknown> = {};\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i]!;\n if (a.startsWith(\"--\")) {\n const [k, inline] = a.slice(2).split(\"=\", 2) as [string, string | undefined];\n if (inline !== undefined) flags[k] = inline;\n else if (argv[i + 1] !== undefined && !argv[i + 1]!.startsWith(\"--\") && !argv[i + 1]!.includes(\"=\")) flags[k] = argv[++i]!;\n else flags[k] = true;\n } else if (a.includes(\"=\") && positional.length > 0) {\n const idx = a.indexOf(\"=\");\n kv[a.slice(0, idx)] = coerce(a.slice(idx + 1));\n } else positional.push(a);\n }\n return { flags, positional, kv };\n}\n\nfunction coerce(v: string): unknown {\n if (/^(true|false|null|-?\\d+(\\.\\d+)?|\\[.*\\]|\\{.*\\}|\".*\")$/s.test(v)) {\n try {\n return JSON.parse(v);\n } catch {\n return v;\n }\n }\n return v;\n}\n\nfunction discoverUrl(flag: string | boolean | undefined, path: string): string {\n if (typeof flag === \"string\") return flag.replace(/\\/$/, \"\").endsWith(path) ? flag.replace(/\\/$/, \"\") : `${flag.replace(/\\/$/, \"\")}${path}`;\n if (process.env.SOLID_PULSE_URL) return process.env.SOLID_PULSE_URL.replace(/\\/$/, \"\");\n let dir = process.cwd();\n for (let i = 0; i < 6; i++) {\n const f = join(dir, \"node_modules\", \".vite\", \"solid-pulse.json\");\n if (existsSync(f)) {\n try {\n const data = JSON.parse(readFileSync(f, \"utf8\")) as { url?: string };\n if (data.url) return data.url.replace(/\\/$/, \"\");\n } catch {\n // fall through\n }\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return `http://localhost:3000${path}`;\n}\n\nasync function api(base: string, route: string, init?: RequestInit): Promise<unknown> {\n let res: Response;\n try {\n res = await fetch(`${base}/api${route}`, init);\n } catch (err) {\n throw new Error(`cannot reach bridge at ${base} — is the dev server (with the solid-pulse Vite plugin) or 'solid-pulse bridge' running? (${(err as Error).message})`);\n }\n const text = await res.text();\n let body: unknown;\n try {\n body = JSON.parse(text);\n } catch {\n body = { error: text };\n }\n if (!res.ok) {\n const msg = (body as { error?: string }).error ?? `${res.status} ${res.statusText}`;\n throw new Error(msg);\n }\n return body;\n}\n\nfunction fmtEvent(e: { seq: number; t: number; kind: string; component?: { name: string } | null; data: Record<string, unknown> }): string {\n const d = e.data;\n const comp = e.component?.name ? ` <${e.component.name}>` : \"\";\n let summary = \"\";\n switch (true) {\n case e.kind === \"solid.flush\":\n summary = `${d.computations} computations ${JSON.stringify(d.byKind)} in ${JSON.stringify(d.components)}`;\n break;\n case e.kind.startsWith(\"solid.component\"):\n summary = `${d.name}${d.hydrated ? \" (hydrated)\" : \"\"}${d.gapMs !== undefined ? ` gap=${d.gapMs}ms` : \"\"}${d.lifetimeMs !== undefined ? ` lived=${d.lifetimeMs}ms` : \"\"}`;\n break;\n case e.kind === \"dom.mutation\": {\n const s = (d.summary as Array<{ tag: string; types: string[]; testId?: string; id?: string }>) ?? [];\n summary = `${d.targets} targets: ${s.slice(0, 4).map((x) => `${x.tag}${x.id ? \"#\" + x.id : \"\"}${x.testId ? `[${x.testId}]` : \"\"}(${x.types.join(\"+\")})`).join(\" \")}${s.length > 4 ? \" …\" : \"\"} [${d.attributedTo}]`;\n break;\n }\n case e.kind === \"dom.reattach\": {\n const el = d.element as { tag: string; testId?: string; classes?: string };\n const resets = (d.scrollReset as Array<{ reset: boolean; before: number; after: number }>).filter((r) => r.reset);\n summary = `${el.tag}${el.testId ? `[${el.testId}]` : el.classes ? \".\" + el.classes.split(\" \")[0] : \"\"} gap=${d.gapMs}ms${resets.length ? ` SCROLL RESET ${resets.map((r) => `${r.before}→${r.after}`).join(\",\")}` : \"\"}${d.focusLost ? \" FOCUS LOST\" : \"\"}${d.suspenseInChain ? \" (Suspense in chain)\" : \"\"}`;\n break;\n }\n case e.kind === \"dom.detach\": {\n const el = d.element as { tag: string; testId?: string };\n summary = `${el.tag}${el.testId ? `[${el.testId}]` : \"\"} scrollers=${(d.scrollers as unknown[]).length}${d.hadFocus ? \" had focus\" : \"\"}`;\n break;\n }\n case e.kind.startsWith(\"net.fetch\"):\n summary = `${d.method} ${d.url}${d.status !== undefined ? ` → ${d.status} ${d.ms}ms${d.sse ? \" (SSE)\" : \"\"}` : \"\"}${d.message ? ` ✗ ${d.message}` : \"\"}`;\n break;\n case e.kind.startsWith(\"net.ws\"):\n summary = `${d.url} ${d.state ?? d.dir ?? \"\"}${d.type ? ` type=${d.type}` : \"\"}${d.code !== undefined ? ` code=${d.code}` : \"\"}`;\n break;\n case e.kind.startsWith(\"net.sse\"):\n summary = `${d.url} ${d.event ?? \"\"}${d.messages !== undefined ? ` messages=${d.messages}` : \"\"}`;\n break;\n case e.kind.startsWith(\"query\") || e.kind.startsWith(\"mutation\"):\n summary = `${d.label ?? \"\"} ${d.role ?? d.trigger ?? d.action ?? \"\"}${d.ms !== undefined && d.ms !== null ? ` ${d.ms}ms` : \"\"}${d.message ? ` ✗ ${d.message}` : \"\"}`;\n break;\n case e.kind === \"focus.lost\":\n summary = `${(d.element as { tag: string }).tag} — ${d.cause}`;\n break;\n default:\n summary = JSON.stringify(d).slice(0, 160);\n }\n return `${String(e.seq).padStart(6)} ${(e.t / 1000).toFixed(3).padStart(9)}s ${e.kind.padEnd(24)}${comp} ${summary}`;\n}\n\nasync function main() {\n const { flags, positional, kv } = parseArgs(process.argv.slice(2));\n const path = typeof flags.path === \"string\" ? flags.path : DEFAULT_PATH;\n const asJson = flags.json === true;\n const client = typeof flags.client === \"string\" ? flags.client : undefined;\n const cmd = positional[0];\n\n if (!cmd || flags.help === true || cmd === \"help\") {\n process.stdout.write(HELP + \"\\n\");\n return;\n }\n\n if (cmd === \"bridge\") {\n const { startBridgeServer } = await import(\"./server.js\");\n const port = flags.port ? Number(flags.port) : 4567;\n const host = typeof flags.host === \"string\" ? flags.host : \"127.0.0.1\";\n const running = startBridgeServer({ port, host, path, allowRemote: flags[\"allow-remote\"] === true, log: (m) => console.error(`[solid-pulse] ${m}`) });\n const { url } = await running.ready;\n console.error(`[solid-pulse] bridge listening at ${url}/api/status — page: initPulse({ bridge: \"${url.replace(/^http/, \"ws\")}/ws\" })`);\n await new Promise(() => {});\n return;\n }\n\n const base = discoverUrl(flags.url, path);\n const q = (extra: Record<string, unknown>) => {\n const p = new URLSearchParams();\n if (client) p.set(\"client\", client);\n for (const [k, v] of Object.entries(extra)) if (v !== undefined) p.set(k, String(v));\n const s = p.toString();\n return s ? `?${s}` : \"\";\n };\n const out = (v: unknown) => process.stdout.write((asJson ? JSON.stringify(v) : JSON.stringify(v, null, 2)) + \"\\n\");\n\n switch (cmd) {\n case \"status\":\n return out(await api(base, \"/status\"));\n case \"clients\":\n return out(await api(base, \"/clients\"));\n case \"commands\": {\n const r = (await api(base, `/commands${q({})}`)) as { commands: Array<{ name: string; summary: string; args?: Record<string, string>; ui?: string }> };\n if (asJson) return out(r);\n for (const c of r.commands) {\n process.stdout.write(`${c.name.padEnd(22)} ${c.summary}\\n`);\n if (c.args) for (const [k, v] of Object.entries(c.args)) process.stdout.write(`${\"\".padEnd(24)}${k}=… ${v}\\n`);\n if (c.ui) process.stdout.write(`${\"\".padEnd(24)}panel: ${c.ui}\\n`);\n }\n return;\n }\n case \"events\": {\n const r = (await api(base, `/events${q({ since: kv.since, kinds: kv.kinds, limit: kv.limit ?? 50 })}`)) as { events: Parameters<typeof fmtEvent>[0][] };\n if (asJson) return out(r);\n for (const e of r.events) process.stdout.write(fmtEvent(e) + \"\\n\");\n return;\n }\n case \"tail\": {\n const res = await fetch(`${base}/api/events/stream${q({ kinds: kv.kinds })}`);\n if (!res.ok || !res.body) throw new Error(`stream failed: ${res.status}`);\n const reader = res.body.getReader();\n const dec = new TextDecoder();\n let buf = \"\";\n for (;;) {\n const { value, done } = await reader.read();\n if (done) break;\n buf += dec.decode(value, { stream: true });\n let idx: number;\n while ((idx = buf.indexOf(\"\\n\\n\")) >= 0) {\n const frame = buf.slice(0, idx);\n buf = buf.slice(idx + 2);\n const data = frame.split(\"\\n\").find((l) => l.startsWith(\"data: \"));\n if (!data) continue;\n const e = JSON.parse(data.slice(6)) as Parameters<typeof fmtEvent>[0];\n process.stdout.write((asJson ? JSON.stringify(e) : fmtEvent(e)) + \"\\n\");\n }\n }\n return;\n }\n case \"run\":\n default: {\n const name = cmd === \"run\" ? positional[1] : cmd;\n if (!name) throw new Error(\"run needs a command name\");\n const r = await api(base, \"/command\", { method: \"POST\", headers: { \"content-type\": \"application/json\" }, body: JSON.stringify({ client, name, args: kv }) });\n return out(r);\n }\n }\n}\n\nmain().catch((err) => {\n process.stderr.write(`solid-pulse: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Event model. Every observation the runtime makes is one PulseEvent with a
3
+ * precise `kind`. Kinds are deliberately specific: Solid has no "rerender", so
4
+ * we never report one. What actually happens is one of:
5
+ *
6
+ * solid.flush a reactive flush completed (n computations re-ran)
7
+ * solid.computation one memo/effect/render-effect re-ran (verbose mode)
8
+ * solid.component.mount a component function ran (fresh mount, or hydrate)
9
+ * solid.component.dispose
10
+ * solid.component.remount same-named component disposed + mounted in one flush
11
+ * solid.root a reactive root was created (multiple roots are fine)
12
+ * dom.mutation the DOM actually changed (childList/attr/text)
13
+ * dom.detach a subtree left the document (with scroll/focus state)
14
+ * dom.reattach the same node instance came back (Suspense flip etc.)
15
+ * focus.lost the focused element vanished from the document
16
+ * net.fetch.* fetch lifecycle (start/end/error), SSE-aware
17
+ * net.ws.* WebSocket lifecycle (open/message/close/error)
18
+ * net.sse.* EventSource lifecycle
19
+ * query.* Solid Query cache/observer events (adapter)
20
+ * mutation.* Solid Query mutation cache events (adapter)
21
+ * pulse.* runtime lifecycle / control-plane notes
22
+ */
23
+ type PulseEventKind = "solid.flush" | "solid.computation" | "solid.component.mount" | "solid.component.dispose" | "solid.component.remount" | "solid.root" | "dom.mutation" | "dom.detach" | "dom.reattach" | "focus.lost" | "net.fetch.start" | "net.fetch.end" | "net.fetch.error" | "net.ws.open" | "net.ws.message" | "net.ws.close" | "net.ws.error" | "net.sse.open" | "net.sse.message" | "net.sse.error" | "net.sse.close" | "query.added" | "query.removed" | "query.observe" | "query.unobserve" | "query.fetch.start" | "query.fetch.success" | "query.fetch.error" | "query.invalidate" | "query.update" | "mutation.start" | "mutation.success" | "mutation.error" | "pulse.note";
24
+ interface Rect {
25
+ x: number;
26
+ y: number;
27
+ w: number;
28
+ h: number;
29
+ }
30
+ interface ComponentRef {
31
+ id: number;
32
+ name: string;
33
+ /** Component ancestry, innermost first (names only). */
34
+ chain?: string[];
35
+ /** Source location if solid-grab's data-solid-source attribute was found. */
36
+ source?: string | null;
37
+ }
38
+ interface ElementRef {
39
+ tag: string;
40
+ id?: string;
41
+ testId?: string;
42
+ classes?: string;
43
+ /** Nearest `data-solid-component` ancestor name, if any. */
44
+ component?: string | null;
45
+ /** Nearest `data-solid-source` value, if any. */
46
+ source?: string | null;
47
+ rect?: Rect;
48
+ }
49
+ interface PulseEventBase {
50
+ /** Monotonic per-runtime sequence. */
51
+ seq: number;
52
+ /** performance.now() at capture. */
53
+ t: number;
54
+ /** Date.now() at capture (for cross-process correlation). */
55
+ wall: number;
56
+ kind: PulseEventKind;
57
+ /** Flush group the event belongs to, when attributable. */
58
+ flush?: number;
59
+ /** Component attribution, when known. */
60
+ component?: ComponentRef | null;
61
+ /** Free-form structured payload; shape depends on `kind`. */
62
+ data: Record<string, unknown>;
63
+ }
64
+ type PulseEvent = PulseEventBase;
65
+ declare const KIND_GROUPS: Record<string, PulseEventKind[]>;
66
+ declare const ALL_KINDS: PulseEventKind[];
67
+ /** Expand a kind filter: exact kinds, `group` names, or `prefix.*` globs. */
68
+ declare function expandKinds(filters: readonly string[]): Set<PulseEventKind>;
69
+
70
+ /** Fixed-capacity FIFO. Overwrites the oldest entry; never grows. */
71
+ declare class RingBuffer<T> {
72
+ readonly capacity: number;
73
+ private items;
74
+ private head;
75
+ private count;
76
+ /** Total number of pushes since creation (dropped + retained). */
77
+ pushed: number;
78
+ constructor(capacity: number);
79
+ get size(): number;
80
+ get dropped(): number;
81
+ push(item: T): void;
82
+ /** Oldest → newest. */
83
+ toArray(): T[];
84
+ clear(): void;
85
+ }
86
+
87
+ type Listener = (event: PulseEvent) => void;
88
+ interface Recording {
89
+ id: string;
90
+ startedAt: number;
91
+ startedWall: number;
92
+ stoppedAt: number | null;
93
+ events: PulseEvent[];
94
+ /** Hard cap; the recording stops itself when reached. */
95
+ limit: number;
96
+ }
97
+ /**
98
+ * Bounded event bus. Emission is synchronous and cheap: one ring-buffer push,
99
+ * one optional recording push, then listeners. Filters are applied by
100
+ * consumers (panel/bridge), not here, so the buffer stays a faithful log.
101
+ */
102
+ declare class EventBus {
103
+ readonly buffer: RingBuffer<PulseEvent>;
104
+ private listeners;
105
+ private seq;
106
+ private recording;
107
+ private recordings;
108
+ /** Kinds that are muted at the source (never buffered). */
109
+ muted: Set<PulseEventKind>;
110
+ paused: boolean;
111
+ constructor(capacity?: number);
112
+ get nextSeq(): number;
113
+ emit(kind: PulseEventKind, data: Record<string, unknown>, extra?: Partial<PulseEvent>): PulseEvent | null;
114
+ subscribe(listener: Listener): () => void;
115
+ /** Events with seq > since (oldest first), optionally filtered by kind. */
116
+ list(opts?: {
117
+ since?: number;
118
+ kinds?: Set<PulseEventKind> | null;
119
+ limit?: number;
120
+ }): PulseEvent[];
121
+ clear(): void;
122
+ startRecording(id?: string, limit?: number): Recording;
123
+ stopRecording(): Recording | null;
124
+ currentRecording(): Recording | null;
125
+ getRecording(id: string): Recording | null;
126
+ listRecordings(): {
127
+ id: string;
128
+ startedWall: number;
129
+ stoppedAt: number | null;
130
+ events: number;
131
+ active: boolean;
132
+ }[];
133
+ }
134
+
135
+ /** Runtime features that can be toggled by humans (panel) and agents (CLI) alike. */
136
+ type Feature = "solid" | "dom" | "flash" | "network" | "query" | "queryOverlay" | "verboseComputations" | "captureBodies";
137
+ declare const FEATURES: Feature[];
138
+ interface CommandSpec {
139
+ /** Dotted command name, e.g. `features.set`. Also the CLI verb. */
140
+ name: string;
141
+ summary: string;
142
+ /** Argument name → short description. */
143
+ args?: Record<string, string>;
144
+ /** Where the equivalent human control lives in the panel (parity doc). */
145
+ ui?: string;
146
+ }
147
+ type CommandHandler = (args: Record<string, unknown>) => unknown | Promise<unknown>;
148
+ type CommandResult = {
149
+ ok: true;
150
+ value: unknown;
151
+ } | {
152
+ ok: false;
153
+ error: string;
154
+ };
155
+ interface Filters {
156
+ /** Kind filters (exact kinds, groups, or `prefix.*`); empty = all. */
157
+ kinds: string[];
158
+ /** Case-insensitive substring on component name. */
159
+ component: string;
160
+ /** Case-insensitive substring on any string in `data` (URLs, keys). */
161
+ text: string;
162
+ }
163
+ type FeatureListener = (feature: Feature, on: boolean) => void;
164
+ /**
165
+ * The single command surface. The panel's buttons, the bridge's HTTP API and
166
+ * the CLI all call `run()`, so parity between human and agent control is a
167
+ * property of the design rather than a checklist. `describe()` is the
168
+ * machine-readable contract the CLI prints and the parity test asserts on.
169
+ */
170
+ declare class PulseController {
171
+ readonly bus: EventBus;
172
+ private commands;
173
+ private features;
174
+ private featureListeners;
175
+ filters: Filters;
176
+ private filterListeners;
177
+ readonly startedWall: number;
178
+ constructor(bus?: EventBus, initial?: Partial<Record<Feature, boolean>>);
179
+ isOn(feature: Feature): boolean;
180
+ setFeature(feature: Feature, on: boolean): void;
181
+ onFeature(listener: FeatureListener): () => boolean;
182
+ snapshotFeatures(): {
183
+ solid: boolean;
184
+ dom: boolean;
185
+ flash: boolean;
186
+ network: boolean;
187
+ query: boolean;
188
+ queryOverlay: boolean;
189
+ verboseComputations: boolean;
190
+ captureBodies: boolean;
191
+ };
192
+ setFilters(next: Partial<Filters>): void;
193
+ onFilters(listener: (f: Filters) => void): () => boolean;
194
+ matchesFilters(e: PulseEvent, f?: Filters): boolean;
195
+ /** Buffered events after `since`, through the active filters. */
196
+ events(opts?: {
197
+ since?: number;
198
+ limit?: number;
199
+ kinds?: string[];
200
+ raw?: boolean;
201
+ }): PulseEventBase[];
202
+ register(spec: CommandSpec, handler: CommandHandler): void;
203
+ unregister(name: string): void;
204
+ has(name: string): boolean;
205
+ describe(): CommandSpec[];
206
+ run(name: string, args?: Record<string, unknown>): Promise<CommandResult>;
207
+ private registerCore;
208
+ }
209
+
210
+ export { ALL_KINDS as A, type CommandSpec as C, type ElementRef as E, FEATURES as F, KIND_GROUPS as K, type Listener as L, type PulseEvent as P, type Recording as R, type CommandResult as a, type CommandHandler as b, type ComponentRef as c, EventBus as d, type Feature as e, type Filters as f, PulseController as g, type PulseEventBase as h, type PulseEventKind as i, type Rect as j, RingBuffer as k, expandKinds as l };
package/dist/core.d.ts ADDED
@@ -0,0 +1,76 @@
1
+ import { P as PulseEvent, C as CommandSpec, a as CommandResult } from './controller-3akN6Qi0.js';
2
+ export { A as ALL_KINDS, b as CommandHandler, c as ComponentRef, E as ElementRef, d as EventBus, F as FEATURES, e as Feature, f as Filters, K as KIND_GROUPS, L as Listener, g as PulseController, h as PulseEventBase, i as PulseEventKind, R as Recording, j as Rect, k as RingBuffer, l as expandKinds } from './controller-3akN6Qi0.js';
3
+
4
+ /**
5
+ * Redaction. Devtools traffic leaves the page (bridge, CLI, exports), so
6
+ * anything that looks like a credential is scrubbed before it becomes an event.
7
+ * Bodies are never captured unless `captureBodies` is turned on explicitly.
8
+ */
9
+ declare const REDACTED = "[redacted]";
10
+ declare function redactUrl(input: string): string;
11
+ declare function redactText(text: string): string;
12
+ declare function redactHeaders(headers: Iterable<[string, string]>): Record<string, string>;
13
+ /** Best-effort key redaction inside small structured payloads. */
14
+ declare function redactValue<T>(value: T, depth?: number): T;
15
+
16
+ /**
17
+ * Bridge protocol (JSON over WebSocket) between a page runtime and the bridge
18
+ * server, and the HTTP shape the server exposes to CLIs and agents.
19
+ *
20
+ * Page → server
21
+ * hello first frame; identifies the tab
22
+ * events batched PulseEvents (≤ 50 ms coalescing)
23
+ * result reply to a `command`
24
+ * Server → page
25
+ * command run a controller command; page answers with `result`
26
+ * welcome ack of hello with the server-assigned client id
27
+ */
28
+
29
+ declare const PROTOCOL_VERSION = 1;
30
+ declare const DEFAULT_PATH = "/__pulse";
31
+ interface HelloFrame {
32
+ type: "hello";
33
+ protocol: number;
34
+ clientId: string;
35
+ url: string;
36
+ title: string;
37
+ userAgent: string;
38
+ commands: CommandSpec[];
39
+ startedWall: number;
40
+ }
41
+ interface EventsFrame {
42
+ type: "events";
43
+ events: PulseEvent[];
44
+ }
45
+ interface ResultFrame {
46
+ type: "result";
47
+ id: string;
48
+ result: CommandResult;
49
+ }
50
+ interface CommandFrame {
51
+ type: "command";
52
+ id: string;
53
+ name: string;
54
+ args: Record<string, unknown>;
55
+ }
56
+ interface WelcomeFrame {
57
+ type: "welcome";
58
+ clientId: string;
59
+ protocol: number;
60
+ }
61
+ type PageFrame = HelloFrame | EventsFrame | ResultFrame;
62
+ type ServerFrame = CommandFrame | WelcomeFrame;
63
+ interface ClientSummary {
64
+ clientId: string;
65
+ url: string;
66
+ title: string;
67
+ userAgent: string;
68
+ connectedWall: number;
69
+ lastSeenWall: number;
70
+ events: number;
71
+ commands: number;
72
+ }
73
+ declare function isPageFrame(value: unknown): value is PageFrame;
74
+ declare function isServerFrame(value: unknown): value is ServerFrame;
75
+
76
+ export { type ClientSummary, type CommandFrame, CommandResult, CommandSpec, DEFAULT_PATH, type EventsFrame, type HelloFrame, PROTOCOL_VERSION, type PageFrame, PulseEvent, REDACTED, type ResultFrame, type ServerFrame, type WelcomeFrame, isPageFrame, isServerFrame, redactHeaders, redactText, redactUrl, redactValue };
package/dist/core.js ADDED
@@ -0,0 +1,41 @@
1
+ import {
2
+ DEFAULT_PATH,
3
+ PROTOCOL_VERSION,
4
+ isPageFrame,
5
+ isServerFrame
6
+ } from "./chunk-WIMCBTHZ.js";
7
+ import {
8
+ REDACTED,
9
+ redactHeaders,
10
+ redactText,
11
+ redactUrl,
12
+ redactValue
13
+ } from "./chunk-5FYH2KEZ.js";
14
+ import {
15
+ ALL_KINDS,
16
+ EventBus,
17
+ FEATURES,
18
+ KIND_GROUPS,
19
+ PulseController,
20
+ RingBuffer,
21
+ expandKinds
22
+ } from "./chunk-C72EYM65.js";
23
+ export {
24
+ ALL_KINDS,
25
+ DEFAULT_PATH,
26
+ EventBus,
27
+ FEATURES,
28
+ KIND_GROUPS,
29
+ PROTOCOL_VERSION,
30
+ PulseController,
31
+ REDACTED,
32
+ RingBuffer,
33
+ expandKinds,
34
+ isPageFrame,
35
+ isServerFrame,
36
+ redactHeaders,
37
+ redactText,
38
+ redactUrl,
39
+ redactValue
40
+ };
41
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}