@rubytech/create-realagent 1.0.686 → 1.0.688

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.
@@ -1,18 +1,29 @@
1
1
  import {
2
+ Hono,
3
+ LOG_DIR,
4
+ actionLogPath,
2
5
  canAccessAdmin,
6
+ clientIpMiddleware,
3
7
  describeRemoteSession,
8
+ getRequestListener,
9
+ isActionActive,
10
+ isKnownAction,
11
+ launchAction,
4
12
  newCorrId,
5
13
  parseCookieValue,
14
+ primeSudo,
15
+ requireAdminCookie,
6
16
  sanitizeClientCorrId,
17
+ streamActionEvents,
7
18
  vncLog
8
- } from "./chunk-3RBKKDHC.js";
19
+ } from "./chunk-ZL2A4ROK.js";
9
20
 
10
21
  // server/edge.ts
11
22
  import { createServer, request as httpRequest } from "http";
12
23
  import { createConnection as createConnection2 } from "net";
13
- import { readFileSync, existsSync, watchFile } from "fs";
24
+ import { readFileSync as readFileSync3, existsSync as existsSync4, watchFile } from "fs";
14
25
  import { homedir } from "os";
15
- import { join } from "path";
26
+ import { join as join3 } from "path";
16
27
 
17
28
  // server/ws-proxy.ts
18
29
  import { createConnection } from "net";
@@ -275,23 +286,402 @@ Content-Length: 0\r
275
286
  socket.destroy();
276
287
  }
277
288
 
289
+ // server/routes/admin/actions/run.ts
290
+ var app = new Hono();
291
+ app.post("/:name", async (c) => {
292
+ const name = c.req.param("name");
293
+ if (!isKnownAction(name)) {
294
+ return c.json({ error: `unknown action: ${name}` }, 400);
295
+ }
296
+ const actionName = name;
297
+ if (await isActionActive(actionName)) {
298
+ return c.json({ error: `action ${actionName} is already running`, code: "already-active" }, 409);
299
+ }
300
+ let body = {};
301
+ try {
302
+ body = await c.req.json();
303
+ } catch {
304
+ }
305
+ const launchArgs = {};
306
+ if (Array.isArray(body.positional)) {
307
+ if (!body.positional.every((x) => typeof x === "string")) {
308
+ return c.json({ error: "positional args must be strings" }, 400);
309
+ }
310
+ launchArgs.positional = body.positional;
311
+ }
312
+ if (typeof body.sudoPassword === "string" && body.sudoPassword) {
313
+ launchArgs.sudoPassword = body.sudoPassword;
314
+ }
315
+ try {
316
+ const result = await launchAction(actionName, launchArgs);
317
+ return c.json({ ok: true, actionId: result.actionId, unit: result.unit, logPath: result.logPath });
318
+ } catch (err) {
319
+ const message = err instanceof Error ? err.message : String(err);
320
+ console.error(`[admin/actions/run] launch failed action=${actionName}: ${message}`);
321
+ return c.json({ error: `launch failed: ${message}` }, 500);
322
+ }
323
+ });
324
+ var run_default = app;
325
+
326
+ // node_modules/hono/dist/utils/stream.js
327
+ var StreamingApi = class {
328
+ writer;
329
+ encoder;
330
+ writable;
331
+ abortSubscribers = [];
332
+ responseReadable;
333
+ /**
334
+ * Whether the stream has been aborted.
335
+ */
336
+ aborted = false;
337
+ /**
338
+ * Whether the stream has been closed normally.
339
+ */
340
+ closed = false;
341
+ constructor(writable, _readable) {
342
+ this.writable = writable;
343
+ this.writer = writable.getWriter();
344
+ this.encoder = new TextEncoder();
345
+ const reader = _readable.getReader();
346
+ this.abortSubscribers.push(async () => {
347
+ await reader.cancel();
348
+ });
349
+ this.responseReadable = new ReadableStream({
350
+ async pull(controller) {
351
+ const { done, value } = await reader.read();
352
+ done ? controller.close() : controller.enqueue(value);
353
+ },
354
+ cancel: () => {
355
+ this.abort();
356
+ }
357
+ });
358
+ }
359
+ async write(input) {
360
+ try {
361
+ if (typeof input === "string") {
362
+ input = this.encoder.encode(input);
363
+ }
364
+ await this.writer.write(input);
365
+ } catch {
366
+ }
367
+ return this;
368
+ }
369
+ async writeln(input) {
370
+ await this.write(input + "\n");
371
+ return this;
372
+ }
373
+ sleep(ms) {
374
+ return new Promise((res) => setTimeout(res, ms));
375
+ }
376
+ async close() {
377
+ try {
378
+ await this.writer.close();
379
+ } catch {
380
+ }
381
+ this.closed = true;
382
+ }
383
+ async pipe(body) {
384
+ this.writer.releaseLock();
385
+ await body.pipeTo(this.writable, { preventClose: true });
386
+ this.writer = this.writable.getWriter();
387
+ }
388
+ onAbort(listener) {
389
+ this.abortSubscribers.push(listener);
390
+ }
391
+ /**
392
+ * Abort the stream.
393
+ * You can call this method when stream is aborted by external event.
394
+ */
395
+ abort() {
396
+ if (!this.aborted) {
397
+ this.aborted = true;
398
+ this.abortSubscribers.forEach((subscriber) => subscriber());
399
+ }
400
+ }
401
+ };
402
+
403
+ // node_modules/hono/dist/helper/streaming/utils.js
404
+ var isOldBunVersion = () => {
405
+ const version = typeof Bun !== "undefined" ? Bun.version : void 0;
406
+ if (version === void 0) {
407
+ return false;
408
+ }
409
+ const result = version.startsWith("1.1") || version.startsWith("1.0") || version.startsWith("0.");
410
+ isOldBunVersion = () => result;
411
+ return result;
412
+ };
413
+
414
+ // node_modules/hono/dist/helper/streaming/stream.js
415
+ var contextStash = /* @__PURE__ */ new WeakMap();
416
+ var stream = (c, cb, onError) => {
417
+ const { readable, writable } = new TransformStream();
418
+ const stream2 = new StreamingApi(writable, readable);
419
+ if (isOldBunVersion()) {
420
+ c.req.raw.signal.addEventListener("abort", () => {
421
+ if (!stream2.closed) {
422
+ stream2.abort();
423
+ }
424
+ });
425
+ }
426
+ contextStash.set(stream2.responseReadable, c);
427
+ (async () => {
428
+ try {
429
+ await cb(stream2);
430
+ } catch (e) {
431
+ if (e === void 0) {
432
+ } else if (e instanceof Error && onError) {
433
+ await onError(e, stream2);
434
+ } else {
435
+ console.error(e);
436
+ }
437
+ } finally {
438
+ stream2.close();
439
+ }
440
+ })();
441
+ return c.newResponse(stream2.responseReadable);
442
+ };
443
+
444
+ // server/routes/admin/actions/stream.ts
445
+ import { existsSync } from "fs";
446
+ var app2 = new Hono();
447
+ var ACTION_ID_RE = /^[a-z0-9-]+-[a-z0-9]+-[a-f0-9]{8}$/;
448
+ app2.get("/:id/stream", async (c) => {
449
+ const id = c.req.param("id");
450
+ if (!ACTION_ID_RE.test(id)) {
451
+ return c.json({ error: "invalid action id" }, 400);
452
+ }
453
+ const logPath = actionLogPath(id);
454
+ if (!existsSync(logPath)) {
455
+ return c.json({ error: "action not found (log missing)" }, 404);
456
+ }
457
+ const lastEventId = c.req.header("Last-Event-ID") ?? c.req.query("from") ?? "";
458
+ const fromByteOffset = lastEventId ? Math.max(0, Number(lastEventId) || 0) : 0;
459
+ const unit = `maxy-action-${id}`;
460
+ const abort = new AbortController();
461
+ c.req.raw.signal.addEventListener("abort", () => abort.abort());
462
+ return stream(c, async (s) => {
463
+ s.onAbort(() => abort.abort());
464
+ c.header("Content-Type", "text/event-stream");
465
+ c.header("Cache-Control", "no-cache, no-transform");
466
+ c.header("Connection", "keep-alive");
467
+ await s.write(": ok\n\n");
468
+ try {
469
+ for await (const ev of streamActionEvents({ actionId: id, unit, fromByteOffset, signal: abort.signal })) {
470
+ const id_ = ev.type === "line" ? String(ev.byteOffset) : "";
471
+ const payload = JSON.stringify(ev);
472
+ const frame = `${id_ ? `id: ${id_}
473
+ ` : ""}event: ${ev.type}
474
+ data: ${payload}
475
+
476
+ `;
477
+ await s.write(frame);
478
+ }
479
+ } catch (err) {
480
+ console.error(`[admin/actions/stream] action=${id} error: ${err instanceof Error ? err.message : err}`);
481
+ }
482
+ });
483
+ });
484
+ var stream_default = app2;
485
+
486
+ // server/routes/admin/actions/sudo-prime.ts
487
+ var app3 = new Hono();
488
+ app3.post("/", async (c) => {
489
+ let body = {};
490
+ try {
491
+ body = await c.req.json();
492
+ } catch {
493
+ }
494
+ if (typeof body.password !== "string" || !body.password) {
495
+ return c.json({ error: "password required" }, 400);
496
+ }
497
+ const clientIp = c.var.clientIp ?? "unknown";
498
+ const result = await primeSudo(body.password, clientIp);
499
+ switch (result) {
500
+ case "ok":
501
+ return c.body(null, 204);
502
+ case "invalid":
503
+ return c.json({ error: "invalid password" }, 401);
504
+ case "limited":
505
+ return c.json({ error: "too many attempts; wait a minute" }, 429);
506
+ case "error":
507
+ return c.json({ error: "sudo prime failed \u2014 check server logs" }, 500);
508
+ }
509
+ });
510
+ var sudo_prime_default = app3;
511
+
512
+ // server/routes/admin/actions/index.ts
513
+ var app4 = new Hono();
514
+ app4.route("/sudo-prime", sudo_prime_default);
515
+ app4.route("/", stream_default);
516
+ app4.route("/", run_default);
517
+ var actions_default = app4;
518
+
519
+ // server/routes/admin/version.ts
520
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
521
+ import { resolve, join as join2 } from "path";
522
+
523
+ // server/lib/client-error-count.ts
524
+ import { existsSync as existsSync2, readFileSync, statSync, openSync, readSync, closeSync } from "fs";
525
+ import { join } from "path";
526
+ var CLIENT_ERRORS_LOG = join(LOG_DIR, "client-errors.log");
527
+ var DAY_MS = 24 * 60 * 60 * 1e3;
528
+ var TAIL_BYTES = 256 * 1024;
529
+ function countClientErrors24h() {
530
+ if (!existsSync2(CLIENT_ERRORS_LOG)) return 0;
531
+ let tail;
532
+ try {
533
+ const size = statSync(CLIENT_ERRORS_LOG).size;
534
+ if (size <= TAIL_BYTES) {
535
+ tail = readFileSync(CLIENT_ERRORS_LOG, "utf-8");
536
+ } else {
537
+ const fd = openSync(CLIENT_ERRORS_LOG, "r");
538
+ try {
539
+ const buf = Buffer.alloc(TAIL_BYTES);
540
+ readSync(fd, buf, 0, TAIL_BYTES, size - TAIL_BYTES);
541
+ const text = buf.toString("utf-8");
542
+ const nl = text.indexOf("\n");
543
+ tail = nl === -1 ? "" : text.slice(nl + 1);
544
+ } finally {
545
+ closeSync(fd);
546
+ }
547
+ }
548
+ } catch (err) {
549
+ console.error(`[client-error-count] read failed: ${err instanceof Error ? err.message : String(err)}`);
550
+ return 0;
551
+ }
552
+ const cutoff = Date.now() - DAY_MS;
553
+ let count = 0;
554
+ for (const line of tail.split("\n")) {
555
+ if (!line) continue;
556
+ try {
557
+ const entry = JSON.parse(line);
558
+ if (typeof entry.ts !== "string") continue;
559
+ const ms = Date.parse(entry.ts);
560
+ if (Number.isFinite(ms) && ms >= cutoff) count++;
561
+ } catch {
562
+ }
563
+ }
564
+ return count;
565
+ }
566
+
567
+ // server/routes/admin/version.ts
568
+ var PLATFORM_ROOT = process.env.MAXY_PLATFORM_ROOT ?? resolve(process.cwd(), "..");
569
+ var brandHostname = "maxy";
570
+ var brandNpmPackage = "@rubytech/create-maxy";
571
+ var brandJsonPath = join2(PLATFORM_ROOT, "config", "brand.json");
572
+ if (existsSync3(brandJsonPath)) {
573
+ try {
574
+ const brand = JSON.parse(readFileSync2(brandJsonPath, "utf-8"));
575
+ if (brand.hostname) brandHostname = brand.hostname;
576
+ if (brand.npm?.packageName) brandNpmPackage = brand.npm.packageName;
577
+ } catch {
578
+ }
579
+ }
580
+ var VERSION_FILE = resolve(PLATFORM_ROOT, `config/.${brandHostname}-version`);
581
+ var NPM_PACKAGE = brandNpmPackage;
582
+ var REGISTRY_URL = `https://registry.npmjs.org/${NPM_PACKAGE}/latest`;
583
+ var FETCH_TIMEOUT_MS = 5e3;
584
+ function readInstalled() {
585
+ if (!existsSync3(VERSION_FILE)) return "unknown";
586
+ const content = readFileSync2(VERSION_FILE, "utf-8").trim();
587
+ return content || "unknown";
588
+ }
589
+ async function fetchLatest() {
590
+ try {
591
+ const controller = new AbortController();
592
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
593
+ const res = await fetch(REGISTRY_URL, { signal: controller.signal });
594
+ clearTimeout(timer);
595
+ if (!res.ok) {
596
+ console.error(`[admin/version] npm registry returned ${res.status}`);
597
+ return null;
598
+ }
599
+ const data = await res.json();
600
+ const version = data?.version;
601
+ if (typeof version !== "string") {
602
+ console.error("[admin/version] npm registry response missing version field");
603
+ return null;
604
+ }
605
+ return version;
606
+ } catch (err) {
607
+ console.error(`[admin/version] npm registry fetch failed: ${err}`);
608
+ return null;
609
+ }
610
+ }
611
+ function isNewer(latest, installed) {
612
+ const parseSemver = (v) => v.replace(/^v/, "").split(".").map(Number);
613
+ const l = parseSemver(latest);
614
+ const i = parseSemver(installed);
615
+ for (let idx = 0; idx < 3; idx++) {
616
+ const lp = l[idx] ?? 0;
617
+ const ip = i[idx] ?? 0;
618
+ if (Number.isNaN(lp) || Number.isNaN(ip)) return false;
619
+ if (lp > ip) return true;
620
+ if (lp < ip) return false;
621
+ }
622
+ return false;
623
+ }
624
+ var app5 = new Hono();
625
+ app5.get("/", async (c) => {
626
+ const installed = readInstalled();
627
+ const latest = await fetchLatest();
628
+ const updateAvailable = installed !== "unknown" && latest !== null && isNewer(latest, installed);
629
+ const outcome = installed === "unknown" ? "stale-version-file" : latest === null ? "fetch-failed" : "ok";
630
+ const clientErrors24h = countClientErrors24h();
631
+ console.log(
632
+ `[admin/version] outcome=${outcome} installed=${installed} latest=${latest ?? "null"} updateAvailable=${updateAvailable} clientErrors24h=${clientErrors24h}`
633
+ );
634
+ return c.json({ installed, latest, updateAvailable, clientErrors24h });
635
+ });
636
+ app5.post("/alert-surfaced", async (c) => {
637
+ let installed = "unknown";
638
+ let latest = null;
639
+ try {
640
+ const body = await c.req.json();
641
+ if (typeof body?.installed === "string") installed = body.installed;
642
+ if (typeof body?.latest === "string") latest = body.latest;
643
+ } catch (err) {
644
+ console.error(`[admin/version] alert-surfaced body parse failed: ${err}`);
645
+ }
646
+ console.log(`[admin/version] alert-surfaced installed=${installed} latest=${latest ?? "null"}`);
647
+ return c.json({ ok: true });
648
+ });
649
+ var version_default = app5;
650
+
651
+ // server/edge-admin.ts
652
+ function createEdgeAdminApp(opts) {
653
+ const app6 = new Hono();
654
+ app6.use("*", clientIpMiddleware);
655
+ app6.use("*", async (c, next) => {
656
+ const start = Date.now();
657
+ await next();
658
+ console.log(
659
+ `[edge-admin] method=${c.req.method} path=${c.req.path} status=${c.res.status} duration_ms=${Date.now() - start}`
660
+ );
661
+ });
662
+ app6.use("*", requireAdminCookie(opts.isPublicHost));
663
+ app6.route("/api/admin/actions", actions_default);
664
+ app6.route("/api/admin/version", version_default);
665
+ return app6;
666
+ }
667
+
278
668
  // server/edge.ts
279
- var PLATFORM_ROOT = process.env.MAXY_PLATFORM_ROOT || "";
280
- var BRAND_JSON_PATH = PLATFORM_ROOT ? join(PLATFORM_ROOT, "config", "brand.json") : "";
669
+ var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT || "";
670
+ var BRAND_JSON_PATH = PLATFORM_ROOT2 ? join3(PLATFORM_ROOT2, "config", "brand.json") : "";
281
671
  var BRAND = { configDir: ".maxy" };
282
- if (BRAND_JSON_PATH && existsSync(BRAND_JSON_PATH)) {
672
+ if (BRAND_JSON_PATH && existsSync4(BRAND_JSON_PATH)) {
283
673
  try {
284
- const parsed = JSON.parse(readFileSync(BRAND_JSON_PATH, "utf-8"));
674
+ const parsed = JSON.parse(readFileSync3(BRAND_JSON_PATH, "utf-8"));
285
675
  if (typeof parsed.configDir === "string") BRAND.configDir = parsed.configDir;
286
676
  } catch (err) {
287
677
  console.error(`[edge] brand.json parse error: ${err.message}`);
288
678
  }
289
679
  }
290
- var ALIAS_DOMAINS_PATH = join(homedir(), BRAND.configDir, "alias-domains.json");
680
+ var ALIAS_DOMAINS_PATH = join3(homedir(), BRAND.configDir, "alias-domains.json");
291
681
  function loadAliasDomains() {
292
682
  try {
293
- if (!existsSync(ALIAS_DOMAINS_PATH)) return /* @__PURE__ */ new Set();
294
- const parsed = JSON.parse(readFileSync(ALIAS_DOMAINS_PATH, "utf-8"));
683
+ if (!existsSync4(ALIAS_DOMAINS_PATH)) return /* @__PURE__ */ new Set();
684
+ const parsed = JSON.parse(readFileSync3(ALIAS_DOMAINS_PATH, "utf-8"));
295
685
  if (!Array.isArray(parsed)) return /* @__PURE__ */ new Set();
296
686
  return new Set(parsed.filter((h) => typeof h === "string"));
297
687
  } catch {
@@ -406,7 +796,18 @@ function forwardUpgrade(req, clientSocket, head) {
406
796
  teardown("upstream-timeout");
407
797
  });
408
798
  }
799
+ var edgeAdminApp = createEdgeAdminApp({ isPublicHost });
800
+ var edgeAdminHandler = getRequestListener(edgeAdminApp.fetch);
801
+ function isEdgeAdminPath(url) {
802
+ const q = url.indexOf("?");
803
+ const pathname = q === -1 ? url : url.slice(0, q);
804
+ return pathname.startsWith("/api/admin/actions") || pathname.startsWith("/api/admin/version");
805
+ }
409
806
  var server = createServer((req, res) => {
807
+ if (isEdgeAdminPath(req.url ?? "")) {
808
+ edgeAdminHandler(req, res);
809
+ return;
810
+ }
410
811
  forwardHttp(req, res);
411
812
  });
412
813
  server.keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT ?? "61000", 10);