@kizenapps/cli 0.8.0 → 0.9.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.
Files changed (67) hide show
  1. package/dist/index.js +308 -78
  2. package/dist/index.js.map +1 -1
  3. package/dist/nwjs/shell/icon.png +0 -0
  4. package/dist/nwjs/shell/index.html +200 -0
  5. package/dist/nwjs/shell/package.json +16 -0
  6. package/dist/viewer/assets/fa-core-DAYjf9Yn.js +558 -0
  7. package/dist/viewer/assets/fa-core-Ff8SZmm_.js +558 -0
  8. package/dist/viewer/assets/index-47Vh9MH0.js +17 -0
  9. package/dist/viewer/assets/index-6uunWX8M.js +17 -0
  10. package/dist/viewer/assets/index-B48OpZHg.js +17 -0
  11. package/dist/viewer/assets/index-BFfw7eD7.js +17 -0
  12. package/dist/viewer/assets/index-BFyUUb06.js +17 -0
  13. package/dist/viewer/assets/index-BL-Yu7Bf.js +17 -0
  14. package/dist/viewer/assets/index-BUmv8vz7.js +17 -0
  15. package/dist/viewer/assets/index-BZ4HmEz8.js +17 -0
  16. package/dist/viewer/assets/index-BZIXccTd.js +17 -0
  17. package/dist/viewer/assets/index-Bk9hnfnO.js +17 -0
  18. package/dist/viewer/assets/index-C0SIHm9A.js +17 -0
  19. package/dist/viewer/assets/index-CDd-WK6Y.js +17 -0
  20. package/dist/viewer/assets/index-CDxQqP40.js +17 -0
  21. package/dist/viewer/assets/{index-C_gRcfsL.js → index-CGJzvpL5.js} +7 -7
  22. package/dist/viewer/assets/index-CIarX3Lt.js +17 -0
  23. package/dist/viewer/assets/index-CIfVP45E.js +17 -0
  24. package/dist/viewer/assets/index-CJeb7uFu.css +2 -0
  25. package/dist/viewer/assets/index-CbwLb2S_.js +17 -0
  26. package/dist/viewer/assets/index-CsfWgoID.js +17 -0
  27. package/dist/viewer/assets/index-CvWmkIY5.js +17 -0
  28. package/dist/viewer/assets/index-Cy-weKfG.css +2 -0
  29. package/dist/viewer/assets/index-Cym12l2M.js +17 -0
  30. package/dist/viewer/assets/index-D0W0oANg.js +17 -0
  31. package/dist/viewer/assets/index-DCPtqd3W.js +17 -0
  32. package/dist/viewer/assets/index-DF_zYdLw.js +17 -0
  33. package/dist/viewer/assets/index-DGqxQcUx.js +17 -0
  34. package/dist/viewer/assets/index-DK0OVb7V.js +17 -0
  35. package/dist/viewer/assets/index-DKjVFdzw.js +17 -0
  36. package/dist/viewer/assets/index-DLhj0I_2.js +17 -0
  37. package/dist/viewer/assets/index-DPS_ZjbM.js +17 -0
  38. package/dist/viewer/assets/index-DSbHkhcS.js +17 -0
  39. package/dist/viewer/assets/index-DTbmNtEU.js +17 -0
  40. package/dist/viewer/assets/index-DUYd6RpI.js +17 -0
  41. package/dist/viewer/assets/index-DZwYe1wZ.css +2 -0
  42. package/dist/viewer/assets/index-DkRokbYc.js +17 -0
  43. package/dist/viewer/assets/index-DtOWzkR5.js +17 -0
  44. package/dist/viewer/assets/index-DuvXujkm.js +17 -0
  45. package/dist/viewer/assets/index-Dv0mX-oM.js +17 -0
  46. package/dist/viewer/assets/index-DyXisBY9.css +2 -0
  47. package/dist/viewer/assets/index-Nm1xKWTO.js +17 -0
  48. package/dist/viewer/assets/index-ZBdAq6dF.js +17 -0
  49. package/dist/viewer/assets/index-ZOxL_JJZ.js +17 -0
  50. package/dist/viewer/assets/index-kMMOwjlY.js +17 -0
  51. package/dist/viewer/assets/{index-_K33TupM.js → index-ov2oU6RI.js} +7 -7
  52. package/dist/viewer/assets/index-uF6u0kT8.js +17 -0
  53. package/dist/viewer/assets/index-xbDHMSaa.css +2 -0
  54. package/dist/viewer/assets/react-D7PmSb6e.js +9 -0
  55. package/dist/viewer/assets/react-DIjeYlvh.js +9 -0
  56. package/dist/viewer/index.html +4 -4
  57. package/package.json +4 -4
  58. package/dist/viewer/assets/index-B09qenTV.js +0 -17
  59. package/dist/viewer/assets/index-CVd4BLit.js +0 -17
  60. package/dist/viewer/assets/index-CcvbGL5I.js +0 -17
  61. package/dist/viewer/assets/index-Ci5up9QD.js +0 -17
  62. package/dist/viewer/assets/index-DOiSISo1.css +0 -2
  63. package/dist/viewer/assets/index-DSJAwx9Y.js +0 -17
  64. package/dist/viewer/assets/index-DUgUEouL.css +0 -2
  65. package/dist/viewer/assets/index-WnCF0zx3.js +0 -17
  66. package/dist/viewer/assets/index-dPV4go8k.js +0 -17
  67. package/dist/viewer/assets/index-xFuxlPJA.js +0 -17
package/dist/index.js CHANGED
@@ -229,14 +229,231 @@ import { render as render2 } from "ink";
229
229
  // src/ui/DevUI.tsx
230
230
  import { useCallback as useCallback2, useEffect as useEffect3, useRef, useState as useState3 } from "react";
231
231
  import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
232
- import { spawn } from "child_process";
233
232
  import { createReadStream, watch } from "fs";
234
- import { access, readFile as readFile4 } from "fs/promises";
233
+ import { access, readFile as readFile5 } from "fs/promises";
235
234
  import { createServer } from "http";
235
+ import { dirname as dirname3, extname, join as join7 } from "path";
236
+ import { fileURLToPath as fileURLToPath2 } from "url";
237
+ import { WebSocketServer } from "ws";
238
+
239
+ // src/chrome/launcher.ts
240
+ import { launch } from "chrome-launcher";
236
241
  import { createRequire } from "module";
237
- import { dirname as dirname2, extname, join as join6 } from "path";
242
+ import { mkdirSync } from "fs";
243
+ import { readFile as readFile2 } from "fs/promises";
244
+ import { dirname, join as join4 } from "path";
238
245
  import { fileURLToPath } from "url";
239
- import { WebSocketServer } from "ws";
246
+ var CDP = createRequire(import.meta.url)("chrome-remote-interface");
247
+ var CSP_HEADERS = /* @__PURE__ */ new Set([
248
+ "content-security-policy",
249
+ "content-security-policy-report-only",
250
+ "x-frame-options"
251
+ ]);
252
+ var KIZEN_DOMAINS = ["kizen.dev", "kizen.com"];
253
+ function isKizenUrl(url) {
254
+ try {
255
+ const { hostname } = new URL(url);
256
+ return KIZEN_DOMAINS.some((d) => hostname === d || hostname.endsWith("." + d));
257
+ } catch {
258
+ return false;
259
+ }
260
+ }
261
+ function parseProp(prop) {
262
+ if (prop.type === "number") return Number(prop.value);
263
+ if (prop.type === "boolean") return prop.value === "true";
264
+ if (prop.type === "undefined") return void 0;
265
+ if (prop.type === "object" && prop.value === "null") return null;
266
+ if (prop.type === "object" && prop.valuePreview) {
267
+ const { valuePreview } = prop;
268
+ if (valuePreview.subtype === "array") {
269
+ return valuePreview.properties.filter((p) => /^\d+$/.test(p.name)).sort((a, b) => parseInt(a.name) - parseInt(b.name)).map(parseProp);
270
+ }
271
+ const obj = {};
272
+ for (const p of valuePreview.properties) obj[p.name] = parseProp(p);
273
+ return obj;
274
+ }
275
+ return prop.value ?? `[${prop.type}]`;
276
+ }
277
+ function serializeArg(arg) {
278
+ if (arg.value !== void 0) return arg.value;
279
+ if (arg.preview) {
280
+ const { preview } = arg;
281
+ if (preview.subtype === "array") {
282
+ return preview.properties.filter((p) => /^\d+$/.test(p.name)).sort((a, b) => parseInt(a.name) - parseInt(b.name)).map(parseProp);
283
+ }
284
+ const obj = {};
285
+ for (const prop of preview.properties) obj[prop.name] = parseProp(prop);
286
+ return obj;
287
+ }
288
+ return arg.description ?? `[${arg.type}]`;
289
+ }
290
+ async function serializeArgAsync(client, arg) {
291
+ if (arg.type === "object" && arg.objectId) {
292
+ try {
293
+ const result = await client.Runtime.callFunctionOn({
294
+ objectId: arg.objectId,
295
+ functionDeclaration: 'function() { const seen = new WeakSet(); return JSON.stringify(this, function(k,v) { if (typeof v === "object" && v !== null) { if (seen.has(v)) return "[Circular]"; seen.add(v); } return v; }); }',
296
+ returnByValue: true
297
+ });
298
+ const val = result.result.value;
299
+ if (typeof val === "string") return JSON.parse(val);
300
+ } catch {
301
+ }
302
+ }
303
+ return serializeArg(arg);
304
+ }
305
+ async function installDockIcon(client) {
306
+ try {
307
+ const iconPath = join4(dirname(fileURLToPath(import.meta.url)), "viewer", "icon.png");
308
+ const icon = await readFile2(iconPath);
309
+ await client.Browser.setDockTile({ image: icon.toString("base64") });
310
+ } catch {
311
+ }
312
+ }
313
+ async function launchChromeViewer(opts) {
314
+ mkdirSync(opts.userDataDir, { recursive: true });
315
+ const chrome = await launch({
316
+ startingUrl: `http://localhost:${opts.port}`,
317
+ chromeFlags: [
318
+ `--app=http://localhost:${opts.port}`,
319
+ "--no-first-run",
320
+ "--no-default-browser-check",
321
+ "--disable-extensions"
322
+ ],
323
+ userDataDir: opts.userDataDir,
324
+ handleSIGINT: false,
325
+ logLevel: "silent"
326
+ });
327
+ let client = null;
328
+ let cookieTimer = null;
329
+ let cdpPort = chrome.port;
330
+ for (let attempt = 0; attempt < 10; attempt++) {
331
+ await new Promise((r) => setTimeout(r, 300));
332
+ try {
333
+ client = await CDP({ port: cdpPort });
334
+ break;
335
+ } catch {
336
+ if (attempt === 9) {
337
+ return {
338
+ kill: () => chrome.kill()
339
+ };
340
+ }
341
+ }
342
+ }
343
+ if (!client) {
344
+ return {
345
+ kill: () => chrome.kill()
346
+ };
347
+ }
348
+ const c = client;
349
+ await installDockIcon(c);
350
+ await c.Fetch.enable({
351
+ patterns: KIZEN_DOMAINS.flatMap((d) => [
352
+ { urlPattern: `*://${d}/*`, requestStage: "Response" },
353
+ { urlPattern: `*://*.${d}/*`, requestStage: "Response" }
354
+ ])
355
+ });
356
+ c.on("Fetch.requestPaused", (rawParams) => {
357
+ void (async () => {
358
+ const params = rawParams;
359
+ const { requestId, request, responseHeaders, responseStatusCode } = params;
360
+ const url = request?.url ?? "";
361
+ const filtered = [];
362
+ for (const h of responseHeaders ?? []) {
363
+ if (CSP_HEADERS.has(h.name.toLowerCase())) continue;
364
+ if (h.name.toLowerCase() === "set-cookie" && isKizenUrl(url)) {
365
+ const patched = h.value.split("\n").map((cookie) => {
366
+ if (!/SameSite=/i.test(cookie)) return cookie + "; SameSite=None; Secure";
367
+ return cookie.replace(/SameSite=\w+/i, "SameSite=None");
368
+ }).join("\n");
369
+ filtered.push({ name: h.name, value: patched });
370
+ } else {
371
+ filtered.push(h);
372
+ }
373
+ }
374
+ try {
375
+ const { body, base64Encoded } = await c.Fetch.getResponseBody({ requestId });
376
+ await c.Fetch.fulfillRequest({
377
+ requestId,
378
+ responseCode: responseStatusCode ?? 200,
379
+ responseHeaders: filtered,
380
+ body,
381
+ ...base64Encoded && { base64Encoded: true }
382
+ });
383
+ } catch {
384
+ try {
385
+ await c.Fetch.continueRequest({ requestId });
386
+ } catch {
387
+ }
388
+ }
389
+ })();
390
+ });
391
+ cookieTimer = setInterval(() => {
392
+ void (async () => {
393
+ try {
394
+ const { cookies } = await c.Network.getAllCookies();
395
+ for (const cookie of cookies) {
396
+ if (!isKizenUrl(`https://${cookie.domain ?? ""}`)) continue;
397
+ if (cookie.sameSite === "None") continue;
398
+ const raw = cookie.domain ?? "";
399
+ const bare = raw.startsWith(".") ? raw.slice(1) : raw;
400
+ await c.Network.setCookie({
401
+ url: `https://${bare}`,
402
+ name: cookie.name,
403
+ value: cookie.value,
404
+ domain: raw,
405
+ path: cookie.path ?? "/",
406
+ secure: true,
407
+ ...cookie.httpOnly !== void 0 && { httpOnly: cookie.httpOnly },
408
+ ...cookie.expirationDate !== void 0 && {
409
+ expires: cookie.expirationDate
410
+ },
411
+ sameSite: "None"
412
+ });
413
+ }
414
+ } catch {
415
+ }
416
+ })();
417
+ }, 2e3);
418
+ await c.send("Runtime.enable", { generatePreview: true });
419
+ c.on("Target.attachedToTarget", (rawParams) => {
420
+ const { sessionId } = rawParams;
421
+ void c.send("Runtime.enable", { generatePreview: true }, sessionId).catch(() => {
422
+ });
423
+ });
424
+ await c.Target.setAutoAttach({
425
+ autoAttach: true,
426
+ waitForDebuggerOnStart: false,
427
+ flatten: true
428
+ });
429
+ await c.Target.setDiscoverTargets({ discover: true });
430
+ c.on("Target.targetCreated", (rawParams) => {
431
+ const { targetInfo } = rawParams;
432
+ if (targetInfo.type === "page" && targetInfo.url === "chrome://newtab/") {
433
+ void c.Target.closeTarget({ targetId: targetInfo.targetId }).catch(() => {
434
+ });
435
+ }
436
+ });
437
+ c.on("Runtime.consoleAPICalled", (rawParams) => {
438
+ void (async () => {
439
+ const { type, args } = rawParams;
440
+ if (args[0]?.type === "string" && String(args[0].value).includes("Chrome Security")) return;
441
+ const level = type === "warning" ? "warn" : ["log", "warn", "error", "info"].includes(type) ? type : "log";
442
+ const serialized = await Promise.all(args.map((arg) => serializeArgAsync(c, arg)));
443
+ opts.broadcast({ type: "console-message", level, args: serialized });
444
+ })().catch(() => {
445
+ });
446
+ });
447
+ const kill = () => {
448
+ if (cookieTimer !== null) clearInterval(cookieTimer);
449
+ try {
450
+ void c.close();
451
+ } catch {
452
+ }
453
+ chrome.kill();
454
+ };
455
+ return { kill };
456
+ }
240
457
 
241
458
  // src/lib/proxyCache.ts
242
459
  function createProxyCache() {
@@ -270,12 +487,12 @@ function createProxyCache() {
270
487
  }
271
488
 
272
489
  // src/lib/credentials.ts
273
- import { mkdir as mkdir2, readFile as readFile2, readdir as readdir2, writeFile as writeFile2 } from "fs/promises";
490
+ import { mkdir as mkdir2, readFile as readFile3, readdir as readdir2, writeFile as writeFile2 } from "fs/promises";
274
491
  import { homedir } from "os";
275
- import { dirname, join as join4 } from "path";
492
+ import { dirname as dirname2, join as join5 } from "path";
276
493
  var ENVIRONMENTS = ["go", "fmo", "staging", "integration", "test1"];
277
- var GLOBAL_CREDENTIALS_DIR = join4(homedir(), ".kizenappbuilder");
278
- var GLOBAL_CREDENTIALS_PATH = join4(GLOBAL_CREDENTIALS_DIR, "credentials.json");
494
+ var GLOBAL_CREDENTIALS_DIR = join5(homedir(), ".kizenappbuilder");
495
+ var GLOBAL_CREDENTIALS_PATH = join5(GLOBAL_CREDENTIALS_DIR, "credentials.json");
279
496
  var DEFAULT_PROFILE_NAME = "credentials";
280
497
  function isValidEnvironment(value) {
281
498
  return ENVIRONMENTS.includes(value);
@@ -294,7 +511,7 @@ function parseCredentials(raw) {
294
511
  };
295
512
  }
296
513
  async function loadCredentialsFromFile(filePath) {
297
- const content = await readFile2(filePath, "utf-8");
514
+ const content = await readFile3(filePath, "utf-8");
298
515
  return parseCredentials(JSON.parse(content));
299
516
  }
300
517
  async function loadGlobalCredentials() {
@@ -305,11 +522,11 @@ async function loadGlobalCredentials() {
305
522
  }
306
523
  }
307
524
  async function saveGlobalCredentials(credentials) {
308
- await mkdir2(dirname(GLOBAL_CREDENTIALS_PATH), { recursive: true });
525
+ await mkdir2(dirname2(GLOBAL_CREDENTIALS_PATH), { recursive: true });
309
526
  await writeFile2(GLOBAL_CREDENTIALS_PATH, JSON.stringify(credentials, null, 2), "utf-8");
310
527
  }
311
528
  function getProfilePath(name) {
312
- return join4(GLOBAL_CREDENTIALS_DIR, `${name}.json`);
529
+ return join5(GLOBAL_CREDENTIALS_DIR, `${name}.json`);
313
530
  }
314
531
  async function listCredentialProfiles() {
315
532
  const profiles = [
@@ -318,10 +535,14 @@ async function listCredentialProfiles() {
318
535
  try {
319
536
  const entries = await readdir2(GLOBAL_CREDENTIALS_DIR);
320
537
  for (const entry of entries) {
321
- if (!entry.endsWith(".json")) continue;
538
+ if (!entry.endsWith(".json")) {
539
+ continue;
540
+ }
322
541
  const name = entry.slice(0, -5);
323
- if (name === DEFAULT_PROFILE_NAME) continue;
324
- profiles.push({ name, path: join4(GLOBAL_CREDENTIALS_DIR, entry), isDefault: false });
542
+ if (name === DEFAULT_PROFILE_NAME) {
543
+ continue;
544
+ }
545
+ profiles.push({ name, path: join5(GLOBAL_CREDENTIALS_DIR, entry), isDefault: false });
325
546
  }
326
547
  } catch {
327
548
  }
@@ -344,11 +565,11 @@ async function loadCredentialProfile(name) {
344
565
  }
345
566
 
346
567
  // src/lib/config.ts
347
- import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
348
- import { join as join5 } from "path";
568
+ import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
569
+ import { join as join6 } from "path";
349
570
  async function loadConfig(outputDir) {
350
571
  try {
351
- const content = await readFile3(join5(outputDir, "config.json"), "utf-8");
572
+ const content = await readFile4(join6(outputDir, "config.json"), "utf-8");
352
573
  return JSON.parse(content);
353
574
  } catch {
354
575
  return {};
@@ -356,7 +577,7 @@ async function loadConfig(outputDir) {
356
577
  }
357
578
  async function saveConfig(outputDir, config) {
358
579
  await mkdir3(outputDir, { recursive: true });
359
- await writeFile3(join5(outputDir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
580
+ await writeFile3(join6(outputDir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
360
581
  }
361
582
 
362
583
  // src/ui/CredentialSetupUI.tsx
@@ -383,7 +604,7 @@ var CredentialSetupUI = ({
383
604
  const [error, setError] = useState2(null);
384
605
  const [activeProfileName, setActiveProfileName] = useState2(void 0);
385
606
  const handleModeChosen = useCallback(
386
- async (mode) => {
607
+ (mode) => {
387
608
  if (mode === "local") {
388
609
  onComplete({ mode: "local", credentials: null });
389
610
  return;
@@ -452,7 +673,7 @@ var CredentialSetupUI = ({
452
673
  setPhase({ type: "mode-select", cursor: 1 });
453
674
  } else if (key.return) {
454
675
  const mode = phase.cursor === 0 ? "global" : "local";
455
- void handleModeChosen(mode);
676
+ handleModeChosen(mode);
456
677
  }
457
678
  return;
458
679
  }
@@ -468,13 +689,16 @@ var CredentialSetupUI = ({
468
689
  if (cursor === addNewIdx) {
469
690
  setPhase({ type: "name-entry", nameBuffer: "", existingProfiles: profiles });
470
691
  } else {
471
- void handleProfileChosen(profiles[cursor] ?? profiles[0]);
692
+ const selected = profiles[cursor] ?? profiles[0];
693
+ if (selected) {
694
+ void handleProfileChosen(selected);
695
+ }
472
696
  }
473
697
  }
474
698
  return;
475
699
  }
476
700
  if (phase.type === "name-entry") {
477
- const { nameBuffer, nameError: _nameError, existingProfiles } = phase;
701
+ const { nameBuffer, existingProfiles } = phase;
478
702
  if (key.backspace || key.delete) {
479
703
  setPhase({ type: "name-entry", nameBuffer: nameBuffer.slice(0, -1), existingProfiles });
480
704
  } else if (key.return) {
@@ -619,7 +843,14 @@ var CredentialSetupUI = ({
619
843
  }),
620
844
  /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
621
845
  /* @__PURE__ */ jsx3(Text3, { ...cursor === addNewIdx && { color: "cyan" }, children: cursor === addNewIdx ? "\u276F" : " " }),
622
- /* @__PURE__ */ jsx3(Text3, { bold: cursor === addNewIdx, ...cursor === addNewIdx && { color: "cyan" }, children: "+ Add new profile" })
846
+ /* @__PURE__ */ jsx3(
847
+ Text3,
848
+ {
849
+ bold: cursor === addNewIdx,
850
+ ...cursor === addNewIdx && { color: "cyan" },
851
+ children: "+ Add new profile"
852
+ }
853
+ )
623
854
  ] })
624
855
  ] }),
625
856
  /* @__PURE__ */ jsx3(Hint, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Esc to cancel" })
@@ -649,7 +880,7 @@ var CredentialSetupUI = ({
649
880
  }
650
881
  const { field: activeField, values, envCursor } = phase;
651
882
  const isEnvField = activeField === FIELDS.length;
652
- const profileLabel = activeProfileName !== void 0 ? activeProfileName : DEFAULT_PROFILE_NAME;
883
+ const profileLabel = activeProfileName ?? DEFAULT_PROFILE_NAME;
653
884
  return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
654
885
  /* @__PURE__ */ jsx3(Logo, {}),
655
886
  /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, children: [
@@ -659,7 +890,8 @@ var CredentialSetupUI = ({
659
890
  /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", gap: 0, children: [
660
891
  /* @__PURE__ */ jsx3(Text3, { bold: true, children: activeProfileName !== void 0 ? `Profile: ${activeProfileName}` : "Global credentials" }),
661
892
  /* @__PURE__ */ jsxs2(Text3, { dimColor: true, children: [
662
- "Saved to: ",
893
+ "Saved to:",
894
+ " ",
663
895
  activeProfileName !== void 0 ? `~/.kizenappbuilder/${activeProfileName}.json` : GLOBAL_CREDENTIALS_PATH
664
896
  ] })
665
897
  ] }),
@@ -739,12 +971,8 @@ var Spinner2 = () => {
739
971
  return /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: SPINNER_FRAMES2[frame] ?? "\u280B" });
740
972
  };
741
973
  function getViewerPath() {
742
- const filename = fileURLToPath(import.meta.url);
743
- return join6(dirname2(filename), "viewer");
744
- }
745
- function getElectronMainPath() {
746
- const filename = fileURLToPath(import.meta.url);
747
- return join6(dirname2(filename), "electron", "main.js");
974
+ const filename = fileURLToPath2(import.meta.url);
975
+ return join7(dirname3(filename), "viewer");
748
976
  }
749
977
  async function fileExists(filePath) {
750
978
  try {
@@ -782,7 +1010,7 @@ function createRequestHandler(viewerPath, createServerLog, createProxyLog, crede
782
1010
  const creds = profileName === void 0 || profileName === "" ? await loadGlobalCredentials() : await loadCredentialProfile(profileName);
783
1011
  if (creds !== null) {
784
1012
  credentialsRef.current = creds;
785
- activeProfileRef.current = profileName || void 0;
1013
+ activeProfileRef.current = profileName ?? void 0;
786
1014
  const active = activeProfileRef.current;
787
1015
  await saveConfig(outputDir, {
788
1016
  credentialMode: "global",
@@ -795,9 +1023,9 @@ function createRequestHandler(viewerPath, createServerLog, createProxyLog, crede
795
1023
  return;
796
1024
  }
797
1025
  if (url === "/api/bundle") {
798
- const bundlePath = join6(process.cwd(), ".kizenapp", "bundle.json");
1026
+ const bundlePath = join7(process.cwd(), ".kizenapp", "bundle.json");
799
1027
  try {
800
- const content = await readFile4(bundlePath, "utf-8");
1028
+ const content = await readFile5(bundlePath, "utf-8");
801
1029
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
802
1030
  res.end(content);
803
1031
  } catch {
@@ -870,8 +1098,8 @@ function createRequestHandler(viewerPath, createServerLog, createProxyLog, crede
870
1098
  return;
871
1099
  }
872
1100
  const rawPath = url === "/" ? "/index.html" : url;
873
- const filePath = join6(viewerPath, rawPath);
874
- const resolvedPath = await fileExists(filePath) ? filePath : join6(viewerPath, "index.html");
1101
+ const filePath = join7(viewerPath, rawPath);
1102
+ const resolvedPath = await fileExists(filePath) ? filePath : join7(viewerPath, "index.html");
875
1103
  const ext = extname(resolvedPath);
876
1104
  const mimeType = MIME_TYPES[ext] ?? "application/octet-stream";
877
1105
  res.writeHead(200, { "Content-Type": mimeType });
@@ -923,10 +1151,40 @@ var DevUI = ({
923
1151
  const [lastBuilt, setLastBuilt] = useState3(null);
924
1152
  const [wsClientCount, setWsClientCount] = useState3(0);
925
1153
  const buildingRef = useRef(false);
926
- const electronLaunchedRef = useRef(false);
1154
+ const viewerLaunchedRef = useRef(false);
1155
+ const chromeHandleRef = useRef(null);
927
1156
  const debounceTimerRef = useRef(null);
928
1157
  const wsClientsRef = useRef(/* @__PURE__ */ new Set());
929
1158
  const pendingMessagesRef = useRef([]);
1159
+ const broadcast = useCallback2((msg) => {
1160
+ const json = JSON.stringify(msg);
1161
+ if (wsClientsRef.current.size === 0) {
1162
+ pendingMessagesRef.current = [...pendingMessagesRef.current, json].slice(-LOG_LIMIT);
1163
+ return;
1164
+ }
1165
+ for (const client of wsClientsRef.current) {
1166
+ if (client.readyState === 1) {
1167
+ client.send(json);
1168
+ }
1169
+ }
1170
+ }, []);
1171
+ const createServerLog = useCallback2((message) => {
1172
+ setServerLogHistory(
1173
+ (h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-LOG_LIMIT)
1174
+ );
1175
+ }, []);
1176
+ const launchChromeViewerCb = useCallback2(() => {
1177
+ chromeHandleRef.current?.kill();
1178
+ chromeHandleRef.current = null;
1179
+ void launchChromeViewer({
1180
+ port,
1181
+ userDataDir: join7(outputDir, ".chrome"),
1182
+ broadcast
1183
+ }).then((handle) => {
1184
+ chromeHandleRef.current = handle;
1185
+ process.on("exit", () => handle.kill());
1186
+ });
1187
+ }, [port, outputDir, broadcast]);
930
1188
  useInput2(
931
1189
  (input, key) => {
932
1190
  if (input === "q" || key.ctrl && input === "c") {
@@ -935,50 +1193,19 @@ var DevUI = ({
935
1193
  if (input === "c") {
936
1194
  setCredMode("editing");
937
1195
  }
938
- if (input === "d") {
939
- broadcast({ type: "open-devtools" });
1196
+ if (input === "v") {
1197
+ launchChromeViewerCb();
940
1198
  }
941
1199
  },
942
1200
  { isActive: credMode === "main" }
943
1201
  );
944
1202
  useEffect3(() => {
945
- if (status !== "running" || electronLaunchedRef.current) {
946
- return;
947
- }
948
- electronLaunchedRef.current = true;
949
- const _require = createRequire(import.meta.url);
950
- const electronBin = _require("electron");
951
- const electronMain = getElectronMainPath();
952
- const userDataDir = join6(outputDir, ".electron");
953
- const proc = spawn(
954
- electronBin,
955
- [electronMain, `--port=${String(port)}`, `--user-data-dir=${userDataDir}`],
956
- {
957
- stdio: "ignore"
958
- }
959
- );
960
- proc.unref();
961
- process.on("exit", () => {
962
- proc.kill();
963
- });
964
- }, [status, port, outputDir]);
965
- const createServerLog = useCallback2((message) => {
966
- setServerLogHistory(
967
- (h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-LOG_LIMIT)
968
- );
969
- }, []);
970
- const broadcast = useCallback2((msg) => {
971
- const json = JSON.stringify(msg);
972
- if (wsClientsRef.current.size === 0) {
973
- pendingMessagesRef.current = [...pendingMessagesRef.current, json].slice(-LOG_LIMIT);
1203
+ if (status !== "running" || viewerLaunchedRef.current) {
974
1204
  return;
975
1205
  }
976
- for (const client of wsClientsRef.current) {
977
- if (client.readyState === 1) {
978
- client.send(json);
979
- }
980
- }
981
- }, []);
1206
+ viewerLaunchedRef.current = true;
1207
+ launchChromeViewerCb();
1208
+ }, [status, launchChromeViewerCb]);
982
1209
  const handleCredentialsDone = useCallback2(
983
1210
  (result) => {
984
1211
  credentialsRef.current = result.credentials;
@@ -1065,7 +1292,7 @@ var DevUI = ({
1065
1292
  }, [pluginDir, triggerBuild, createBuildLog]);
1066
1293
  useEffect3(() => {
1067
1294
  const viewerPath = getViewerPath();
1068
- void fileExists(join6(viewerPath, "index.html")).then((viewerBuilt) => {
1295
+ void fileExists(join7(viewerPath, "index.html")).then((viewerBuilt) => {
1069
1296
  if (!viewerBuilt) {
1070
1297
  setErrorMessage("Viewer not built. Run 'pnpm build:viewer' first.");
1071
1298
  setStatus("error");
@@ -1110,7 +1337,7 @@ var DevUI = ({
1110
1337
  server.close();
1111
1338
  };
1112
1339
  });
1113
- }, [port, createServerLog, createProxyLog]);
1340
+ }, [port, createServerLog, createProxyLog, broadcast, outputDir]);
1114
1341
  if (credMode === "editing") {
1115
1342
  return /* @__PURE__ */ jsx4(
1116
1343
  CredentialSetupUI,
@@ -1218,8 +1445,11 @@ var DevUI = ({
1218
1445
  /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "] quit [" }),
1219
1446
  /* @__PURE__ */ jsx4(Text4, { children: "c" }),
1220
1447
  /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "] credentials [" }),
1221
- /* @__PURE__ */ jsx4(Text4, { children: "d" }),
1222
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "] devtools" })
1448
+ /* @__PURE__ */ jsx4(Text4, { children: "v" }),
1449
+ /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
1450
+ "] ",
1451
+ wsClientCount === 0 ? "open viewer" : "relaunch viewer"
1452
+ ] })
1223
1453
  ] })
1224
1454
  ] });
1225
1455
  };