@kizenapps/cli 0.9.0 → 0.11.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 (108) hide show
  1. package/dist/{devtools-PDXM3L35.js → devtools-LZRAH4XS.js} +30 -7
  2. package/dist/devtools-LZRAH4XS.js.map +1 -0
  3. package/dist/index.js +645 -554
  4. package/dist/index.js.map +1 -1
  5. package/dist/viewer/assets/AppDetailPage-BJrg5Wsz.js +1 -0
  6. package/dist/viewer/assets/BootstrapContext-D3lhzWap.js +1 -0
  7. package/dist/viewer/assets/Card-CPMAuqAb.js +1 -0
  8. package/dist/viewer/assets/ConfigurationPage-D8I8rWjF.js +1 -0
  9. package/dist/viewer/assets/CredentialsContext-BLZX6RwF.js +1 -0
  10. package/dist/viewer/assets/DevSidebar-BiROAoKF.js +1 -0
  11. package/dist/viewer/assets/SandboxPage-ByixszHi.js +1 -0
  12. package/dist/viewer/assets/SidebarContext-Byo6PYOF.js +1 -0
  13. package/dist/viewer/assets/VersionsPage-BKul6bXI.js +1 -0
  14. package/dist/viewer/assets/{fa-core-DAYjf9Yn.js → fa-core-mHW_3JUd.js} +1 -1
  15. package/dist/viewer/assets/index-BoSQQwMQ.css +2 -0
  16. package/dist/viewer/assets/index-Bomh3C3X.js +5 -0
  17. package/dist/viewer/assets/{react-C90crogl.js → react-D_K8ni3_.js} +1 -1
  18. package/dist/viewer/assets/useObjectLookups-Cx6A7PGj.js +14 -0
  19. package/dist/viewer/assets/validIcons-TcFXfn-E.js +1 -0
  20. package/dist/viewer/index.html +8 -5
  21. package/package.json +10 -6
  22. package/dist/chromium-extension/manifest.json +0 -17
  23. package/dist/chromium-extension/rules.json +0 -17
  24. package/dist/chunk-4OYGMEFM.js +0 -3675
  25. package/dist/chunk-4OYGMEFM.js.map +0 -1
  26. package/dist/chunk-5WPGAZLP.js +0 -6136
  27. package/dist/chunk-5WPGAZLP.js.map +0 -1
  28. package/dist/chunk-6XGFZ4W5.js +0 -3673
  29. package/dist/chunk-6XGFZ4W5.js.map +0 -1
  30. package/dist/chunk-KMKDY2I6.js +0 -46
  31. package/dist/chunk-KMKDY2I6.js.map +0 -1
  32. package/dist/chunk-NNDSADQG.js +0 -3641
  33. package/dist/chunk-NNDSADQG.js.map +0 -1
  34. package/dist/devtools-HNR7DD7Q.js +0 -68
  35. package/dist/devtools-HNR7DD7Q.js.map +0 -1
  36. package/dist/devtools-HW3UQAHH.js +0 -69
  37. package/dist/devtools-HW3UQAHH.js.map +0 -1
  38. package/dist/devtools-KXIXZGBY.js +0 -64
  39. package/dist/devtools-KXIXZGBY.js.map +0 -1
  40. package/dist/devtools-PDXM3L35.js.map +0 -1
  41. package/dist/devtools-SDEASRYI.js +0 -66
  42. package/dist/devtools-SDEASRYI.js.map +0 -1
  43. package/dist/devtools-XBEEGBQ4.js +0 -66
  44. package/dist/devtools-XBEEGBQ4.js.map +0 -1
  45. package/dist/dist-4P5P64FK.js +0 -8474
  46. package/dist/dist-4P5P64FK.js.map +0 -1
  47. package/dist/dist-AVHDSU4X.js +0 -137
  48. package/dist/dist-AVHDSU4X.js.map +0 -1
  49. package/dist/electron/icon.png +0 -0
  50. package/dist/electron/main.js +0 -201
  51. package/dist/electron/main.js.map +0 -1
  52. package/dist/electron/preload.cjs +0 -9
  53. package/dist/gzip-size-SHZBT5GU.js +0 -139
  54. package/dist/gzip-size-SHZBT5GU.js.map +0 -1
  55. package/dist/nwjs/shell/icon.png +0 -0
  56. package/dist/nwjs/shell/index.html +0 -200
  57. package/dist/nwjs/shell/package.json +0 -16
  58. package/dist/viewer/assets/fa-core-BU3ZZt7W.js +0 -558
  59. package/dist/viewer/assets/fa-core-Ff8SZmm_.js +0 -558
  60. package/dist/viewer/assets/index-47Vh9MH0.js +0 -17
  61. package/dist/viewer/assets/index-6uunWX8M.js +0 -17
  62. package/dist/viewer/assets/index-B48OpZHg.js +0 -17
  63. package/dist/viewer/assets/index-BFfw7eD7.js +0 -17
  64. package/dist/viewer/assets/index-BFyUUb06.js +0 -17
  65. package/dist/viewer/assets/index-BL-Yu7Bf.js +0 -17
  66. package/dist/viewer/assets/index-BUmv8vz7.js +0 -17
  67. package/dist/viewer/assets/index-BZ4HmEz8.js +0 -17
  68. package/dist/viewer/assets/index-BZIXccTd.js +0 -17
  69. package/dist/viewer/assets/index-Bk9hnfnO.js +0 -17
  70. package/dist/viewer/assets/index-C0SIHm9A.js +0 -17
  71. package/dist/viewer/assets/index-CDd-WK6Y.js +0 -17
  72. package/dist/viewer/assets/index-CDxQqP40.js +0 -17
  73. package/dist/viewer/assets/index-CGJzvpL5.js +0 -17
  74. package/dist/viewer/assets/index-CIarX3Lt.js +0 -17
  75. package/dist/viewer/assets/index-CIfVP45E.js +0 -17
  76. package/dist/viewer/assets/index-CJeb7uFu.css +0 -2
  77. package/dist/viewer/assets/index-CbwLb2S_.js +0 -17
  78. package/dist/viewer/assets/index-CsfWgoID.js +0 -17
  79. package/dist/viewer/assets/index-CvWmkIY5.js +0 -17
  80. package/dist/viewer/assets/index-Cy-weKfG.css +0 -2
  81. package/dist/viewer/assets/index-Cym12l2M.js +0 -17
  82. package/dist/viewer/assets/index-D0W0oANg.js +0 -17
  83. package/dist/viewer/assets/index-DCPtqd3W.js +0 -17
  84. package/dist/viewer/assets/index-DF_zYdLw.js +0 -17
  85. package/dist/viewer/assets/index-DGqxQcUx.js +0 -17
  86. package/dist/viewer/assets/index-DK0OVb7V.js +0 -17
  87. package/dist/viewer/assets/index-DKjVFdzw.js +0 -17
  88. package/dist/viewer/assets/index-DLhj0I_2.js +0 -17
  89. package/dist/viewer/assets/index-DPS_ZjbM.js +0 -17
  90. package/dist/viewer/assets/index-DSbHkhcS.js +0 -17
  91. package/dist/viewer/assets/index-DTbmNtEU.js +0 -17
  92. package/dist/viewer/assets/index-DUYd6RpI.js +0 -17
  93. package/dist/viewer/assets/index-DZwYe1wZ.css +0 -2
  94. package/dist/viewer/assets/index-DkRokbYc.js +0 -17
  95. package/dist/viewer/assets/index-DtOWzkR5.js +0 -17
  96. package/dist/viewer/assets/index-DuvXujkm.js +0 -17
  97. package/dist/viewer/assets/index-Dv0mX-oM.js +0 -17
  98. package/dist/viewer/assets/index-DyXisBY9.css +0 -2
  99. package/dist/viewer/assets/index-Nm1xKWTO.js +0 -17
  100. package/dist/viewer/assets/index-ZBdAq6dF.js +0 -17
  101. package/dist/viewer/assets/index-ZOxL_JJZ.js +0 -17
  102. package/dist/viewer/assets/index-kMMOwjlY.js +0 -17
  103. package/dist/viewer/assets/index-ov2oU6RI.js +0 -17
  104. package/dist/viewer/assets/index-uF6u0kT8.js +0 -17
  105. package/dist/viewer/assets/index-xbDHMSaa.css +0 -2
  106. package/dist/viewer/assets/react-D7PmSb6e.js +0 -9
  107. package/dist/viewer/assets/react-DIjeYlvh.js +0 -9
  108. /package/dist/viewer/assets/{fa-pro-icons-D4afj0tU.js → fa-pro-icons-CIQGXozY.js} +0 -0
package/dist/index.js CHANGED
@@ -8,8 +8,8 @@ import { createElement } from "react";
8
8
  import { render } from "ink";
9
9
 
10
10
  // src/ui/BuildUI.tsx
11
- import { useEffect, useState } from "react";
12
- import { Box as Box2, Text as Text2, useApp } from "ink";
11
+ import { useEffect as useEffect2, useState as useState2 } from "react";
12
+ import { Box as Box3, Text as Text4, useApp } from "ink";
13
13
 
14
14
  // src/lib/runBuild.ts
15
15
  import { mkdir, writeFile } from "fs/promises";
@@ -88,9 +88,26 @@ async function runBuild(pluginDir, outputDir, onStep) {
88
88
  })).sort((a, b) => b.version.localeCompare(a.version, void 0, { numeric: true })) : [];
89
89
  return serializePlugin(plugin, allReleaseNotes);
90
90
  });
91
- await writeFile(join2(outputDir, "bundle.json"), JSON.stringify(bundle, null, 2), "utf-8");
91
+ const jsonStr = JSON.stringify(bundle, null, 2);
92
+ const bundleSize = Buffer.byteLength(jsonStr, "utf-8");
93
+ await writeFile(join2(outputDir, "bundle.json"), jsonStr, "utf-8");
94
+ return { bundleSize };
92
95
  }
93
96
 
97
+ // src/lib/formatBytes.ts
98
+ function formatBytes(n) {
99
+ if (n < 1024) {
100
+ return `${String(n)} B`;
101
+ }
102
+ if (n < 1024 * 1024) {
103
+ return `${(n / 1024).toFixed(1)} KB`;
104
+ }
105
+ return `${(n / (1024 * 1024)).toFixed(2)} MB`;
106
+ }
107
+
108
+ // src/ui/AppHeader.tsx
109
+ import { Box as Box2, Text as Text2 } from "ink";
110
+
94
111
  // src/ui/Logo.tsx
95
112
  import { Box, Text } from "ink";
96
113
  import { jsx } from "react/jsx-runtime";
@@ -116,8 +133,20 @@ var LOGO_LINES = [
116
133
  ];
117
134
  var Logo = () => /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: LOGO_LINES.map((line, i) => /* @__PURE__ */ jsx(Text, { color: "cyan", children: line }, i)) });
118
135
 
119
- // src/ui/BuildUI.tsx
136
+ // src/ui/AppHeader.tsx
120
137
  import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
138
+ var AppHeader = ({ marginBottom = 0 }) => /* @__PURE__ */ jsxs(Fragment, { children: [
139
+ /* @__PURE__ */ jsx2(Logo, {}),
140
+ /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", marginTop: 1, marginBottom, children: [
141
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "Kizen App Builder" }),
142
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2500".repeat(24) })
143
+ ] })
144
+ ] });
145
+
146
+ // src/ui/Spinner.tsx
147
+ import { useEffect, useState } from "react";
148
+ import { Text as Text3 } from "ink";
149
+ import { jsx as jsx3 } from "react/jsx-runtime";
121
150
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
122
151
  var Spinner = () => {
123
152
  const [frame, setFrame] = useState(0);
@@ -129,8 +158,11 @@ var Spinner = () => {
129
158
  clearInterval(id);
130
159
  };
131
160
  }, []);
132
- return /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: SPINNER_FRAMES[frame] ?? "\u280B" });
161
+ return /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: SPINNER_FRAMES[frame] ?? "\u280B" });
133
162
  };
163
+
164
+ // src/ui/BuildUI.tsx
165
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
134
166
  var STEPS = [
135
167
  "creating-dir",
136
168
  "reading-files",
@@ -147,10 +179,12 @@ var STEP_LABELS = {
147
179
  };
148
180
  var BuildUI = ({ outputDir, pluginDir }) => {
149
181
  const app = useApp();
150
- const [step, setStep] = useState("creating-dir");
151
- const [errorMessage, setErrorMessage] = useState(null);
152
- useEffect(() => {
153
- void runBuild(pluginDir, outputDir, setStep).then(() => {
182
+ const [step, setStep] = useState2("creating-dir");
183
+ const [errorMessage, setErrorMessage] = useState2(null);
184
+ const [bundleSize, setBundleSize] = useState2(null);
185
+ useEffect2(() => {
186
+ void runBuild(pluginDir, outputDir, setStep).then(({ bundleSize: size }) => {
187
+ setBundleSize(size);
154
188
  setStep("done");
155
189
  app.exit();
156
190
  }).catch((err) => {
@@ -163,32 +197,32 @@ var BuildUI = ({ outputDir, pluginDir }) => {
163
197
  const currentIndex = STEPS.indexOf(step);
164
198
  const isError = step === "error";
165
199
  const isDone = step === "done";
166
- return /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
167
- /* @__PURE__ */ jsx2(Logo, {}),
168
- /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [
169
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "Kizen App Builder" }),
170
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2500".repeat(24) })
171
- ] }),
172
- /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", gap: 0, children: [
200
+ return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
201
+ /* @__PURE__ */ jsx4(AppHeader, { marginBottom: 1 }),
202
+ /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", gap: 0, children: [
173
203
  STEPS.map((s, i) => {
174
204
  const isActive = step === s;
175
205
  const isStepDone = isDone || currentIndex > i;
176
206
  const isFailed = isError && i === currentIndex;
177
- return /* @__PURE__ */ jsx2(Box2, { gap: 1, children: isFailed ? /* @__PURE__ */ jsxs(Text2, { color: "red", children: [
207
+ return /* @__PURE__ */ jsx4(Box3, { gap: 1, children: isFailed ? /* @__PURE__ */ jsxs2(Text4, { color: "red", children: [
178
208
  "\u2717 ",
179
209
  STEP_LABELS[s]
180
- ] }) : isStepDone ? /* @__PURE__ */ jsxs(Text2, { color: "green", children: [
210
+ ] }) : isStepDone ? /* @__PURE__ */ jsxs2(Text4, { color: "green", children: [
181
211
  "\u2713 ",
182
212
  STEP_LABELS[s]
183
- ] }) : isActive ? /* @__PURE__ */ jsxs(Fragment, { children: [
184
- /* @__PURE__ */ jsx2(Spinner, {}),
185
- /* @__PURE__ */ jsx2(Text2, { children: STEP_LABELS[s] })
186
- ] }) : /* @__PURE__ */ jsxs(Text2, { dimColor: true, children: [
213
+ ] }) : isActive ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
214
+ /* @__PURE__ */ jsx4(Spinner, {}),
215
+ /* @__PURE__ */ jsx4(Text4, { children: STEP_LABELS[s] })
216
+ ] }) : /* @__PURE__ */ jsxs2(Text4, { dimColor: true, children: [
187
217
  " ",
188
218
  STEP_LABELS[s]
189
219
  ] }) }, s);
190
220
  }),
191
- errorMessage !== null && /* @__PURE__ */ jsx2(Box2, { marginTop: 1, borderStyle: "single", borderColor: "red", paddingX: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "red", children: errorMessage }) })
221
+ isDone && bundleSize !== null && /* @__PURE__ */ jsx4(Box3, { marginTop: 1, gap: 1, children: /* @__PURE__ */ jsxs2(Text4, { color: "green", children: [
222
+ "\u2713 bundle.json \u2014 ",
223
+ formatBytes(bundleSize)
224
+ ] }) }),
225
+ errorMessage !== null && /* @__PURE__ */ jsx4(Box3, { marginTop: 1, borderStyle: "single", borderColor: "red", paddingX: 1, children: /* @__PURE__ */ jsx4(Text4, { color: "red", children: errorMessage }) })
192
226
  ] })
193
227
  ] });
194
228
  };
@@ -227,22 +261,350 @@ import { createElement as createElement2 } from "react";
227
261
  import { render as render2 } from "ink";
228
262
 
229
263
  // src/ui/DevUI.tsx
230
- import { useCallback as useCallback2, useEffect as useEffect3, useRef, useState as useState3 } from "react";
231
- import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
232
- import { createReadStream, watch } from "fs";
233
- import { access, readFile as readFile5 } from "fs/promises";
264
+ import { useCallback as useCallback3, useEffect as useEffect4, useRef, useState as useState5 } from "react";
265
+ import { Box as Box5, Text as Text6, useInput as useInput2 } from "ink";
266
+ import { watch } from "fs";
234
267
  import { createServer } from "http";
235
- import { dirname as dirname3, extname, join as join7 } from "path";
236
- import { fileURLToPath as fileURLToPath2 } from "url";
268
+ import { join as join8 } from "path";
237
269
  import { WebSocketServer } from "ws";
238
270
 
271
+ // src/server/requestHandler.ts
272
+ import { createReadStream } from "fs";
273
+ import { access, readFile as readFile4 } from "fs/promises";
274
+ import { dirname as dirname2, extname, join as join6 } from "path";
275
+ import { fileURLToPath } from "url";
276
+
277
+ // src/lib/proxyCache.ts
278
+ function createProxyCache(options = {}) {
279
+ const cache = /* @__PURE__ */ new Map();
280
+ const { onChange } = options;
281
+ const emitChange = () => {
282
+ onChange?.(cache.size);
283
+ };
284
+ return {
285
+ get(key, fetcher) {
286
+ const existing = cache.get(key);
287
+ if (existing !== void 0) {
288
+ return existing.then((response) => ({ response, fromCache: true }));
289
+ }
290
+ const pending = fetcher().then(async (response) => {
291
+ const headers = Object.fromEntries(response.headers);
292
+ delete headers["content-encoding"];
293
+ delete headers["content-length"];
294
+ return {
295
+ status: response.status,
296
+ headers,
297
+ body: Buffer.from(await response.arrayBuffer())
298
+ };
299
+ }).catch((error) => {
300
+ cache.delete(key);
301
+ emitChange();
302
+ throw error;
303
+ });
304
+ cache.set(key, pending);
305
+ emitChange();
306
+ return pending.then((response) => {
307
+ if (response.status >= 400) {
308
+ cache.delete(key);
309
+ emitChange();
310
+ }
311
+ return { response, fromCache: false };
312
+ });
313
+ },
314
+ clear() {
315
+ cache.clear();
316
+ emitChange();
317
+ }
318
+ };
319
+ }
320
+
321
+ // src/lib/constants.ts
322
+ var LOG_LIMIT = 50;
323
+ var LOG_DISPLAY = 8;
324
+ var FILE_WATCH_DEBOUNCE_MS = 150;
325
+ var MIME_TYPES = {
326
+ ".html": "text/html; charset=utf-8",
327
+ ".js": "application/javascript; charset=utf-8",
328
+ ".mjs": "application/javascript; charset=utf-8",
329
+ ".css": "text/css; charset=utf-8",
330
+ ".png": "image/png",
331
+ ".jpg": "image/jpeg",
332
+ ".svg": "image/svg+xml",
333
+ ".ico": "image/x-icon",
334
+ ".json": "application/json; charset=utf-8",
335
+ ".woff": "font/woff",
336
+ ".woff2": "font/woff2",
337
+ ".ttf": "font/ttf",
338
+ ".map": "application/json"
339
+ };
340
+
341
+ // src/lib/credentials.ts
342
+ import { mkdir as mkdir2, readFile as readFile2, readdir as readdir2, writeFile as writeFile2 } from "fs/promises";
343
+ import { homedir } from "os";
344
+ import { dirname, join as join4 } from "path";
345
+ var ENVIRONMENTS = ["go", "fmo", "staging", "integration", "test1"];
346
+ var GLOBAL_CREDENTIALS_DIR = join4(homedir(), ".kizenappbuilder");
347
+ var GLOBAL_CREDENTIALS_PATH = join4(GLOBAL_CREDENTIALS_DIR, "credentials.json");
348
+ var DEFAULT_PROFILE_NAME = "credentials";
349
+ function isValidEnvironment(value) {
350
+ return ENVIRONMENTS.includes(value);
351
+ }
352
+ function parseCredentials(raw) {
353
+ if (typeof raw !== "object" || raw === null) {
354
+ throw new Error("Credentials must be a JSON object");
355
+ }
356
+ const obj = raw;
357
+ const env = obj.environment;
358
+ return {
359
+ apiKey: typeof obj.apiKey === "string" ? obj.apiKey : "",
360
+ userId: typeof obj.userId === "string" ? obj.userId : "",
361
+ businessId: typeof obj.businessId === "string" ? obj.businessId : "",
362
+ environment: isValidEnvironment(env) ? env : "go"
363
+ };
364
+ }
365
+ async function loadCredentialsFromFile(filePath) {
366
+ const content = await readFile2(filePath, "utf-8");
367
+ return parseCredentials(JSON.parse(content));
368
+ }
369
+ async function loadGlobalCredentials() {
370
+ try {
371
+ return await loadCredentialsFromFile(GLOBAL_CREDENTIALS_PATH);
372
+ } catch {
373
+ return null;
374
+ }
375
+ }
376
+ async function saveGlobalCredentials(credentials) {
377
+ await mkdir2(dirname(GLOBAL_CREDENTIALS_PATH), { recursive: true });
378
+ await writeFile2(GLOBAL_CREDENTIALS_PATH, JSON.stringify(credentials, null, 2), "utf-8");
379
+ }
380
+ function getProfilePath(name) {
381
+ return join4(GLOBAL_CREDENTIALS_DIR, `${name}.json`);
382
+ }
383
+ async function listCredentialProfiles() {
384
+ const profiles = [
385
+ { name: DEFAULT_PROFILE_NAME, path: GLOBAL_CREDENTIALS_PATH, isDefault: true }
386
+ ];
387
+ try {
388
+ const entries = await readdir2(GLOBAL_CREDENTIALS_DIR);
389
+ for (const entry of entries) {
390
+ if (!entry.endsWith(".json")) {
391
+ continue;
392
+ }
393
+ const name = entry.slice(0, -5);
394
+ if (name === DEFAULT_PROFILE_NAME) {
395
+ continue;
396
+ }
397
+ profiles.push({ name, path: join4(GLOBAL_CREDENTIALS_DIR, entry), isDefault: false });
398
+ }
399
+ } catch {
400
+ }
401
+ return profiles;
402
+ }
403
+ async function saveCredentialProfile(name, credentials) {
404
+ if (name === DEFAULT_PROFILE_NAME) {
405
+ await saveGlobalCredentials(credentials);
406
+ return;
407
+ }
408
+ await mkdir2(GLOBAL_CREDENTIALS_DIR, { recursive: true });
409
+ await writeFile2(getProfilePath(name), JSON.stringify(credentials, null, 2), "utf-8");
410
+ }
411
+ async function loadCredentialProfile(name) {
412
+ try {
413
+ return await loadCredentialsFromFile(getProfilePath(name));
414
+ } catch {
415
+ return null;
416
+ }
417
+ }
418
+
419
+ // src/lib/config.ts
420
+ import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
421
+ import { join as join5 } from "path";
422
+ async function loadConfig(outputDir) {
423
+ try {
424
+ const content = await readFile3(join5(outputDir, "config.json"), "utf-8");
425
+ return JSON.parse(content);
426
+ } catch {
427
+ return {};
428
+ }
429
+ }
430
+ async function saveConfig(outputDir, config) {
431
+ await mkdir3(outputDir, { recursive: true });
432
+ await writeFile3(join5(outputDir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
433
+ }
434
+
435
+ // src/server/requestHandler.ts
436
+ function proxyLogEntryToString(entry) {
437
+ if (entry.kind === "info") {
438
+ return entry.message;
439
+ }
440
+ const cache = entry.fromCache ? " [cache]" : "";
441
+ return `${entry.method} ${entry.url} \u2192 ${String(entry.status)}${cache}`;
442
+ }
443
+ function getViewerPath() {
444
+ const filename = fileURLToPath(import.meta.url);
445
+ return join6(dirname2(filename), "viewer");
446
+ }
447
+ async function fileExists(filePath) {
448
+ try {
449
+ await access(filePath);
450
+ return true;
451
+ } catch {
452
+ return false;
453
+ }
454
+ }
455
+ function createRequestHandler(viewerPath, createServerLog, createProxyLog, credentialsRef, activeProfileRef, outputDir, broadcast, onProxyCacheChange) {
456
+ const proxyCache = createProxyCache({ onChange: onProxyCacheChange });
457
+ return (req, res) => {
458
+ void (async () => {
459
+ const url = req.url ?? "/";
460
+ try {
461
+ createServerLog(`Received request: ${url}`);
462
+ if (url === "/api/credentials") {
463
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
464
+ res.end(credentialsRef.current !== null ? JSON.stringify(credentialsRef.current) : "{}");
465
+ return;
466
+ }
467
+ if (url === "/api/credential-profiles" && req.method === "GET") {
468
+ const profiles = await listCredentialProfiles();
469
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
470
+ res.end(JSON.stringify({ profiles, active: activeProfileRef.current }));
471
+ return;
472
+ }
473
+ if (url === "/api/credentials/switch" && req.method === "POST") {
474
+ const chunks = [];
475
+ for await (const chunk of req) {
476
+ chunks.push(chunk);
477
+ }
478
+ const body = JSON.parse(Buffer.concat(chunks).toString("utf-8") || "{}");
479
+ const profileName = body.profile;
480
+ const creds = profileName === void 0 || profileName === "" ? await loadGlobalCredentials() : await loadCredentialProfile(profileName);
481
+ if (creds !== null) {
482
+ credentialsRef.current = creds;
483
+ activeProfileRef.current = profileName ?? void 0;
484
+ const active = activeProfileRef.current;
485
+ await saveConfig(outputDir, {
486
+ credentialMode: "global",
487
+ ...active !== void 0 && { activeCredentialProfile: active }
488
+ });
489
+ broadcast({ type: "credentials-updated", credentials: creds });
490
+ }
491
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
492
+ res.end(JSON.stringify({ ok: creds !== null }));
493
+ return;
494
+ }
495
+ if (url === "/api/bundle") {
496
+ const bundlePath = join6(process.cwd(), ".kizenapp", "bundle.json");
497
+ try {
498
+ const content = await readFile4(bundlePath, "utf-8");
499
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
500
+ res.end(content);
501
+ } catch {
502
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
503
+ res.end("{}");
504
+ }
505
+ return;
506
+ }
507
+ if (url === "/api/proxy-cache/clear") {
508
+ proxyCache.clear();
509
+ createProxyLog({ kind: "info", message: "Proxy cache cleared" });
510
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
511
+ res.end('{"ok":true}');
512
+ return;
513
+ }
514
+ if (url.startsWith("/api/proxy")) {
515
+ const proxyTarget = req.headers["x-proxy-target"];
516
+ if (typeof proxyTarget !== "string") {
517
+ res.writeHead(400);
518
+ res.end("Missing x-proxy-target header");
519
+ return;
520
+ }
521
+ const upstreamPath = url.slice("/api/proxy".length) || "/";
522
+ const upstreamUrl = `${proxyTarget}${upstreamPath}`;
523
+ const method = req.method ?? "GET";
524
+ const chunks = [];
525
+ for await (const chunk of req) {
526
+ chunks.push(chunk);
527
+ }
528
+ const { host, "x-proxy-target": _drop, ...forwardHeaders } = req.headers;
529
+ if (method === "GET") {
530
+ const cacheKey = `GET::${upstreamUrl}`;
531
+ const { response: cached, fromCache } = await proxyCache.get(
532
+ cacheKey,
533
+ () => fetch(upstreamUrl, {
534
+ method: "GET",
535
+ headers: forwardHeaders
536
+ })
537
+ );
538
+ createProxyLog({
539
+ kind: "request",
540
+ method: "GET",
541
+ status: cached.status,
542
+ fromCache,
543
+ url: upstreamPath
544
+ });
545
+ res.writeHead(cached.status, cached.headers);
546
+ res.end(cached.body);
547
+ return;
548
+ }
549
+ const body = chunks.length > 0 ? Buffer.concat(chunks) : void 0;
550
+ const resolvedBody = body && body.length > 0 ? body : void 0;
551
+ const upstream = await fetch(upstreamUrl, {
552
+ method,
553
+ headers: forwardHeaders,
554
+ ...resolvedBody !== void 0 && { body: resolvedBody }
555
+ });
556
+ createProxyLog({
557
+ kind: "request",
558
+ method,
559
+ status: upstream.status,
560
+ fromCache: false,
561
+ url: upstreamPath
562
+ });
563
+ const responseHeaders = Object.fromEntries(upstream.headers);
564
+ delete responseHeaders["content-encoding"];
565
+ delete responseHeaders["content-length"];
566
+ res.writeHead(upstream.status, responseHeaders);
567
+ res.end(Buffer.from(await upstream.arrayBuffer()));
568
+ return;
569
+ }
570
+ const rawPath = url === "/" ? "/index.html" : url;
571
+ const filePath = join6(viewerPath, rawPath);
572
+ const resolvedPath = await fileExists(filePath) ? filePath : join6(viewerPath, "index.html");
573
+ const ext = extname(resolvedPath);
574
+ const mimeType = MIME_TYPES[ext] ?? "application/octet-stream";
575
+ res.writeHead(200, { "Content-Type": mimeType });
576
+ createReadStream(resolvedPath).pipe(res);
577
+ } catch (err) {
578
+ if (url.startsWith("/api/proxy")) {
579
+ createProxyLog({
580
+ kind: "request",
581
+ method: req.method ?? "GET",
582
+ status: 502,
583
+ fromCache: false,
584
+ url: url.slice("/api/proxy".length) || "/"
585
+ });
586
+ } else {
587
+ createProxyLog({
588
+ kind: "info",
589
+ message: `Error handling ${url}: ${err instanceof Error ? err.message : String(err)}`
590
+ });
591
+ }
592
+ if (!res.headersSent) {
593
+ res.writeHead(502);
594
+ res.end("Bad Gateway");
595
+ }
596
+ }
597
+ })();
598
+ };
599
+ }
600
+
239
601
  // src/chrome/launcher.ts
240
602
  import { launch } from "chrome-launcher";
241
603
  import { createRequire } from "module";
242
604
  import { mkdirSync } from "fs";
243
- import { readFile as readFile2 } from "fs/promises";
244
- import { dirname, join as join4 } from "path";
245
- import { fileURLToPath } from "url";
605
+ import { readFile as readFile5 } from "fs/promises";
606
+ import { dirname as dirname3, join as join7 } from "path";
607
+ import { fileURLToPath as fileURLToPath2 } from "url";
246
608
  var CDP = createRequire(import.meta.url)("chrome-remote-interface");
247
609
  var CSP_HEADERS = /* @__PURE__ */ new Set([
248
610
  "content-security-policy",
@@ -259,30 +621,44 @@ function isKizenUrl(url) {
259
621
  }
260
622
  }
261
623
  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;
624
+ if (prop.type === "number") {
625
+ return Number(prop.value);
626
+ }
627
+ if (prop.type === "boolean") {
628
+ return prop.value === "true";
629
+ }
630
+ if (prop.type === "undefined") {
631
+ return void 0;
632
+ }
633
+ if (prop.type === "object" && prop.value === "null") {
634
+ return null;
635
+ }
266
636
  if (prop.type === "object" && prop.valuePreview) {
267
637
  const { valuePreview } = prop;
268
638
  if (valuePreview.subtype === "array") {
269
639
  return valuePreview.properties.filter((p) => /^\d+$/.test(p.name)).sort((a, b) => parseInt(a.name) - parseInt(b.name)).map(parseProp);
270
640
  }
271
641
  const obj = {};
272
- for (const p of valuePreview.properties) obj[p.name] = parseProp(p);
642
+ for (const p of valuePreview.properties) {
643
+ obj[p.name] = parseProp(p);
644
+ }
273
645
  return obj;
274
646
  }
275
647
  return prop.value ?? `[${prop.type}]`;
276
648
  }
277
649
  function serializeArg(arg) {
278
- if (arg.value !== void 0) return arg.value;
650
+ if (arg.value !== void 0) {
651
+ return arg.value;
652
+ }
279
653
  if (arg.preview) {
280
654
  const { preview } = arg;
281
655
  if (preview.subtype === "array") {
282
656
  return preview.properties.filter((p) => /^\d+$/.test(p.name)).sort((a, b) => parseInt(a.name) - parseInt(b.name)).map(parseProp);
283
657
  }
284
658
  const obj = {};
285
- for (const prop of preview.properties) obj[prop.name] = parseProp(prop);
659
+ for (const prop of preview.properties) {
660
+ obj[prop.name] = parseProp(prop);
661
+ }
286
662
  return obj;
287
663
  }
288
664
  return arg.description ?? `[${arg.type}]`;
@@ -296,7 +672,9 @@ async function serializeArgAsync(client, arg) {
296
672
  returnByValue: true
297
673
  });
298
674
  const val = result.result.value;
299
- if (typeof val === "string") return JSON.parse(val);
675
+ if (typeof val === "string") {
676
+ return JSON.parse(val);
677
+ }
300
678
  } catch {
301
679
  }
302
680
  }
@@ -304,8 +682,8 @@ async function serializeArgAsync(client, arg) {
304
682
  }
305
683
  async function installDockIcon(client) {
306
684
  try {
307
- const iconPath = join4(dirname(fileURLToPath(import.meta.url)), "viewer", "icon.png");
308
- const icon = await readFile2(iconPath);
685
+ const iconPath = join7(dirname3(fileURLToPath2(import.meta.url)), "viewer", "icon.png");
686
+ const icon = await readFile5(iconPath);
309
687
  await client.Browser.setDockTile({ image: icon.toString("base64") });
310
688
  } catch {
311
689
  }
@@ -313,9 +691,9 @@ async function installDockIcon(client) {
313
691
  async function launchChromeViewer(opts) {
314
692
  mkdirSync(opts.userDataDir, { recursive: true });
315
693
  const chrome = await launch({
316
- startingUrl: `http://localhost:${opts.port}`,
694
+ startingUrl: `http://localhost:${String(opts.port)}`,
317
695
  chromeFlags: [
318
- `--app=http://localhost:${opts.port}`,
696
+ `--app=http://localhost:${String(opts.port)}`,
319
697
  "--no-first-run",
320
698
  "--no-default-browser-check",
321
699
  "--disable-extensions"
@@ -326,7 +704,7 @@ async function launchChromeViewer(opts) {
326
704
  });
327
705
  let client = null;
328
706
  let cookieTimer = null;
329
- let cdpPort = chrome.port;
707
+ const cdpPort = chrome.port;
330
708
  for (let attempt = 0; attempt < 10; attempt++) {
331
709
  await new Promise((r) => setTimeout(r, 300));
332
710
  try {
@@ -335,14 +713,18 @@ async function launchChromeViewer(opts) {
335
713
  } catch {
336
714
  if (attempt === 9) {
337
715
  return {
338
- kill: () => chrome.kill()
716
+ kill: () => {
717
+ chrome.kill();
718
+ }
339
719
  };
340
720
  }
341
721
  }
342
722
  }
343
723
  if (!client) {
344
724
  return {
345
- kill: () => chrome.kill()
725
+ kill: () => {
726
+ chrome.kill();
727
+ }
346
728
  };
347
729
  }
348
730
  const c = client;
@@ -357,13 +739,17 @@ async function launchChromeViewer(opts) {
357
739
  void (async () => {
358
740
  const params = rawParams;
359
741
  const { requestId, request, responseHeaders, responseStatusCode } = params;
360
- const url = request?.url ?? "";
742
+ const url = request.url;
361
743
  const filtered = [];
362
744
  for (const h of responseHeaders ?? []) {
363
- if (CSP_HEADERS.has(h.name.toLowerCase())) continue;
745
+ if (CSP_HEADERS.has(h.name.toLowerCase())) {
746
+ continue;
747
+ }
364
748
  if (h.name.toLowerCase() === "set-cookie" && isKizenUrl(url)) {
365
749
  const patched = h.value.split("\n").map((cookie) => {
366
- if (!/SameSite=/i.test(cookie)) return cookie + "; SameSite=None; Secure";
750
+ if (!/SameSite=/i.test(cookie)) {
751
+ return cookie + "; SameSite=None; Secure";
752
+ }
367
753
  return cookie.replace(/SameSite=\w+/i, "SameSite=None");
368
754
  }).join("\n");
369
755
  filtered.push({ name: h.name, value: patched });
@@ -393,8 +779,12 @@ async function launchChromeViewer(opts) {
393
779
  try {
394
780
  const { cookies } = await c.Network.getAllCookies();
395
781
  for (const cookie of cookies) {
396
- if (!isKizenUrl(`https://${cookie.domain ?? ""}`)) continue;
397
- if (cookie.sameSite === "None") continue;
782
+ if (!isKizenUrl(`https://${cookie.domain ?? ""}`)) {
783
+ continue;
784
+ }
785
+ if (cookie.sameSite === "None") {
786
+ continue;
787
+ }
398
788
  const raw = cookie.domain ?? "";
399
789
  const bare = raw.startsWith(".") ? raw.slice(1) : raw;
400
790
  await c.Network.setCookie({
@@ -437,173 +827,63 @@ async function launchChromeViewer(opts) {
437
827
  c.on("Runtime.consoleAPICalled", (rawParams) => {
438
828
  void (async () => {
439
829
  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
- }
457
-
458
- // src/lib/proxyCache.ts
459
- function createProxyCache() {
460
- const cache = /* @__PURE__ */ new Map();
461
- return {
462
- get(key, fetcher) {
463
- const existing = cache.get(key);
464
- if (existing !== void 0) {
465
- return existing.then((response) => ({ response, fromCache: true }));
466
- }
467
- const pending = fetcher().then(async (response) => {
468
- const headers = Object.fromEntries(response.headers);
469
- delete headers["content-encoding"];
470
- delete headers["content-length"];
471
- return {
472
- status: response.status,
473
- headers,
474
- body: Buffer.from(await response.arrayBuffer())
475
- };
476
- }).catch((error) => {
477
- cache.delete(key);
478
- throw error;
479
- });
480
- cache.set(key, pending);
481
- return pending.then((response) => ({ response, fromCache: false }));
482
- },
483
- clear() {
484
- cache.clear();
485
- }
486
- };
487
- }
488
-
489
- // src/lib/credentials.ts
490
- import { mkdir as mkdir2, readFile as readFile3, readdir as readdir2, writeFile as writeFile2 } from "fs/promises";
491
- import { homedir } from "os";
492
- import { dirname as dirname2, join as join5 } from "path";
493
- var ENVIRONMENTS = ["go", "fmo", "staging", "integration", "test1"];
494
- var GLOBAL_CREDENTIALS_DIR = join5(homedir(), ".kizenappbuilder");
495
- var GLOBAL_CREDENTIALS_PATH = join5(GLOBAL_CREDENTIALS_DIR, "credentials.json");
496
- var DEFAULT_PROFILE_NAME = "credentials";
497
- function isValidEnvironment(value) {
498
- return ENVIRONMENTS.includes(value);
499
- }
500
- function parseCredentials(raw) {
501
- if (typeof raw !== "object" || raw === null) {
502
- throw new Error("Credentials must be a JSON object");
503
- }
504
- const obj = raw;
505
- const env = obj.environment;
506
- return {
507
- apiKey: typeof obj.apiKey === "string" ? obj.apiKey : "",
508
- userId: typeof obj.userId === "string" ? obj.userId : "",
509
- businessId: typeof obj.businessId === "string" ? obj.businessId : "",
510
- environment: isValidEnvironment(env) ? env : "go"
511
- };
512
- }
513
- async function loadCredentialsFromFile(filePath) {
514
- const content = await readFile3(filePath, "utf-8");
515
- return parseCredentials(JSON.parse(content));
516
- }
517
- async function loadGlobalCredentials() {
518
- try {
519
- return await loadCredentialsFromFile(GLOBAL_CREDENTIALS_PATH);
520
- } catch {
521
- return null;
522
- }
523
- }
524
- async function saveGlobalCredentials(credentials) {
525
- await mkdir2(dirname2(GLOBAL_CREDENTIALS_PATH), { recursive: true });
526
- await writeFile2(GLOBAL_CREDENTIALS_PATH, JSON.stringify(credentials, null, 2), "utf-8");
527
- }
528
- function getProfilePath(name) {
529
- return join5(GLOBAL_CREDENTIALS_DIR, `${name}.json`);
530
- }
531
- async function listCredentialProfiles() {
532
- const profiles = [
533
- { name: DEFAULT_PROFILE_NAME, path: GLOBAL_CREDENTIALS_PATH, isDefault: true }
534
- ];
535
- try {
536
- const entries = await readdir2(GLOBAL_CREDENTIALS_DIR);
537
- for (const entry of entries) {
538
- if (!entry.endsWith(".json")) {
539
- continue;
540
- }
541
- const name = entry.slice(0, -5);
542
- if (name === DEFAULT_PROFILE_NAME) {
543
- continue;
830
+ if (args[0]?.type === "string" && String(args[0].value).includes("Chrome Security")) {
831
+ return;
544
832
  }
545
- profiles.push({ name, path: join5(GLOBAL_CREDENTIALS_DIR, entry), isDefault: false });
833
+ const level = type === "warning" ? "warn" : ["log", "warn", "error", "info"].includes(type) ? type : "log";
834
+ const serialized = await Promise.all(args.map((arg) => serializeArgAsync(c, arg)));
835
+ opts.broadcast({ type: "console-message", level, args: serialized });
836
+ })().catch(() => {
837
+ });
838
+ });
839
+ const kill = () => {
840
+ clearInterval(cookieTimer);
841
+ try {
842
+ void c.close();
843
+ } catch {
546
844
  }
547
- } catch {
548
- }
549
- return profiles;
550
- }
551
- async function saveCredentialProfile(name, credentials) {
552
- if (name === DEFAULT_PROFILE_NAME) {
553
- await saveGlobalCredentials(credentials);
554
- return;
555
- }
556
- await mkdir2(GLOBAL_CREDENTIALS_DIR, { recursive: true });
557
- await writeFile2(getProfilePath(name), JSON.stringify(credentials, null, 2), "utf-8");
558
- }
559
- async function loadCredentialProfile(name) {
560
- try {
561
- return await loadCredentialsFromFile(getProfilePath(name));
562
- } catch {
563
- return null;
564
- }
845
+ chrome.kill();
846
+ };
847
+ return { kill };
565
848
  }
566
849
 
567
- // src/lib/config.ts
568
- import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
569
- import { join as join6 } from "path";
570
- async function loadConfig(outputDir) {
571
- try {
572
- const content = await readFile4(join6(outputDir, "config.json"), "utf-8");
573
- return JSON.parse(content);
574
- } catch {
575
- return {};
576
- }
577
- }
578
- async function saveConfig(outputDir, config) {
579
- await mkdir3(outputDir, { recursive: true });
580
- await writeFile3(join6(outputDir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
850
+ // src/hooks/useLogHistory.ts
851
+ import { useCallback, useState as useState3 } from "react";
852
+ function useLogHistory(limit) {
853
+ const [history, setHistory] = useState3([]);
854
+ const append = useCallback(
855
+ (message) => {
856
+ setHistory((h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-limit));
857
+ },
858
+ [limit]
859
+ );
860
+ return [history, append];
581
861
  }
582
862
 
583
863
  // src/ui/CredentialSetupUI.tsx
584
- import { useCallback, useEffect as useEffect2, useState as useState2 } from "react";
585
- import { Box as Box3, Text as Text3, useInput } from "ink";
586
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
864
+ import { useCallback as useCallback2, useEffect as useEffect3, useState as useState4 } from "react";
865
+ import { Box as Box4, Text as Text5, useInput } from "ink";
866
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
587
867
  var FIELDS = ["apiKey", "userId", "businessId"];
588
868
  var FIELD_LABELS = {
589
869
  apiKey: "API Key",
590
870
  userId: "User ID",
591
871
  businessId: "Business ID"
592
872
  };
593
- var Hint = ({ text }) => /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: text });
873
+ var Hint = ({ text }) => /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: text });
594
874
  var CredentialSetupUI = ({
595
875
  initialMode,
596
876
  showProfileManager,
597
877
  onComplete,
598
878
  onCancel
599
879
  }) => {
600
- const [phase, setPhase] = useState2(
880
+ const [phase, setPhase] = useState4(
601
881
  initialMode === "global" && !showProfileManager ? { type: "loading" } : initialMode === "global" && showProfileManager ? { type: "profile-loading" } : { type: "mode-select", cursor: 0 }
602
882
  );
603
- const [inputBuffer, setInputBuffer] = useState2("");
604
- const [error, setError] = useState2(null);
605
- const [activeProfileName, setActiveProfileName] = useState2(void 0);
606
- const handleModeChosen = useCallback(
883
+ const [inputBuffer, setInputBuffer] = useState4("");
884
+ const [error, setError] = useState4(null);
885
+ const [activeProfileName, setActiveProfileName] = useState4(void 0);
886
+ const handleModeChosen = useCallback2(
607
887
  (mode) => {
608
888
  if (mode === "local") {
609
889
  onComplete({ mode: "local", credentials: null });
@@ -613,7 +893,7 @@ var CredentialSetupUI = ({
613
893
  },
614
894
  [onComplete]
615
895
  );
616
- const handleProfileChosen = useCallback(async (profile) => {
896
+ const handleProfileChosen = useCallback2(async (profile) => {
617
897
  if (profile === null) {
618
898
  setPhase({ type: "profile-loading" });
619
899
  const profiles = await listCredentialProfiles();
@@ -626,7 +906,7 @@ var CredentialSetupUI = ({
626
906
  const envCursor2 = existing ? Math.max(0, ENVIRONMENTS.indexOf(existing.environment)) : 0;
627
907
  setPhase({ type: "creds-entry", field: 0, values: existing ?? {}, envCursor: envCursor2 });
628
908
  }, []);
629
- const handleSave = useCallback(
909
+ const handleSave = useCallback2(
630
910
  async (values2, envCursor2) => {
631
911
  const environment = ENVIRONMENTS[envCursor2] ?? "go";
632
912
  const credentials = {
@@ -767,16 +1047,16 @@ var CredentialSetupUI = ({
767
1047
  }
768
1048
  }
769
1049
  });
770
- const loadProfileList = useCallback(async () => {
1050
+ const loadProfileList = useCallback2(async () => {
771
1051
  const profiles = await listCredentialProfiles();
772
1052
  setPhase({ type: "profile-select", profiles, cursor: 0 });
773
1053
  }, []);
774
- useEffect2(() => {
1054
+ useEffect3(() => {
775
1055
  if (phase.type === "profile-loading") {
776
1056
  void loadProfileList();
777
1057
  }
778
1058
  }, [phase.type, loadProfileList]);
779
- useEffect2(() => {
1059
+ useEffect3(() => {
780
1060
  if (initialMode === "global" && !showProfileManager) {
781
1061
  void (async () => {
782
1062
  const existing = await loadGlobalCredentials();
@@ -785,14 +1065,14 @@ var CredentialSetupUI = ({
785
1065
  })();
786
1066
  }
787
1067
  }, []);
788
- useEffect2(() => {
1068
+ useEffect3(() => {
789
1069
  if (phase.type === "creds-entry" && phase.field === 0) {
790
1070
  setInputBuffer(phase.values.apiKey ?? "");
791
1071
  }
792
1072
  }, [phase.type]);
793
1073
  if (phase.type === "loading" || phase.type === "saving" || phase.type === "profile-loading") {
794
1074
  const label = phase.type === "saving" ? "Saving credentials\u2026" : phase.type === "profile-loading" ? "Loading profiles\u2026" : "Loading credentials\u2026";
795
- return /* @__PURE__ */ jsx3(Box3, { paddingY: 1, paddingX: 2, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: label }) });
1075
+ return /* @__PURE__ */ jsx5(Box4, { paddingY: 1, paddingX: 2, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: label }) });
796
1076
  }
797
1077
  if (phase.type === "done") {
798
1078
  return null;
@@ -802,49 +1082,41 @@ var CredentialSetupUI = ({
802
1082
  { label: "Global", desc: `~/.kizenappbuilder/credentials.json`, mode: "global" },
803
1083
  { label: "Local", desc: "Enter credentials in the browser dev tools", mode: "local" }
804
1084
  ];
805
- return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
806
- /* @__PURE__ */ jsx3(Logo, {}),
807
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, children: [
808
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "Kizen App Builder" }),
809
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2500".repeat(24) })
810
- ] }),
811
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", gap: 0, children: [
812
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Credential mode" }),
1085
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
1086
+ /* @__PURE__ */ jsx5(AppHeader, {}),
1087
+ /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
1088
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Credential mode" }),
813
1089
  options.map((opt, i) => {
814
1090
  const selected = phase.cursor === i;
815
- return /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
816
- /* @__PURE__ */ jsx3(Text3, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
817
- /* @__PURE__ */ jsx3(Text3, { bold: selected, ...selected && { color: "cyan" }, children: opt.label }),
818
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: opt.desc })
1091
+ return /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1092
+ /* @__PURE__ */ jsx5(Text5, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
1093
+ /* @__PURE__ */ jsx5(Text5, { bold: selected, ...selected && { color: "cyan" }, children: opt.label }),
1094
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: opt.desc })
819
1095
  ] }, opt.mode);
820
1096
  })
821
1097
  ] }),
822
- /* @__PURE__ */ jsx3(Hint, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Ctrl+C to quit" })
1098
+ /* @__PURE__ */ jsx5(Hint, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Ctrl+C to quit" })
823
1099
  ] });
824
1100
  }
825
1101
  if (phase.type === "profile-select") {
826
1102
  const { profiles, cursor } = phase;
827
1103
  const addNewIdx = profiles.length;
828
- return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
829
- /* @__PURE__ */ jsx3(Logo, {}),
830
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, children: [
831
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "Kizen App Builder" }),
832
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2500".repeat(24) })
833
- ] }),
834
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", gap: 0, children: [
835
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Credential profiles" }),
1104
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
1105
+ /* @__PURE__ */ jsx5(AppHeader, {}),
1106
+ /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
1107
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Credential profiles" }),
836
1108
  profiles.map((profile, i) => {
837
1109
  const selected = cursor === i;
838
- return /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
839
- /* @__PURE__ */ jsx3(Text3, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
840
- /* @__PURE__ */ jsx3(Text3, { bold: selected, ...selected && { color: "cyan" }, children: profile.isDefault ? "Default" : profile.name }),
841
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: profile.path })
1110
+ return /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1111
+ /* @__PURE__ */ jsx5(Text5, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
1112
+ /* @__PURE__ */ jsx5(Text5, { bold: selected, ...selected && { color: "cyan" }, children: profile.isDefault ? "Default" : profile.name }),
1113
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: profile.path })
842
1114
  ] }, profile.name);
843
1115
  }),
844
- /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
845
- /* @__PURE__ */ jsx3(Text3, { ...cursor === addNewIdx && { color: "cyan" }, children: cursor === addNewIdx ? "\u276F" : " " }),
846
- /* @__PURE__ */ jsx3(
847
- Text3,
1116
+ /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1117
+ /* @__PURE__ */ jsx5(Text5, { ...cursor === addNewIdx && { color: "cyan" }, children: cursor === addNewIdx ? "\u276F" : " " }),
1118
+ /* @__PURE__ */ jsx5(
1119
+ Text5,
848
1120
  {
849
1121
  bold: cursor === addNewIdx,
850
1122
  ...cursor === addNewIdx && { color: "cyan" },
@@ -853,77 +1125,69 @@ var CredentialSetupUI = ({
853
1125
  )
854
1126
  ] })
855
1127
  ] }),
856
- /* @__PURE__ */ jsx3(Hint, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Esc to cancel" })
1128
+ /* @__PURE__ */ jsx5(Hint, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Esc to cancel" })
857
1129
  ] });
858
1130
  }
859
1131
  if (phase.type === "name-entry") {
860
1132
  const { nameBuffer, nameError } = phase;
861
- return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
862
- /* @__PURE__ */ jsx3(Logo, {}),
863
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, children: [
864
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "Kizen App Builder" }),
865
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2500".repeat(24) })
866
- ] }),
867
- /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", gap: 0, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: "New credential profile" }) }),
868
- nameError && /* @__PURE__ */ jsxs2(Text3, { color: "red", children: [
1133
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
1134
+ /* @__PURE__ */ jsx5(AppHeader, {}),
1135
+ /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", gap: 0, children: /* @__PURE__ */ jsx5(Text5, { bold: true, children: "New credential profile" }) }),
1136
+ nameError && /* @__PURE__ */ jsxs3(Text5, { color: "red", children: [
869
1137
  "Error: ",
870
1138
  nameError
871
1139
  ] }),
872
- /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
873
- /* @__PURE__ */ jsx3(Box3, { width: 12, children: /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "Profile name" }) }),
874
- /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: ">" }),
875
- /* @__PURE__ */ jsx3(Text3, { children: nameBuffer }),
876
- /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: "\u2588" })
1140
+ /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1141
+ /* @__PURE__ */ jsx5(Box4, { width: 12, children: /* @__PURE__ */ jsx5(Text5, { bold: true, color: "cyan", children: "Profile name" }) }),
1142
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: ">" }),
1143
+ /* @__PURE__ */ jsx5(Text5, { children: nameBuffer }),
1144
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u2588" })
877
1145
  ] }),
878
- /* @__PURE__ */ jsx3(Hint, { text: "Type a name \xB7 Enter to confirm \xB7 Esc to go back" })
1146
+ /* @__PURE__ */ jsx5(Hint, { text: "Type a name \xB7 Enter to confirm \xB7 Esc to go back" })
879
1147
  ] });
880
1148
  }
881
1149
  const { field: activeField, values, envCursor } = phase;
882
1150
  const isEnvField = activeField === FIELDS.length;
883
1151
  const profileLabel = activeProfileName ?? DEFAULT_PROFILE_NAME;
884
- return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
885
- /* @__PURE__ */ jsx3(Logo, {}),
886
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, children: [
887
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "Kizen App Builder" }),
888
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2500".repeat(24) })
889
- ] }),
890
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", gap: 0, children: [
891
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: activeProfileName !== void 0 ? `Profile: ${activeProfileName}` : "Global credentials" }),
892
- /* @__PURE__ */ jsxs2(Text3, { dimColor: true, children: [
1152
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
1153
+ /* @__PURE__ */ jsx5(AppHeader, {}),
1154
+ /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
1155
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: activeProfileName !== void 0 ? `Profile: ${activeProfileName}` : "Global credentials" }),
1156
+ /* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
893
1157
  "Saved to:",
894
1158
  " ",
895
1159
  activeProfileName !== void 0 ? `~/.kizenappbuilder/${activeProfileName}.json` : GLOBAL_CREDENTIALS_PATH
896
1160
  ] })
897
1161
  ] }),
898
- error && /* @__PURE__ */ jsxs2(Text3, { color: "red", children: [
1162
+ error && /* @__PURE__ */ jsxs3(Text5, { color: "red", children: [
899
1163
  "Error: ",
900
1164
  error
901
1165
  ] }),
902
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", gap: 0, children: [
1166
+ /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
903
1167
  FIELDS.map((name, i) => {
904
1168
  const isActive = activeField === i;
905
1169
  const displayValue = isActive ? inputBuffer : values[name] ?? "";
906
- return /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
907
- /* @__PURE__ */ jsx3(Box3, { width: 12, children: /* @__PURE__ */ jsx3(Text3, { bold: isActive, ...isActive && { color: "cyan" }, children: FIELD_LABELS[name] }) }),
908
- /* @__PURE__ */ jsx3(Text3, { ...isActive && { color: "cyan" }, children: ">" }),
909
- /* @__PURE__ */ jsx3(Text3, { children: displayValue }),
910
- isActive && /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: "\u2588" })
1170
+ return /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1171
+ /* @__PURE__ */ jsx5(Box4, { width: 12, children: /* @__PURE__ */ jsx5(Text5, { bold: isActive, ...isActive && { color: "cyan" }, children: FIELD_LABELS[name] }) }),
1172
+ /* @__PURE__ */ jsx5(Text5, { ...isActive && { color: "cyan" }, children: ">" }),
1173
+ /* @__PURE__ */ jsx5(Text5, { children: displayValue }),
1174
+ isActive && /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u2588" })
911
1175
  ] }, name);
912
1176
  }),
913
- /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
914
- /* @__PURE__ */ jsx3(Box3, { width: 12, children: /* @__PURE__ */ jsx3(Text3, { bold: isEnvField, ...isEnvField && { color: "cyan" }, children: "Environment" }) }),
915
- /* @__PURE__ */ jsx3(Text3, { ...isEnvField && { color: "cyan" }, children: ">" }),
916
- /* @__PURE__ */ jsx3(Box3, { gap: 1, children: ENVIRONMENTS.map((env, i) => {
1177
+ /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1178
+ /* @__PURE__ */ jsx5(Box4, { width: 12, children: /* @__PURE__ */ jsx5(Text5, { bold: isEnvField, ...isEnvField && { color: "cyan" }, children: "Environment" }) }),
1179
+ /* @__PURE__ */ jsx5(Text5, { ...isEnvField && { color: "cyan" }, children: ">" }),
1180
+ /* @__PURE__ */ jsx5(Box4, { gap: 1, children: ENVIRONMENTS.map((env, i) => {
917
1181
  const isSelected = i === envCursor;
918
1182
  if (isEnvField) {
919
- return /* @__PURE__ */ jsx3(Text3, { bold: isSelected, ...isSelected && { color: "cyan" }, children: isSelected ? `[${env}]` : env }, env);
1183
+ return /* @__PURE__ */ jsx5(Text5, { bold: isSelected, ...isSelected && { color: "cyan" }, children: isSelected ? `[${env}]` : env }, env);
920
1184
  }
921
- return /* @__PURE__ */ jsx3(Text3, { dimColor: !isSelected, bold: isSelected, children: env }, env);
1185
+ return /* @__PURE__ */ jsx5(Text5, { dimColor: !isSelected, bold: isSelected, children: env }, env);
922
1186
  }) })
923
1187
  ] })
924
1188
  ] }),
925
- isEnvField ? /* @__PURE__ */ jsx3(Hint, { text: "\u2190\u2192 to select \xB7 \u2191\u2193 to move \xB7 Enter to save \xB7 Esc to cancel" }) : /* @__PURE__ */ jsx3(Hint, { text: "\u2191\u2193 to move \xB7 Backspace to delete \xB7 Esc to cancel" }),
926
- /* @__PURE__ */ jsxs2(Text3, { dimColor: true, children: [
1189
+ isEnvField ? /* @__PURE__ */ jsx5(Hint, { text: "\u2190\u2192 to select \xB7 \u2191\u2193 to move \xB7 Enter to save \xB7 Esc to cancel" }) : /* @__PURE__ */ jsx5(Hint, { text: "\u2191\u2193 to move \xB7 Backspace to delete \xB7 Esc to cancel" }),
1190
+ /* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
927
1191
  "Profile: ",
928
1192
  profileLabel
929
1193
  ] })
@@ -931,202 +1195,8 @@ var CredentialSetupUI = ({
931
1195
  };
932
1196
 
933
1197
  // src/ui/DevUI.tsx
934
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
935
- function proxyLogEntryToString(entry) {
936
- if (entry.kind === "info") {
937
- return entry.message;
938
- }
939
- const cache = entry.fromCache ? " [cache]" : "";
940
- return `${entry.method} ${entry.url} \u2192 ${String(entry.status)}${cache}`;
941
- }
1198
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
942
1199
  var SKIP_WATCH_PREFIXES = [".kizenapp", ".git"];
943
- var LOG_LIMIT = 50;
944
- var LOG_DISPLAY = 8;
945
- var MIME_TYPES = {
946
- ".html": "text/html; charset=utf-8",
947
- ".js": "application/javascript; charset=utf-8",
948
- ".mjs": "application/javascript; charset=utf-8",
949
- ".css": "text/css; charset=utf-8",
950
- ".png": "image/png",
951
- ".jpg": "image/jpeg",
952
- ".svg": "image/svg+xml",
953
- ".ico": "image/x-icon",
954
- ".json": "application/json; charset=utf-8",
955
- ".woff": "font/woff",
956
- ".woff2": "font/woff2",
957
- ".ttf": "font/ttf",
958
- ".map": "application/json"
959
- };
960
- var SPINNER_FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
961
- var Spinner2 = () => {
962
- const [frame, setFrame] = useState3(0);
963
- useEffect3(() => {
964
- const id = setInterval(() => {
965
- setFrame((prev) => (prev + 1) % SPINNER_FRAMES2.length);
966
- }, 80);
967
- return () => {
968
- clearInterval(id);
969
- };
970
- }, []);
971
- return /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: SPINNER_FRAMES2[frame] ?? "\u280B" });
972
- };
973
- function getViewerPath() {
974
- const filename = fileURLToPath2(import.meta.url);
975
- return join7(dirname3(filename), "viewer");
976
- }
977
- async function fileExists(filePath) {
978
- try {
979
- await access(filePath);
980
- return true;
981
- } catch {
982
- return false;
983
- }
984
- }
985
- function createRequestHandler(viewerPath, createServerLog, createProxyLog, credentialsRef, activeProfileRef, outputDir, broadcast) {
986
- const proxyCache = createProxyCache();
987
- return (req, res) => {
988
- void (async () => {
989
- const url = req.url ?? "/";
990
- try {
991
- createServerLog(`Received request: ${url}`);
992
- if (url === "/api/credentials") {
993
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
994
- res.end(credentialsRef.current !== null ? JSON.stringify(credentialsRef.current) : "{}");
995
- return;
996
- }
997
- if (url === "/api/credential-profiles" && req.method === "GET") {
998
- const profiles = await listCredentialProfiles();
999
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1000
- res.end(JSON.stringify({ profiles, active: activeProfileRef.current }));
1001
- return;
1002
- }
1003
- if (url === "/api/credentials/switch" && req.method === "POST") {
1004
- const chunks = [];
1005
- for await (const chunk of req) {
1006
- chunks.push(chunk);
1007
- }
1008
- const body = JSON.parse(Buffer.concat(chunks).toString("utf-8") || "{}");
1009
- const profileName = body.profile;
1010
- const creds = profileName === void 0 || profileName === "" ? await loadGlobalCredentials() : await loadCredentialProfile(profileName);
1011
- if (creds !== null) {
1012
- credentialsRef.current = creds;
1013
- activeProfileRef.current = profileName ?? void 0;
1014
- const active = activeProfileRef.current;
1015
- await saveConfig(outputDir, {
1016
- credentialMode: "global",
1017
- ...active !== void 0 && { activeCredentialProfile: active }
1018
- });
1019
- broadcast({ type: "credentials-updated", credentials: creds });
1020
- }
1021
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1022
- res.end(JSON.stringify({ ok: creds !== null }));
1023
- return;
1024
- }
1025
- if (url === "/api/bundle") {
1026
- const bundlePath = join7(process.cwd(), ".kizenapp", "bundle.json");
1027
- try {
1028
- const content = await readFile5(bundlePath, "utf-8");
1029
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1030
- res.end(content);
1031
- } catch {
1032
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1033
- res.end("{}");
1034
- }
1035
- return;
1036
- }
1037
- if (url === "/api/proxy-cache/clear") {
1038
- proxyCache.clear();
1039
- createProxyLog({ kind: "info", message: "Proxy cache cleared" });
1040
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1041
- res.end('{"ok":true}');
1042
- return;
1043
- }
1044
- if (url.startsWith("/api/proxy")) {
1045
- const proxyTarget = req.headers["x-proxy-target"];
1046
- if (typeof proxyTarget !== "string") {
1047
- res.writeHead(400);
1048
- res.end("Missing x-proxy-target header");
1049
- return;
1050
- }
1051
- const upstreamPath = url.slice("/api/proxy".length) || "/";
1052
- const upstreamUrl = `${proxyTarget}${upstreamPath}`;
1053
- const method = req.method ?? "GET";
1054
- const chunks = [];
1055
- for await (const chunk of req) {
1056
- chunks.push(chunk);
1057
- }
1058
- const { host, "x-proxy-target": _drop, ...forwardHeaders } = req.headers;
1059
- if (method === "GET") {
1060
- const cacheKey = `GET::${upstreamUrl}`;
1061
- const { response: cached, fromCache } = await proxyCache.get(
1062
- cacheKey,
1063
- () => fetch(upstreamUrl, {
1064
- method: "GET",
1065
- headers: forwardHeaders
1066
- })
1067
- );
1068
- createProxyLog({
1069
- kind: "request",
1070
- method: "GET",
1071
- status: cached.status,
1072
- fromCache,
1073
- url: upstreamPath
1074
- });
1075
- res.writeHead(cached.status, cached.headers);
1076
- res.end(cached.body);
1077
- return;
1078
- }
1079
- const body = chunks.length > 0 ? Buffer.concat(chunks) : void 0;
1080
- const resolvedBody = body && body.length > 0 ? body : void 0;
1081
- const upstream = await fetch(upstreamUrl, {
1082
- method,
1083
- headers: forwardHeaders,
1084
- ...resolvedBody !== void 0 && { body: resolvedBody }
1085
- });
1086
- createProxyLog({
1087
- kind: "request",
1088
- method,
1089
- status: upstream.status,
1090
- fromCache: false,
1091
- url: upstreamPath
1092
- });
1093
- const responseHeaders = Object.fromEntries(upstream.headers);
1094
- delete responseHeaders["content-encoding"];
1095
- delete responseHeaders["content-length"];
1096
- res.writeHead(upstream.status, responseHeaders);
1097
- res.end(Buffer.from(await upstream.arrayBuffer()));
1098
- return;
1099
- }
1100
- const rawPath = url === "/" ? "/index.html" : url;
1101
- const filePath = join7(viewerPath, rawPath);
1102
- const resolvedPath = await fileExists(filePath) ? filePath : join7(viewerPath, "index.html");
1103
- const ext = extname(resolvedPath);
1104
- const mimeType = MIME_TYPES[ext] ?? "application/octet-stream";
1105
- res.writeHead(200, { "Content-Type": mimeType });
1106
- createReadStream(resolvedPath).pipe(res);
1107
- } catch (err) {
1108
- if (url.startsWith("/api/proxy")) {
1109
- createProxyLog({
1110
- kind: "request",
1111
- method: req.method ?? "GET",
1112
- status: 502,
1113
- fromCache: false,
1114
- url: url.slice("/api/proxy".length) || "/"
1115
- });
1116
- } else {
1117
- createProxyLog({
1118
- kind: "info",
1119
- message: `Error handling ${url}: ${err instanceof Error ? err.message : String(err)}`
1120
- });
1121
- }
1122
- if (!res.headersSent) {
1123
- res.writeHead(502);
1124
- res.end("Bad Gateway");
1125
- }
1126
- }
1127
- })();
1128
- };
1129
- }
1130
1200
  var DevUI = ({
1131
1201
  port,
1132
1202
  pluginDir,
@@ -1137,26 +1207,30 @@ var DevUI = ({
1137
1207
  }) => {
1138
1208
  const credentialsRef = useRef(initialCredentials);
1139
1209
  const activeProfileRef = useRef(initialActiveProfile);
1140
- const [credMode, setCredMode] = useState3("main");
1141
- const [credentialMode, setCredentialMode] = useState3(
1210
+ const [credMode, setCredMode] = useState5("main");
1211
+ const [credentialMode, setCredentialMode] = useState5(
1142
1212
  initialCredentialMode
1143
1213
  );
1144
- const [status, setStatus] = useState3("starting");
1145
- const [errorMessage, setErrorMessage] = useState3(null);
1146
- const [serverLogHistory, setServerLogHistory] = useState3([]);
1147
- const [buildLogHistory, setBuildLogHistory] = useState3([]);
1148
- const [proxyLogHistory, setProxyLogHistory] = useState3([]);
1149
- const [buildStatus, setBuildStatus] = useState3("pending");
1150
- const [buildError, setBuildError] = useState3(null);
1151
- const [lastBuilt, setLastBuilt] = useState3(null);
1152
- const [wsClientCount, setWsClientCount] = useState3(0);
1214
+ const [status, setStatus] = useState5("starting");
1215
+ const [errorMessage, setErrorMessage] = useState5(null);
1216
+ const [serverLogHistory, appendServerLog] = useLogHistory(LOG_LIMIT);
1217
+ const [buildLogHistory, appendBuildLog] = useLogHistory(LOG_LIMIT);
1218
+ const [proxyLogHistory, setProxyLogHistory] = useState5([]);
1219
+ const [buildStatus, setBuildStatus] = useState5("pending");
1220
+ const [buildError, setBuildError] = useState5(null);
1221
+ const [lastBuilt, setLastBuilt] = useState5(null);
1222
+ const [lastBundleSize, setLastBundleSize] = useState5(null);
1223
+ const [wsClientCount, setWsClientCount] = useState5(0);
1224
+ const [proxyCacheSize, setProxyCacheSize] = useState5(0);
1225
+ const [proxyCacheHits, setProxyCacheHits] = useState5(0);
1226
+ const [proxyCacheMisses, setProxyCacheMisses] = useState5(0);
1153
1227
  const buildingRef = useRef(false);
1154
1228
  const viewerLaunchedRef = useRef(false);
1155
1229
  const chromeHandleRef = useRef(null);
1156
1230
  const debounceTimerRef = useRef(null);
1157
1231
  const wsClientsRef = useRef(/* @__PURE__ */ new Set());
1158
1232
  const pendingMessagesRef = useRef([]);
1159
- const broadcast = useCallback2((msg) => {
1233
+ const broadcast = useCallback3((msg) => {
1160
1234
  const json = JSON.stringify(msg);
1161
1235
  if (wsClientsRef.current.size === 0) {
1162
1236
  pendingMessagesRef.current = [...pendingMessagesRef.current, json].slice(-LOG_LIMIT);
@@ -1168,21 +1242,19 @@ var DevUI = ({
1168
1242
  }
1169
1243
  }
1170
1244
  }, []);
1171
- const createServerLog = useCallback2((message) => {
1172
- setServerLogHistory(
1173
- (h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-LOG_LIMIT)
1174
- );
1175
- }, []);
1176
- const launchChromeViewerCb = useCallback2(() => {
1245
+ const createServerLog = appendServerLog;
1246
+ const launchChromeViewerCb = useCallback3(() => {
1177
1247
  chromeHandleRef.current?.kill();
1178
1248
  chromeHandleRef.current = null;
1179
1249
  void launchChromeViewer({
1180
1250
  port,
1181
- userDataDir: join7(outputDir, ".chrome"),
1251
+ userDataDir: join8(outputDir, ".chrome"),
1182
1252
  broadcast
1183
1253
  }).then((handle) => {
1184
1254
  chromeHandleRef.current = handle;
1185
- process.on("exit", () => handle.kill());
1255
+ process.on("exit", () => {
1256
+ handle.kill();
1257
+ });
1186
1258
  });
1187
1259
  }, [port, outputDir, broadcast]);
1188
1260
  useInput2(
@@ -1199,14 +1271,14 @@ var DevUI = ({
1199
1271
  },
1200
1272
  { isActive: credMode === "main" }
1201
1273
  );
1202
- useEffect3(() => {
1274
+ useEffect4(() => {
1203
1275
  if (status !== "running" || viewerLaunchedRef.current) {
1204
1276
  return;
1205
1277
  }
1206
1278
  viewerLaunchedRef.current = true;
1207
1279
  launchChromeViewerCb();
1208
1280
  }, [status, launchChromeViewerCb]);
1209
- const handleCredentialsDone = useCallback2(
1281
+ const handleCredentialsDone = useCallback3(
1210
1282
  (result) => {
1211
1283
  credentialsRef.current = result.credentials;
1212
1284
  activeProfileRef.current = result.profileName;
@@ -1221,27 +1293,32 @@ var DevUI = ({
1221
1293
  },
1222
1294
  [outputDir, broadcast]
1223
1295
  );
1224
- const createProxyLog = useCallback2(
1296
+ const createProxyLog = useCallback3(
1225
1297
  (entry) => {
1226
1298
  setProxyLogHistory(
1227
1299
  (h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${proxyLogEntryToString(entry)}`].slice(
1228
1300
  -LOG_LIMIT
1229
1301
  )
1230
1302
  );
1303
+ if (entry.kind === "request") {
1304
+ if (entry.fromCache) {
1305
+ setProxyCacheHits((n) => n + 1);
1306
+ } else {
1307
+ setProxyCacheMisses((n) => n + 1);
1308
+ }
1309
+ }
1231
1310
  broadcast({ type: "proxy-log", entry });
1232
1311
  },
1233
1312
  [broadcast]
1234
1313
  );
1235
- const createBuildLog = useCallback2(
1314
+ const createBuildLog = useCallback3(
1236
1315
  (message) => {
1237
- setBuildLogHistory(
1238
- (h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-LOG_LIMIT)
1239
- );
1316
+ appendBuildLog(message);
1240
1317
  broadcast({ type: "log", message });
1241
1318
  },
1242
- [broadcast]
1319
+ [appendBuildLog, broadcast]
1243
1320
  );
1244
- const triggerBuild = useCallback2(() => {
1321
+ const triggerBuild = useCallback3(() => {
1245
1322
  if (buildingRef.current) {
1246
1323
  return;
1247
1324
  }
@@ -1249,10 +1326,11 @@ var DevUI = ({
1249
1326
  setBuildStatus("building");
1250
1327
  setBuildError(null);
1251
1328
  createBuildLog("Build started");
1252
- void runBuild(pluginDir, outputDir).then(() => {
1253
- createBuildLog("Build finished");
1329
+ void runBuild(pluginDir, outputDir).then(({ bundleSize }) => {
1330
+ createBuildLog(`Build finished \u2014 bundle: ${formatBytes(bundleSize)}`);
1254
1331
  setBuildStatus("done");
1255
1332
  setLastBuilt(/* @__PURE__ */ new Date());
1333
+ setLastBundleSize(bundleSize);
1256
1334
  createBuildLog("Notifying viewers to reload");
1257
1335
  for (const client of wsClientsRef.current) {
1258
1336
  if (client.readyState === 1) {
@@ -1267,7 +1345,7 @@ var DevUI = ({
1267
1345
  buildingRef.current = false;
1268
1346
  });
1269
1347
  }, [pluginDir, outputDir, createBuildLog]);
1270
- useEffect3(() => {
1348
+ useEffect4(() => {
1271
1349
  triggerBuild();
1272
1350
  const watcher = watch(pluginDir, { recursive: true }, (_, filename) => {
1273
1351
  if (!filename) {
@@ -1281,7 +1359,7 @@ var DevUI = ({
1281
1359
  if (debounceTimerRef.current !== null) {
1282
1360
  clearTimeout(debounceTimerRef.current);
1283
1361
  }
1284
- debounceTimerRef.current = setTimeout(triggerBuild, 150);
1362
+ debounceTimerRef.current = setTimeout(triggerBuild, FILE_WATCH_DEBOUNCE_MS);
1285
1363
  });
1286
1364
  return () => {
1287
1365
  watcher.close();
@@ -1290,9 +1368,9 @@ var DevUI = ({
1290
1368
  }
1291
1369
  };
1292
1370
  }, [pluginDir, triggerBuild, createBuildLog]);
1293
- useEffect3(() => {
1371
+ useEffect4(() => {
1294
1372
  const viewerPath = getViewerPath();
1295
- void fileExists(join7(viewerPath, "index.html")).then((viewerBuilt) => {
1373
+ void fileExists(join8(viewerPath, "index.html")).then((viewerBuilt) => {
1296
1374
  if (!viewerBuilt) {
1297
1375
  setErrorMessage("Viewer not built. Run 'pnpm build:viewer' first.");
1298
1376
  setStatus("error");
@@ -1305,7 +1383,8 @@ var DevUI = ({
1305
1383
  credentialsRef,
1306
1384
  activeProfileRef,
1307
1385
  outputDir,
1308
- broadcast
1386
+ broadcast,
1387
+ setProxyCacheSize
1309
1388
  );
1310
1389
  const server = createServer(handler);
1311
1390
  const wss = new WebSocketServer({ server });
@@ -1339,7 +1418,7 @@ var DevUI = ({
1339
1418
  });
1340
1419
  }, [port, createServerLog, createProxyLog, broadcast, outputDir]);
1341
1420
  if (credMode === "editing") {
1342
- return /* @__PURE__ */ jsx4(
1421
+ return /* @__PURE__ */ jsx6(
1343
1422
  CredentialSetupUI,
1344
1423
  {
1345
1424
  ...credentialMode !== void 0 && { initialMode: credentialMode },
@@ -1353,100 +1432,112 @@ var DevUI = ({
1353
1432
  }
1354
1433
  const elapsedSeconds = lastBuilt !== null ? Math.round((Date.now() - lastBuilt.getTime()) / 1e3) : null;
1355
1434
  const elapsedLabel = elapsedSeconds === null ? "" : elapsedSeconds < 5 ? " (just now)" : ` (${String(elapsedSeconds)}s ago)`;
1356
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
1357
- /* @__PURE__ */ jsx4(Logo, {}),
1358
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [
1359
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "Kizen App Builder" }),
1360
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2500".repeat(24) })
1361
- ] }),
1362
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
1363
- /* @__PURE__ */ jsxs3(Box4, { gap: 2, marginBottom: 0, children: [
1364
- /* @__PURE__ */ jsx4(Text4, { bold: true, dimColor: true, children: "Build" }),
1365
- buildStatus === "pending" && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "waiting\u2026" }),
1366
- buildStatus === "building" && /* @__PURE__ */ jsxs3(Box4, { gap: 1, children: [
1367
- /* @__PURE__ */ jsx4(Spinner2, {}),
1368
- /* @__PURE__ */ jsx4(Text4, { children: "Building plugin package\u2026" })
1435
+ return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
1436
+ /* @__PURE__ */ jsx6(AppHeader, { marginBottom: 1 }),
1437
+ /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
1438
+ /* @__PURE__ */ jsxs4(Box5, { gap: 2, marginBottom: 0, children: [
1439
+ /* @__PURE__ */ jsx6(Text6, { bold: true, dimColor: true, children: "App Build" }),
1440
+ buildStatus === "pending" && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "waiting\u2026" }),
1441
+ buildStatus === "building" && /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
1442
+ /* @__PURE__ */ jsx6(Spinner, {}),
1443
+ /* @__PURE__ */ jsx6(Text6, { children: "Building plugin package\u2026" })
1369
1444
  ] }),
1370
- buildStatus === "done" && /* @__PURE__ */ jsxs3(Box4, { gap: 1, children: [
1371
- /* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u2713 Built" }),
1372
- lastBuilt !== null && /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
1445
+ buildStatus === "done" && /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
1446
+ /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713 Built" }),
1447
+ lastBuilt !== null && /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
1373
1448
  "at ",
1374
1449
  lastBuilt.toLocaleTimeString(),
1375
1450
  elapsedLabel
1451
+ ] }),
1452
+ lastBundleSize !== null && /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
1453
+ "\xB7 bundle: ",
1454
+ formatBytes(lastBundleSize)
1376
1455
  ] })
1377
1456
  ] }),
1378
- buildStatus === "error" && /* @__PURE__ */ jsxs3(Text4, { color: "red", children: [
1457
+ buildStatus === "error" && /* @__PURE__ */ jsxs4(Text6, { color: "red", children: [
1379
1458
  "\u2717 ",
1380
1459
  buildError ?? "unknown error"
1381
1460
  ] })
1382
1461
  ] }),
1383
- /* @__PURE__ */ jsx4(
1384
- Box4,
1462
+ /* @__PURE__ */ jsx6(
1463
+ Box5,
1385
1464
  {
1386
1465
  flexDirection: "column",
1387
- height: LOG_DISPLAY,
1466
+ height: LOG_DISPLAY + 2,
1388
1467
  borderStyle: "single",
1389
1468
  borderColor: "gray",
1390
1469
  overflow: "hidden",
1391
- children: buildLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx4(Text4, { dimColor: true, wrap: "truncate", children: log }, index))
1470
+ children: buildLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "truncate", children: log }, index))
1392
1471
  }
1393
1472
  )
1394
1473
  ] }),
1395
- /* @__PURE__ */ jsxs3(Box4, { marginTop: 1, flexDirection: "column", children: [
1396
- /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1397
- /* @__PURE__ */ jsx4(Text4, { bold: true, dimColor: true, children: "Server" }),
1398
- status === "starting" && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "starting\u2026" }),
1399
- status === "running" && /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1400
- /* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u2713 Running" }),
1401
- /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
1474
+ /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", children: [
1475
+ /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
1476
+ /* @__PURE__ */ jsx6(Text6, { bold: true, dimColor: true, children: "Web Server" }),
1477
+ status === "starting" && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "starting\u2026" }),
1478
+ status === "running" && /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
1479
+ /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713 Running" }),
1480
+ /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
1402
1481
  "\u25CF ",
1403
1482
  wsClientCount,
1404
1483
  " viewer",
1405
1484
  wsClientCount !== 1 ? "s" : ""
1406
1485
  ] })
1407
1486
  ] }),
1408
- status === "error" && /* @__PURE__ */ jsxs3(Text4, { color: "red", children: [
1487
+ status === "error" && /* @__PURE__ */ jsxs4(Text6, { color: "red", children: [
1409
1488
  "\u2717 ",
1410
1489
  errorMessage ?? "Server error"
1411
1490
  ] })
1412
1491
  ] }),
1413
- /* @__PURE__ */ jsx4(
1414
- Box4,
1492
+ /* @__PURE__ */ jsx6(
1493
+ Box5,
1415
1494
  {
1416
1495
  flexDirection: "column",
1417
- height: LOG_DISPLAY,
1496
+ height: LOG_DISPLAY + 2,
1418
1497
  borderStyle: "single",
1419
1498
  borderColor: "gray",
1420
1499
  overflow: "hidden",
1421
- children: serverLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx4(Text4, { dimColor: true, wrap: "truncate", children: log }, index))
1500
+ children: serverLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "truncate", children: log }, index))
1422
1501
  }
1423
1502
  )
1424
1503
  ] }),
1425
- /* @__PURE__ */ jsxs3(Box4, { marginTop: 1, flexDirection: "column", children: [
1426
- /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1427
- /* @__PURE__ */ jsx4(Text4, { bold: true, dimColor: true, children: "Proxy" }),
1428
- /* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u2713 Active" })
1504
+ /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", children: [
1505
+ /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
1506
+ /* @__PURE__ */ jsx6(Text6, { bold: true, dimColor: true, children: "Network Proxy" }),
1507
+ /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713 Active" }),
1508
+ /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
1509
+ "\u25CF ",
1510
+ proxyCacheSize,
1511
+ " key",
1512
+ proxyCacheSize !== 1 ? "s" : "",
1513
+ " in the network cache \xB7",
1514
+ " ",
1515
+ proxyCacheHits,
1516
+ " hit \xB7 ",
1517
+ proxyCacheMisses,
1518
+ " miss"
1519
+ ] })
1429
1520
  ] }),
1430
- /* @__PURE__ */ jsx4(
1431
- Box4,
1521
+ /* @__PURE__ */ jsx6(
1522
+ Box5,
1432
1523
  {
1433
1524
  flexDirection: "column",
1434
- height: LOG_DISPLAY,
1525
+ height: LOG_DISPLAY + 2,
1435
1526
  borderStyle: "single",
1436
1527
  borderColor: "gray",
1437
1528
  overflow: "hidden",
1438
- children: proxyLogHistory.length === 0 ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " No requests yet." }) : proxyLogHistory.slice(-LOG_DISPLAY).map((log, i) => /* @__PURE__ */ jsx4(Text4, { dimColor: true, wrap: "truncate", children: log }, i))
1529
+ children: proxyLogHistory.length === 0 ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " No requests yet." }) : proxyLogHistory.slice(-LOG_DISPLAY).map((log, i) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "truncate", children: log }, i))
1439
1530
  }
1440
1531
  )
1441
1532
  ] }),
1442
- /* @__PURE__ */ jsxs3(Box4, { marginTop: 1, gap: 1, children: [
1443
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "[" }),
1444
- /* @__PURE__ */ jsx4(Text4, { children: "q" }),
1445
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "] quit [" }),
1446
- /* @__PURE__ */ jsx4(Text4, { children: "c" }),
1447
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "] credentials [" }),
1448
- /* @__PURE__ */ jsx4(Text4, { children: "v" }),
1449
- /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
1533
+ /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, gap: 1, children: [
1534
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "[" }),
1535
+ /* @__PURE__ */ jsx6(Text6, { children: "q" }),
1536
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "] quit [" }),
1537
+ /* @__PURE__ */ jsx6(Text6, { children: "c" }),
1538
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "] credentials [" }),
1539
+ /* @__PURE__ */ jsx6(Text6, { children: "v" }),
1540
+ /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
1450
1541
  "] ",
1451
1542
  wsClientCount === 0 ? "open viewer" : "relaunch viewer"
1452
1543
  ] })