@launchsecure/launch-kit 0.0.27 → 0.0.29

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.
Files changed (48) hide show
  1. package/dist/beacon/beacon.mjs +1003 -440
  2. package/dist/beacon/beacon.mjs.map +1 -1
  3. package/dist/beacon/beacon.umd.js +45 -24
  4. package/dist/beacon/beacon.umd.js.map +1 -1
  5. package/dist/beacon/types/capture/events.d.ts +20 -0
  6. package/dist/beacon/types/capture/events.d.ts.map +1 -0
  7. package/dist/beacon/types/element.d.ts +1 -0
  8. package/dist/beacon/types/element.d.ts.map +1 -1
  9. package/dist/beacon/types/index.d.ts +2 -1
  10. package/dist/beacon/types/index.d.ts.map +1 -1
  11. package/dist/beacon/types/monitor/dom.d.ts +13 -0
  12. package/dist/beacon/types/monitor/dom.d.ts.map +1 -0
  13. package/dist/beacon/types/monitor/index.d.ts +19 -0
  14. package/dist/beacon/types/monitor/index.d.ts.map +1 -0
  15. package/dist/beacon/types/monitor/network.d.ts +12 -0
  16. package/dist/beacon/types/monitor/network.d.ts.map +1 -0
  17. package/dist/beacon/types/monitor/transport.d.ts +27 -0
  18. package/dist/beacon/types/monitor/transport.d.ts.map +1 -0
  19. package/dist/beacon/types/monitor/types.d.ts +117 -0
  20. package/dist/beacon/types/monitor/types.d.ts.map +1 -0
  21. package/dist/beacon/types/types.d.ts +10 -0
  22. package/dist/beacon/types/types.d.ts.map +1 -1
  23. package/dist/beacon/types/ui/drawer.d.ts +3 -1
  24. package/dist/beacon/types/ui/drawer.d.ts.map +1 -1
  25. package/dist/beacon/types/ui/monitor-panel.d.ts +19 -0
  26. package/dist/beacon/types/ui/monitor-panel.d.ts.map +1 -0
  27. package/dist/server/beacon-monitor-entry.js +353 -0
  28. package/dist/server/chart-serve.js +3 -1
  29. package/dist/server/cli.js +276 -218
  30. package/dist/server/course-entry.js +246 -0
  31. package/dist/server/graph-mcp-entry.js +35 -72
  32. package/dist/server/init-entry.js +1051 -122
  33. package/dist/server/orbit-entry.js +187 -24
  34. package/package.json +5 -3
  35. package/scaffolds/ls-marketplace/.claude-plugin/marketplace.json +15 -0
  36. package/scaffolds/ls-marketplace/plugins/kit/.claude-plugin/plugin.json +19 -0
  37. package/scaffolds/ls-marketplace/plugins/kit/commands/activate-beacon.md +216 -0
  38. package/scaffolds/ls-marketplace/plugins/kit/commands/activate-statusline.md +46 -0
  39. package/scaffolds/ls-marketplace/plugins/kit/commands/beacon-array.md +92 -0
  40. package/scaffolds/ls-marketplace/plugins/kit/commands/beacon-clear.md +68 -0
  41. package/scaffolds/ls-marketplace/plugins/kit/commands/beacon-pulse.md +80 -0
  42. package/scaffolds/ls-marketplace/plugins/kit/commands/beacon-scan.md +62 -0
  43. package/scaffolds/ls-marketplace/plugins/kit/commands/deactivate-statusline.md +34 -0
  44. package/scaffolds/ls-marketplace/plugins/kit/commands/show-mcp-status.md +109 -0
  45. package/scaffolds/ls-marketplace/plugins/kit/commands/standup.md +191 -0
  46. package/scaffolds/recall-hook/scripts/ensure-recall.sh +69 -0
  47. package/scaffolds/statusline/statusline-mcp.sh +192 -0
  48. package/scaffolds/statusline/statusline-wrapper.sh +50 -0
@@ -0,0 +1,353 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // src/server/beacon-monitor-entry.ts
5
+ var import_node_crypto = require("node:crypto");
6
+ var import_node_path2 = require("node:path");
7
+
8
+ // src/server/beacon/ndjson-writer.ts
9
+ var import_node_fs = require("node:fs");
10
+ var import_node_path = require("node:path");
11
+ function openNdjsonWriter(opts) {
12
+ (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(opts.path), { recursive: true });
13
+ const stream = (0, import_node_fs.createWriteStream)(opts.path, { flags: "a" });
14
+ let total = 0;
15
+ let closed = false;
16
+ stream.on("error", (err) => {
17
+ opts.onError?.(err);
18
+ });
19
+ return {
20
+ write(event) {
21
+ if (closed) return;
22
+ let line;
23
+ try {
24
+ line = JSON.stringify(event);
25
+ } catch (err) {
26
+ opts.onError?.(err instanceof Error ? err : new Error(String(err)));
27
+ return;
28
+ }
29
+ stream.write(line + "\n");
30
+ total += 1;
31
+ },
32
+ count() {
33
+ return total;
34
+ },
35
+ path() {
36
+ return opts.path;
37
+ },
38
+ close() {
39
+ return new Promise((resolveClose) => {
40
+ if (closed) {
41
+ resolveClose();
42
+ return;
43
+ }
44
+ closed = true;
45
+ stream.end(() => resolveClose());
46
+ });
47
+ }
48
+ };
49
+ }
50
+
51
+ // src/server/beacon/server.ts
52
+ var import_node_http = require("node:http");
53
+ var MAX_BODY_BYTES = 1048576;
54
+ var TOKEN_PATH_PREFIX = "/m/";
55
+ function writeCors(res, origin) {
56
+ res.setHeader("Access-Control-Allow-Origin", origin && origin !== "null" ? origin : "*");
57
+ res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
58
+ res.setHeader("Access-Control-Allow-Headers", "content-type");
59
+ res.setHeader("Access-Control-Max-Age", "600");
60
+ res.setHeader("Vary", "Origin");
61
+ }
62
+ function readBody(req) {
63
+ return new Promise((resolve2, reject) => {
64
+ const chunks = [];
65
+ let total = 0;
66
+ req.on("data", (chunk) => {
67
+ total += chunk.length;
68
+ if (total > MAX_BODY_BYTES) {
69
+ reject(new Error("payload too large"));
70
+ req.destroy();
71
+ return;
72
+ }
73
+ chunks.push(chunk);
74
+ });
75
+ req.on("end", () => resolve2(Buffer.concat(chunks).toString("utf8")));
76
+ req.on("error", (err) => reject(err));
77
+ });
78
+ }
79
+ function isMonitorBatchEnvelope(value) {
80
+ if (!value || typeof value !== "object") return false;
81
+ const v = value;
82
+ if (typeof v.sessionId !== "string") return false;
83
+ if (!Array.isArray(v.events)) return false;
84
+ if (!v.meta || typeof v.meta !== "object") return false;
85
+ const meta = v.meta;
86
+ if (typeof meta.url !== "string" || typeof meta.userAgent !== "string") return false;
87
+ return true;
88
+ }
89
+ function startBeaconServer(opts) {
90
+ return new Promise((resolveStart, rejectStart) => {
91
+ const server = (0, import_node_http.createServer)(async (req, res) => {
92
+ writeCors(res, req.headers.origin);
93
+ if (req.method === "OPTIONS") {
94
+ res.writeHead(204);
95
+ res.end();
96
+ return;
97
+ }
98
+ if (req.method !== "POST") {
99
+ res.writeHead(405, { "content-type": "text/plain" });
100
+ res.end("method not allowed");
101
+ return;
102
+ }
103
+ const url = req.url ?? "/";
104
+ const pathOnly = url.split("?")[0] ?? "/";
105
+ if (!pathOnly.startsWith(TOKEN_PATH_PREFIX)) {
106
+ res.writeHead(404, { "content-type": "text/plain" });
107
+ res.end("not found");
108
+ return;
109
+ }
110
+ const provided = pathOnly.slice(TOKEN_PATH_PREFIX.length).replace(/\/+$/, "");
111
+ if (provided !== opts.token) {
112
+ res.writeHead(403, { "content-type": "text/plain" });
113
+ res.end("bad token");
114
+ return;
115
+ }
116
+ let bodyText;
117
+ try {
118
+ bodyText = await readBody(req);
119
+ } catch (err) {
120
+ opts.onBadBatch?.(`body read failed: ${err.message}`, req);
121
+ res.writeHead(413, { "content-type": "text/plain" });
122
+ res.end("payload error");
123
+ return;
124
+ }
125
+ let parsed;
126
+ try {
127
+ parsed = JSON.parse(bodyText);
128
+ } catch {
129
+ opts.onBadBatch?.("invalid JSON", req);
130
+ res.writeHead(400, { "content-type": "text/plain" });
131
+ res.end("invalid json");
132
+ return;
133
+ }
134
+ if (!isMonitorBatchEnvelope(parsed)) {
135
+ opts.onBadBatch?.("schema mismatch", req);
136
+ res.writeHead(400, { "content-type": "text/plain" });
137
+ res.end("bad batch shape");
138
+ return;
139
+ }
140
+ try {
141
+ opts.onBatch(parsed, req);
142
+ } catch (err) {
143
+ opts.onServerError?.(err instanceof Error ? err : new Error(String(err)));
144
+ }
145
+ res.writeHead(204);
146
+ res.end();
147
+ });
148
+ server.on("error", (err) => {
149
+ opts.onServerError?.(err);
150
+ rejectStart(err);
151
+ });
152
+ server.listen(opts.port, opts.host, () => {
153
+ const addr = server.address();
154
+ const port = addr ? addr.port : opts.port;
155
+ const url = `http://${opts.host}:${port}/m/${opts.token}`;
156
+ resolveStart({
157
+ url,
158
+ port,
159
+ close() {
160
+ return new Promise((resolveClose) => {
161
+ server.close(() => resolveClose());
162
+ });
163
+ }
164
+ });
165
+ });
166
+ });
167
+ }
168
+
169
+ // src/server/beacon-monitor-entry.ts
170
+ function parseMonitorFlags(argv) {
171
+ const flags = { port: 9876, host: "127.0.0.1", quiet: false };
172
+ for (let i = 0; i < argv.length; i++) {
173
+ const a = argv[i];
174
+ if (a === "--port") {
175
+ const n = Number(argv[++i]);
176
+ if (!Number.isInteger(n) || n < 0 || n > 65535) {
177
+ die(`--port must be an integer in 0..65535 (got "${argv[i]}")`);
178
+ }
179
+ flags.port = n;
180
+ } else if (a === "--host") {
181
+ const v = argv[++i];
182
+ if (!v) die("--host requires a value");
183
+ flags.host = v;
184
+ } else if (a === "--out") {
185
+ const v = argv[++i];
186
+ if (!v) die("--out requires a path");
187
+ flags.out = v;
188
+ } else if (a === "--token") {
189
+ const v = argv[++i];
190
+ if (!v) die("--token requires a value");
191
+ if (!/^[A-Za-z0-9_-]+$/.test(v)) die("--token must be alphanumeric (plus _ or -)");
192
+ flags.token = v;
193
+ } else if (a === "--quiet") {
194
+ flags.quiet = true;
195
+ } else if (a === "--help" || a === "-h") {
196
+ printHelp();
197
+ process.exit(0);
198
+ } else if (a.startsWith("--")) {
199
+ die(`unknown flag: ${a}`);
200
+ } else {
201
+ die(`unexpected argument: ${a}`);
202
+ }
203
+ }
204
+ return flags;
205
+ }
206
+ function mintToken() {
207
+ return (0, import_node_crypto.randomBytes)(4).toString("hex");
208
+ }
209
+ async function runMonitor(argv) {
210
+ const flags = parseMonitorFlags(argv);
211
+ const token = flags.token ?? mintToken();
212
+ const projectRoot = process.cwd();
213
+ const outPath = flags.out ? (0, import_node_path2.resolve)(projectRoot, flags.out) : (0, import_node_path2.resolve)(projectRoot, ".launchsecure", `beacon-${token}.ndjson`);
214
+ const writer = openNdjsonWriter({
215
+ path: outPath,
216
+ onError: (err) => {
217
+ process.stderr.write(`
218
+ [launch-beacon] ndjson write error: ${err.message}
219
+ `);
220
+ }
221
+ });
222
+ const stats = {
223
+ events: 0,
224
+ badBatches: 0,
225
+ lastKind: "",
226
+ lastAt: 0
227
+ };
228
+ let server;
229
+ try {
230
+ server = await startBeaconServer({
231
+ port: flags.port,
232
+ host: flags.host,
233
+ token,
234
+ onBatch: (batch) => {
235
+ for (const raw of batch.events) {
236
+ writer.write(raw);
237
+ stats.events += 1;
238
+ const ev = raw;
239
+ if (typeof ev.kind === "string") stats.lastKind = ev.kind;
240
+ if (typeof ev.ts === "number") stats.lastAt = ev.ts;
241
+ }
242
+ draw();
243
+ },
244
+ onBadBatch: (reason) => {
245
+ stats.badBatches += 1;
246
+ process.stderr.write(`
247
+ [launch-beacon] bad batch: ${reason}
248
+ `);
249
+ },
250
+ onServerError: (err) => {
251
+ process.stderr.write(`
252
+ [launch-beacon] server error: ${err.message}
253
+ `);
254
+ }
255
+ });
256
+ } catch (err) {
257
+ const msg = err instanceof Error ? err.message : String(err);
258
+ if (msg.includes("EADDRINUSE")) {
259
+ die(`port ${flags.port} already in use \u2014 pass --port <n> to choose another`);
260
+ }
261
+ die(`failed to start: ${msg}`);
262
+ return;
263
+ }
264
+ process.stdout.write(`
265
+ launch-beacon monitor \u2014 listening
266
+ `);
267
+ process.stdout.write(` paste URL ${server.url}
268
+ `);
269
+ process.stdout.write(` writing ${outPath}
270
+ `);
271
+ process.stdout.write(` session ${token}
272
+
273
+ `);
274
+ let heartbeatTimer = null;
275
+ const draw = () => {
276
+ if (flags.quiet) return;
277
+ const since = stats.lastAt ? Math.max(0, Math.round((Date.now() - stats.lastAt) / 1e3)) : null;
278
+ const sinceStr = since === null ? "\u2014" : since < 5 ? "just now" : `${since}s ago`;
279
+ const lastStr = stats.lastKind ? `${stats.lastKind} (${sinceStr})` : "\u2014";
280
+ const badStr = stats.badBatches > 0 ? ` | bad: ${stats.badBatches}` : "";
281
+ const line = ` events: ${stats.events}${badStr} | last: ${lastStr}`;
282
+ process.stdout.write(`\r\x1B[K${line}`);
283
+ };
284
+ if (!flags.quiet) {
285
+ heartbeatTimer = setInterval(draw, 1e3);
286
+ draw();
287
+ }
288
+ const shutdown = async (signal) => {
289
+ if (heartbeatTimer) clearInterval(heartbeatTimer);
290
+ process.stdout.write(`
291
+
292
+ [launch-beacon] caught ${signal}, shutting down\u2026
293
+ `);
294
+ try {
295
+ await server.close();
296
+ } catch (err) {
297
+ process.stderr.write(`[launch-beacon] server close error: ${err.message}
298
+ `);
299
+ }
300
+ try {
301
+ await writer.close();
302
+ } catch (err) {
303
+ process.stderr.write(`[launch-beacon] writer close error: ${err.message}
304
+ `);
305
+ }
306
+ process.stdout.write(` events written: ${writer.count()}
307
+ `);
308
+ process.stdout.write(` file: ${outPath}
309
+ `);
310
+ process.exit(0);
311
+ };
312
+ process.on("SIGINT", () => void shutdown("SIGINT"));
313
+ process.on("SIGTERM", () => void shutdown("SIGTERM"));
314
+ }
315
+ function printHelp() {
316
+ process.stdout.write(
317
+ `launch-beacon
318
+
319
+ Usage:
320
+ launch-beacon monitor [flags] Start the HTTP receiver for beacon monitor batches
321
+
322
+ Flags for "monitor":
323
+ --port <n> port to listen on (default: 9876)
324
+ --host <host> host to bind to (default: 127.0.0.1)
325
+ --out <path> output NDJSON file (default: .launchsecure/beacon-<token>.ndjson)
326
+ --token <str> use this token instead of a random 8-char hex
327
+ --quiet suppress the live heartbeat line
328
+
329
+ The printed URL is what you paste into the beacon debug panel. Beacon POSTs
330
+ monitor batches there; this process writes each event as a JSON line to the
331
+ output file. Ctrl+C shuts down cleanly.
332
+ `
333
+ );
334
+ }
335
+ async function main() {
336
+ const argv = process.argv.slice(2);
337
+ const subcommand = argv[0];
338
+ if (!subcommand || subcommand === "monitor") {
339
+ await runMonitor(subcommand === "monitor" ? argv.slice(1) : argv);
340
+ return;
341
+ }
342
+ if (subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
343
+ printHelp();
344
+ return;
345
+ }
346
+ die(`unknown subcommand: ${subcommand}`);
347
+ }
348
+ function die(msg) {
349
+ process.stderr.write(`[launch-beacon] ${msg}
350
+ `);
351
+ process.exit(2);
352
+ }
353
+ void main();
@@ -6281,8 +6281,10 @@ async function startChartServer(opts = {}) {
6281
6281
  req.on("end", () => {
6282
6282
  try {
6283
6283
  const newConfig = JSON.parse(body);
6284
+ const existingConfig = loadConfig(reqRoot);
6285
+ const merged = { ...existingConfig, ...newConfig };
6284
6286
  const configPath = import_node_path24.default.join(reqRoot, LAUNCHCHART_CONFIG_FILE);
6285
- import_node_fs21.default.writeFileSync(configPath, JSON.stringify(newConfig, null, 2) + "\n", "utf-8");
6287
+ import_node_fs21.default.writeFileSync(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
6286
6288
  res.writeHead(200, { "Content-Type": "application/json" });
6287
6289
  res.end(JSON.stringify({ ok: true }));
6288
6290
  } catch (err) {