@kizenapps/cli 0.12.0 → 0.13.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 (113) hide show
  1. package/dist/index.js +1374 -605
  2. package/dist/index.js.map +1 -1
  3. package/dist/viewer/assets/AppDetailPage-Bh4FLKqJ.js +1 -0
  4. package/dist/viewer/assets/AppDetailPage-BoFB93TE.js +1 -0
  5. package/dist/viewer/assets/AppDetailPage-Ck_VyLCS.js +1 -0
  6. package/dist/viewer/assets/BootstrapContext-CL_onTG7.js +1 -0
  7. package/dist/viewer/assets/BootstrapContext-c_1rWAfc.js +1 -0
  8. package/dist/viewer/assets/Card-BpDJPrPZ.js +1 -0
  9. package/dist/viewer/assets/Card-C7KVwKfm.js +1 -0
  10. package/dist/viewer/assets/CodeStepsPage-BHbbvjvr.js +1 -0
  11. package/dist/viewer/assets/CodeStepsPage-DnjVkPGB.js +1 -0
  12. package/dist/viewer/assets/CodeStepsPage-vHoGY4qA.js +1 -0
  13. package/dist/viewer/assets/CodeViewer--wbTev7Q.js +4 -0
  14. package/dist/viewer/assets/CodeViewer-Cm7Th9pG.js +4 -0
  15. package/dist/viewer/assets/ConfigurationPage-DAVSSfuO.js +1 -0
  16. package/dist/viewer/assets/ConfigurationPage-_PScSEXP.js +1 -0
  17. package/dist/viewer/assets/ConfigurationPage-zSJDg1f4.js +1 -0
  18. package/dist/viewer/assets/CredentialsContext-Rkt9WDIb.js +1 -0
  19. package/dist/viewer/assets/CredentialsContext-k-q4M-VC.js +1 -0
  20. package/dist/viewer/assets/{DevSidebar-BhTD2bBO.js → DevSidebar-CKdTotWS.js} +1 -1
  21. package/dist/viewer/assets/DevSidebar-CcSTChgY.js +1 -0
  22. package/dist/viewer/assets/IconReferencePage-BhQv3kD4.js +1 -0
  23. package/dist/viewer/assets/IconReferencePage-C_I2Fxnr.js +1 -0
  24. package/dist/viewer/assets/PluginIcon-CWkWM-Eu.js +1 -0
  25. package/dist/viewer/assets/SandboxPage-BMz04QVs.js +1 -0
  26. package/dist/viewer/assets/SandboxPage-BWtQb8kY.js +1 -0
  27. package/dist/viewer/assets/SandboxPage-DOBUc8VL.js +1 -0
  28. package/dist/viewer/assets/SidebarContext-Cn8QRb77.js +1 -0
  29. package/dist/viewer/assets/SidebarContext-DMhpPBf_.js +1 -0
  30. package/dist/viewer/assets/SourceBrowserPage-C-IhTiL0.js +1 -0
  31. package/dist/viewer/assets/SourceBrowserPage-Cr6XOIdV.js +1 -0
  32. package/dist/viewer/assets/VersionsPage-Bpj4_jPC.js +1 -0
  33. package/dist/viewer/assets/VersionsPage-DAm4Ozs0.js +1 -0
  34. package/dist/viewer/assets/VersionsPage-T8tsqgQx.js +1 -0
  35. package/dist/viewer/assets/{api-BOqcXygX.js → api-DFDquUsn.js} +1 -1
  36. package/dist/viewer/assets/{api-DqAiw0w9.js → api-bszpvP9L.js} +1 -1
  37. package/dist/viewer/assets/bundleQuery-BQLpZ06V.js +1 -0
  38. package/dist/viewer/assets/bundleQuery-CjbA01rb.js +1 -0
  39. package/dist/viewer/assets/fa-core-BoBqCOD3.js +558 -0
  40. package/dist/viewer/assets/{fa-core-wsCxaNB7.js → fa-core-C4eeAd4v.js} +1 -1
  41. package/dist/viewer/assets/formatBytes-BCP0hIUu.js +1 -0
  42. package/dist/viewer/assets/index-7CwR8Ogd.js +5 -0
  43. package/dist/viewer/assets/index-CCB9tECd.css +2 -0
  44. package/dist/viewer/assets/index-FEIsXxbf.js +5 -0
  45. package/dist/viewer/assets/index-fU82pXSW.js +5 -0
  46. package/dist/viewer/assets/{react-CyWK9rg5.js → react-CmC5kGa_.js} +1 -1
  47. package/dist/viewer/assets/{react-CkuOm_dq.js → react-csXWLXSE.js} +1 -1
  48. package/dist/viewer/assets/{recordDetail.worker-BIHPFt8Y.js → recordDetail.worker-C0-mQTaj.js} +1 -1
  49. package/dist/viewer/assets/useCriticalExceptionDialog-BeVl0uaO.js +14 -0
  50. package/dist/viewer/assets/useCriticalExceptionDialog-DseJ_u9B.js +14 -0
  51. package/dist/viewer/assets/useCurrentApp-CWtSEVC4.js +1 -0
  52. package/dist/viewer/assets/useCurrentApp-x1p8URhM.js +1 -0
  53. package/dist/viewer/assets/{useLocalStorage-BWwvt0nG.js → useLocalStorage-HYhrPUxS.js} +1 -1
  54. package/dist/viewer/assets/{useLocalStorage-BlZWItfY.js → useLocalStorage-eJ1PxgAP.js} +1 -1
  55. package/dist/viewer/assets/{useNavigate-9J1ZzN1J.js → useNavigate-BblEmQ3O.js} +1 -1
  56. package/dist/viewer/assets/{useNavigate-V8wjv5GQ.js → useNavigate-Ci7hClRo.js} +1 -1
  57. package/dist/viewer/assets/useQuery-5OkVbV1s.js +1 -0
  58. package/dist/viewer/assets/useQuery-B3Ek2Yet.js +1 -0
  59. package/dist/viewer/assets/{validIcons-UmfwFGTT.js → validIcons-BzGQ4tWD.js} +1 -1
  60. package/dist/viewer/assets/{validIcons-Dqgxgpv-.js → validIcons-CzxkK9y8.js} +1 -1
  61. package/dist/viewer/index.html +12 -10
  62. package/package.json +3 -6
  63. package/dist/viewer/assets/AppDetailPage-BPvWscWJ.js +0 -1
  64. package/dist/viewer/assets/AppDetailPage-DIuYKCTQ.js +0 -1
  65. package/dist/viewer/assets/AppDetailPage-r3jzO9Kf.js +0 -1
  66. package/dist/viewer/assets/BootstrapContext-CExPp3R2.js +0 -1
  67. package/dist/viewer/assets/BootstrapContext-CTQMWXf-.js +0 -1
  68. package/dist/viewer/assets/BootstrapContext-DbnbWmQT.js +0 -1
  69. package/dist/viewer/assets/Card-Bmn1HMWl.js +0 -1
  70. package/dist/viewer/assets/Card-DMz6T3VL.js +0 -1
  71. package/dist/viewer/assets/Card-DsqKctyM.js +0 -1
  72. package/dist/viewer/assets/CodeStepsPage-BHqLfCdV.js +0 -4
  73. package/dist/viewer/assets/CodeStepsPage-BymhEfzO.js +0 -4
  74. package/dist/viewer/assets/CodeStepsPage-CD_MhDwY.js +0 -4
  75. package/dist/viewer/assets/CodeStepsPage-CgI-LqEx.js +0 -4
  76. package/dist/viewer/assets/CodeStepsPage-Dak3saZE.js +0 -5
  77. package/dist/viewer/assets/CodeStepsPage-Zj6DscGd.js +0 -4
  78. package/dist/viewer/assets/ConfigurationPage-Cjy9TH3N.js +0 -1
  79. package/dist/viewer/assets/ConfigurationPage-DmX-6MM-.js +0 -1
  80. package/dist/viewer/assets/ConfigurationPage-J56kCD_e.js +0 -1
  81. package/dist/viewer/assets/CredentialsContext-Xbse1T0q.js +0 -1
  82. package/dist/viewer/assets/CredentialsContext-zl2e9kpM.js +0 -1
  83. package/dist/viewer/assets/DevSidebar-B30mgxSn.js +0 -1
  84. package/dist/viewer/assets/DevSidebar-CsEdCTej.js +0 -1
  85. package/dist/viewer/assets/IconReferencePage-B6j9wlte.js +0 -1
  86. package/dist/viewer/assets/IconReferencePage-CJa9yg9Z.js +0 -1
  87. package/dist/viewer/assets/IconReferencePage-Q5I-lMFQ.js +0 -1
  88. package/dist/viewer/assets/SandboxPage-C0h7GUkw.js +0 -1
  89. package/dist/viewer/assets/SandboxPage-GDcydJnJ.js +0 -1
  90. package/dist/viewer/assets/SandboxPage-JREBQ4id.js +0 -1
  91. package/dist/viewer/assets/SidebarContext-BPkYyxET.js +0 -1
  92. package/dist/viewer/assets/SidebarContext-DVDXFWJI.js +0 -1
  93. package/dist/viewer/assets/SidebarContext-HucmR353.js +0 -1
  94. package/dist/viewer/assets/VersionsPage-B76eugMm.js +0 -1
  95. package/dist/viewer/assets/VersionsPage-BIpB0iBz.js +0 -1
  96. package/dist/viewer/assets/VersionsPage-D0qenxK5.js +0 -1
  97. package/dist/viewer/assets/bundleQuery-206rQR6g.js +0 -1
  98. package/dist/viewer/assets/bundleQuery-L4H4wq5q.js +0 -1
  99. package/dist/viewer/assets/fa-core-DaD7O-yO.js +0 -558
  100. package/dist/viewer/assets/index-BPiwld5D.js +0 -5
  101. package/dist/viewer/assets/index-BiyeMU_r.css +0 -2
  102. package/dist/viewer/assets/index-BlJv_543.js +0 -5
  103. package/dist/viewer/assets/index-CAYNux5J.js +0 -5
  104. package/dist/viewer/assets/index-D4AnoAXz.js +0 -5
  105. package/dist/viewer/assets/index-D8Koqlb-.js +0 -5
  106. package/dist/viewer/assets/index-DKrC0qkC.js +0 -5
  107. package/dist/viewer/assets/useLocalStorage-CQO0VGQ6.js +0 -1
  108. package/dist/viewer/assets/useObjectLookups-BAqe9wvu.js +0 -14
  109. package/dist/viewer/assets/useObjectLookups-C65zm-bM.js +0 -14
  110. package/dist/viewer/assets/useObjectLookups-CslM2vSA.js +0 -14
  111. /package/dist/viewer/assets/{CodeStepsPage-DdKuH37F.css → CodeViewer-DdKuH37F.css} +0 -0
  112. /package/dist/viewer/assets/{fa-pro-icons-CiTC899m.js → fa-pro-icons-Dr3O0gTt.js} +0 -0
  113. /package/dist/viewer/assets/{fa-pro-icons-Dzkwxnip.js → fa-pro-icons-j8-_MCQ7.js} +0 -0
package/dist/index.js CHANGED
@@ -89,7 +89,8 @@ async function runBuild(pluginDir, outputDir, onStep) {
89
89
  return serializePlugin(plugin, allReleaseNotes);
90
90
  });
91
91
  const jsonStr = JSON.stringify(bundle, null, 2);
92
- const bundleSize = Buffer.byteLength(jsonStr, "utf-8");
92
+ const sizedBundle = bundle.map(({ allReleaseNotes: _notes, ...rest }) => rest);
93
+ const bundleSize = Buffer.byteLength(JSON.stringify(sizedBundle, null, 2), "utf-8");
93
94
  await writeFile(join2(outputDir, "bundle.json"), jsonStr, "utf-8");
94
95
  return { bundleSize };
95
96
  }
@@ -220,7 +221,7 @@ var BuildUI = ({ outputDir, pluginDir }) => {
220
221
  ] }) }, s);
221
222
  }),
222
223
  isDone && bundleSize !== null && /* @__PURE__ */ jsx4(Box3, { marginTop: 1, gap: 1, children: /* @__PURE__ */ jsxs2(Text4, { color: "green", children: [
223
- "\u2713 bundle.json \u2014 ",
224
+ "\u2713 bundle \u2014 ",
224
225
  formatBytes(bundleSize)
225
226
  ] }) }),
226
227
  errorMessage !== null && /* @__PURE__ */ jsx4(Box3, { marginTop: 1, borderStyle: "single", borderColor: "red", paddingX: 1, children: /* @__PURE__ */ jsx4(Text4, { color: "red", children: errorMessage }) })
@@ -257,95 +258,418 @@ function buildCommand(program2) {
257
258
  });
258
259
  }
259
260
 
260
- // src/commands/dev.ts
261
+ // src/commands/create.ts
261
262
  import { createElement as createElement2 } from "react";
262
263
  import { render as render2 } from "ink";
263
264
 
264
- // src/ui/DevUI.tsx
265
- import { useCallback as useCallback3, useEffect as useEffect4, useRef, useState as useState5 } from "react";
266
- import { Box as Box5, Text as Text6, useInput as useInput2 } from "ink";
267
- import { watch } from "fs";
268
- import { createServer } from "http";
269
- import { join as join9 } from "path";
270
- import { WebSocketServer } from "ws";
271
-
272
- // src/server/requestHandler.ts
273
- import { createReadStream } from "fs";
274
- import { access as access2, readFile as readFile4 } from "fs/promises";
275
- import { dirname as dirname2, extname, join as join7 } from "path";
276
- import { fileURLToPath } from "url";
265
+ // src/ui/CreateUI.tsx
266
+ import { useCallback, useState as useState3 } from "react";
267
+ import { Box as Box4, Text as Text5, useApp as useApp2, useInput } from "ink";
268
+ import { join as join5 } from "path";
277
269
 
278
- // src/lib/proxyCache.ts
279
- function createProxyCache(options = {}) {
280
- const cache = /* @__PURE__ */ new Map();
281
- const { onChange } = options;
282
- const emitChange = () => {
283
- onChange?.(cache.size);
270
+ // src/lib/createPlugin.ts
271
+ import { access, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
272
+ import { join as join4 } from "path";
273
+ async function pathExists(path2) {
274
+ try {
275
+ await access(path2);
276
+ return true;
277
+ } catch {
278
+ return false;
279
+ }
280
+ }
281
+ async function precheckTargetDir(dir) {
282
+ if (await pathExists(join4(dir, "kizen.json"))) {
283
+ return "has-manifest";
284
+ }
285
+ if (await pathExists(join4(dir, ".kizenapp"))) {
286
+ return "has-kizenapp";
287
+ }
288
+ return "ok";
289
+ }
290
+ async function findAvailableSubDir(parentDir, baseName) {
291
+ const base = join4(parentDir, baseName);
292
+ if (!await pathExists(base)) {
293
+ return base;
294
+ }
295
+ for (let i = 1; i < 1e3; i++) {
296
+ const candidate = join4(parentDir, `${baseName}-${String(i)}`);
297
+ if (!await pathExists(candidate)) {
298
+ return candidate;
299
+ }
300
+ }
301
+ throw new Error(`Could not find an available directory name for "${baseName}"`);
302
+ }
303
+ function buildManifest(input) {
304
+ return {
305
+ name: input.name,
306
+ version: "1.0.0",
307
+ published: true,
308
+ api_name: input.apiName,
309
+ external_link: input.externalLink,
310
+ description: input.description,
311
+ entry: "src/",
312
+ engine: "1.0.0",
313
+ release_notes_directory: "releaseNotes/",
314
+ release_environments: ["prod"],
315
+ config_template: {},
316
+ base_config: {},
317
+ developer_business_id: input.developerBusinessId
284
318
  };
319
+ }
320
+ async function createPlugin(input) {
321
+ await mkdir2(input.targetDir, { recursive: true });
322
+ const manifest = buildManifest(input);
323
+ await writeFile2(
324
+ join4(input.targetDir, "kizen.json"),
325
+ JSON.stringify(manifest, null, 2) + "\n",
326
+ "utf-8"
327
+ );
328
+ await mkdir2(join4(input.targetDir, "src"), { recursive: true });
329
+ await mkdir2(join4(input.targetDir, "releaseNotes"), { recursive: true });
330
+ ensureGitignore(input.targetDir);
331
+ }
332
+
333
+ // src/ui/CreateUI.tsx
334
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
335
+ var FIELDS = ["name", "apiName", "externalLink", "description", "developerBusinessId"];
336
+ var FIELD_LABELS = {
337
+ name: "Name",
338
+ apiName: "API name",
339
+ externalLink: "External link",
340
+ description: "Description",
341
+ developerBusinessId: "Business ID"
342
+ };
343
+ var REQUIRED_FIELDS = ["name", "apiName"];
344
+ function inferApiName(name) {
345
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
346
+ }
347
+ var Hint = ({ text }) => /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: text });
348
+ function emptyValues(defaultBusinessId) {
285
349
  return {
286
- get(key, fetcher) {
287
- const existing = cache.get(key);
288
- if (existing !== void 0) {
289
- return existing.then((response) => ({ response, fromCache: true }));
350
+ name: "",
351
+ apiName: "",
352
+ externalLink: "",
353
+ description: "",
354
+ developerBusinessId: defaultBusinessId
355
+ };
356
+ }
357
+ var CreateUI = ({ parentDir, defaultBusinessId }) => {
358
+ const app = useApp2();
359
+ const [phase, setPhase] = useState3({ type: "target-select", cursor: 0 });
360
+ const [mode, setMode] = useState3("in-place");
361
+ const [values, setValues] = useState3(() => emptyValues(defaultBusinessId));
362
+ const [inputBuffer, setInputBuffer] = useState3("");
363
+ const [targetDir, setTargetDir] = useState3(parentDir);
364
+ const enterFieldEntry = useCallback((nextMode, initialValues) => {
365
+ setMode(nextMode);
366
+ setInputBuffer(initialValues.name);
367
+ setPhase({ type: "field-entry", field: 0, values: initialValues });
368
+ }, []);
369
+ const runPrecheck = useCallback(
370
+ async (dir) => {
371
+ const result = await precheckTargetDir(dir);
372
+ if (result !== "ok") {
373
+ setPhase({ type: "precheck-error", targetDir: dir, reason: result });
374
+ return;
290
375
  }
291
- const pending = fetcher().then(async (response) => {
292
- const headers = Object.fromEntries(response.headers);
293
- delete headers["content-encoding"];
294
- delete headers["content-length"];
295
- return {
296
- status: response.status,
297
- headers,
298
- body: Buffer.from(await response.arrayBuffer())
299
- };
300
- }).catch((error) => {
301
- cache.delete(key);
302
- emitChange();
303
- throw error;
376
+ setTargetDir(dir);
377
+ enterFieldEntry("in-place", values);
378
+ },
379
+ [values, enterFieldEntry]
380
+ );
381
+ const resolveSubDirAndConfirm = useCallback(
382
+ async (finalValues) => {
383
+ try {
384
+ const dir = await findAvailableSubDir(parentDir, finalValues.apiName);
385
+ setTargetDir(dir);
386
+ setPhase({ type: "confirm", values: finalValues });
387
+ } catch (err) {
388
+ setPhase({
389
+ type: "error",
390
+ message: err instanceof Error ? err.message : String(err)
391
+ });
392
+ }
393
+ },
394
+ [parentDir]
395
+ );
396
+ const runWrite = useCallback(async (finalValues, dir) => {
397
+ try {
398
+ await createPlugin({
399
+ targetDir: dir,
400
+ name: finalValues.name,
401
+ apiName: finalValues.apiName,
402
+ externalLink: finalValues.externalLink,
403
+ description: finalValues.description,
404
+ developerBusinessId: finalValues.developerBusinessId
304
405
  });
305
- cache.set(key, pending);
306
- emitChange();
307
- return pending.then((response) => {
308
- if (response.status >= 400) {
309
- cache.delete(key);
310
- emitChange();
311
- }
312
- return { response, fromCache: false };
406
+ setPhase({ type: "done", targetDir: dir });
407
+ } catch (err) {
408
+ setPhase({
409
+ type: "error",
410
+ message: err instanceof Error ? err.message : String(err)
313
411
  });
314
- },
315
- clear() {
316
- cache.clear();
317
- emitChange();
318
412
  }
319
- };
320
- }
321
-
322
- // src/lib/constants.ts
323
- var LOG_LIMIT = 50;
324
- var LOG_DISPLAY = 8;
325
- var FILE_WATCH_DEBOUNCE_MS = 150;
326
- var MIME_TYPES = {
327
- ".html": "text/html; charset=utf-8",
328
- ".js": "application/javascript; charset=utf-8",
329
- ".mjs": "application/javascript; charset=utf-8",
330
- ".css": "text/css; charset=utf-8",
331
- ".png": "image/png",
332
- ".jpg": "image/jpeg",
333
- ".svg": "image/svg+xml",
334
- ".ico": "image/x-icon",
335
- ".json": "application/json; charset=utf-8",
336
- ".woff": "font/woff",
337
- ".woff2": "font/woff2",
338
- ".ttf": "font/ttf",
339
- ".map": "application/json"
413
+ }, []);
414
+ useInput((input, key) => {
415
+ if (key.ctrl && input === "c") {
416
+ app.exit();
417
+ return;
418
+ }
419
+ if (phase.type === "target-select") {
420
+ if (key.upArrow) {
421
+ setPhase({ type: "target-select", cursor: 0 });
422
+ } else if (key.downArrow) {
423
+ setPhase({ type: "target-select", cursor: 1 });
424
+ } else if (key.return) {
425
+ if (phase.cursor === 0) {
426
+ void runPrecheck(parentDir);
427
+ } else {
428
+ enterFieldEntry("sub-dir", values);
429
+ }
430
+ } else if (key.escape) {
431
+ app.exit();
432
+ }
433
+ return;
434
+ }
435
+ if (phase.type === "precheck-error") {
436
+ if (key.return || key.escape) {
437
+ app.exit();
438
+ }
439
+ return;
440
+ }
441
+ if (phase.type === "field-entry") {
442
+ const { field, values: current } = phase;
443
+ const fieldName = FIELDS[field];
444
+ if (!fieldName) {
445
+ return;
446
+ }
447
+ if (key.escape) {
448
+ app.exit();
449
+ return;
450
+ }
451
+ if (key.backspace || key.delete) {
452
+ setInputBuffer((prev) => prev.slice(0, -1));
453
+ return;
454
+ }
455
+ if (key.upArrow && field > 0) {
456
+ const updated = { ...current, [fieldName]: inputBuffer };
457
+ const prevField = field - 1;
458
+ const prevName = FIELDS[prevField];
459
+ setPhase({ type: "field-entry", field: prevField, values: updated });
460
+ setValues(updated);
461
+ setInputBuffer(prevName ? updated[prevName] : "");
462
+ return;
463
+ }
464
+ if (key.return || key.tab || key.downArrow) {
465
+ const buffer = inputBuffer.trim();
466
+ const isRequired = REQUIRED_FIELDS.includes(fieldName);
467
+ if (isRequired && !buffer) {
468
+ setPhase({
469
+ type: "field-entry",
470
+ field,
471
+ values: current,
472
+ fieldError: `${FIELD_LABELS[fieldName]} is required`
473
+ });
474
+ return;
475
+ }
476
+ const valueToStore = fieldName === "description" ? inputBuffer : buffer;
477
+ const updated = { ...current, [fieldName]: valueToStore };
478
+ setValues(updated);
479
+ const nextField = field + 1;
480
+ if (nextField >= FIELDS.length) {
481
+ setInputBuffer("");
482
+ if (mode === "sub-dir") {
483
+ setPhase({ type: "resolving-target" });
484
+ void resolveSubDirAndConfirm(updated);
485
+ } else {
486
+ setPhase({ type: "confirm", values: updated });
487
+ }
488
+ } else {
489
+ const nextName = FIELDS[nextField];
490
+ const existing = nextName ? updated[nextName] : "";
491
+ const nextBuffer = nextName === "apiName" && !existing ? inferApiName(updated.name) : existing;
492
+ setPhase({ type: "field-entry", field: nextField, values: updated });
493
+ setInputBuffer(nextBuffer);
494
+ }
495
+ return;
496
+ }
497
+ if (input && !key.ctrl && !key.meta) {
498
+ setInputBuffer((prev) => prev + input);
499
+ }
500
+ return;
501
+ }
502
+ if (phase.type === "confirm") {
503
+ if (key.escape) {
504
+ const lastField = FIELDS.length - 1;
505
+ const lastName = FIELDS[lastField];
506
+ setPhase({ type: "field-entry", field: lastField, values: phase.values });
507
+ setInputBuffer(lastName ? phase.values[lastName] : "");
508
+ return;
509
+ }
510
+ if (key.return) {
511
+ setPhase({ type: "writing" });
512
+ void runWrite(phase.values, targetDir);
513
+ }
514
+ return;
515
+ }
516
+ if (phase.type === "done" || phase.type === "error") {
517
+ if (key.return || key.escape) {
518
+ app.exit();
519
+ }
520
+ }
521
+ });
522
+ if (phase.type === "target-select") {
523
+ const options = [
524
+ { label: "Use current directory", desc: parentDir },
525
+ { label: "Create new sub-directory", desc: `${parentDir}/<inferred from API name>` }
526
+ ];
527
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
528
+ /* @__PURE__ */ jsx5(AppHeader, {}),
529
+ /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
530
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Where should the new plugin live?" }),
531
+ options.map((opt, i) => {
532
+ const selected = phase.cursor === i;
533
+ return /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
534
+ /* @__PURE__ */ jsx5(Text5, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
535
+ /* @__PURE__ */ jsx5(Text5, { bold: selected, ...selected && { color: "cyan" }, children: opt.label }),
536
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: opt.desc })
537
+ ] }, opt.label);
538
+ })
539
+ ] }),
540
+ /* @__PURE__ */ jsx5(Hint, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Esc to cancel" })
541
+ ] });
542
+ }
543
+ if (phase.type === "precheck-error") {
544
+ const { targetDir: dir, reason } = phase;
545
+ const conflict = reason === "has-manifest" ? "kizen.json" : ".kizenapp/";
546
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
547
+ /* @__PURE__ */ jsx5(AppHeader, {}),
548
+ /* @__PURE__ */ jsx5(Text5, { color: "red", bold: true, children: "Cannot create plugin" }),
549
+ /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
550
+ /* @__PURE__ */ jsxs3(Text5, { children: [
551
+ dir,
552
+ " already contains ",
553
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: conflict }),
554
+ "."
555
+ ] }),
556
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Remove the conflicting file/directory or choose a different target to avoid overwriting an existing plugin." })
557
+ ] }),
558
+ /* @__PURE__ */ jsx5(Hint, { text: "Enter or Esc to exit" })
559
+ ] });
560
+ }
561
+ if (phase.type === "field-entry") {
562
+ const { field: activeField, fieldError } = phase;
563
+ const targetLine = mode === "sub-dir" ? `Target: sub-directory of ${parentDir} (inferred from API name)` : `Target: ${targetDir}`;
564
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
565
+ /* @__PURE__ */ jsx5(AppHeader, {}),
566
+ /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
567
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Plugin details" }),
568
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: targetLine })
569
+ ] }),
570
+ fieldError !== void 0 && /* @__PURE__ */ jsxs3(Text5, { color: "red", children: [
571
+ "Error: ",
572
+ fieldError
573
+ ] }),
574
+ /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", gap: 0, children: FIELDS.map((name, i) => {
575
+ const isActive = activeField === i;
576
+ const displayValue = isActive ? inputBuffer : values[name];
577
+ const isRequired = REQUIRED_FIELDS.includes(name);
578
+ const label = `${FIELD_LABELS[name]}${isRequired ? "" : " (optional)"}`;
579
+ return /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
580
+ /* @__PURE__ */ jsx5(Box4, { width: 24, children: /* @__PURE__ */ jsx5(Text5, { bold: isActive, ...isActive && { color: "cyan" }, children: label }) }),
581
+ /* @__PURE__ */ jsx5(Text5, { ...isActive && { color: "cyan" }, children: ">" }),
582
+ /* @__PURE__ */ jsx5(Text5, { children: displayValue }),
583
+ isActive && /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u2588" })
584
+ ] }, name);
585
+ }) }),
586
+ /* @__PURE__ */ jsx5(Hint, { text: "Enter/Tab/\u2193 next \xB7 \u2191 previous \xB7 Backspace delete \xB7 Esc cancel" })
587
+ ] });
588
+ }
589
+ if (phase.type === "confirm") {
590
+ const manifest = buildManifest({
591
+ targetDir,
592
+ name: phase.values.name,
593
+ apiName: phase.values.apiName,
594
+ externalLink: phase.values.externalLink,
595
+ description: phase.values.description,
596
+ developerBusinessId: phase.values.developerBusinessId
597
+ });
598
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
599
+ /* @__PURE__ */ jsx5(AppHeader, {}),
600
+ /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
601
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Ready to create" }),
602
+ /* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
603
+ "Target: ",
604
+ targetDir
605
+ ] })
606
+ ] }),
607
+ /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
608
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: "kizen.json preview" }),
609
+ /* @__PURE__ */ jsx5(Text5, { children: JSON.stringify(manifest, null, 2) })
610
+ ] }),
611
+ /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
612
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Also creates:" }),
613
+ /* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
614
+ " ",
615
+ join5(targetDir, "src/")
616
+ ] }),
617
+ /* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
618
+ " ",
619
+ join5(targetDir, "releaseNotes/")
620
+ ] }),
621
+ /* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
622
+ " ",
623
+ join5(targetDir, ".gitignore"),
624
+ " (adds .kizenapp/)"
625
+ ] })
626
+ ] }),
627
+ /* @__PURE__ */ jsx5(Hint, { text: "Enter to create \xB7 Esc to go back" })
628
+ ] });
629
+ }
630
+ if (phase.type === "resolving-target") {
631
+ return /* @__PURE__ */ jsx5(Box4, { paddingY: 1, paddingX: 2, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Finding available sub-directory\u2026" }) });
632
+ }
633
+ if (phase.type === "writing") {
634
+ return /* @__PURE__ */ jsx5(Box4, { paddingY: 1, paddingX: 2, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Creating plugin\u2026" }) });
635
+ }
636
+ if (phase.type === "done") {
637
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
638
+ /* @__PURE__ */ jsx5(AppHeader, {}),
639
+ /* @__PURE__ */ jsxs3(Text5, { color: "green", bold: true, children: [
640
+ "Plugin created at ",
641
+ phase.targetDir
642
+ ] }),
643
+ /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
644
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Next steps:" }),
645
+ /* @__PURE__ */ jsxs3(Text5, { children: [
646
+ " ",
647
+ "cd ",
648
+ phase.targetDir
649
+ ] }),
650
+ /* @__PURE__ */ jsxs3(Text5, { children: [
651
+ " ",
652
+ "appbuilder dev"
653
+ ] })
654
+ ] }),
655
+ /* @__PURE__ */ jsx5(Hint, { text: "Enter or Esc to exit" })
656
+ ] });
657
+ }
658
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
659
+ /* @__PURE__ */ jsx5(AppHeader, {}),
660
+ /* @__PURE__ */ jsx5(Text5, { color: "red", bold: true, children: "Failed to create plugin" }),
661
+ /* @__PURE__ */ jsx5(Text5, { children: phase.message }),
662
+ /* @__PURE__ */ jsx5(Hint, { text: "Enter or Esc to exit" })
663
+ ] });
340
664
  };
341
665
 
342
666
  // src/lib/credentials.ts
343
- import { mkdir as mkdir2, readFile as readFile2, readdir as readdir2, writeFile as writeFile2 } from "fs/promises";
667
+ import { mkdir as mkdir3, readFile as readFile2, readdir as readdir2, writeFile as writeFile3 } from "fs/promises";
344
668
  import { homedir } from "os";
345
- import { dirname, join as join4 } from "path";
669
+ import { dirname, join as join6 } from "path";
346
670
  var ENVIRONMENTS = ["go", "fmo", "staging", "integration", "test1"];
347
- var GLOBAL_CREDENTIALS_DIR = join4(homedir(), ".kizenappbuilder");
348
- var GLOBAL_CREDENTIALS_PATH = join4(GLOBAL_CREDENTIALS_DIR, "credentials.json");
671
+ var GLOBAL_CREDENTIALS_DIR = join6(homedir(), ".kizenappbuilder");
672
+ var GLOBAL_CREDENTIALS_PATH = join6(GLOBAL_CREDENTIALS_DIR, "credentials.json");
349
673
  var DEFAULT_PROFILE_NAME = "credentials";
350
674
  function isValidEnvironment(value) {
351
675
  return ENVIRONMENTS.includes(value);
@@ -375,11 +699,11 @@ async function loadGlobalCredentials() {
375
699
  }
376
700
  }
377
701
  async function saveGlobalCredentials(credentials) {
378
- await mkdir2(dirname(GLOBAL_CREDENTIALS_PATH), { recursive: true });
379
- await writeFile2(GLOBAL_CREDENTIALS_PATH, JSON.stringify(credentials, null, 2), "utf-8");
702
+ await mkdir3(dirname(GLOBAL_CREDENTIALS_PATH), { recursive: true });
703
+ await writeFile3(GLOBAL_CREDENTIALS_PATH, JSON.stringify(credentials, null, 2), "utf-8");
380
704
  }
381
705
  function getProfilePath(name) {
382
- return join4(GLOBAL_CREDENTIALS_DIR, `${name}.json`);
706
+ return join6(GLOBAL_CREDENTIALS_DIR, `${name}.json`);
383
707
  }
384
708
  async function listCredentialProfiles() {
385
709
  const profiles = [
@@ -395,7 +719,7 @@ async function listCredentialProfiles() {
395
719
  if (name === DEFAULT_PROFILE_NAME) {
396
720
  continue;
397
721
  }
398
- profiles.push({ name, path: join4(GLOBAL_CREDENTIALS_DIR, entry), isDefault: false });
722
+ profiles.push({ name, path: join6(GLOBAL_CREDENTIALS_DIR, entry), isDefault: false });
399
723
  }
400
724
  } catch {
401
725
  }
@@ -406,8 +730,8 @@ async function saveCredentialProfile(name, credentials) {
406
730
  await saveGlobalCredentials(credentials);
407
731
  return;
408
732
  }
409
- await mkdir2(GLOBAL_CREDENTIALS_DIR, { recursive: true });
410
- await writeFile2(getProfilePath(name), JSON.stringify(credentials, null, 2), "utf-8");
733
+ await mkdir3(GLOBAL_CREDENTIALS_DIR, { recursive: true });
734
+ await writeFile3(getProfilePath(name), JSON.stringify(credentials, null, 2), "utf-8");
411
735
  }
412
736
  async function loadCredentialProfile(name) {
413
737
  try {
@@ -417,26 +741,121 @@ async function loadCredentialProfile(name) {
417
741
  }
418
742
  }
419
743
 
744
+ // src/commands/create.ts
745
+ function createCommand(program2) {
746
+ program2.command("create").description("Scaffold a new Kizen plugin project").action(async () => {
747
+ const parentDir = process.cwd();
748
+ const globalCreds = await loadGlobalCredentials();
749
+ const defaultBusinessId = globalCreds?.businessId ?? "";
750
+ const { waitUntilExit } = render2(createElement2(CreateUI, { parentDir, defaultBusinessId }), {
751
+ exitOnCtrlC: false
752
+ });
753
+ await waitUntilExit();
754
+ });
755
+ }
756
+
757
+ // src/commands/dev.ts
758
+ import { createElement as createElement3 } from "react";
759
+ import { render as render3 } from "ink";
760
+
761
+ // src/ui/DevUI.tsx
762
+ import { useCallback as useCallback4, useEffect as useEffect4, useRef, useState as useState6 } from "react";
763
+ import { Box as Box6, Text as Text7, useInput as useInput3 } from "ink";
764
+ import { watch } from "fs";
765
+ import { createServer } from "http";
766
+ import { join as join11 } from "path";
767
+ import { WebSocketServer } from "ws";
768
+
769
+ // src/server/requestHandler.ts
770
+ import { createReadStream } from "fs";
771
+ import { access as access3, readdir as readdir3, readFile as readFile4, stat } from "fs/promises";
772
+ import { dirname as dirname2, extname, join as join9, resolve, sep } from "path";
773
+ import { fileURLToPath } from "url";
774
+
775
+ // src/lib/proxyCache.ts
776
+ function createProxyCache(options = {}) {
777
+ const cache = /* @__PURE__ */ new Map();
778
+ const { onChange } = options;
779
+ const emitChange = () => {
780
+ onChange?.(cache.size);
781
+ };
782
+ return {
783
+ get(key, fetcher) {
784
+ const existing = cache.get(key);
785
+ if (existing !== void 0) {
786
+ return existing.then((response) => ({ response, fromCache: true }));
787
+ }
788
+ const pending = fetcher().then(async (response) => {
789
+ const headers = Object.fromEntries(response.headers);
790
+ delete headers["content-encoding"];
791
+ delete headers["content-length"];
792
+ return {
793
+ status: response.status,
794
+ headers,
795
+ body: Buffer.from(await response.arrayBuffer())
796
+ };
797
+ }).catch((error) => {
798
+ cache.delete(key);
799
+ emitChange();
800
+ throw error;
801
+ });
802
+ cache.set(key, pending);
803
+ emitChange();
804
+ return pending.then((response) => {
805
+ if (response.status >= 400) {
806
+ cache.delete(key);
807
+ emitChange();
808
+ }
809
+ return { response, fromCache: false };
810
+ });
811
+ },
812
+ clear() {
813
+ cache.clear();
814
+ emitChange();
815
+ }
816
+ };
817
+ }
818
+
819
+ // src/lib/constants.ts
820
+ var LOG_LIMIT = 50;
821
+ var LOG_DISPLAY = 8;
822
+ var FILE_WATCH_DEBOUNCE_MS = 150;
823
+ var MIME_TYPES = {
824
+ ".html": "text/html; charset=utf-8",
825
+ ".js": "application/javascript; charset=utf-8",
826
+ ".mjs": "application/javascript; charset=utf-8",
827
+ ".css": "text/css; charset=utf-8",
828
+ ".png": "image/png",
829
+ ".jpg": "image/jpeg",
830
+ ".svg": "image/svg+xml",
831
+ ".ico": "image/x-icon",
832
+ ".json": "application/json; charset=utf-8",
833
+ ".woff": "font/woff",
834
+ ".woff2": "font/woff2",
835
+ ".ttf": "font/ttf",
836
+ ".map": "application/json"
837
+ };
838
+
420
839
  // src/lib/config.ts
421
- import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
422
- import { join as join5 } from "path";
840
+ import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
841
+ import { join as join7 } from "path";
423
842
  async function loadConfig(outputDir) {
424
843
  try {
425
- const content = await readFile3(join5(outputDir, "config.json"), "utf-8");
844
+ const content = await readFile3(join7(outputDir, "config.json"), "utf-8");
426
845
  return JSON.parse(content);
427
846
  } catch {
428
847
  return {};
429
848
  }
430
849
  }
431
850
  async function saveConfig(outputDir, config) {
432
- await mkdir3(outputDir, { recursive: true });
433
- await writeFile3(join5(outputDir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
851
+ await mkdir4(outputDir, { recursive: true });
852
+ await writeFile4(join7(outputDir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
434
853
  }
435
854
 
436
855
  // src/server/pythonExecutor.ts
437
- import { spawn, execFileSync } from "child_process";
438
- import { writeFile as writeFile4, rm, mkdtemp, access } from "fs/promises";
439
- import { join as join6 } from "path";
856
+ import { spawn } from "child_process";
857
+ import { writeFile as writeFile5, rm, mkdtemp, access as access2 } from "fs/promises";
858
+ import { join as join8 } from "path";
440
859
  import { tmpdir } from "os";
441
860
  var WRAPPER_SCRIPT = `
442
861
  import sys, json, os, traceback
@@ -487,6 +906,80 @@ class _OutputsProxy:
487
906
 
488
907
  outputs = _OutputsProxy()
489
908
 
909
+ # --- HTTP request capture (requests library only) ---
910
+ _kizen_http_requests = []
911
+
912
+ def _kizen_install_http_capture():
913
+ try:
914
+ import requests
915
+ except Exception:
916
+ return
917
+ import time, datetime
918
+
919
+ _original_send = requests.Session.send
920
+
921
+ def _kizen_send(self, request, **kwargs):
922
+ submitted_at = datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')
923
+ start = time.time()
924
+ error_type = None
925
+ response = None
926
+ try:
927
+ response = _original_send(self, request, **kwargs)
928
+ return response
929
+ except Exception as exc:
930
+ error_type = type(exc).__name__
931
+ raise
932
+ finally:
933
+ body = request.body
934
+ if isinstance(body, bytes):
935
+ try:
936
+ body = body.decode('utf-8')
937
+ except Exception:
938
+ body = '<binary>'
939
+ elif body is None:
940
+ body = ''
941
+
942
+ status = 0
943
+ resp_headers = {}
944
+ resp_body = ''
945
+ if response is not None:
946
+ status = response.status_code
947
+ resp_headers = dict(response.headers)
948
+ if kwargs.get('stream'):
949
+ resp_body = '<streamed>'
950
+ else:
951
+ try:
952
+ resp_body = response.text
953
+ except Exception:
954
+ resp_body = '<unreadable>'
955
+
956
+ _kizen_http_requests.append({
957
+ 'method': request.method or '',
958
+ 'url': request.url or '',
959
+ 'headers': dict(request.headers or {}),
960
+ 'body': body,
961
+ 'requestSubmittedAt': submitted_at,
962
+ 'session': True,
963
+ 'responseStatusCode': status,
964
+ 'responseHeaders': resp_headers,
965
+ 'responseBody': resp_body,
966
+ 'requestErrorType': error_type,
967
+ 'duration': time.time() - start,
968
+ })
969
+
970
+ requests.Session.send = _kizen_send
971
+
972
+ _kizen_install_http_capture()
973
+
974
+ def _kizen_http_summary():
975
+ if not _kizen_http_requests:
976
+ return None
977
+ return {
978
+ 'count': len(_kizen_http_requests),
979
+ 'notLogged': 0,
980
+ 'requests': _kizen_http_requests,
981
+ }
982
+
490
983
  # --- Execute user script ---
491
984
  try:
492
985
  exec(open(os.environ['__KIZEN_SCRIPT_PATH__'], encoding='utf-8').read())
@@ -495,6 +988,7 @@ try:
495
988
  'outputValues': outputs._values,
496
989
  'logs': outputs._logs,
497
990
  'error': None,
991
+ 'httpRequests': _kizen_http_summary(),
498
992
  }
499
993
  except Exception:
500
994
  # Build a clean traceback that hides the wrapper internals.
@@ -514,6 +1008,7 @@ except Exception:
514
1008
  'outputValues': outputs._values,
515
1009
  'logs': outputs._logs,
516
1010
  'error': ''.join(lines),
1011
+ 'httpRequests': _kizen_http_summary(),
517
1012
  }
518
1013
 
519
1014
  # Write results between markers so we can extract them reliably
@@ -524,8 +1019,21 @@ sys.stdout.write('\\n__KIZEN_RESULT_END__\\n')
524
1019
  sys.stdout.flush()
525
1020
  `.trim();
526
1021
  var DEFAULT_TIMEOUT = 3e4;
527
- var VENV_DIR = join6(process.cwd(), ".kizenapp", "venv");
528
- var VENV_PACKAGES = ["requests"];
1022
+ var VENV_DIR = join8(process.cwd(), ".kizenapp", "venv");
1023
+ var VENV_PACKAGES = [
1024
+ "awslambdaric",
1025
+ "bcrypt",
1026
+ "fpdf2",
1027
+ "lxml",
1028
+ "msgpack",
1029
+ "numpy",
1030
+ "paramiko",
1031
+ "pyjwt",
1032
+ "pypdf",
1033
+ "pytz",
1034
+ "requests",
1035
+ "tzdata"
1036
+ ];
529
1037
  var venvReady = null;
530
1038
  function resolvePythonBinary(runtime) {
531
1039
  const match = /^python-(\d+\.\d+)$/.exec(runtime);
@@ -534,19 +1042,82 @@ function resolvePythonBinary(runtime) {
534
1042
  }
535
1043
  return "python3";
536
1044
  }
537
- function ensureVenv(runtime) {
1045
+ function runCommand(bin, args, onLine) {
1046
+ return new Promise((resolve2, reject) => {
1047
+ const proc = spawn(bin, args, { stdio: ["ignore", "pipe", "pipe"] });
1048
+ const pipeLines = (source, stream) => {
1049
+ const captured = [];
1050
+ let buffer = "";
1051
+ source.on("data", (chunk) => {
1052
+ buffer += chunk.toString("utf-8");
1053
+ let idx = buffer.indexOf("\n");
1054
+ while (idx >= 0) {
1055
+ const line = buffer.slice(0, idx).replace(/\r$/, "");
1056
+ buffer = buffer.slice(idx + 1);
1057
+ captured.push(line);
1058
+ onLine?.(line, stream);
1059
+ idx = buffer.indexOf("\n");
1060
+ }
1061
+ });
1062
+ source.on("end", () => {
1063
+ if (buffer.length > 0) {
1064
+ const line = buffer.replace(/\r$/, "");
1065
+ captured.push(line);
1066
+ onLine?.(line, stream);
1067
+ }
1068
+ });
1069
+ return captured;
1070
+ };
1071
+ const stderrCaptured = pipeLines(proc.stderr, "stderr");
1072
+ pipeLines(proc.stdout, "stdout");
1073
+ proc.on("error", reject);
1074
+ proc.on("close", (code) => {
1075
+ if (code === 0) {
1076
+ resolve2();
1077
+ } else {
1078
+ const tail = stderrCaptured.slice(-10).join("\n").trim();
1079
+ const detail = tail.length > 0 ? `: ${tail}` : "";
1080
+ reject(new Error(`${bin} exited with code ${String(code)}${detail}`));
1081
+ }
1082
+ });
1083
+ });
1084
+ }
1085
+ var installListeners = /* @__PURE__ */ new Set();
1086
+ function emitInstall(event) {
1087
+ for (const listener of installListeners) {
1088
+ listener(event);
1089
+ }
1090
+ }
1091
+ function ensureVenv(runtime, listener) {
1092
+ if (listener) {
1093
+ installListeners.add(listener);
1094
+ }
538
1095
  if (!venvReady) {
539
1096
  venvReady = (async () => {
540
- const venvPython = join6(VENV_DIR, "bin", "python");
1097
+ const venvPython = join8(VENV_DIR, "bin", "python");
541
1098
  try {
542
- await access(venvPython);
1099
+ await access2(venvPython);
543
1100
  } catch {
544
1101
  const systemPython = resolvePythonBinary(runtime);
545
- execFileSync(systemPython, ["-m", "venv", VENV_DIR], { stdio: "ignore" });
1102
+ await runCommand(systemPython, ["-m", "venv", VENV_DIR]);
1103
+ }
1104
+ emitInstall({ kind: "start" });
1105
+ try {
1106
+ await runCommand(
1107
+ venvPython,
1108
+ ["-m", "pip", "install", "--disable-pip-version-check", ...VENV_PACKAGES],
1109
+ (line, stream) => {
1110
+ emitInstall({ kind: "log", line, stream });
1111
+ }
1112
+ );
1113
+ emitInstall({ kind: "complete" });
1114
+ } catch (err) {
1115
+ const message = err instanceof Error ? err.message : String(err);
1116
+ emitInstall({ kind: "error", message });
1117
+ throw err;
1118
+ } finally {
1119
+ installListeners.clear();
546
1120
  }
547
- execFileSync(venvPython, ["-m", "pip", "install", "-q", ...VENV_PACKAGES], {
548
- stdio: "ignore"
549
- });
550
1121
  return venvPython;
551
1122
  })();
552
1123
  }
@@ -571,16 +1142,16 @@ function parseResult(stdout) {
571
1142
  }
572
1143
  async function executePythonStep(params) {
573
1144
  const timeout = params.timeout ?? DEFAULT_TIMEOUT;
574
- const pythonBin = await ensureVenv(params.scriptRuntime);
1145
+ const pythonBin = await ensureVenv(params.scriptRuntime, params.onInstallProgress);
575
1146
  const start = Date.now();
576
1147
  let tmpDir;
577
1148
  try {
578
- tmpDir = await mkdtemp(join6(tmpdir(), "kizen-step-"));
579
- const wrapperPath = join6(tmpDir, "wrapper.py");
580
- const scriptPath = join6(tmpDir, "script.py");
1149
+ tmpDir = await mkdtemp(join8(tmpdir(), "kizen-step-"));
1150
+ const wrapperPath = join8(tmpDir, "wrapper.py");
1151
+ const scriptPath = join8(tmpDir, "script.py");
581
1152
  await Promise.all([
582
- writeFile4(wrapperPath, WRAPPER_SCRIPT, "utf-8"),
583
- writeFile4(scriptPath, params.script, "utf-8")
1153
+ writeFile5(wrapperPath, WRAPPER_SCRIPT, "utf-8"),
1154
+ writeFile5(scriptPath, params.script, "utf-8")
584
1155
  ]);
585
1156
  const env = {
586
1157
  ...process.env,
@@ -588,7 +1159,7 @@ async function executePythonStep(params) {
588
1159
  __KIZEN_SECRETS__: JSON.stringify(params.secrets),
589
1160
  __KIZEN_SCRIPT_PATH__: scriptPath
590
1161
  };
591
- const { exitCode, stdout, stderr } = await new Promise((resolve) => {
1162
+ const { exitCode, stdout, stderr } = await new Promise((resolve2) => {
592
1163
  const stdoutChunks = [];
593
1164
  const stderrChunks = [];
594
1165
  let settled = false;
@@ -612,7 +1183,7 @@ async function executePythonStep(params) {
612
1183
  }
613
1184
  settled = true;
614
1185
  clearTimeout(timer);
615
- resolve({
1186
+ resolve2({
616
1187
  exitCode: code ?? 1,
617
1188
  stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
618
1189
  stderr: Buffer.concat(stderrChunks).toString("utf-8")
@@ -625,7 +1196,7 @@ async function executePythonStep(params) {
625
1196
  }
626
1197
  settled = true;
627
1198
  clearTimeout(timer);
628
- resolve({
1199
+ resolve2({
629
1200
  exitCode: 1,
630
1201
  stdout: "",
631
1202
  stderr: err.message
@@ -647,6 +1218,9 @@ async function executePythonStep(params) {
647
1218
  if (result.error !== null) {
648
1219
  base2.error = result.error;
649
1220
  }
1221
+ if (result.httpRequests) {
1222
+ base2.httpRequests = result.httpRequests;
1223
+ }
650
1224
  return base2;
651
1225
  }
652
1226
  const base = {
@@ -661,149 +1235,63 @@ async function executePythonStep(params) {
661
1235
  if (exitCode !== 0) {
662
1236
  base.error = `Process exited with code ${String(exitCode)}`;
663
1237
  }
664
- return base;
665
- } finally {
666
- if (tmpDir) {
667
- void rm(tmpDir, { recursive: true, force: true });
668
- }
669
- }
670
- }
671
-
672
- // src/server/remoteCodeRunner.ts
673
- var BASE_URLS = {
674
- go: "https://app.go.kizen.com/api",
675
- fmo: "https://app.fmo.kizen.com/api",
676
- staging: "https://staging.kizen.dev/api",
677
- integration: "https://integration.kizen.dev/api",
678
- test1: "https://test1.kizen.dev/api"
679
- };
680
- var SUPPORTED_RUNTIMES = ["python-3-13", "python-3-12"];
681
- var DEFAULT_RUNTIME = "python-3-12";
682
- var DATA_TYPE_TO_KIZEN = {
683
- text: "s",
684
- string: "s",
685
- number: "n",
686
- integer: "n",
687
- boolean: "b",
688
- date: "d",
689
- datetime: "dt",
690
- phone: "p",
691
- phonenumber: "p",
692
- uuid: "u",
693
- file: "f",
694
- list: "l"
695
- };
696
- function toKizenType(dataType) {
697
- return DATA_TYPE_TO_KIZEN[dataType.toLowerCase()] ?? "s";
698
- }
699
- function resolveRuntime(runtime) {
700
- if (SUPPORTED_RUNTIMES.includes(runtime)) {
701
- return runtime;
702
- }
703
- return DEFAULT_RUNTIME;
704
- }
705
- function encodeInputs(inputs, inputTypes) {
706
- const keys = Object.keys(inputs);
707
- if (keys.length === 0) {
708
- return null;
709
- }
710
- const encoded = {};
711
- for (const key of keys) {
712
- encoded[key] = {
713
- t: toKizenType(inputTypes[key] ?? "string"),
714
- v: inputs[key]
715
- };
716
- }
717
- return encoded;
718
- }
719
- function encodeOutputTypes(outputTypes) {
720
- const keys = Object.keys(outputTypes);
721
- if (keys.length === 0) {
722
- return null;
723
- }
724
- const encoded = {};
725
- for (const key of keys) {
726
- encoded[key] = toKizenType(outputTypes[key] ?? "string");
727
- }
728
- return encoded;
729
- }
730
- function decodeOutputValues(values) {
731
- if (!values) {
732
- return {};
733
- }
734
- const decoded = {};
735
- for (const [key, encoded] of Object.entries(values)) {
736
- decoded[key] = encoded.v;
737
- }
738
- return decoded;
739
- }
740
- async function executeRemoteStep(params, credentials) {
741
- const baseUrl = BASE_URLS[credentials.environment];
742
- const url = `${baseUrl}/coderunner/run`;
743
- const start = Date.now();
744
- const body = {
745
- user_script: params.script,
746
- runtime: resolveRuntime(params.scriptRuntime),
747
- secrets: params.secretNames.length > 0 ? params.secretNames : void 0,
748
- inputs: encodeInputs(params.inputs, params.inputTypes),
749
- output_types: encodeOutputTypes(params.outputTypes)
750
- };
751
- try {
752
- const response = await fetch(url, {
753
- method: "POST",
754
- headers: {
755
- "Content-Type": "application/json",
756
- "X-API-KEY": credentials.apiKey,
757
- "X-USER-ID": credentials.userId,
758
- "X-BUSINESS-ID": credentials.businessId
759
- },
760
- body: JSON.stringify(body)
761
- });
762
- const durationMs = Date.now() - start;
763
- if (!response.ok) {
764
- const text = await response.text();
765
- return {
766
- success: false,
767
- outputValues: {},
768
- logs: [],
769
- stdout: "",
770
- stderr: "",
771
- error: `Remote runner returned ${String(response.status)}: ${text}`,
772
- exitCode: 1,
773
- durationMs
774
- };
775
- }
776
- const data = await response.json();
777
- const hasError = data.error !== null;
778
- const result = {
779
- success: !hasError,
780
- outputValues: decodeOutputValues(data.values),
781
- logs: data.logs ?? [],
782
- stdout: "",
783
- stderr: "",
784
- exitCode: hasError ? 1 : 0,
785
- durationMs: data.duration_ms ?? durationMs
786
- };
787
- if (hasError && data.error) {
788
- result.error = data.error.detail ? `${data.error.error}
789
- ${data.error.detail}` : data.error.error;
1238
+ return base;
1239
+ } finally {
1240
+ if (tmpDir) {
1241
+ void rm(tmpDir, { recursive: true, force: true });
790
1242
  }
791
- return result;
792
- } catch (err) {
793
- return {
794
- success: false,
795
- outputValues: {},
796
- logs: [],
797
- stdout: "",
798
- stderr: "",
799
- error: `Remote execution failed: ${err instanceof Error ? err.message : String(err)}`,
800
- exitCode: 1,
801
- durationMs: Date.now() - start
802
- };
803
1243
  }
804
1244
  }
805
1245
 
806
1246
  // src/server/requestHandler.ts
1247
+ var SOURCE_FILE_MAX_BYTES = 2 * 1024 * 1024;
1248
+ var SKIP_FILES = /* @__PURE__ */ new Set([".DS_Store", ".gitignore"]);
1249
+ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
1250
+ ".ts",
1251
+ ".tsx",
1252
+ ".js",
1253
+ ".jsx",
1254
+ ".mjs",
1255
+ ".cjs",
1256
+ ".json",
1257
+ ".md",
1258
+ ".mdx",
1259
+ ".yml",
1260
+ ".yaml",
1261
+ ".toml",
1262
+ ".css",
1263
+ ".scss",
1264
+ ".html",
1265
+ ".htm",
1266
+ ".xml",
1267
+ ".svg",
1268
+ ".txt",
1269
+ ".sh",
1270
+ ".py",
1271
+ ".env",
1272
+ ".gitignore",
1273
+ ".prettierrc",
1274
+ ".eslintrc"
1275
+ ]);
1276
+ function resolveSafePath(pluginDir, relParam) {
1277
+ if (relParam.includes("\0")) {
1278
+ return null;
1279
+ }
1280
+ const rel = relParam.replace(/^\/+/, "");
1281
+ const abs = resolve(pluginDir, rel);
1282
+ if (abs !== pluginDir && !abs.startsWith(pluginDir + sep)) {
1283
+ return null;
1284
+ }
1285
+ return abs;
1286
+ }
1287
+ function isInsideSkippedDir(pluginDir, abs) {
1288
+ const rel = abs === pluginDir ? "" : abs.slice(pluginDir.length + 1);
1289
+ if (rel === "") {
1290
+ return false;
1291
+ }
1292
+ const segments = rel.split(sep);
1293
+ return segments.some((segment) => SKIP_DIRS.has(segment));
1294
+ }
807
1295
  function proxyLogEntryToString(entry) {
808
1296
  if (entry.kind === "info") {
809
1297
  return entry.message;
@@ -813,17 +1301,17 @@ function proxyLogEntryToString(entry) {
813
1301
  }
814
1302
  function getViewerPath() {
815
1303
  const filename = fileURLToPath(import.meta.url);
816
- return join7(dirname2(filename), "viewer");
1304
+ return join9(dirname2(filename), "viewer");
817
1305
  }
818
1306
  async function fileExists(filePath) {
819
1307
  try {
820
- await access2(filePath);
1308
+ await access3(filePath);
821
1309
  return true;
822
1310
  } catch {
823
1311
  return false;
824
1312
  }
825
1313
  }
826
- function createRequestHandler(viewerPath, createServerLog, createProxyLog, credentialsRef, activeProfileRef, outputDir, broadcast, onProxyCacheChange, lastPathRef) {
1314
+ function createRequestHandler(viewerPath, createServerLog, createProxyLog, credentialsRef, activeProfileRef, outputDir, broadcast, onProxyCacheChange, lastPathRef, pluginDir) {
827
1315
  const proxyCache = createProxyCache({ onChange: onProxyCacheChange });
828
1316
  return (req, res) => {
829
1317
  void (async () => {
@@ -864,7 +1352,7 @@ function createRequestHandler(viewerPath, createServerLog, createProxyLog, crede
864
1352
  return;
865
1353
  }
866
1354
  if (url === "/api/bundle") {
867
- const bundlePath = join7(process.cwd(), ".kizenapp", "bundle.json");
1355
+ const bundlePath = join9(process.cwd(), ".kizenapp", "bundle.json");
868
1356
  try {
869
1357
  const content = await readFile4(bundlePath, "utf-8");
870
1358
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
@@ -901,36 +1389,31 @@ function createRequestHandler(viewerPath, createServerLog, createProxyLog, crede
901
1389
  res.end(JSON.stringify({ error: "Missing required field: script" }));
902
1390
  return;
903
1391
  }
904
- let result;
905
- if (body.mode === "remote") {
906
- if (!credentialsRef.current) {
907
- res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
908
- res.end(JSON.stringify({ error: "Credentials are required for remote execution" }));
909
- return;
910
- }
911
- result = await executeRemoteStep(
912
- {
913
- script: body.script,
914
- scriptRuntime: body.scriptRuntime ?? "python-3-12",
915
- inputs: body.inputs ?? {},
916
- inputTypes: body.inputTypes ?? {},
917
- outputTypes: body.outputTypes ?? {},
918
- secretNames: body.secretNames ?? []
919
- },
920
- credentialsRef.current
921
- );
922
- } else {
923
- const stepParams = {
924
- script: body.script,
925
- scriptRuntime: body.scriptRuntime ?? "python-3.9",
926
- inputs: body.inputs ?? {},
927
- secrets: body.secrets ?? {}
928
- };
929
- if (typeof body.timeout === "number") {
930
- stepParams.timeout = body.timeout;
1392
+ const stepParams = {
1393
+ script: body.script,
1394
+ scriptRuntime: body.scriptRuntime ?? "python-3.9",
1395
+ inputs: body.inputs ?? {},
1396
+ secrets: body.secrets ?? {},
1397
+ onInstallProgress: (event) => {
1398
+ if (event.kind === "start") {
1399
+ broadcast({ type: "venv-install-start" });
1400
+ } else if (event.kind === "log") {
1401
+ broadcast({
1402
+ type: "venv-install-log",
1403
+ line: event.line,
1404
+ stream: event.stream
1405
+ });
1406
+ } else if (event.kind === "complete") {
1407
+ broadcast({ type: "venv-install-complete" });
1408
+ } else {
1409
+ broadcast({ type: "venv-install-error", message: event.message });
1410
+ }
931
1411
  }
932
- result = await executePythonStep(stepParams);
1412
+ };
1413
+ if (typeof body.timeout === "number") {
1414
+ stepParams.timeout = body.timeout;
933
1415
  }
1416
+ const result = await executePythonStep(stepParams);
934
1417
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
935
1418
  res.end(JSON.stringify(result));
936
1419
  return;
@@ -998,9 +1481,113 @@ function createRequestHandler(viewerPath, createServerLog, createProxyLog, crede
998
1481
  res.end(Buffer.from(await upstream.arrayBuffer()));
999
1482
  return;
1000
1483
  }
1484
+ if (url.startsWith("/api/source/tree") && req.method === "GET") {
1485
+ const query = new URL(url, "http://localhost").searchParams;
1486
+ const relParam = query.get("path") ?? "";
1487
+ const abs = resolveSafePath(pluginDir, relParam);
1488
+ if (abs === null) {
1489
+ res.writeHead(403, { "Content-Type": "application/json; charset=utf-8" });
1490
+ res.end(JSON.stringify({ error: "Invalid path" }));
1491
+ return;
1492
+ }
1493
+ if (isInsideSkippedDir(pluginDir, abs)) {
1494
+ res.writeHead(403, { "Content-Type": "application/json; charset=utf-8" });
1495
+ res.end(JSON.stringify({ error: "Path is excluded" }));
1496
+ return;
1497
+ }
1498
+ try {
1499
+ const dirEntries = await readdir3(abs, { withFileTypes: true });
1500
+ const entries = await Promise.all(
1501
+ dirEntries.filter((entry) => {
1502
+ if (entry.isDirectory()) {
1503
+ return !SKIP_DIRS.has(entry.name);
1504
+ }
1505
+ if (entry.isFile()) {
1506
+ return !SKIP_FILES.has(entry.name);
1507
+ }
1508
+ return false;
1509
+ }).map(async (entry) => {
1510
+ const kind = entry.isDirectory() ? "dir" : "file";
1511
+ const result = {
1512
+ name: entry.name,
1513
+ kind
1514
+ };
1515
+ if (kind === "file") {
1516
+ try {
1517
+ const stats = await stat(join9(abs, entry.name));
1518
+ result.size = stats.size;
1519
+ } catch {
1520
+ }
1521
+ }
1522
+ return result;
1523
+ })
1524
+ );
1525
+ entries.sort((a, b) => {
1526
+ if (a.kind !== b.kind) {
1527
+ return a.kind === "dir" ? -1 : 1;
1528
+ }
1529
+ return a.name.localeCompare(b.name);
1530
+ });
1531
+ const normalizedPath = abs === pluginDir ? "" : abs.slice(pluginDir.length + 1);
1532
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1533
+ res.end(JSON.stringify({ path: normalizedPath, entries }));
1534
+ } catch (err) {
1535
+ const message = err instanceof Error ? err.message : String(err);
1536
+ res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
1537
+ res.end(JSON.stringify({ error: message }));
1538
+ }
1539
+ return;
1540
+ }
1541
+ if (url.startsWith("/api/source/file") && req.method === "GET") {
1542
+ const query = new URL(url, "http://localhost").searchParams;
1543
+ const relParam = query.get("path") ?? "";
1544
+ if (relParam === "") {
1545
+ res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
1546
+ res.end(JSON.stringify({ error: "Missing path" }));
1547
+ return;
1548
+ }
1549
+ const abs = resolveSafePath(pluginDir, relParam);
1550
+ if (abs === null) {
1551
+ res.writeHead(403, { "Content-Type": "application/json; charset=utf-8" });
1552
+ res.end(JSON.stringify({ error: "Invalid path" }));
1553
+ return;
1554
+ }
1555
+ if (isInsideSkippedDir(pluginDir, abs)) {
1556
+ res.writeHead(403, { "Content-Type": "application/json; charset=utf-8" });
1557
+ res.end(JSON.stringify({ error: "Path is excluded" }));
1558
+ return;
1559
+ }
1560
+ try {
1561
+ const stats = await stat(abs);
1562
+ if (!stats.isFile()) {
1563
+ res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
1564
+ res.end(JSON.stringify({ error: "Not a file" }));
1565
+ return;
1566
+ }
1567
+ if (stats.size > SOURCE_FILE_MAX_BYTES) {
1568
+ res.writeHead(413, { "Content-Type": "application/json; charset=utf-8" });
1569
+ res.end(JSON.stringify({ error: "File too large", size: stats.size }));
1570
+ return;
1571
+ }
1572
+ const ext2 = extname(abs).toLowerCase();
1573
+ const explicitMime = MIME_TYPES[ext2];
1574
+ const isKnownText = TEXT_EXTENSIONS.has(ext2);
1575
+ const contentType = explicitMime ?? (isKnownText ? "text/plain; charset=utf-8" : "application/octet-stream");
1576
+ res.writeHead(200, {
1577
+ "Content-Type": contentType,
1578
+ "Content-Length": String(stats.size)
1579
+ });
1580
+ createReadStream(abs).pipe(res);
1581
+ } catch (err) {
1582
+ const message = err instanceof Error ? err.message : String(err);
1583
+ res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
1584
+ res.end(JSON.stringify({ error: message }));
1585
+ }
1586
+ return;
1587
+ }
1001
1588
  const rawPath = url === "/" ? "/index.html" : url;
1002
- const filePath = join7(viewerPath, rawPath);
1003
- const resolvedPath = await fileExists(filePath) ? filePath : join7(viewerPath, "index.html");
1589
+ const filePath = join9(viewerPath, rawPath);
1590
+ const resolvedPath = await fileExists(filePath) ? filePath : join9(viewerPath, "index.html");
1004
1591
  const ext = extname(resolvedPath);
1005
1592
  const mimeType = MIME_TYPES[ext] ?? "application/octet-stream";
1006
1593
  res.writeHead(200, { "Content-Type": mimeType });
@@ -1034,7 +1621,7 @@ import { launch } from "chrome-launcher";
1034
1621
  import { createRequire } from "module";
1035
1622
  import { mkdirSync } from "fs";
1036
1623
  import { readFile as readFile5 } from "fs/promises";
1037
- import { dirname as dirname3, join as join8 } from "path";
1624
+ import { dirname as dirname3, join as join10 } from "path";
1038
1625
  import { fileURLToPath as fileURLToPath2 } from "url";
1039
1626
  var CDP = createRequire(import.meta.url)("chrome-remote-interface");
1040
1627
  var CSP_HEADERS = /* @__PURE__ */ new Set([
@@ -1113,7 +1700,7 @@ async function serializeArgAsync(client, arg) {
1113
1700
  }
1114
1701
  async function installDockIcon(client) {
1115
1702
  try {
1116
- const iconPath = join8(dirname3(fileURLToPath2(import.meta.url)), "viewer", "icon.png");
1703
+ const iconPath = join10(dirname3(fileURLToPath2(import.meta.url)), "viewer", "icon.png");
1117
1704
  const icon = await readFile5(iconPath);
1118
1705
  await client.Browser.setDockTile({ image: icon.toString("base64") });
1119
1706
  } catch {
@@ -1134,19 +1721,261 @@ async function launchChromeViewer(opts) {
1134
1721
  handleSIGINT: false,
1135
1722
  logLevel: "silent"
1136
1723
  });
1724
+ const cdpPort = chrome.port;
1137
1725
  let client = null;
1138
1726
  let cookieTimer = null;
1139
- const cdpPort = chrome.port;
1727
+ let mainTargetId = null;
1728
+ let reconnectTimer = null;
1729
+ let reconnectAttempt = 0;
1730
+ let killed = false;
1731
+ const dbg = (msg) => {
1732
+ opts.debugLog?.(msg);
1733
+ };
1734
+ const vb = (msg) => {
1735
+ if (opts.verbose) {
1736
+ opts.debugLog?.(msg);
1737
+ }
1738
+ };
1739
+ const clearCookieTimer = () => {
1740
+ if (cookieTimer !== null) {
1741
+ clearInterval(cookieTimer);
1742
+ cookieTimer = null;
1743
+ }
1744
+ };
1745
+ const isNewTabUrl = (url) => url === "chrome://new-tab-page/" || url === "chrome://newtab/";
1746
+ const setupClient = async (c) => {
1747
+ clearCookieTimer();
1748
+ mainTargetId = null;
1749
+ await installDockIcon(c);
1750
+ await c.Fetch.enable({
1751
+ patterns: KIZEN_DOMAINS.flatMap((d) => [
1752
+ { urlPattern: `*://${d}/*`, requestStage: "Response" },
1753
+ { urlPattern: `*://*.${d}/*`, requestStage: "Response" }
1754
+ ])
1755
+ });
1756
+ c.on("Fetch.requestPaused", (rawParams) => {
1757
+ void (async () => {
1758
+ const params = rawParams;
1759
+ const { requestId, request, responseHeaders, responseStatusCode } = params;
1760
+ const url = request.url;
1761
+ vb(`Fetch ${String(responseStatusCode ?? "?")} ${url}`);
1762
+ const filtered = [];
1763
+ let rewroteSetCookie = false;
1764
+ for (const h of responseHeaders ?? []) {
1765
+ if (CSP_HEADERS.has(h.name.toLowerCase())) {
1766
+ continue;
1767
+ }
1768
+ if (h.name.toLowerCase() === "set-cookie" && isKizenUrl(url)) {
1769
+ const patched = h.value.split("\n").map((cookie) => {
1770
+ if (!/SameSite=/i.test(cookie)) {
1771
+ return cookie + "; SameSite=None; Secure";
1772
+ }
1773
+ return cookie.replace(/SameSite=\w+/i, "SameSite=None");
1774
+ }).join("\n");
1775
+ filtered.push({ name: h.name, value: patched });
1776
+ rewroteSetCookie = true;
1777
+ } else {
1778
+ filtered.push(h);
1779
+ }
1780
+ }
1781
+ if (rewroteSetCookie) {
1782
+ vb(` rewrote Set-Cookie SameSite on ${url}`);
1783
+ }
1784
+ try {
1785
+ const { body, base64Encoded } = await c.Fetch.getResponseBody({ requestId });
1786
+ await c.Fetch.fulfillRequest({
1787
+ requestId,
1788
+ responseCode: responseStatusCode ?? 200,
1789
+ responseHeaders: filtered,
1790
+ body,
1791
+ ...base64Encoded && { base64Encoded: true }
1792
+ });
1793
+ } catch (e) {
1794
+ vb(` fulfillRequest failed (${e.message}), falling through`);
1795
+ try {
1796
+ await c.Fetch.continueRequest({ requestId });
1797
+ } catch (e2) {
1798
+ vb(` continueRequest also failed (${e2.message})`);
1799
+ }
1800
+ }
1801
+ })();
1802
+ });
1803
+ cookieTimer = setInterval(() => {
1804
+ void (async () => {
1805
+ try {
1806
+ const { cookies } = await c.Network.getAllCookies();
1807
+ let patched = 0;
1808
+ for (const cookie of cookies) {
1809
+ if (!isKizenUrl(`https://${cookie.domain ?? ""}`)) {
1810
+ continue;
1811
+ }
1812
+ if (cookie.sameSite === "None") {
1813
+ continue;
1814
+ }
1815
+ const raw = cookie.domain ?? "";
1816
+ const bare = raw.startsWith(".") ? raw.slice(1) : raw;
1817
+ await c.Network.setCookie({
1818
+ url: `https://${bare}`,
1819
+ name: cookie.name,
1820
+ value: cookie.value,
1821
+ domain: raw,
1822
+ path: cookie.path ?? "/",
1823
+ secure: true,
1824
+ ...cookie.httpOnly !== void 0 && { httpOnly: cookie.httpOnly },
1825
+ ...cookie.expirationDate !== void 0 && {
1826
+ expires: cookie.expirationDate
1827
+ },
1828
+ sameSite: "None"
1829
+ });
1830
+ patched++;
1831
+ }
1832
+ if (patched > 0) {
1833
+ vb(`cookie-patch cycle: updated ${String(patched)} cookie(s) to SameSite=None`);
1834
+ }
1835
+ } catch (e) {
1836
+ vb(`cookie-patch cycle failed: ${e.message}`);
1837
+ }
1838
+ })();
1839
+ }, 2e3);
1840
+ await c.send("Runtime.enable", { generatePreview: true });
1841
+ c.on("Target.attachedToTarget", (rawParams) => {
1842
+ const params = rawParams;
1843
+ const { sessionId, targetInfo } = params;
1844
+ vb(`attached session ${sessionId} (${targetInfo.type} ${targetInfo.url})`);
1845
+ void c.send("Runtime.enable", { generatePreview: true }, sessionId).catch((e) => {
1846
+ vb(` Runtime.enable on session ${sessionId} failed: ${e.message}`);
1847
+ });
1848
+ });
1849
+ await c.Target.setAutoAttach({
1850
+ autoAttach: true,
1851
+ waitForDebuggerOnStart: false,
1852
+ flatten: true
1853
+ });
1854
+ await c.Target.setDiscoverTargets({ discover: true });
1855
+ const handleTarget = (targetInfo) => {
1856
+ if (targetInfo.type !== "page") {
1857
+ return;
1858
+ }
1859
+ if (isNewTabUrl(targetInfo.url)) {
1860
+ dbg(`closed stray new-tab-page target ${targetInfo.targetId}`);
1861
+ if (mainTargetId !== null) {
1862
+ void c.Target.activateTarget({ targetId: mainTargetId }).catch(() => {
1863
+ });
1864
+ }
1865
+ void c.Target.closeTarget({ targetId: targetInfo.targetId }).catch(() => {
1866
+ });
1867
+ return;
1868
+ }
1869
+ if (mainTargetId === null) {
1870
+ mainTargetId = targetInfo.targetId;
1871
+ dbg(`captured main target ${targetInfo.targetId} (${targetInfo.url})`);
1872
+ }
1873
+ };
1874
+ c.on("Target.targetCreated", (rawParams) => {
1875
+ const { targetInfo } = rawParams;
1876
+ vb(`target +${targetInfo.type} ${targetInfo.targetId} ${targetInfo.url}`);
1877
+ handleTarget(targetInfo);
1878
+ });
1879
+ c.on("Target.targetInfoChanged", (rawParams) => {
1880
+ const { targetInfo } = rawParams;
1881
+ vb(`target ~${targetInfo.type} ${targetInfo.targetId} ${targetInfo.url}`);
1882
+ handleTarget(targetInfo);
1883
+ });
1884
+ c.on("Runtime.consoleAPICalled", (rawParams) => {
1885
+ void (async () => {
1886
+ const { type, args } = rawParams;
1887
+ if (args[0]?.type === "string" && String(args[0].value).includes("Chrome Security")) {
1888
+ return;
1889
+ }
1890
+ const level = type === "warning" ? "warn" : ["log", "warn", "error", "info"].includes(type) ? type : "log";
1891
+ vb(`console.${level} (${String(args.length)} arg(s))`);
1892
+ const serialized = await Promise.all(args.map((arg) => serializeArgAsync(c, arg)));
1893
+ opts.broadcast({ type: "console-message", level, args: serialized });
1894
+ })().catch((e) => {
1895
+ vb(`console serialization failed: ${e.message}`);
1896
+ });
1897
+ });
1898
+ c.on("disconnect", () => {
1899
+ if (killed || client !== c) {
1900
+ return;
1901
+ }
1902
+ dbg("CDP disconnected");
1903
+ client = null;
1904
+ clearCookieTimer();
1905
+ scheduleReconnect();
1906
+ });
1907
+ };
1908
+ const scheduleReconnect = () => {
1909
+ if (killed || reconnectTimer !== null) {
1910
+ return;
1911
+ }
1912
+ const delay = Math.min(1e3 * 2 ** reconnectAttempt, 1e4);
1913
+ const attemptNumber = reconnectAttempt + 1;
1914
+ reconnectAttempt++;
1915
+ dbg(`CDP reconnect scheduled in ${String(delay)}ms (attempt #${String(attemptNumber)})`);
1916
+ reconnectTimer = setTimeout(() => {
1917
+ reconnectTimer = null;
1918
+ void (async () => {
1919
+ let c;
1920
+ try {
1921
+ c = await CDP({ port: cdpPort });
1922
+ } catch {
1923
+ dbg(`CDP reconnect attempt #${String(attemptNumber)} failed`);
1924
+ scheduleReconnect();
1925
+ return;
1926
+ }
1927
+ if (killed) {
1928
+ try {
1929
+ await c.close();
1930
+ } catch {
1931
+ }
1932
+ return;
1933
+ }
1934
+ client = c;
1935
+ try {
1936
+ await setupClient(c);
1937
+ dbg(`CDP reconnected after ${String(attemptNumber)} attempt(s)`);
1938
+ reconnectAttempt = 0;
1939
+ } catch {
1940
+ if (client === c) {
1941
+ client = null;
1942
+ }
1943
+ try {
1944
+ await c.close();
1945
+ } catch {
1946
+ }
1947
+ dbg(`CDP reconnect attempt #${String(attemptNumber)} failed during setup`);
1948
+ scheduleReconnect();
1949
+ }
1950
+ })();
1951
+ }, delay);
1952
+ };
1140
1953
  for (let attempt = 0; attempt < 10; attempt++) {
1141
1954
  await new Promise((r) => setTimeout(r, 300));
1955
+ dbg(`CDP connect attempt ${String(attempt + 1)}/10`);
1142
1956
  try {
1143
- client = await CDP({ port: cdpPort });
1957
+ const c = await CDP({ port: cdpPort });
1958
+ client = c;
1144
1959
  if (opts.path) {
1145
- void client.send("Page.navigate", { url: `${baseUrl}${opts.path}` });
1960
+ void c.send("Page.navigate", { url: `${baseUrl}${opts.path}` });
1961
+ }
1962
+ try {
1963
+ await setupClient(c);
1964
+ dbg("CDP connected");
1965
+ } catch {
1966
+ if (client === c) {
1967
+ client = null;
1968
+ }
1969
+ try {
1970
+ await c.close();
1971
+ } catch {
1972
+ }
1973
+ dbg("CDP setup failed after connect");
1146
1974
  }
1147
1975
  break;
1148
1976
  } catch {
1149
1977
  if (attempt === 9) {
1978
+ dbg("CDP unavailable after 10 attempts \u2014 viewer will run without interception");
1150
1979
  return {
1151
1980
  kill: () => {
1152
1981
  chrome.kill();
@@ -1162,120 +1991,20 @@ async function launchChromeViewer(opts) {
1162
1991
  }
1163
1992
  };
1164
1993
  }
1165
- const c = client;
1166
- await installDockIcon(c);
1167
- await c.Fetch.enable({
1168
- patterns: KIZEN_DOMAINS.flatMap((d) => [
1169
- { urlPattern: `*://${d}/*`, requestStage: "Response" },
1170
- { urlPattern: `*://*.${d}/*`, requestStage: "Response" }
1171
- ])
1172
- });
1173
- c.on("Fetch.requestPaused", (rawParams) => {
1174
- void (async () => {
1175
- const params = rawParams;
1176
- const { requestId, request, responseHeaders, responseStatusCode } = params;
1177
- const url = request.url;
1178
- const filtered = [];
1179
- for (const h of responseHeaders ?? []) {
1180
- if (CSP_HEADERS.has(h.name.toLowerCase())) {
1181
- continue;
1182
- }
1183
- if (h.name.toLowerCase() === "set-cookie" && isKizenUrl(url)) {
1184
- const patched = h.value.split("\n").map((cookie) => {
1185
- if (!/SameSite=/i.test(cookie)) {
1186
- return cookie + "; SameSite=None; Secure";
1187
- }
1188
- return cookie.replace(/SameSite=\w+/i, "SameSite=None");
1189
- }).join("\n");
1190
- filtered.push({ name: h.name, value: patched });
1191
- } else {
1192
- filtered.push(h);
1193
- }
1194
- }
1195
- try {
1196
- const { body, base64Encoded } = await c.Fetch.getResponseBody({ requestId });
1197
- await c.Fetch.fulfillRequest({
1198
- requestId,
1199
- responseCode: responseStatusCode ?? 200,
1200
- responseHeaders: filtered,
1201
- body,
1202
- ...base64Encoded && { base64Encoded: true }
1203
- });
1204
- } catch {
1205
- try {
1206
- await c.Fetch.continueRequest({ requestId });
1207
- } catch {
1208
- }
1209
- }
1210
- })();
1211
- });
1212
- cookieTimer = setInterval(() => {
1213
- void (async () => {
1994
+ const kill = () => {
1995
+ dbg("CDP kill requested \u2014 tearing down");
1996
+ killed = true;
1997
+ if (reconnectTimer !== null) {
1998
+ clearTimeout(reconnectTimer);
1999
+ reconnectTimer = null;
2000
+ }
2001
+ clearCookieTimer();
2002
+ if (client !== null) {
1214
2003
  try {
1215
- const { cookies } = await c.Network.getAllCookies();
1216
- for (const cookie of cookies) {
1217
- if (!isKizenUrl(`https://${cookie.domain ?? ""}`)) {
1218
- continue;
1219
- }
1220
- if (cookie.sameSite === "None") {
1221
- continue;
1222
- }
1223
- const raw = cookie.domain ?? "";
1224
- const bare = raw.startsWith(".") ? raw.slice(1) : raw;
1225
- await c.Network.setCookie({
1226
- url: `https://${bare}`,
1227
- name: cookie.name,
1228
- value: cookie.value,
1229
- domain: raw,
1230
- path: cookie.path ?? "/",
1231
- secure: true,
1232
- ...cookie.httpOnly !== void 0 && { httpOnly: cookie.httpOnly },
1233
- ...cookie.expirationDate !== void 0 && {
1234
- expires: cookie.expirationDate
1235
- },
1236
- sameSite: "None"
1237
- });
1238
- }
2004
+ void client.close();
1239
2005
  } catch {
1240
2006
  }
1241
- })();
1242
- }, 2e3);
1243
- await c.send("Runtime.enable", { generatePreview: true });
1244
- c.on("Target.attachedToTarget", (rawParams) => {
1245
- const { sessionId } = rawParams;
1246
- void c.send("Runtime.enable", { generatePreview: true }, sessionId).catch(() => {
1247
- });
1248
- });
1249
- await c.Target.setAutoAttach({
1250
- autoAttach: true,
1251
- waitForDebuggerOnStart: false,
1252
- flatten: true
1253
- });
1254
- await c.Target.setDiscoverTargets({ discover: true });
1255
- c.on("Target.targetCreated", (rawParams) => {
1256
- const { targetInfo } = rawParams;
1257
- if (targetInfo.type === "page" && targetInfo.url === "chrome://newtab/") {
1258
- void c.Target.closeTarget({ targetId: targetInfo.targetId }).catch(() => {
1259
- });
1260
- }
1261
- });
1262
- c.on("Runtime.consoleAPICalled", (rawParams) => {
1263
- void (async () => {
1264
- const { type, args } = rawParams;
1265
- if (args[0]?.type === "string" && String(args[0].value).includes("Chrome Security")) {
1266
- return;
1267
- }
1268
- const level = type === "warning" ? "warn" : ["log", "warn", "error", "info"].includes(type) ? type : "log";
1269
- const serialized = await Promise.all(args.map((arg) => serializeArgAsync(c, arg)));
1270
- opts.broadcast({ type: "console-message", level, args: serialized });
1271
- })().catch(() => {
1272
- });
1273
- });
1274
- const kill = () => {
1275
- clearInterval(cookieTimer);
1276
- try {
1277
- void c.close();
1278
- } catch {
2007
+ client = null;
1279
2008
  }
1280
2009
  chrome.kill();
1281
2010
  };
@@ -1283,10 +2012,10 @@ async function launchChromeViewer(opts) {
1283
2012
  }
1284
2013
 
1285
2014
  // src/hooks/useLogHistory.ts
1286
- import { useCallback, useState as useState3 } from "react";
2015
+ import { useCallback as useCallback2, useState as useState4 } from "react";
1287
2016
  function useLogHistory(limit) {
1288
- const [history, setHistory] = useState3([]);
1289
- const append = useCallback(
2017
+ const [history, setHistory] = useState4([]);
2018
+ const append = useCallback2(
1290
2019
  (message) => {
1291
2020
  setHistory((h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-limit));
1292
2021
  },
@@ -1296,29 +2025,29 @@ function useLogHistory(limit) {
1296
2025
  }
1297
2026
 
1298
2027
  // src/ui/CredentialSetupUI.tsx
1299
- import { useCallback as useCallback2, useEffect as useEffect3, useState as useState4 } from "react";
1300
- import { Box as Box4, Text as Text5, useInput } from "ink";
1301
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1302
- var FIELDS = ["apiKey", "userId", "businessId"];
1303
- var FIELD_LABELS = {
2028
+ import { useCallback as useCallback3, useEffect as useEffect3, useState as useState5 } from "react";
2029
+ import { Box as Box5, Text as Text6, useInput as useInput2 } from "ink";
2030
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
2031
+ var FIELDS2 = ["apiKey", "userId", "businessId"];
2032
+ var FIELD_LABELS2 = {
1304
2033
  apiKey: "API Key",
1305
2034
  userId: "User ID",
1306
2035
  businessId: "Business ID"
1307
2036
  };
1308
- var Hint = ({ text }) => /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: text });
2037
+ var Hint2 = ({ text }) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: text });
1309
2038
  var CredentialSetupUI = ({
1310
2039
  initialMode,
1311
2040
  showProfileManager,
1312
2041
  onComplete,
1313
2042
  onCancel
1314
2043
  }) => {
1315
- const [phase, setPhase] = useState4(
2044
+ const [phase, setPhase] = useState5(
1316
2045
  initialMode === "global" && !showProfileManager ? { type: "loading" } : initialMode === "global" && showProfileManager ? { type: "profile-loading" } : { type: "mode-select", cursor: 0 }
1317
2046
  );
1318
- const [inputBuffer, setInputBuffer] = useState4("");
1319
- const [error, setError] = useState4(null);
1320
- const [activeProfileName, setActiveProfileName] = useState4(void 0);
1321
- const handleModeChosen = useCallback2(
2047
+ const [inputBuffer, setInputBuffer] = useState5("");
2048
+ const [error, setError] = useState5(null);
2049
+ const [activeProfileName, setActiveProfileName] = useState5(void 0);
2050
+ const handleModeChosen = useCallback3(
1322
2051
  (mode) => {
1323
2052
  if (mode === "local") {
1324
2053
  onComplete({ mode: "local", credentials: null });
@@ -1328,7 +2057,7 @@ var CredentialSetupUI = ({
1328
2057
  },
1329
2058
  [onComplete]
1330
2059
  );
1331
- const handleProfileChosen = useCallback2(async (profile) => {
2060
+ const handleProfileChosen = useCallback3(async (profile) => {
1332
2061
  if (profile === null) {
1333
2062
  setPhase({ type: "profile-loading" });
1334
2063
  const profiles = await listCredentialProfiles();
@@ -1341,7 +2070,7 @@ var CredentialSetupUI = ({
1341
2070
  const envCursor2 = existing ? Math.max(0, ENVIRONMENTS.indexOf(existing.environment)) : 0;
1342
2071
  setPhase({ type: "creds-entry", field: 0, values: existing ?? {}, envCursor: envCursor2 });
1343
2072
  }, []);
1344
- const handleSave = useCallback2(
2073
+ const handleSave = useCallback3(
1345
2074
  async (values2, envCursor2) => {
1346
2075
  const environment = ENVIRONMENTS[envCursor2] ?? "go";
1347
2076
  const credentials = {
@@ -1366,7 +2095,7 @@ var CredentialSetupUI = ({
1366
2095
  },
1367
2096
  [onComplete, activeProfileName]
1368
2097
  );
1369
- useInput((input, key) => {
2098
+ useInput2((input, key) => {
1370
2099
  if (key.ctrl && input === "c") {
1371
2100
  process.exit(0);
1372
2101
  }
@@ -1440,7 +2169,7 @@ var CredentialSetupUI = ({
1440
2169
  }
1441
2170
  if (phase.type === "creds-entry") {
1442
2171
  const { field, values: values2, envCursor: envCursor2 } = phase;
1443
- const isEnvField2 = field === FIELDS.length;
2172
+ const isEnvField2 = field === FIELDS2.length;
1444
2173
  if (isEnvField2) {
1445
2174
  if (key.leftArrow) {
1446
2175
  setPhase({
@@ -1451,13 +2180,13 @@ var CredentialSetupUI = ({
1451
2180
  setPhase({ ...phase, envCursor: (envCursor2 + 1) % ENVIRONMENTS.length });
1452
2181
  } else if (key.upArrow) {
1453
2182
  setPhase({ ...phase, field: field - 1 });
1454
- setInputBuffer(values2[FIELDS[field - 1]] ?? "");
2183
+ setInputBuffer(values2[FIELDS2[field - 1]] ?? "");
1455
2184
  } else if (key.return) {
1456
2185
  void handleSave(values2, envCursor2);
1457
2186
  }
1458
2187
  return;
1459
2188
  }
1460
- const fieldName = FIELDS[field];
2189
+ const fieldName = FIELDS2[field];
1461
2190
  if (!fieldName) {
1462
2191
  return;
1463
2192
  }
@@ -1467,13 +2196,13 @@ var CredentialSetupUI = ({
1467
2196
  const updatedValues = { ...values2, [fieldName]: inputBuffer };
1468
2197
  const prevField = field - 1;
1469
2198
  setPhase({ ...phase, field: prevField, values: updatedValues });
1470
- setInputBuffer(updatedValues[FIELDS[prevField]] ?? "");
2199
+ setInputBuffer(updatedValues[FIELDS2[prevField]] ?? "");
1471
2200
  } else if (key.return || key.tab || key.downArrow) {
1472
2201
  const updatedValues = { ...values2, [fieldName]: inputBuffer };
1473
2202
  const nextField = field + 1;
1474
2203
  setPhase({ ...phase, field: nextField, values: updatedValues });
1475
- if (nextField < FIELDS.length) {
1476
- setInputBuffer(updatedValues[FIELDS[nextField]] ?? "");
2204
+ if (nextField < FIELDS2.length) {
2205
+ setInputBuffer(updatedValues[FIELDS2[nextField]] ?? "");
1477
2206
  } else {
1478
2207
  setInputBuffer("");
1479
2208
  }
@@ -1482,7 +2211,7 @@ var CredentialSetupUI = ({
1482
2211
  }
1483
2212
  }
1484
2213
  });
1485
- const loadProfileList = useCallback2(async () => {
2214
+ const loadProfileList = useCallback3(async () => {
1486
2215
  const profiles = await listCredentialProfiles();
1487
2216
  setPhase({ type: "profile-select", profiles, cursor: 0 });
1488
2217
  }, []);
@@ -1507,7 +2236,7 @@ var CredentialSetupUI = ({
1507
2236
  }, [phase.type]);
1508
2237
  if (phase.type === "loading" || phase.type === "saving" || phase.type === "profile-loading") {
1509
2238
  const label = phase.type === "saving" ? "Saving credentials\u2026" : phase.type === "profile-loading" ? "Loading profiles\u2026" : "Loading credentials\u2026";
1510
- return /* @__PURE__ */ jsx5(Box4, { paddingY: 1, paddingX: 2, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: label }) });
2239
+ return /* @__PURE__ */ jsx6(Box5, { paddingY: 1, paddingX: 2, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: label }) });
1511
2240
  }
1512
2241
  if (phase.type === "done") {
1513
2242
  return null;
@@ -1517,41 +2246,41 @@ var CredentialSetupUI = ({
1517
2246
  { label: "Global", desc: `~/.kizenappbuilder/credentials.json`, mode: "global" },
1518
2247
  { label: "Local", desc: "Enter credentials in the browser dev tools", mode: "local" }
1519
2248
  ];
1520
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
1521
- /* @__PURE__ */ jsx5(AppHeader, {}),
1522
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
1523
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Credential mode" }),
2249
+ return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
2250
+ /* @__PURE__ */ jsx6(AppHeader, {}),
2251
+ /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 0, children: [
2252
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Credential mode" }),
1524
2253
  options.map((opt, i) => {
1525
2254
  const selected = phase.cursor === i;
1526
- return /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1527
- /* @__PURE__ */ jsx5(Text5, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
1528
- /* @__PURE__ */ jsx5(Text5, { bold: selected, ...selected && { color: "cyan" }, children: opt.label }),
1529
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: opt.desc })
2255
+ return /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
2256
+ /* @__PURE__ */ jsx6(Text6, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
2257
+ /* @__PURE__ */ jsx6(Text6, { bold: selected, ...selected && { color: "cyan" }, children: opt.label }),
2258
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: opt.desc })
1530
2259
  ] }, opt.mode);
1531
2260
  })
1532
2261
  ] }),
1533
- /* @__PURE__ */ jsx5(Hint, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Ctrl+C to quit" })
2262
+ /* @__PURE__ */ jsx6(Hint2, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Ctrl+C to quit" })
1534
2263
  ] });
1535
2264
  }
1536
2265
  if (phase.type === "profile-select") {
1537
2266
  const { profiles, cursor } = phase;
1538
2267
  const addNewIdx = profiles.length;
1539
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
1540
- /* @__PURE__ */ jsx5(AppHeader, {}),
1541
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
1542
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Credential profiles" }),
2268
+ return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
2269
+ /* @__PURE__ */ jsx6(AppHeader, {}),
2270
+ /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 0, children: [
2271
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Credential profiles" }),
1543
2272
  profiles.map((profile, i) => {
1544
2273
  const selected = cursor === i;
1545
- return /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1546
- /* @__PURE__ */ jsx5(Text5, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
1547
- /* @__PURE__ */ jsx5(Text5, { bold: selected, ...selected && { color: "cyan" }, children: profile.isDefault ? "Default" : profile.name }),
1548
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: profile.path })
2274
+ return /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
2275
+ /* @__PURE__ */ jsx6(Text6, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
2276
+ /* @__PURE__ */ jsx6(Text6, { bold: selected, ...selected && { color: "cyan" }, children: profile.isDefault ? "Default" : profile.name }),
2277
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: profile.path })
1549
2278
  ] }, profile.name);
1550
2279
  }),
1551
- /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1552
- /* @__PURE__ */ jsx5(Text5, { ...cursor === addNewIdx && { color: "cyan" }, children: cursor === addNewIdx ? "\u276F" : " " }),
1553
- /* @__PURE__ */ jsx5(
1554
- Text5,
2280
+ /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
2281
+ /* @__PURE__ */ jsx6(Text6, { ...cursor === addNewIdx && { color: "cyan" }, children: cursor === addNewIdx ? "\u276F" : " " }),
2282
+ /* @__PURE__ */ jsx6(
2283
+ Text6,
1555
2284
  {
1556
2285
  bold: cursor === addNewIdx,
1557
2286
  ...cursor === addNewIdx && { color: "cyan" },
@@ -1560,69 +2289,69 @@ var CredentialSetupUI = ({
1560
2289
  )
1561
2290
  ] })
1562
2291
  ] }),
1563
- /* @__PURE__ */ jsx5(Hint, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Esc to cancel" })
2292
+ /* @__PURE__ */ jsx6(Hint2, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Esc to cancel" })
1564
2293
  ] });
1565
2294
  }
1566
2295
  if (phase.type === "name-entry") {
1567
2296
  const { nameBuffer, nameError } = phase;
1568
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
1569
- /* @__PURE__ */ jsx5(AppHeader, {}),
1570
- /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", gap: 0, children: /* @__PURE__ */ jsx5(Text5, { bold: true, children: "New credential profile" }) }),
1571
- nameError && /* @__PURE__ */ jsxs3(Text5, { color: "red", children: [
2297
+ return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
2298
+ /* @__PURE__ */ jsx6(AppHeader, {}),
2299
+ /* @__PURE__ */ jsx6(Box5, { flexDirection: "column", gap: 0, children: /* @__PURE__ */ jsx6(Text6, { bold: true, children: "New credential profile" }) }),
2300
+ nameError && /* @__PURE__ */ jsxs4(Text6, { color: "red", children: [
1572
2301
  "Error: ",
1573
2302
  nameError
1574
2303
  ] }),
1575
- /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1576
- /* @__PURE__ */ jsx5(Box4, { width: 12, children: /* @__PURE__ */ jsx5(Text5, { bold: true, color: "cyan", children: "Profile name" }) }),
1577
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: ">" }),
1578
- /* @__PURE__ */ jsx5(Text5, { children: nameBuffer }),
1579
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u2588" })
2304
+ /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
2305
+ /* @__PURE__ */ jsx6(Box5, { width: 12, children: /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: "Profile name" }) }),
2306
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: ">" }),
2307
+ /* @__PURE__ */ jsx6(Text6, { children: nameBuffer }),
2308
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "\u2588" })
1580
2309
  ] }),
1581
- /* @__PURE__ */ jsx5(Hint, { text: "Type a name \xB7 Enter to confirm \xB7 Esc to go back" })
2310
+ /* @__PURE__ */ jsx6(Hint2, { text: "Type a name \xB7 Enter to confirm \xB7 Esc to go back" })
1582
2311
  ] });
1583
2312
  }
1584
2313
  const { field: activeField, values, envCursor } = phase;
1585
- const isEnvField = activeField === FIELDS.length;
2314
+ const isEnvField = activeField === FIELDS2.length;
1586
2315
  const profileLabel = activeProfileName ?? DEFAULT_PROFILE_NAME;
1587
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
1588
- /* @__PURE__ */ jsx5(AppHeader, {}),
1589
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
1590
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: activeProfileName !== void 0 ? `Profile: ${activeProfileName}` : "Global credentials" }),
1591
- /* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
2316
+ return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
2317
+ /* @__PURE__ */ jsx6(AppHeader, {}),
2318
+ /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 0, children: [
2319
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: activeProfileName !== void 0 ? `Profile: ${activeProfileName}` : "Global credentials" }),
2320
+ /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
1592
2321
  "Saved to:",
1593
2322
  " ",
1594
2323
  activeProfileName !== void 0 ? `~/.kizenappbuilder/${activeProfileName}.json` : GLOBAL_CREDENTIALS_PATH
1595
2324
  ] })
1596
2325
  ] }),
1597
- error && /* @__PURE__ */ jsxs3(Text5, { color: "red", children: [
2326
+ error && /* @__PURE__ */ jsxs4(Text6, { color: "red", children: [
1598
2327
  "Error: ",
1599
2328
  error
1600
2329
  ] }),
1601
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
1602
- FIELDS.map((name, i) => {
2330
+ /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 0, children: [
2331
+ FIELDS2.map((name, i) => {
1603
2332
  const isActive = activeField === i;
1604
2333
  const displayValue = isActive ? inputBuffer : values[name] ?? "";
1605
- return /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1606
- /* @__PURE__ */ jsx5(Box4, { width: 12, children: /* @__PURE__ */ jsx5(Text5, { bold: isActive, ...isActive && { color: "cyan" }, children: FIELD_LABELS[name] }) }),
1607
- /* @__PURE__ */ jsx5(Text5, { ...isActive && { color: "cyan" }, children: ">" }),
1608
- /* @__PURE__ */ jsx5(Text5, { children: displayValue }),
1609
- isActive && /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u2588" })
2334
+ return /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
2335
+ /* @__PURE__ */ jsx6(Box5, { width: 12, children: /* @__PURE__ */ jsx6(Text6, { bold: isActive, ...isActive && { color: "cyan" }, children: FIELD_LABELS2[name] }) }),
2336
+ /* @__PURE__ */ jsx6(Text6, { ...isActive && { color: "cyan" }, children: ">" }),
2337
+ /* @__PURE__ */ jsx6(Text6, { children: displayValue }),
2338
+ isActive && /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "\u2588" })
1610
2339
  ] }, name);
1611
2340
  }),
1612
- /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1613
- /* @__PURE__ */ jsx5(Box4, { width: 12, children: /* @__PURE__ */ jsx5(Text5, { bold: isEnvField, ...isEnvField && { color: "cyan" }, children: "Environment" }) }),
1614
- /* @__PURE__ */ jsx5(Text5, { ...isEnvField && { color: "cyan" }, children: ">" }),
1615
- /* @__PURE__ */ jsx5(Box4, { gap: 1, children: ENVIRONMENTS.map((env, i) => {
2341
+ /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
2342
+ /* @__PURE__ */ jsx6(Box5, { width: 12, children: /* @__PURE__ */ jsx6(Text6, { bold: isEnvField, ...isEnvField && { color: "cyan" }, children: "Environment" }) }),
2343
+ /* @__PURE__ */ jsx6(Text6, { ...isEnvField && { color: "cyan" }, children: ">" }),
2344
+ /* @__PURE__ */ jsx6(Box5, { gap: 1, children: ENVIRONMENTS.map((env, i) => {
1616
2345
  const isSelected = i === envCursor;
1617
2346
  if (isEnvField) {
1618
- return /* @__PURE__ */ jsx5(Text5, { bold: isSelected, ...isSelected && { color: "cyan" }, children: isSelected ? `[${env}]` : env }, env);
2347
+ return /* @__PURE__ */ jsx6(Text6, { bold: isSelected, ...isSelected && { color: "cyan" }, children: isSelected ? `[${env}]` : env }, env);
1619
2348
  }
1620
- return /* @__PURE__ */ jsx5(Text5, { dimColor: !isSelected, bold: isSelected, children: env }, env);
2349
+ return /* @__PURE__ */ jsx6(Text6, { dimColor: !isSelected, bold: isSelected, children: env }, env);
1621
2350
  }) })
1622
2351
  ] })
1623
2352
  ] }),
1624
- 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" }),
1625
- /* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
2353
+ isEnvField ? /* @__PURE__ */ jsx6(Hint2, { text: "\u2190\u2192 to select \xB7 \u2191\u2193 to move \xB7 Enter to save \xB7 Esc to cancel" }) : /* @__PURE__ */ jsx6(Hint2, { text: "\u2191\u2193 to move \xB7 Backspace to delete \xB7 Esc to cancel" }),
2354
+ /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
1626
2355
  "Profile: ",
1627
2356
  profileLabel
1628
2357
  ] })
@@ -1630,7 +2359,7 @@ var CredentialSetupUI = ({
1630
2359
  };
1631
2360
 
1632
2361
  // src/ui/DevUI.tsx
1633
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
2362
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1634
2363
  var SKIP_WATCH_PREFIXES = [".kizenapp", ".git"];
1635
2364
  var DevUI = ({
1636
2365
  port,
@@ -1639,35 +2368,38 @@ var DevUI = ({
1639
2368
  credentials: initialCredentials,
1640
2369
  credentialMode: initialCredentialMode,
1641
2370
  activeCredentialProfile: initialActiveProfile,
1642
- lastPath: initialLastPath
2371
+ lastPath: initialLastPath,
2372
+ debug = false,
2373
+ verbose = false
1643
2374
  }) => {
1644
2375
  const credentialsRef = useRef(initialCredentials);
1645
2376
  const activeProfileRef = useRef(initialActiveProfile);
1646
2377
  const lastPathRef = useRef(initialLastPath);
1647
- const [credMode, setCredMode] = useState5("main");
1648
- const [credentialMode, setCredentialMode] = useState5(
2378
+ const [credMode, setCredMode] = useState6("main");
2379
+ const [credentialMode, setCredentialMode] = useState6(
1649
2380
  initialCredentialMode
1650
2381
  );
1651
- const [status, setStatus] = useState5("starting");
1652
- const [errorMessage, setErrorMessage] = useState5(null);
2382
+ const [status, setStatus] = useState6("starting");
2383
+ const [errorMessage, setErrorMessage] = useState6(null);
1653
2384
  const [serverLogHistory, appendServerLog] = useLogHistory(LOG_LIMIT);
1654
2385
  const [buildLogHistory, appendBuildLog] = useLogHistory(LOG_LIMIT);
1655
- const [proxyLogHistory, setProxyLogHistory] = useState5([]);
1656
- const [buildStatus, setBuildStatus] = useState5("pending");
1657
- const [buildError, setBuildError] = useState5(null);
1658
- const [lastBuilt, setLastBuilt] = useState5(null);
1659
- const [lastBundleSize, setLastBundleSize] = useState5(null);
1660
- const [wsClientCount, setWsClientCount] = useState5(0);
1661
- const [proxyCacheSize, setProxyCacheSize] = useState5(0);
1662
- const [proxyCacheHits, setProxyCacheHits] = useState5(0);
1663
- const [proxyCacheMisses, setProxyCacheMisses] = useState5(0);
2386
+ const [cdpLogHistory, appendCdpLog] = useLogHistory(LOG_LIMIT);
2387
+ const [proxyLogHistory, setProxyLogHistory] = useState6([]);
2388
+ const [buildStatus, setBuildStatus] = useState6("pending");
2389
+ const [buildError, setBuildError] = useState6(null);
2390
+ const [lastBuilt, setLastBuilt] = useState6(null);
2391
+ const [lastBundleSize, setLastBundleSize] = useState6(null);
2392
+ const [wsClientCount, setWsClientCount] = useState6(0);
2393
+ const [proxyCacheSize, setProxyCacheSize] = useState6(0);
2394
+ const [proxyCacheHits, setProxyCacheHits] = useState6(0);
2395
+ const [proxyCacheMisses, setProxyCacheMisses] = useState6(0);
1664
2396
  const buildingRef = useRef(false);
1665
2397
  const viewerLaunchedRef = useRef(false);
1666
2398
  const chromeHandleRef = useRef(null);
1667
2399
  const debounceTimerRef = useRef(null);
1668
2400
  const wsClientsRef = useRef(/* @__PURE__ */ new Set());
1669
2401
  const pendingMessagesRef = useRef([]);
1670
- const broadcast = useCallback3((msg) => {
2402
+ const broadcast = useCallback4((msg) => {
1671
2403
  const json = JSON.stringify(msg);
1672
2404
  if (wsClientsRef.current.size === 0) {
1673
2405
  pendingMessagesRef.current = [...pendingMessagesRef.current, json].slice(-LOG_LIMIT);
@@ -1680,22 +2412,30 @@ var DevUI = ({
1680
2412
  }
1681
2413
  }, []);
1682
2414
  const createServerLog = appendServerLog;
1683
- const launchChromeViewerCb = useCallback3(() => {
2415
+ const launchChromeViewerCb = useCallback4(() => {
1684
2416
  chromeHandleRef.current?.kill();
1685
2417
  chromeHandleRef.current = null;
1686
2418
  void launchChromeViewer({
1687
2419
  port,
1688
- userDataDir: join9(outputDir, ".chrome"),
2420
+ userDataDir: join11(outputDir, ".chrome"),
1689
2421
  broadcast,
1690
- ...lastPathRef.current !== void 0 && { path: lastPathRef.current }
2422
+ ...lastPathRef.current !== void 0 && { path: lastPathRef.current },
2423
+ ...debug && { debugLog: appendCdpLog },
2424
+ ...verbose && { verbose: true }
1691
2425
  }).then((handle) => {
1692
2426
  chromeHandleRef.current = handle;
1693
- process.on("exit", () => {
1694
- handle.kill();
1695
- });
1696
2427
  });
1697
- }, [port, outputDir, broadcast]);
1698
- useInput2(
2428
+ }, [port, outputDir, broadcast, debug, verbose, appendCdpLog]);
2429
+ useEffect4(() => {
2430
+ const onExit = () => {
2431
+ chromeHandleRef.current?.kill();
2432
+ };
2433
+ process.on("exit", onExit);
2434
+ return () => {
2435
+ process.off("exit", onExit);
2436
+ };
2437
+ }, []);
2438
+ useInput3(
1699
2439
  (input, key) => {
1700
2440
  if (input === "q" || key.ctrl && input === "c") {
1701
2441
  process.exit(0);
@@ -1716,7 +2456,7 @@ var DevUI = ({
1716
2456
  viewerLaunchedRef.current = true;
1717
2457
  launchChromeViewerCb();
1718
2458
  }, [status, launchChromeViewerCb]);
1719
- const handleCredentialsDone = useCallback3(
2459
+ const handleCredentialsDone = useCallback4(
1720
2460
  (result) => {
1721
2461
  credentialsRef.current = result.credentials;
1722
2462
  activeProfileRef.current = result.profileName;
@@ -1731,7 +2471,7 @@ var DevUI = ({
1731
2471
  },
1732
2472
  [outputDir, broadcast]
1733
2473
  );
1734
- const createProxyLog = useCallback3(
2474
+ const createProxyLog = useCallback4(
1735
2475
  (entry) => {
1736
2476
  setProxyLogHistory(
1737
2477
  (h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${proxyLogEntryToString(entry)}`].slice(
@@ -1749,14 +2489,14 @@ var DevUI = ({
1749
2489
  },
1750
2490
  [broadcast]
1751
2491
  );
1752
- const createBuildLog = useCallback3(
2492
+ const createBuildLog = useCallback4(
1753
2493
  (message) => {
1754
2494
  appendBuildLog(message);
1755
2495
  broadcast({ type: "log", message });
1756
2496
  },
1757
2497
  [appendBuildLog, broadcast]
1758
2498
  );
1759
- const triggerBuild = useCallback3(() => {
2499
+ const triggerBuild = useCallback4(() => {
1760
2500
  if (buildingRef.current) {
1761
2501
  return;
1762
2502
  }
@@ -1808,7 +2548,7 @@ var DevUI = ({
1808
2548
  }, [pluginDir, triggerBuild, createBuildLog]);
1809
2549
  useEffect4(() => {
1810
2550
  const viewerPath = getViewerPath();
1811
- void fileExists(join9(viewerPath, "index.html")).then((viewerBuilt) => {
2551
+ void fileExists(join11(viewerPath, "index.html")).then((viewerBuilt) => {
1812
2552
  if (!viewerBuilt) {
1813
2553
  setErrorMessage("Viewer not built. Run 'pnpm build:viewer' first.");
1814
2554
  setStatus("error");
@@ -1823,7 +2563,8 @@ var DevUI = ({
1823
2563
  outputDir,
1824
2564
  broadcast,
1825
2565
  setProxyCacheSize,
1826
- lastPathRef
2566
+ lastPathRef,
2567
+ pluginDir
1827
2568
  );
1828
2569
  const server = createServer(handler);
1829
2570
  const wss = new WebSocketServer({ server });
@@ -1855,9 +2596,9 @@ var DevUI = ({
1855
2596
  server.close();
1856
2597
  };
1857
2598
  });
1858
- }, [port, createServerLog, createProxyLog, broadcast, outputDir]);
2599
+ }, [port, createServerLog, createProxyLog, broadcast, outputDir, pluginDir]);
1859
2600
  if (credMode === "editing") {
1860
- return /* @__PURE__ */ jsx6(
2601
+ return /* @__PURE__ */ jsx7(
1861
2602
  CredentialSetupUI,
1862
2603
  {
1863
2604
  ...credentialMode !== void 0 && { initialMode: credentialMode },
@@ -1871,80 +2612,80 @@ var DevUI = ({
1871
2612
  }
1872
2613
  const elapsedSeconds = lastBuilt !== null ? Math.round((Date.now() - lastBuilt.getTime()) / 1e3) : null;
1873
2614
  const elapsedLabel = elapsedSeconds === null ? "" : elapsedSeconds < 5 ? " (just now)" : ` (${String(elapsedSeconds)}s ago)`;
1874
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
1875
- /* @__PURE__ */ jsx6(AppHeader, { marginBottom: 1 }),
1876
- /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
1877
- /* @__PURE__ */ jsxs4(Box5, { gap: 2, marginBottom: 0, children: [
1878
- /* @__PURE__ */ jsx6(Text6, { bold: true, dimColor: true, children: "App Build" }),
1879
- buildStatus === "pending" && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "waiting\u2026" }),
1880
- buildStatus === "building" && /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
1881
- /* @__PURE__ */ jsx6(Spinner, {}),
1882
- /* @__PURE__ */ jsx6(Text6, { children: "Building plugin package\u2026" })
2615
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
2616
+ /* @__PURE__ */ jsx7(AppHeader, { marginBottom: 1 }),
2617
+ /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
2618
+ /* @__PURE__ */ jsxs5(Box6, { gap: 2, marginBottom: 0, children: [
2619
+ /* @__PURE__ */ jsx7(Text7, { bold: true, dimColor: true, children: "App Build" }),
2620
+ buildStatus === "pending" && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "waiting\u2026" }),
2621
+ buildStatus === "building" && /* @__PURE__ */ jsxs5(Box6, { gap: 1, children: [
2622
+ /* @__PURE__ */ jsx7(Spinner, {}),
2623
+ /* @__PURE__ */ jsx7(Text7, { children: "Building plugin package\u2026" })
1883
2624
  ] }),
1884
- buildStatus === "done" && /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
1885
- /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713 Built" }),
1886
- lastBuilt !== null && /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
2625
+ buildStatus === "done" && /* @__PURE__ */ jsxs5(Box6, { gap: 1, children: [
2626
+ /* @__PURE__ */ jsx7(Text7, { color: "green", children: "\u2713 Built" }),
2627
+ lastBuilt !== null && /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
1887
2628
  "at ",
1888
2629
  lastBuilt.toLocaleTimeString(),
1889
2630
  elapsedLabel
1890
2631
  ] }),
1891
- lastBundleSize !== null && /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
2632
+ lastBundleSize !== null && /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
1892
2633
  "\xB7 bundle: ",
1893
2634
  formatBytes(lastBundleSize)
1894
2635
  ] })
1895
2636
  ] }),
1896
- buildStatus === "error" && /* @__PURE__ */ jsxs4(Text6, { color: "red", children: [
2637
+ buildStatus === "error" && /* @__PURE__ */ jsxs5(Text7, { color: "red", children: [
1897
2638
  "\u2717 ",
1898
2639
  buildError ?? "unknown error"
1899
2640
  ] })
1900
2641
  ] }),
1901
- /* @__PURE__ */ jsx6(
1902
- Box5,
2642
+ /* @__PURE__ */ jsx7(
2643
+ Box6,
1903
2644
  {
1904
2645
  flexDirection: "column",
1905
2646
  height: LOG_DISPLAY + 2,
1906
2647
  borderStyle: "single",
1907
2648
  borderColor: "gray",
1908
2649
  overflow: "hidden",
1909
- children: buildLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "truncate", children: log }, index))
2650
+ children: buildLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx7(Text7, { dimColor: true, wrap: "truncate", children: log }, index))
1910
2651
  }
1911
2652
  )
1912
2653
  ] }),
1913
- /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", children: [
1914
- /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
1915
- /* @__PURE__ */ jsx6(Text6, { bold: true, dimColor: true, children: "Web Server" }),
1916
- status === "starting" && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "starting\u2026" }),
1917
- status === "running" && /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
1918
- /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713 Running" }),
1919
- /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
2654
+ /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
2655
+ /* @__PURE__ */ jsxs5(Box6, { gap: 2, children: [
2656
+ /* @__PURE__ */ jsx7(Text7, { bold: true, dimColor: true, children: "Web Server" }),
2657
+ status === "starting" && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "starting\u2026" }),
2658
+ status === "running" && /* @__PURE__ */ jsxs5(Box6, { gap: 2, children: [
2659
+ /* @__PURE__ */ jsx7(Text7, { color: "green", children: "\u2713 Running" }),
2660
+ /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
1920
2661
  "\u25CF ",
1921
2662
  wsClientCount,
1922
2663
  " viewer",
1923
2664
  wsClientCount !== 1 ? "s" : ""
1924
2665
  ] })
1925
2666
  ] }),
1926
- status === "error" && /* @__PURE__ */ jsxs4(Text6, { color: "red", children: [
2667
+ status === "error" && /* @__PURE__ */ jsxs5(Text7, { color: "red", children: [
1927
2668
  "\u2717 ",
1928
2669
  errorMessage ?? "Server error"
1929
2670
  ] })
1930
2671
  ] }),
1931
- /* @__PURE__ */ jsx6(
1932
- Box5,
2672
+ /* @__PURE__ */ jsx7(
2673
+ Box6,
1933
2674
  {
1934
2675
  flexDirection: "column",
1935
2676
  height: LOG_DISPLAY + 2,
1936
2677
  borderStyle: "single",
1937
2678
  borderColor: "gray",
1938
2679
  overflow: "hidden",
1939
- children: serverLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "truncate", children: log }, index))
2680
+ children: serverLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx7(Text7, { dimColor: true, wrap: "truncate", children: log }, index))
1940
2681
  }
1941
2682
  )
1942
2683
  ] }),
1943
- /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", children: [
1944
- /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
1945
- /* @__PURE__ */ jsx6(Text6, { bold: true, dimColor: true, children: "Network Proxy" }),
1946
- /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713 Active" }),
1947
- /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
2684
+ /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
2685
+ /* @__PURE__ */ jsxs5(Box6, { gap: 2, children: [
2686
+ /* @__PURE__ */ jsx7(Text7, { bold: true, dimColor: true, children: "Network Proxy" }),
2687
+ /* @__PURE__ */ jsx7(Text7, { color: "green", children: "\u2713 Active" }),
2688
+ /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
1948
2689
  "\u25CF ",
1949
2690
  proxyCacheSize,
1950
2691
  " key",
@@ -1957,26 +2698,47 @@ var DevUI = ({
1957
2698
  " miss"
1958
2699
  ] })
1959
2700
  ] }),
1960
- /* @__PURE__ */ jsx6(
1961
- Box5,
2701
+ /* @__PURE__ */ jsx7(
2702
+ Box6,
1962
2703
  {
1963
2704
  flexDirection: "column",
1964
2705
  height: LOG_DISPLAY + 2,
1965
2706
  borderStyle: "single",
1966
2707
  borderColor: "gray",
1967
2708
  overflow: "hidden",
1968
- 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))
2709
+ children: proxyLogHistory.length === 0 ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " No requests yet." }) : proxyLogHistory.slice(-LOG_DISPLAY).map((log, i) => /* @__PURE__ */ jsx7(Text7, { dimColor: true, wrap: "truncate", children: log }, i))
1969
2710
  }
1970
2711
  )
1971
2712
  ] }),
1972
- /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, gap: 1, children: [
1973
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "[" }),
1974
- /* @__PURE__ */ jsx6(Text6, { children: "q" }),
1975
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "] quit [" }),
1976
- /* @__PURE__ */ jsx6(Text6, { children: "c" }),
1977
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "] credentials [" }),
1978
- /* @__PURE__ */ jsx6(Text6, { children: "v" }),
1979
- /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
2713
+ debug && /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
2714
+ /* @__PURE__ */ jsxs5(Box6, { gap: 2, children: [
2715
+ /* @__PURE__ */ jsx7(Text7, { bold: true, dimColor: true, children: "CDP Debug" }),
2716
+ /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
2717
+ "\u25CF ",
2718
+ cdpLogHistory.length,
2719
+ " event(s)"
2720
+ ] })
2721
+ ] }),
2722
+ /* @__PURE__ */ jsx7(
2723
+ Box6,
2724
+ {
2725
+ flexDirection: "column",
2726
+ height: LOG_DISPLAY + 2,
2727
+ borderStyle: "single",
2728
+ borderColor: "gray",
2729
+ overflow: "hidden",
2730
+ children: cdpLogHistory.length === 0 ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " No CDP events yet." }) : cdpLogHistory.slice(-LOG_DISPLAY).map((log, i) => /* @__PURE__ */ jsx7(Text7, { dimColor: true, wrap: "truncate", children: log }, i))
2731
+ }
2732
+ )
2733
+ ] }),
2734
+ /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, gap: 1, children: [
2735
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "[" }),
2736
+ /* @__PURE__ */ jsx7(Text7, { children: "q" }),
2737
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "] quit [" }),
2738
+ /* @__PURE__ */ jsx7(Text7, { children: "c" }),
2739
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "] credentials [" }),
2740
+ /* @__PURE__ */ jsx7(Text7, { children: "v" }),
2741
+ /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
1980
2742
  "] ",
1981
2743
  wsClientCount === 0 ? "open viewer" : "relaunch viewer"
1982
2744
  ] })
@@ -1986,14 +2748,14 @@ var DevUI = ({
1986
2748
 
1987
2749
  // src/commands/dev.ts
1988
2750
  async function runSetupUI(initialMode) {
1989
- return new Promise((resolve) => {
2751
+ return new Promise((resolve2) => {
1990
2752
  let unmountFn = null;
1991
2753
  const handleComplete = (result) => {
1992
2754
  unmountFn?.();
1993
- resolve(result);
2755
+ resolve2(result);
1994
2756
  };
1995
- const { unmount } = render2(
1996
- createElement2(CredentialSetupUI, {
2757
+ const { unmount } = render3(
2758
+ createElement3(CredentialSetupUI, {
1997
2759
  ...initialMode !== void 0 && { initialMode },
1998
2760
  onComplete: handleComplete
1999
2761
  }),
@@ -2003,68 +2765,75 @@ async function runSetupUI(initialMode) {
2003
2765
  });
2004
2766
  }
2005
2767
  function devCommand(program2) {
2006
- program2.command("dev").description("Start the plugin viewer dev server").option("-p, --port <port>", "port to listen on", "3121").option("-c, --credentials <path>", "path to a credentials JSON file").action(async (options) => {
2007
- const port = parseInt(options.port, 10);
2008
- const pluginDir = process.cwd();
2009
- const outputDir = `${pluginDir}/.kizenapp`;
2010
- ensureGitignore(pluginDir);
2011
- let credentials = null;
2012
- let credentialMode;
2013
- let activeCredentialProfile;
2014
- const { lastPath } = await loadConfig(outputDir);
2015
- if (options.credentials) {
2016
- credentials = await loadCredentialsFromFile(options.credentials);
2017
- } else {
2018
- const config = await loadConfig(outputDir);
2019
- if (config.credentialMode === "local") {
2020
- credentialMode = "local";
2021
- } else if (config.credentialMode === "global") {
2022
- credentialMode = "global";
2023
- activeCredentialProfile = config.activeCredentialProfile;
2024
- if (activeCredentialProfile) {
2025
- credentials = await loadCredentialProfile(activeCredentialProfile);
2026
- if (!credentials) {
2768
+ program2.command("dev").description("Start the plugin viewer dev server").option("-p, --port <port>", "port to listen on", "3121").option("-c, --credentials <path>", "path to a credentials JSON file").option("-d, --debug", "Enable CDP lifecycle logs in the TUI").option("-v, --verbose", "Log every CDP event and handled error (implies --debug)").action(
2769
+ async (options) => {
2770
+ const port = parseInt(options.port, 10);
2771
+ const pluginDir = process.cwd();
2772
+ const outputDir = `${pluginDir}/.kizenapp`;
2773
+ ensureGitignore(pluginDir);
2774
+ let credentials = null;
2775
+ let credentialMode;
2776
+ let activeCredentialProfile;
2777
+ const { lastPath } = await loadConfig(outputDir);
2778
+ if (options.credentials) {
2779
+ credentials = await loadCredentialsFromFile(options.credentials);
2780
+ } else {
2781
+ const config = await loadConfig(outputDir);
2782
+ if (config.credentialMode === "local") {
2783
+ credentialMode = "local";
2784
+ } else if (config.credentialMode === "global") {
2785
+ credentialMode = "global";
2786
+ activeCredentialProfile = config.activeCredentialProfile;
2787
+ if (activeCredentialProfile) {
2788
+ credentials = await loadCredentialProfile(activeCredentialProfile);
2789
+ if (!credentials) {
2790
+ credentials = await loadGlobalCredentials();
2791
+ activeCredentialProfile = void 0;
2792
+ }
2793
+ } else {
2027
2794
  credentials = await loadGlobalCredentials();
2028
- activeCredentialProfile = void 0;
2795
+ }
2796
+ if (!credentials) {
2797
+ const result = await runSetupUI("global");
2798
+ credentials = result.credentials;
2799
+ activeCredentialProfile = result.profileName;
2029
2800
  }
2030
2801
  } else {
2031
- credentials = await loadGlobalCredentials();
2032
- }
2033
- if (!credentials) {
2034
- const result = await runSetupUI("global");
2802
+ const result = await runSetupUI();
2035
2803
  credentials = result.credentials;
2804
+ credentialMode = result.mode;
2036
2805
  activeCredentialProfile = result.profileName;
2806
+ const profile = result.profileName;
2807
+ await saveConfig(outputDir, {
2808
+ credentialMode: result.mode,
2809
+ ...profile !== void 0 && { activeCredentialProfile: profile }
2810
+ });
2037
2811
  }
2038
- } else {
2039
- const result = await runSetupUI();
2040
- credentials = result.credentials;
2041
- credentialMode = result.mode;
2042
- activeCredentialProfile = result.profileName;
2043
- const profile = result.profileName;
2044
- await saveConfig(outputDir, {
2045
- credentialMode: result.mode,
2046
- ...profile !== void 0 && { activeCredentialProfile: profile }
2047
- });
2048
2812
  }
2813
+ const verbose = options.verbose === true;
2814
+ const debug = verbose || options.debug === true;
2815
+ const { waitUntilExit } = render3(
2816
+ createElement3(DevUI, {
2817
+ port,
2818
+ pluginDir,
2819
+ outputDir,
2820
+ credentials,
2821
+ ...credentialMode !== void 0 && { credentialMode },
2822
+ ...activeCredentialProfile !== void 0 && { activeCredentialProfile },
2823
+ ...lastPath !== void 0 && { lastPath },
2824
+ debug,
2825
+ verbose
2826
+ }),
2827
+ { exitOnCtrlC: false }
2828
+ );
2829
+ await waitUntilExit();
2049
2830
  }
2050
- const { waitUntilExit } = render2(
2051
- createElement2(DevUI, {
2052
- port,
2053
- pluginDir,
2054
- outputDir,
2055
- credentials,
2056
- ...credentialMode !== void 0 && { credentialMode },
2057
- ...activeCredentialProfile !== void 0 && { activeCredentialProfile },
2058
- ...lastPath !== void 0 && { lastPath }
2059
- }),
2060
- { exitOnCtrlC: false }
2061
- );
2062
- await waitUntilExit();
2063
- });
2831
+ );
2064
2832
  }
2065
2833
 
2066
2834
  // src/index.ts
2067
2835
  program.name("appbuilder").description("Kizen plugin app builder").version("0.1.0");
2836
+ createCommand(program);
2068
2837
  buildCommand(program);
2069
2838
  devCommand(program);
2070
2839
  program.parse();