@kizenapps/cli 0.11.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 (78) hide show
  1. package/dist/index.js +1644 -434
  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/CodeViewer-DdKuH37F.css +1 -0
  16. package/dist/viewer/assets/ConfigurationPage-DAVSSfuO.js +1 -0
  17. package/dist/viewer/assets/ConfigurationPage-_PScSEXP.js +1 -0
  18. package/dist/viewer/assets/ConfigurationPage-zSJDg1f4.js +1 -0
  19. package/dist/viewer/assets/CredentialsContext-Rkt9WDIb.js +1 -0
  20. package/dist/viewer/assets/CredentialsContext-k-q4M-VC.js +1 -0
  21. package/dist/viewer/assets/{DevSidebar-BiROAoKF.js → DevSidebar-CKdTotWS.js} +1 -1
  22. package/dist/viewer/assets/DevSidebar-CcSTChgY.js +1 -0
  23. package/dist/viewer/assets/IconReferencePage-BhQv3kD4.js +1 -0
  24. package/dist/viewer/assets/IconReferencePage-C_I2Fxnr.js +1 -0
  25. package/dist/viewer/assets/PluginIcon-CWkWM-Eu.js +1 -0
  26. package/dist/viewer/assets/SandboxPage-BMz04QVs.js +1 -0
  27. package/dist/viewer/assets/SandboxPage-BWtQb8kY.js +1 -0
  28. package/dist/viewer/assets/SandboxPage-DOBUc8VL.js +1 -0
  29. package/dist/viewer/assets/SidebarContext-Cn8QRb77.js +1 -0
  30. package/dist/viewer/assets/SidebarContext-DMhpPBf_.js +1 -0
  31. package/dist/viewer/assets/SourceBrowserPage-C-IhTiL0.js +1 -0
  32. package/dist/viewer/assets/SourceBrowserPage-Cr6XOIdV.js +1 -0
  33. package/dist/viewer/assets/VersionsPage-Bpj4_jPC.js +1 -0
  34. package/dist/viewer/assets/VersionsPage-DAm4Ozs0.js +1 -0
  35. package/dist/viewer/assets/VersionsPage-T8tsqgQx.js +1 -0
  36. package/dist/viewer/assets/api-DFDquUsn.js +1 -0
  37. package/dist/viewer/assets/api-bszpvP9L.js +1 -0
  38. package/dist/viewer/assets/bundleQuery-BQLpZ06V.js +1 -0
  39. package/dist/viewer/assets/bundleQuery-CjbA01rb.js +1 -0
  40. package/dist/viewer/assets/fa-core-BoBqCOD3.js +558 -0
  41. package/dist/viewer/assets/fa-core-C4eeAd4v.js +558 -0
  42. package/dist/viewer/assets/fa-pro-icons-j8-_MCQ7.js +1 -0
  43. package/dist/viewer/assets/formatBytes-BCP0hIUu.js +1 -0
  44. package/dist/viewer/assets/index-7CwR8Ogd.js +5 -0
  45. package/dist/viewer/assets/index-CCB9tECd.css +2 -0
  46. package/dist/viewer/assets/index-FEIsXxbf.js +5 -0
  47. package/dist/viewer/assets/index-fU82pXSW.js +5 -0
  48. package/dist/viewer/assets/{react-D_K8ni3_.js → react-CmC5kGa_.js} +1 -1
  49. package/dist/viewer/assets/react-csXWLXSE.js +9 -0
  50. package/dist/viewer/assets/{recordDetail.worker-BIHPFt8Y.js → recordDetail.worker-C0-mQTaj.js} +1 -1
  51. package/dist/viewer/assets/useCriticalExceptionDialog-BeVl0uaO.js +14 -0
  52. package/dist/viewer/assets/useCriticalExceptionDialog-DseJ_u9B.js +14 -0
  53. package/dist/viewer/assets/useCurrentApp-CWtSEVC4.js +1 -0
  54. package/dist/viewer/assets/useCurrentApp-x1p8URhM.js +1 -0
  55. package/dist/viewer/assets/useLocalStorage-HYhrPUxS.js +1 -0
  56. package/dist/viewer/assets/useLocalStorage-eJ1PxgAP.js +1 -0
  57. package/dist/viewer/assets/useNavigate-BblEmQ3O.js +1 -0
  58. package/dist/viewer/assets/useNavigate-Ci7hClRo.js +1 -0
  59. package/dist/viewer/assets/useQuery-5OkVbV1s.js +1 -0
  60. package/dist/viewer/assets/useQuery-B3Ek2Yet.js +1 -0
  61. package/dist/viewer/assets/{validIcons-TcFXfn-E.js → validIcons-BzGQ4tWD.js} +1 -1
  62. package/dist/viewer/assets/validIcons-CzxkK9y8.js +1 -0
  63. package/dist/viewer/index.html +12 -8
  64. package/dist/viewer/mark.svg +17 -0
  65. package/package.json +10 -12
  66. package/dist/viewer/assets/AppDetailPage-BJrg5Wsz.js +0 -1
  67. package/dist/viewer/assets/BootstrapContext-D3lhzWap.js +0 -1
  68. package/dist/viewer/assets/Card-CPMAuqAb.js +0 -1
  69. package/dist/viewer/assets/ConfigurationPage-D8I8rWjF.js +0 -1
  70. package/dist/viewer/assets/CredentialsContext-BLZX6RwF.js +0 -1
  71. package/dist/viewer/assets/SandboxPage-ByixszHi.js +0 -1
  72. package/dist/viewer/assets/SidebarContext-Byo6PYOF.js +0 -1
  73. package/dist/viewer/assets/VersionsPage-BKul6bXI.js +0 -1
  74. package/dist/viewer/assets/fa-core-mHW_3JUd.js +0 -558
  75. package/dist/viewer/assets/index-BoSQQwMQ.css +0 -2
  76. package/dist/viewer/assets/index-Bomh3C3X.js +0 -5
  77. package/dist/viewer/assets/useObjectLookups-Cx6A7PGj.js +0 -14
  78. /package/dist/viewer/assets/{fa-pro-icons-CIQGXozY.js → fa-pro-icons-Dr3O0gTt.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
  }
@@ -112,24 +113,28 @@ import { Box as Box2, Text as Text2 } from "ink";
112
113
  import { Box, Text } from "ink";
113
114
  import { jsx } from "react/jsx-runtime";
114
115
  var LOGO_LINES = [
115
- " @@@ ",
116
- " @@@@@ @@@@@@@ ",
117
- " @@@@ @@@@@@@@@@ ",
118
- " @@@@ @@@@@ ",
119
- " @@@ @@@@ @@@@@ @@@@@@@ ",
120
- " @@@@ @@@@ @@@@ @@@@@@@@@@@@@",
121
- " @@@@ @@@@ @@@@@",
122
- " @@@@ @@@ @@@@ @@@",
123
- " @@@@ @@@@@@ ",
124
- " @@@@@@ @@@@ ",
125
- " @@@ @@@@@ @@@ @@@@ ",
126
- " @@@@@ @@@@ @@@@ ",
127
- " @@@@@@@@@@@@@ @@@ @@@ @@@ ",
128
- " @@@@@@@@ @@@@ @@@ @@@@ ",
129
- " @@@@@ @@@@ ",
130
- " @@@@@@@@@@@ @@@@ ",
131
- " @@@@@@@ @@@@@ ",
132
- " @@@@ "
116
+ " :::::::: ",
117
+ " :::::: ::::::::::: ",
118
+ " :::::: :::::::::: ",
119
+ " :::::: :::::: ::::::: ",
120
+ " ::::: :::::: ::::::::::::::::: ",
121
+ " ::::: ::::: ::::::::::::::::::: ",
122
+ " ::::: ::::: :::::: ::::: ",
123
+ ":::: ::::: ::: ::::: :: ",
124
+ "::::: :::::: :::::::::: ",
125
+ "::::: :::::: ::::::::::: ",
126
+ " ::::: ::: ::::::: ",
127
+ " ::::::: ::: ::::: ",
128
+ " ::::::::::: :::::: :::::",
129
+ " :::::::::: :::::: :::::",
130
+ " :: ::::: ::: ::::: ::::",
131
+ " ::::: :::::: ::::: ::::: ::: ",
132
+ " ::::::::::::::::::: ::::: ::::: ",
133
+ " ::::::::::::::::: ::::: ::::: ",
134
+ " ::::::::: :::::: :::::: ",
135
+ " :::::::::: :::::: ",
136
+ " ::::::::::: :::::: ",
137
+ " :::::::: "
133
138
  ];
134
139
  var Logo = () => /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: LOGO_LINES.map((line, i) => /* @__PURE__ */ jsx(Text, { color: "cyan", children: line }, i)) });
135
140
 
@@ -137,10 +142,7 @@ var Logo = () => /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: L
137
142
  import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
138
143
  var AppHeader = ({ marginBottom = 0 }) => /* @__PURE__ */ jsxs(Fragment, { children: [
139
144
  /* @__PURE__ */ jsx2(Logo, {}),
140
- /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", marginTop: 1, marginBottom, children: [
141
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "Kizen App Builder" }),
142
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2500".repeat(24) })
143
- ] })
145
+ /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, marginBottom, children: /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "Kizen App Development Toolkit" }) })
144
146
  ] });
145
147
 
146
148
  // src/ui/Spinner.tsx
@@ -219,7 +221,7 @@ var BuildUI = ({ outputDir, pluginDir }) => {
219
221
  ] }) }, s);
220
222
  }),
221
223
  isDone && bundleSize !== null && /* @__PURE__ */ jsx4(Box3, { marginTop: 1, gap: 1, children: /* @__PURE__ */ jsxs2(Text4, { color: "green", children: [
222
- "\u2713 bundle.json \u2014 ",
224
+ "\u2713 bundle \u2014 ",
223
225
  formatBytes(bundleSize)
224
226
  ] }) }),
225
227
  errorMessage !== null && /* @__PURE__ */ jsx4(Box3, { marginTop: 1, borderStyle: "single", borderColor: "red", paddingX: 1, children: /* @__PURE__ */ jsx4(Text4, { color: "red", children: errorMessage }) })
@@ -256,22 +258,518 @@ function buildCommand(program2) {
256
258
  });
257
259
  }
258
260
 
259
- // src/commands/dev.ts
261
+ // src/commands/create.ts
260
262
  import { createElement as createElement2 } from "react";
261
263
  import { render as render2 } from "ink";
262
264
 
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";
269
+
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
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) {
349
+ return {
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;
375
+ }
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
405
+ });
406
+ setPhase({ type: "done", targetDir: dir });
407
+ } catch (err) {
408
+ setPhase({
409
+ type: "error",
410
+ message: err instanceof Error ? err.message : String(err)
411
+ });
412
+ }
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
+ ] });
664
+ };
665
+
666
+ // src/lib/credentials.ts
667
+ import { mkdir as mkdir3, readFile as readFile2, readdir as readdir2, writeFile as writeFile3 } from "fs/promises";
668
+ import { homedir } from "os";
669
+ import { dirname, join as join6 } from "path";
670
+ var ENVIRONMENTS = ["go", "fmo", "staging", "integration", "test1"];
671
+ var GLOBAL_CREDENTIALS_DIR = join6(homedir(), ".kizenappbuilder");
672
+ var GLOBAL_CREDENTIALS_PATH = join6(GLOBAL_CREDENTIALS_DIR, "credentials.json");
673
+ var DEFAULT_PROFILE_NAME = "credentials";
674
+ function isValidEnvironment(value) {
675
+ return ENVIRONMENTS.includes(value);
676
+ }
677
+ function parseCredentials(raw) {
678
+ if (typeof raw !== "object" || raw === null) {
679
+ throw new Error("Credentials must be a JSON object");
680
+ }
681
+ const obj = raw;
682
+ const env = obj.environment;
683
+ return {
684
+ apiKey: typeof obj.apiKey === "string" ? obj.apiKey : "",
685
+ userId: typeof obj.userId === "string" ? obj.userId : "",
686
+ businessId: typeof obj.businessId === "string" ? obj.businessId : "",
687
+ environment: isValidEnvironment(env) ? env : "go"
688
+ };
689
+ }
690
+ async function loadCredentialsFromFile(filePath) {
691
+ const content = await readFile2(filePath, "utf-8");
692
+ return parseCredentials(JSON.parse(content));
693
+ }
694
+ async function loadGlobalCredentials() {
695
+ try {
696
+ return await loadCredentialsFromFile(GLOBAL_CREDENTIALS_PATH);
697
+ } catch {
698
+ return null;
699
+ }
700
+ }
701
+ async function saveGlobalCredentials(credentials) {
702
+ await mkdir3(dirname(GLOBAL_CREDENTIALS_PATH), { recursive: true });
703
+ await writeFile3(GLOBAL_CREDENTIALS_PATH, JSON.stringify(credentials, null, 2), "utf-8");
704
+ }
705
+ function getProfilePath(name) {
706
+ return join6(GLOBAL_CREDENTIALS_DIR, `${name}.json`);
707
+ }
708
+ async function listCredentialProfiles() {
709
+ const profiles = [
710
+ { name: DEFAULT_PROFILE_NAME, path: GLOBAL_CREDENTIALS_PATH, isDefault: true }
711
+ ];
712
+ try {
713
+ const entries = await readdir2(GLOBAL_CREDENTIALS_DIR);
714
+ for (const entry of entries) {
715
+ if (!entry.endsWith(".json")) {
716
+ continue;
717
+ }
718
+ const name = entry.slice(0, -5);
719
+ if (name === DEFAULT_PROFILE_NAME) {
720
+ continue;
721
+ }
722
+ profiles.push({ name, path: join6(GLOBAL_CREDENTIALS_DIR, entry), isDefault: false });
723
+ }
724
+ } catch {
725
+ }
726
+ return profiles;
727
+ }
728
+ async function saveCredentialProfile(name, credentials) {
729
+ if (name === DEFAULT_PROFILE_NAME) {
730
+ await saveGlobalCredentials(credentials);
731
+ return;
732
+ }
733
+ await mkdir3(GLOBAL_CREDENTIALS_DIR, { recursive: true });
734
+ await writeFile3(getProfilePath(name), JSON.stringify(credentials, null, 2), "utf-8");
735
+ }
736
+ async function loadCredentialProfile(name) {
737
+ try {
738
+ return await loadCredentialsFromFile(getProfilePath(name));
739
+ } catch {
740
+ return null;
741
+ }
742
+ }
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
+
263
761
  // src/ui/DevUI.tsx
264
- import { useCallback as useCallback3, useEffect as useEffect4, useRef, useState as useState5 } from "react";
265
- import { Box as Box5, Text as Text6, useInput as useInput2 } from "ink";
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";
266
764
  import { watch } from "fs";
267
765
  import { createServer } from "http";
268
- import { join as join8 } from "path";
766
+ import { join as join11 } from "path";
269
767
  import { WebSocketServer } from "ws";
270
768
 
271
769
  // src/server/requestHandler.ts
272
770
  import { createReadStream } from "fs";
273
- import { access, readFile as readFile4 } from "fs/promises";
274
- import { dirname as dirname2, extname, join as join6 } from "path";
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";
275
773
  import { fileURLToPath } from "url";
276
774
 
277
775
  // src/lib/proxyCache.ts
@@ -338,101 +836,462 @@ var MIME_TYPES = {
338
836
  ".map": "application/json"
339
837
  };
340
838
 
341
- // src/lib/credentials.ts
342
- import { mkdir as mkdir2, readFile as readFile2, readdir as readdir2, writeFile as writeFile2 } from "fs/promises";
343
- import { homedir } from "os";
344
- import { dirname, join as join4 } from "path";
345
- var ENVIRONMENTS = ["go", "fmo", "staging", "integration", "test1"];
346
- var GLOBAL_CREDENTIALS_DIR = join4(homedir(), ".kizenappbuilder");
347
- var GLOBAL_CREDENTIALS_PATH = join4(GLOBAL_CREDENTIALS_DIR, "credentials.json");
348
- var DEFAULT_PROFILE_NAME = "credentials";
349
- function isValidEnvironment(value) {
350
- return ENVIRONMENTS.includes(value);
351
- }
352
- function parseCredentials(raw) {
353
- if (typeof raw !== "object" || raw === null) {
354
- throw new Error("Credentials must be a JSON object");
839
+ // src/lib/config.ts
840
+ import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
841
+ import { join as join7 } from "path";
842
+ async function loadConfig(outputDir) {
843
+ try {
844
+ const content = await readFile3(join7(outputDir, "config.json"), "utf-8");
845
+ return JSON.parse(content);
846
+ } catch {
847
+ return {};
355
848
  }
356
- const obj = raw;
357
- const env = obj.environment;
358
- return {
359
- apiKey: typeof obj.apiKey === "string" ? obj.apiKey : "",
360
- userId: typeof obj.userId === "string" ? obj.userId : "",
361
- businessId: typeof obj.businessId === "string" ? obj.businessId : "",
362
- environment: isValidEnvironment(env) ? env : "go"
363
- };
364
849
  }
365
- async function loadCredentialsFromFile(filePath) {
366
- const content = await readFile2(filePath, "utf-8");
367
- return parseCredentials(JSON.parse(content));
850
+ async function saveConfig(outputDir, config) {
851
+ await mkdir4(outputDir, { recursive: true });
852
+ await writeFile4(join7(outputDir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
368
853
  }
369
- async function loadGlobalCredentials() {
370
- try {
371
- return await loadCredentialsFromFile(GLOBAL_CREDENTIALS_PATH);
372
- } catch {
373
- return null;
854
+
855
+ // src/server/pythonExecutor.ts
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";
859
+ import { tmpdir } from "os";
860
+ var WRAPPER_SCRIPT = `
861
+ import sys, json, os, traceback
862
+
863
+ # --- Inputs proxy (attribute access) ---
864
+ class _InputsProxy:
865
+ def __init__(self, data):
866
+ object.__setattr__(self, '_data', data)
867
+
868
+ def __getattr__(self, name):
869
+ if name.startswith('_'):
870
+ raise AttributeError(name)
871
+ return self._data.get(name)
872
+
873
+ def __repr__(self):
874
+ return repr(self._data)
875
+
876
+ def __str__(self):
877
+ return str(self._data)
878
+
879
+ def __bool__(self):
880
+ return bool(self._data)
881
+
882
+ inputs = _InputsProxy(json.loads(os.environ.get('__KIZEN_INPUTS__', '{}')))
883
+
884
+ # --- Secrets ---
885
+ secrets = json.loads(os.environ.get('__KIZEN_SECRETS__', '{}'))
886
+
887
+ # --- Outputs proxy ---
888
+ class _OutputsProxy:
889
+ def __init__(self):
890
+ object.__setattr__(self, '_values', {})
891
+ object.__setattr__(self, '_logs', [])
892
+
893
+ def __setattr__(self, name, value):
894
+ if name.startswith('_'):
895
+ object.__setattr__(self, name, value)
896
+ else:
897
+ self._values[name] = value
898
+
899
+ def __getattr__(self, name):
900
+ if name.startswith('_'):
901
+ raise AttributeError(name)
902
+ return self._values.get(name)
903
+
904
+ def log(self, message):
905
+ self._logs.append(str(message))
906
+
907
+ outputs = _OutputsProxy()
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
+
983
+ # --- Execute user script ---
984
+ try:
985
+ exec(open(os.environ['__KIZEN_SCRIPT_PATH__'], encoding='utf-8').read())
986
+ result = {
987
+ 'success': True,
988
+ 'outputValues': outputs._values,
989
+ 'logs': outputs._logs,
990
+ 'error': None,
991
+ 'httpRequests': _kizen_http_summary(),
992
+ }
993
+ except Exception:
994
+ # Build a clean traceback that hides the wrapper internals.
995
+ # Keep only frames from the user script (shown as File "<string>")
996
+ # and re-label them with "script.py" for clarity.
997
+ exc_type, exc_value, exc_tb = sys.exc_info()
998
+ entries = traceback.extract_tb(exc_tb)
999
+ user_entries = [e for e in entries if e.filename != __file__]
1000
+ for e in user_entries:
1001
+ if e.filename == '<string>':
1002
+ e.filename = 'script.py'
1003
+ lines = ['Traceback (most recent call last):\\n']
1004
+ lines += traceback.format_list(user_entries)
1005
+ lines += traceback.format_exception_only(exc_type, exc_value)
1006
+ result = {
1007
+ 'success': False,
1008
+ 'outputValues': outputs._values,
1009
+ 'logs': outputs._logs,
1010
+ 'error': ''.join(lines),
1011
+ 'httpRequests': _kizen_http_summary(),
1012
+ }
1013
+
1014
+ # Write results between markers so we can extract them reliably
1015
+ # even if the user script wrote to stdout
1016
+ sys.stdout.write('\\n__KIZEN_RESULT_START__\\n')
1017
+ sys.stdout.write(json.dumps(result, ensure_ascii=False))
1018
+ sys.stdout.write('\\n__KIZEN_RESULT_END__\\n')
1019
+ sys.stdout.flush()
1020
+ `.trim();
1021
+ var DEFAULT_TIMEOUT = 3e4;
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
+ ];
1037
+ var venvReady = null;
1038
+ function resolvePythonBinary(runtime) {
1039
+ const match = /^python-(\d+\.\d+)$/.exec(runtime);
1040
+ if (match?.[1]) {
1041
+ return `python${match[1]}`;
374
1042
  }
1043
+ return "python3";
375
1044
  }
376
- async function saveGlobalCredentials(credentials) {
377
- await mkdir2(dirname(GLOBAL_CREDENTIALS_PATH), { recursive: true });
378
- await writeFile2(GLOBAL_CREDENTIALS_PATH, JSON.stringify(credentials, null, 2), "utf-8");
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
+ });
379
1084
  }
380
- function getProfilePath(name) {
381
- return join4(GLOBAL_CREDENTIALS_DIR, `${name}.json`);
1085
+ var installListeners = /* @__PURE__ */ new Set();
1086
+ function emitInstall(event) {
1087
+ for (const listener of installListeners) {
1088
+ listener(event);
1089
+ }
382
1090
  }
383
- async function listCredentialProfiles() {
384
- const profiles = [
385
- { name: DEFAULT_PROFILE_NAME, path: GLOBAL_CREDENTIALS_PATH, isDefault: true }
386
- ];
387
- try {
388
- const entries = await readdir2(GLOBAL_CREDENTIALS_DIR);
389
- for (const entry of entries) {
390
- if (!entry.endsWith(".json")) {
391
- continue;
1091
+ function ensureVenv(runtime, listener) {
1092
+ if (listener) {
1093
+ installListeners.add(listener);
1094
+ }
1095
+ if (!venvReady) {
1096
+ venvReady = (async () => {
1097
+ const venvPython = join8(VENV_DIR, "bin", "python");
1098
+ try {
1099
+ await access2(venvPython);
1100
+ } catch {
1101
+ const systemPython = resolvePythonBinary(runtime);
1102
+ await runCommand(systemPython, ["-m", "venv", VENV_DIR]);
392
1103
  }
393
- const name = entry.slice(0, -5);
394
- if (name === DEFAULT_PROFILE_NAME) {
395
- continue;
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();
396
1120
  }
397
- profiles.push({ name, path: join4(GLOBAL_CREDENTIALS_DIR, entry), isDefault: false });
398
- }
399
- } catch {
1121
+ return venvPython;
1122
+ })();
400
1123
  }
401
- return profiles;
1124
+ return venvReady;
402
1125
  }
403
- async function saveCredentialProfile(name, credentials) {
404
- if (name === DEFAULT_PROFILE_NAME) {
405
- await saveGlobalCredentials(credentials);
406
- return;
1126
+ function parseResult(stdout) {
1127
+ const startMarker = "__KIZEN_RESULT_START__";
1128
+ const endMarker = "__KIZEN_RESULT_END__";
1129
+ const startIdx = stdout.indexOf(startMarker);
1130
+ const endIdx = stdout.indexOf(endMarker);
1131
+ if (startIdx === -1 || endIdx === -1) {
1132
+ return { userStdout: stdout, result: null };
407
1133
  }
408
- await mkdir2(GLOBAL_CREDENTIALS_DIR, { recursive: true });
409
- await writeFile2(getProfilePath(name), JSON.stringify(credentials, null, 2), "utf-8");
410
- }
411
- async function loadCredentialProfile(name) {
1134
+ const userStdout = stdout.slice(0, startIdx).trimEnd();
1135
+ const jsonStr = stdout.slice(startIdx + startMarker.length, endIdx).trim();
412
1136
  try {
413
- return await loadCredentialsFromFile(getProfilePath(name));
1137
+ const result = JSON.parse(jsonStr);
1138
+ return { userStdout, result };
414
1139
  } catch {
415
- return null;
1140
+ return { userStdout: stdout, result: null };
416
1141
  }
417
1142
  }
418
-
419
- // src/lib/config.ts
420
- import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
421
- import { join as join5 } from "path";
422
- async function loadConfig(outputDir) {
1143
+ async function executePythonStep(params) {
1144
+ const timeout = params.timeout ?? DEFAULT_TIMEOUT;
1145
+ const pythonBin = await ensureVenv(params.scriptRuntime, params.onInstallProgress);
1146
+ const start = Date.now();
1147
+ let tmpDir;
423
1148
  try {
424
- const content = await readFile3(join5(outputDir, "config.json"), "utf-8");
425
- return JSON.parse(content);
426
- } catch {
427
- return {};
1149
+ tmpDir = await mkdtemp(join8(tmpdir(), "kizen-step-"));
1150
+ const wrapperPath = join8(tmpDir, "wrapper.py");
1151
+ const scriptPath = join8(tmpDir, "script.py");
1152
+ await Promise.all([
1153
+ writeFile5(wrapperPath, WRAPPER_SCRIPT, "utf-8"),
1154
+ writeFile5(scriptPath, params.script, "utf-8")
1155
+ ]);
1156
+ const env = {
1157
+ ...process.env,
1158
+ __KIZEN_INPUTS__: JSON.stringify(params.inputs),
1159
+ __KIZEN_SECRETS__: JSON.stringify(params.secrets),
1160
+ __KIZEN_SCRIPT_PATH__: scriptPath
1161
+ };
1162
+ const { exitCode, stdout, stderr } = await new Promise((resolve2) => {
1163
+ const stdoutChunks = [];
1164
+ const stderrChunks = [];
1165
+ let settled = false;
1166
+ const proc = spawn(pythonBin, [wrapperPath], {
1167
+ env,
1168
+ cwd: tmpDir,
1169
+ stdio: ["ignore", "pipe", "pipe"]
1170
+ });
1171
+ const timer = setTimeout(() => {
1172
+ proc.kill("SIGKILL");
1173
+ }, timeout);
1174
+ proc.stdout.on("data", (chunk) => {
1175
+ stdoutChunks.push(chunk);
1176
+ });
1177
+ proc.stderr.on("data", (chunk) => {
1178
+ stderrChunks.push(chunk);
1179
+ });
1180
+ const finish = (code) => {
1181
+ if (settled) {
1182
+ return;
1183
+ }
1184
+ settled = true;
1185
+ clearTimeout(timer);
1186
+ resolve2({
1187
+ exitCode: code ?? 1,
1188
+ stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
1189
+ stderr: Buffer.concat(stderrChunks).toString("utf-8")
1190
+ });
1191
+ };
1192
+ proc.on("close", finish);
1193
+ proc.on("error", (err) => {
1194
+ if (settled) {
1195
+ return;
1196
+ }
1197
+ settled = true;
1198
+ clearTimeout(timer);
1199
+ resolve2({
1200
+ exitCode: 1,
1201
+ stdout: "",
1202
+ stderr: err.message
1203
+ });
1204
+ });
1205
+ });
1206
+ const durationMs = Date.now() - start;
1207
+ const { userStdout, result } = parseResult(stdout);
1208
+ if (result) {
1209
+ const base2 = {
1210
+ success: result.success,
1211
+ outputValues: result.outputValues,
1212
+ logs: result.logs,
1213
+ stdout: userStdout,
1214
+ stderr,
1215
+ exitCode,
1216
+ durationMs
1217
+ };
1218
+ if (result.error !== null) {
1219
+ base2.error = result.error;
1220
+ }
1221
+ if (result.httpRequests) {
1222
+ base2.httpRequests = result.httpRequests;
1223
+ }
1224
+ return base2;
1225
+ }
1226
+ const base = {
1227
+ success: false,
1228
+ outputValues: {},
1229
+ logs: [],
1230
+ stdout: userStdout,
1231
+ stderr,
1232
+ exitCode,
1233
+ durationMs
1234
+ };
1235
+ if (exitCode !== 0) {
1236
+ base.error = `Process exited with code ${String(exitCode)}`;
1237
+ }
1238
+ return base;
1239
+ } finally {
1240
+ if (tmpDir) {
1241
+ void rm(tmpDir, { recursive: true, force: true });
1242
+ }
428
1243
  }
429
1244
  }
430
- async function saveConfig(outputDir, config) {
431
- await mkdir3(outputDir, { recursive: true });
432
- await writeFile3(join5(outputDir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
433
- }
434
1245
 
435
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
+ }
436
1295
  function proxyLogEntryToString(entry) {
437
1296
  if (entry.kind === "info") {
438
1297
  return entry.message;
@@ -442,17 +1301,17 @@ function proxyLogEntryToString(entry) {
442
1301
  }
443
1302
  function getViewerPath() {
444
1303
  const filename = fileURLToPath(import.meta.url);
445
- return join6(dirname2(filename), "viewer");
1304
+ return join9(dirname2(filename), "viewer");
446
1305
  }
447
1306
  async function fileExists(filePath) {
448
1307
  try {
449
- await access(filePath);
1308
+ await access3(filePath);
450
1309
  return true;
451
1310
  } catch {
452
1311
  return false;
453
1312
  }
454
1313
  }
455
- function createRequestHandler(viewerPath, createServerLog, createProxyLog, credentialsRef, activeProfileRef, outputDir, broadcast, onProxyCacheChange) {
1314
+ function createRequestHandler(viewerPath, createServerLog, createProxyLog, credentialsRef, activeProfileRef, outputDir, broadcast, onProxyCacheChange, lastPathRef, pluginDir) {
456
1315
  const proxyCache = createProxyCache({ onChange: onProxyCacheChange });
457
1316
  return (req, res) => {
458
1317
  void (async () => {
@@ -493,7 +1352,7 @@ function createRequestHandler(viewerPath, createServerLog, createProxyLog, crede
493
1352
  return;
494
1353
  }
495
1354
  if (url === "/api/bundle") {
496
- const bundlePath = join6(process.cwd(), ".kizenapp", "bundle.json");
1355
+ const bundlePath = join9(process.cwd(), ".kizenapp", "bundle.json");
497
1356
  try {
498
1357
  const content = await readFile4(bundlePath, "utf-8");
499
1358
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
@@ -504,6 +1363,61 @@ function createRequestHandler(viewerPath, createServerLog, createProxyLog, crede
504
1363
  }
505
1364
  return;
506
1365
  }
1366
+ if (url === "/api/last-path" && req.method === "POST") {
1367
+ const chunks = [];
1368
+ for await (const chunk of req) {
1369
+ chunks.push(chunk);
1370
+ }
1371
+ const body = JSON.parse(Buffer.concat(chunks).toString("utf-8") || "{}");
1372
+ if (typeof body.path === "string") {
1373
+ lastPathRef.current = body.path;
1374
+ const currentConfig = await loadConfig(outputDir);
1375
+ await saveConfig(outputDir, { ...currentConfig, lastPath: body.path });
1376
+ }
1377
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1378
+ res.end('{"ok":true}');
1379
+ return;
1380
+ }
1381
+ if (url === "/api/execute-step" && req.method === "POST") {
1382
+ const chunks = [];
1383
+ for await (const chunk of req) {
1384
+ chunks.push(chunk);
1385
+ }
1386
+ const body = JSON.parse(Buffer.concat(chunks).toString("utf-8") || "{}");
1387
+ if (typeof body.script !== "string") {
1388
+ res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
1389
+ res.end(JSON.stringify({ error: "Missing required field: script" }));
1390
+ return;
1391
+ }
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
+ }
1411
+ }
1412
+ };
1413
+ if (typeof body.timeout === "number") {
1414
+ stepParams.timeout = body.timeout;
1415
+ }
1416
+ const result = await executePythonStep(stepParams);
1417
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1418
+ res.end(JSON.stringify(result));
1419
+ return;
1420
+ }
507
1421
  if (url === "/api/proxy-cache/clear") {
508
1422
  proxyCache.clear();
509
1423
  createProxyLog({ kind: "info", message: "Proxy cache cleared" });
@@ -567,9 +1481,113 @@ function createRequestHandler(viewerPath, createServerLog, createProxyLog, crede
567
1481
  res.end(Buffer.from(await upstream.arrayBuffer()));
568
1482
  return;
569
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
+ }
570
1588
  const rawPath = url === "/" ? "/index.html" : url;
571
- const filePath = join6(viewerPath, rawPath);
572
- const resolvedPath = await fileExists(filePath) ? filePath : join6(viewerPath, "index.html");
1589
+ const filePath = join9(viewerPath, rawPath);
1590
+ const resolvedPath = await fileExists(filePath) ? filePath : join9(viewerPath, "index.html");
573
1591
  const ext = extname(resolvedPath);
574
1592
  const mimeType = MIME_TYPES[ext] ?? "application/octet-stream";
575
1593
  res.writeHead(200, { "Content-Type": mimeType });
@@ -603,7 +1621,7 @@ import { launch } from "chrome-launcher";
603
1621
  import { createRequire } from "module";
604
1622
  import { mkdirSync } from "fs";
605
1623
  import { readFile as readFile5 } from "fs/promises";
606
- import { dirname as dirname3, join as join7 } from "path";
1624
+ import { dirname as dirname3, join as join10 } from "path";
607
1625
  import { fileURLToPath as fileURLToPath2 } from "url";
608
1626
  var CDP = createRequire(import.meta.url)("chrome-remote-interface");
609
1627
  var CSP_HEADERS = /* @__PURE__ */ new Set([
@@ -682,7 +1700,7 @@ async function serializeArgAsync(client, arg) {
682
1700
  }
683
1701
  async function installDockIcon(client) {
684
1702
  try {
685
- const iconPath = join7(dirname3(fileURLToPath2(import.meta.url)), "viewer", "icon.png");
1703
+ const iconPath = join10(dirname3(fileURLToPath2(import.meta.url)), "viewer", "icon.png");
686
1704
  const icon = await readFile5(iconPath);
687
1705
  await client.Browser.setDockTile({ image: icon.toString("base64") });
688
1706
  } catch {
@@ -690,10 +1708,11 @@ async function installDockIcon(client) {
690
1708
  }
691
1709
  async function launchChromeViewer(opts) {
692
1710
  mkdirSync(opts.userDataDir, { recursive: true });
1711
+ const baseUrl = `http://localhost:${String(opts.port)}`;
693
1712
  const chrome = await launch({
694
- startingUrl: `http://localhost:${String(opts.port)}`,
1713
+ startingUrl: baseUrl,
695
1714
  chromeFlags: [
696
- `--app=http://localhost:${String(opts.port)}`,
1715
+ `--app=${baseUrl}`,
697
1716
  "--no-first-run",
698
1717
  "--no-default-browser-check",
699
1718
  "--disable-extensions"
@@ -702,16 +1721,261 @@ async function launchChromeViewer(opts) {
702
1721
  handleSIGINT: false,
703
1722
  logLevel: "silent"
704
1723
  });
1724
+ const cdpPort = chrome.port;
705
1725
  let client = null;
706
1726
  let cookieTimer = null;
707
- 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
+ };
708
1953
  for (let attempt = 0; attempt < 10; attempt++) {
709
1954
  await new Promise((r) => setTimeout(r, 300));
1955
+ dbg(`CDP connect attempt ${String(attempt + 1)}/10`);
710
1956
  try {
711
- client = await CDP({ port: cdpPort });
1957
+ const c = await CDP({ port: cdpPort });
1958
+ client = c;
1959
+ if (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");
1974
+ }
712
1975
  break;
713
1976
  } catch {
714
1977
  if (attempt === 9) {
1978
+ dbg("CDP unavailable after 10 attempts \u2014 viewer will run without interception");
715
1979
  return {
716
1980
  kill: () => {
717
1981
  chrome.kill();
@@ -727,120 +1991,20 @@ async function launchChromeViewer(opts) {
727
1991
  }
728
1992
  };
729
1993
  }
730
- const c = client;
731
- await installDockIcon(c);
732
- await c.Fetch.enable({
733
- patterns: KIZEN_DOMAINS.flatMap((d) => [
734
- { urlPattern: `*://${d}/*`, requestStage: "Response" },
735
- { urlPattern: `*://*.${d}/*`, requestStage: "Response" }
736
- ])
737
- });
738
- c.on("Fetch.requestPaused", (rawParams) => {
739
- void (async () => {
740
- const params = rawParams;
741
- const { requestId, request, responseHeaders, responseStatusCode } = params;
742
- const url = request.url;
743
- const filtered = [];
744
- for (const h of responseHeaders ?? []) {
745
- if (CSP_HEADERS.has(h.name.toLowerCase())) {
746
- continue;
747
- }
748
- if (h.name.toLowerCase() === "set-cookie" && isKizenUrl(url)) {
749
- const patched = h.value.split("\n").map((cookie) => {
750
- if (!/SameSite=/i.test(cookie)) {
751
- return cookie + "; SameSite=None; Secure";
752
- }
753
- return cookie.replace(/SameSite=\w+/i, "SameSite=None");
754
- }).join("\n");
755
- filtered.push({ name: h.name, value: patched });
756
- } else {
757
- filtered.push(h);
758
- }
759
- }
760
- try {
761
- const { body, base64Encoded } = await c.Fetch.getResponseBody({ requestId });
762
- await c.Fetch.fulfillRequest({
763
- requestId,
764
- responseCode: responseStatusCode ?? 200,
765
- responseHeaders: filtered,
766
- body,
767
- ...base64Encoded && { base64Encoded: true }
768
- });
769
- } catch {
770
- try {
771
- await c.Fetch.continueRequest({ requestId });
772
- } catch {
773
- }
774
- }
775
- })();
776
- });
777
- cookieTimer = setInterval(() => {
778
- 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) {
779
2003
  try {
780
- const { cookies } = await c.Network.getAllCookies();
781
- for (const cookie of cookies) {
782
- if (!isKizenUrl(`https://${cookie.domain ?? ""}`)) {
783
- continue;
784
- }
785
- if (cookie.sameSite === "None") {
786
- continue;
787
- }
788
- const raw = cookie.domain ?? "";
789
- const bare = raw.startsWith(".") ? raw.slice(1) : raw;
790
- await c.Network.setCookie({
791
- url: `https://${bare}`,
792
- name: cookie.name,
793
- value: cookie.value,
794
- domain: raw,
795
- path: cookie.path ?? "/",
796
- secure: true,
797
- ...cookie.httpOnly !== void 0 && { httpOnly: cookie.httpOnly },
798
- ...cookie.expirationDate !== void 0 && {
799
- expires: cookie.expirationDate
800
- },
801
- sameSite: "None"
802
- });
803
- }
2004
+ void client.close();
804
2005
  } catch {
805
2006
  }
806
- })();
807
- }, 2e3);
808
- await c.send("Runtime.enable", { generatePreview: true });
809
- c.on("Target.attachedToTarget", (rawParams) => {
810
- const { sessionId } = rawParams;
811
- void c.send("Runtime.enable", { generatePreview: true }, sessionId).catch(() => {
812
- });
813
- });
814
- await c.Target.setAutoAttach({
815
- autoAttach: true,
816
- waitForDebuggerOnStart: false,
817
- flatten: true
818
- });
819
- await c.Target.setDiscoverTargets({ discover: true });
820
- c.on("Target.targetCreated", (rawParams) => {
821
- const { targetInfo } = rawParams;
822
- if (targetInfo.type === "page" && targetInfo.url === "chrome://newtab/") {
823
- void c.Target.closeTarget({ targetId: targetInfo.targetId }).catch(() => {
824
- });
825
- }
826
- });
827
- c.on("Runtime.consoleAPICalled", (rawParams) => {
828
- void (async () => {
829
- const { type, args } = rawParams;
830
- if (args[0]?.type === "string" && String(args[0].value).includes("Chrome Security")) {
831
- return;
832
- }
833
- const level = type === "warning" ? "warn" : ["log", "warn", "error", "info"].includes(type) ? type : "log";
834
- const serialized = await Promise.all(args.map((arg) => serializeArgAsync(c, arg)));
835
- opts.broadcast({ type: "console-message", level, args: serialized });
836
- })().catch(() => {
837
- });
838
- });
839
- const kill = () => {
840
- clearInterval(cookieTimer);
841
- try {
842
- void c.close();
843
- } catch {
2007
+ client = null;
844
2008
  }
845
2009
  chrome.kill();
846
2010
  };
@@ -848,10 +2012,10 @@ async function launchChromeViewer(opts) {
848
2012
  }
849
2013
 
850
2014
  // src/hooks/useLogHistory.ts
851
- import { useCallback, useState as useState3 } from "react";
2015
+ import { useCallback as useCallback2, useState as useState4 } from "react";
852
2016
  function useLogHistory(limit) {
853
- const [history, setHistory] = useState3([]);
854
- const append = useCallback(
2017
+ const [history, setHistory] = useState4([]);
2018
+ const append = useCallback2(
855
2019
  (message) => {
856
2020
  setHistory((h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-limit));
857
2021
  },
@@ -861,29 +2025,29 @@ function useLogHistory(limit) {
861
2025
  }
862
2026
 
863
2027
  // src/ui/CredentialSetupUI.tsx
864
- import { useCallback as useCallback2, useEffect as useEffect3, useState as useState4 } from "react";
865
- import { Box as Box4, Text as Text5, useInput } from "ink";
866
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
867
- var FIELDS = ["apiKey", "userId", "businessId"];
868
- 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 = {
869
2033
  apiKey: "API Key",
870
2034
  userId: "User ID",
871
2035
  businessId: "Business ID"
872
2036
  };
873
- var Hint = ({ text }) => /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: text });
2037
+ var Hint2 = ({ text }) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: text });
874
2038
  var CredentialSetupUI = ({
875
2039
  initialMode,
876
2040
  showProfileManager,
877
2041
  onComplete,
878
2042
  onCancel
879
2043
  }) => {
880
- const [phase, setPhase] = useState4(
2044
+ const [phase, setPhase] = useState5(
881
2045
  initialMode === "global" && !showProfileManager ? { type: "loading" } : initialMode === "global" && showProfileManager ? { type: "profile-loading" } : { type: "mode-select", cursor: 0 }
882
2046
  );
883
- const [inputBuffer, setInputBuffer] = useState4("");
884
- const [error, setError] = useState4(null);
885
- const [activeProfileName, setActiveProfileName] = useState4(void 0);
886
- 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(
887
2051
  (mode) => {
888
2052
  if (mode === "local") {
889
2053
  onComplete({ mode: "local", credentials: null });
@@ -893,7 +2057,7 @@ var CredentialSetupUI = ({
893
2057
  },
894
2058
  [onComplete]
895
2059
  );
896
- const handleProfileChosen = useCallback2(async (profile) => {
2060
+ const handleProfileChosen = useCallback3(async (profile) => {
897
2061
  if (profile === null) {
898
2062
  setPhase({ type: "profile-loading" });
899
2063
  const profiles = await listCredentialProfiles();
@@ -906,7 +2070,7 @@ var CredentialSetupUI = ({
906
2070
  const envCursor2 = existing ? Math.max(0, ENVIRONMENTS.indexOf(existing.environment)) : 0;
907
2071
  setPhase({ type: "creds-entry", field: 0, values: existing ?? {}, envCursor: envCursor2 });
908
2072
  }, []);
909
- const handleSave = useCallback2(
2073
+ const handleSave = useCallback3(
910
2074
  async (values2, envCursor2) => {
911
2075
  const environment = ENVIRONMENTS[envCursor2] ?? "go";
912
2076
  const credentials = {
@@ -931,7 +2095,7 @@ var CredentialSetupUI = ({
931
2095
  },
932
2096
  [onComplete, activeProfileName]
933
2097
  );
934
- useInput((input, key) => {
2098
+ useInput2((input, key) => {
935
2099
  if (key.ctrl && input === "c") {
936
2100
  process.exit(0);
937
2101
  }
@@ -1005,7 +2169,7 @@ var CredentialSetupUI = ({
1005
2169
  }
1006
2170
  if (phase.type === "creds-entry") {
1007
2171
  const { field, values: values2, envCursor: envCursor2 } = phase;
1008
- const isEnvField2 = field === FIELDS.length;
2172
+ const isEnvField2 = field === FIELDS2.length;
1009
2173
  if (isEnvField2) {
1010
2174
  if (key.leftArrow) {
1011
2175
  setPhase({
@@ -1016,13 +2180,13 @@ var CredentialSetupUI = ({
1016
2180
  setPhase({ ...phase, envCursor: (envCursor2 + 1) % ENVIRONMENTS.length });
1017
2181
  } else if (key.upArrow) {
1018
2182
  setPhase({ ...phase, field: field - 1 });
1019
- setInputBuffer(values2[FIELDS[field - 1]] ?? "");
2183
+ setInputBuffer(values2[FIELDS2[field - 1]] ?? "");
1020
2184
  } else if (key.return) {
1021
2185
  void handleSave(values2, envCursor2);
1022
2186
  }
1023
2187
  return;
1024
2188
  }
1025
- const fieldName = FIELDS[field];
2189
+ const fieldName = FIELDS2[field];
1026
2190
  if (!fieldName) {
1027
2191
  return;
1028
2192
  }
@@ -1032,13 +2196,13 @@ var CredentialSetupUI = ({
1032
2196
  const updatedValues = { ...values2, [fieldName]: inputBuffer };
1033
2197
  const prevField = field - 1;
1034
2198
  setPhase({ ...phase, field: prevField, values: updatedValues });
1035
- setInputBuffer(updatedValues[FIELDS[prevField]] ?? "");
2199
+ setInputBuffer(updatedValues[FIELDS2[prevField]] ?? "");
1036
2200
  } else if (key.return || key.tab || key.downArrow) {
1037
2201
  const updatedValues = { ...values2, [fieldName]: inputBuffer };
1038
2202
  const nextField = field + 1;
1039
2203
  setPhase({ ...phase, field: nextField, values: updatedValues });
1040
- if (nextField < FIELDS.length) {
1041
- setInputBuffer(updatedValues[FIELDS[nextField]] ?? "");
2204
+ if (nextField < FIELDS2.length) {
2205
+ setInputBuffer(updatedValues[FIELDS2[nextField]] ?? "");
1042
2206
  } else {
1043
2207
  setInputBuffer("");
1044
2208
  }
@@ -1047,7 +2211,7 @@ var CredentialSetupUI = ({
1047
2211
  }
1048
2212
  }
1049
2213
  });
1050
- const loadProfileList = useCallback2(async () => {
2214
+ const loadProfileList = useCallback3(async () => {
1051
2215
  const profiles = await listCredentialProfiles();
1052
2216
  setPhase({ type: "profile-select", profiles, cursor: 0 });
1053
2217
  }, []);
@@ -1072,7 +2236,7 @@ var CredentialSetupUI = ({
1072
2236
  }, [phase.type]);
1073
2237
  if (phase.type === "loading" || phase.type === "saving" || phase.type === "profile-loading") {
1074
2238
  const label = phase.type === "saving" ? "Saving credentials\u2026" : phase.type === "profile-loading" ? "Loading profiles\u2026" : "Loading credentials\u2026";
1075
- 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 }) });
1076
2240
  }
1077
2241
  if (phase.type === "done") {
1078
2242
  return null;
@@ -1082,41 +2246,41 @@ var CredentialSetupUI = ({
1082
2246
  { label: "Global", desc: `~/.kizenappbuilder/credentials.json`, mode: "global" },
1083
2247
  { label: "Local", desc: "Enter credentials in the browser dev tools", mode: "local" }
1084
2248
  ];
1085
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
1086
- /* @__PURE__ */ jsx5(AppHeader, {}),
1087
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
1088
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Credential mode" }),
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" }),
1089
2253
  options.map((opt, i) => {
1090
2254
  const selected = phase.cursor === i;
1091
- return /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1092
- /* @__PURE__ */ jsx5(Text5, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
1093
- /* @__PURE__ */ jsx5(Text5, { bold: selected, ...selected && { color: "cyan" }, children: opt.label }),
1094
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: opt.desc })
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 })
1095
2259
  ] }, opt.mode);
1096
2260
  })
1097
2261
  ] }),
1098
- /* @__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" })
1099
2263
  ] });
1100
2264
  }
1101
2265
  if (phase.type === "profile-select") {
1102
2266
  const { profiles, cursor } = phase;
1103
2267
  const addNewIdx = profiles.length;
1104
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
1105
- /* @__PURE__ */ jsx5(AppHeader, {}),
1106
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
1107
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Credential profiles" }),
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" }),
1108
2272
  profiles.map((profile, i) => {
1109
2273
  const selected = cursor === i;
1110
- return /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1111
- /* @__PURE__ */ jsx5(Text5, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
1112
- /* @__PURE__ */ jsx5(Text5, { bold: selected, ...selected && { color: "cyan" }, children: profile.isDefault ? "Default" : profile.name }),
1113
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: profile.path })
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 })
1114
2278
  ] }, profile.name);
1115
2279
  }),
1116
- /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1117
- /* @__PURE__ */ jsx5(Text5, { ...cursor === addNewIdx && { color: "cyan" }, children: cursor === addNewIdx ? "\u276F" : " " }),
1118
- /* @__PURE__ */ jsx5(
1119
- 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,
1120
2284
  {
1121
2285
  bold: cursor === addNewIdx,
1122
2286
  ...cursor === addNewIdx && { color: "cyan" },
@@ -1125,69 +2289,69 @@ var CredentialSetupUI = ({
1125
2289
  )
1126
2290
  ] })
1127
2291
  ] }),
1128
- /* @__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" })
1129
2293
  ] });
1130
2294
  }
1131
2295
  if (phase.type === "name-entry") {
1132
2296
  const { nameBuffer, nameError } = phase;
1133
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
1134
- /* @__PURE__ */ jsx5(AppHeader, {}),
1135
- /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", gap: 0, children: /* @__PURE__ */ jsx5(Text5, { bold: true, children: "New credential profile" }) }),
1136
- nameError && /* @__PURE__ */ jsxs3(Text5, { color: "red", children: [
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: [
1137
2301
  "Error: ",
1138
2302
  nameError
1139
2303
  ] }),
1140
- /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1141
- /* @__PURE__ */ jsx5(Box4, { width: 12, children: /* @__PURE__ */ jsx5(Text5, { bold: true, color: "cyan", children: "Profile name" }) }),
1142
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: ">" }),
1143
- /* @__PURE__ */ jsx5(Text5, { children: nameBuffer }),
1144
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u2588" })
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" })
1145
2309
  ] }),
1146
- /* @__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" })
1147
2311
  ] });
1148
2312
  }
1149
2313
  const { field: activeField, values, envCursor } = phase;
1150
- const isEnvField = activeField === FIELDS.length;
2314
+ const isEnvField = activeField === FIELDS2.length;
1151
2315
  const profileLabel = activeProfileName ?? DEFAULT_PROFILE_NAME;
1152
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
1153
- /* @__PURE__ */ jsx5(AppHeader, {}),
1154
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
1155
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: activeProfileName !== void 0 ? `Profile: ${activeProfileName}` : "Global credentials" }),
1156
- /* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
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: [
1157
2321
  "Saved to:",
1158
2322
  " ",
1159
2323
  activeProfileName !== void 0 ? `~/.kizenappbuilder/${activeProfileName}.json` : GLOBAL_CREDENTIALS_PATH
1160
2324
  ] })
1161
2325
  ] }),
1162
- error && /* @__PURE__ */ jsxs3(Text5, { color: "red", children: [
2326
+ error && /* @__PURE__ */ jsxs4(Text6, { color: "red", children: [
1163
2327
  "Error: ",
1164
2328
  error
1165
2329
  ] }),
1166
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
1167
- FIELDS.map((name, i) => {
2330
+ /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 0, children: [
2331
+ FIELDS2.map((name, i) => {
1168
2332
  const isActive = activeField === i;
1169
2333
  const displayValue = isActive ? inputBuffer : values[name] ?? "";
1170
- return /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1171
- /* @__PURE__ */ jsx5(Box4, { width: 12, children: /* @__PURE__ */ jsx5(Text5, { bold: isActive, ...isActive && { color: "cyan" }, children: FIELD_LABELS[name] }) }),
1172
- /* @__PURE__ */ jsx5(Text5, { ...isActive && { color: "cyan" }, children: ">" }),
1173
- /* @__PURE__ */ jsx5(Text5, { children: displayValue }),
1174
- isActive && /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u2588" })
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" })
1175
2339
  ] }, name);
1176
2340
  }),
1177
- /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1178
- /* @__PURE__ */ jsx5(Box4, { width: 12, children: /* @__PURE__ */ jsx5(Text5, { bold: isEnvField, ...isEnvField && { color: "cyan" }, children: "Environment" }) }),
1179
- /* @__PURE__ */ jsx5(Text5, { ...isEnvField && { color: "cyan" }, children: ">" }),
1180
- /* @__PURE__ */ jsx5(Box4, { gap: 1, children: ENVIRONMENTS.map((env, i) => {
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) => {
1181
2345
  const isSelected = i === envCursor;
1182
2346
  if (isEnvField) {
1183
- 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);
1184
2348
  }
1185
- return /* @__PURE__ */ jsx5(Text5, { dimColor: !isSelected, bold: isSelected, children: env }, env);
2349
+ return /* @__PURE__ */ jsx6(Text6, { dimColor: !isSelected, bold: isSelected, children: env }, env);
1186
2350
  }) })
1187
2351
  ] })
1188
2352
  ] }),
1189
- isEnvField ? /* @__PURE__ */ jsx5(Hint, { text: "\u2190\u2192 to select \xB7 \u2191\u2193 to move \xB7 Enter to save \xB7 Esc to cancel" }) : /* @__PURE__ */ jsx5(Hint, { text: "\u2191\u2193 to move \xB7 Backspace to delete \xB7 Esc to cancel" }),
1190
- /* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
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: [
1191
2355
  "Profile: ",
1192
2356
  profileLabel
1193
2357
  ] })
@@ -1195,7 +2359,7 @@ var CredentialSetupUI = ({
1195
2359
  };
1196
2360
 
1197
2361
  // src/ui/DevUI.tsx
1198
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
2362
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1199
2363
  var SKIP_WATCH_PREFIXES = [".kizenapp", ".git"];
1200
2364
  var DevUI = ({
1201
2365
  port,
@@ -1203,34 +2367,39 @@ var DevUI = ({
1203
2367
  outputDir,
1204
2368
  credentials: initialCredentials,
1205
2369
  credentialMode: initialCredentialMode,
1206
- activeCredentialProfile: initialActiveProfile
2370
+ activeCredentialProfile: initialActiveProfile,
2371
+ lastPath: initialLastPath,
2372
+ debug = false,
2373
+ verbose = false
1207
2374
  }) => {
1208
2375
  const credentialsRef = useRef(initialCredentials);
1209
2376
  const activeProfileRef = useRef(initialActiveProfile);
1210
- const [credMode, setCredMode] = useState5("main");
1211
- const [credentialMode, setCredentialMode] = useState5(
2377
+ const lastPathRef = useRef(initialLastPath);
2378
+ const [credMode, setCredMode] = useState6("main");
2379
+ const [credentialMode, setCredentialMode] = useState6(
1212
2380
  initialCredentialMode
1213
2381
  );
1214
- const [status, setStatus] = useState5("starting");
1215
- const [errorMessage, setErrorMessage] = useState5(null);
2382
+ const [status, setStatus] = useState6("starting");
2383
+ const [errorMessage, setErrorMessage] = useState6(null);
1216
2384
  const [serverLogHistory, appendServerLog] = useLogHistory(LOG_LIMIT);
1217
2385
  const [buildLogHistory, appendBuildLog] = useLogHistory(LOG_LIMIT);
1218
- const [proxyLogHistory, setProxyLogHistory] = useState5([]);
1219
- const [buildStatus, setBuildStatus] = useState5("pending");
1220
- const [buildError, setBuildError] = useState5(null);
1221
- const [lastBuilt, setLastBuilt] = useState5(null);
1222
- const [lastBundleSize, setLastBundleSize] = useState5(null);
1223
- const [wsClientCount, setWsClientCount] = useState5(0);
1224
- const [proxyCacheSize, setProxyCacheSize] = useState5(0);
1225
- const [proxyCacheHits, setProxyCacheHits] = useState5(0);
1226
- const [proxyCacheMisses, setProxyCacheMisses] = useState5(0);
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);
1227
2396
  const buildingRef = useRef(false);
1228
2397
  const viewerLaunchedRef = useRef(false);
1229
2398
  const chromeHandleRef = useRef(null);
1230
2399
  const debounceTimerRef = useRef(null);
1231
2400
  const wsClientsRef = useRef(/* @__PURE__ */ new Set());
1232
2401
  const pendingMessagesRef = useRef([]);
1233
- const broadcast = useCallback3((msg) => {
2402
+ const broadcast = useCallback4((msg) => {
1234
2403
  const json = JSON.stringify(msg);
1235
2404
  if (wsClientsRef.current.size === 0) {
1236
2405
  pendingMessagesRef.current = [...pendingMessagesRef.current, json].slice(-LOG_LIMIT);
@@ -1243,21 +2412,30 @@ var DevUI = ({
1243
2412
  }
1244
2413
  }, []);
1245
2414
  const createServerLog = appendServerLog;
1246
- const launchChromeViewerCb = useCallback3(() => {
2415
+ const launchChromeViewerCb = useCallback4(() => {
1247
2416
  chromeHandleRef.current?.kill();
1248
2417
  chromeHandleRef.current = null;
1249
2418
  void launchChromeViewer({
1250
2419
  port,
1251
- userDataDir: join8(outputDir, ".chrome"),
1252
- broadcast
2420
+ userDataDir: join11(outputDir, ".chrome"),
2421
+ broadcast,
2422
+ ...lastPathRef.current !== void 0 && { path: lastPathRef.current },
2423
+ ...debug && { debugLog: appendCdpLog },
2424
+ ...verbose && { verbose: true }
1253
2425
  }).then((handle) => {
1254
2426
  chromeHandleRef.current = handle;
1255
- process.on("exit", () => {
1256
- handle.kill();
1257
- });
1258
2427
  });
1259
- }, [port, outputDir, broadcast]);
1260
- 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(
1261
2439
  (input, key) => {
1262
2440
  if (input === "q" || key.ctrl && input === "c") {
1263
2441
  process.exit(0);
@@ -1278,7 +2456,7 @@ var DevUI = ({
1278
2456
  viewerLaunchedRef.current = true;
1279
2457
  launchChromeViewerCb();
1280
2458
  }, [status, launchChromeViewerCb]);
1281
- const handleCredentialsDone = useCallback3(
2459
+ const handleCredentialsDone = useCallback4(
1282
2460
  (result) => {
1283
2461
  credentialsRef.current = result.credentials;
1284
2462
  activeProfileRef.current = result.profileName;
@@ -1293,7 +2471,7 @@ var DevUI = ({
1293
2471
  },
1294
2472
  [outputDir, broadcast]
1295
2473
  );
1296
- const createProxyLog = useCallback3(
2474
+ const createProxyLog = useCallback4(
1297
2475
  (entry) => {
1298
2476
  setProxyLogHistory(
1299
2477
  (h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${proxyLogEntryToString(entry)}`].slice(
@@ -1311,14 +2489,14 @@ var DevUI = ({
1311
2489
  },
1312
2490
  [broadcast]
1313
2491
  );
1314
- const createBuildLog = useCallback3(
2492
+ const createBuildLog = useCallback4(
1315
2493
  (message) => {
1316
2494
  appendBuildLog(message);
1317
2495
  broadcast({ type: "log", message });
1318
2496
  },
1319
2497
  [appendBuildLog, broadcast]
1320
2498
  );
1321
- const triggerBuild = useCallback3(() => {
2499
+ const triggerBuild = useCallback4(() => {
1322
2500
  if (buildingRef.current) {
1323
2501
  return;
1324
2502
  }
@@ -1370,7 +2548,7 @@ var DevUI = ({
1370
2548
  }, [pluginDir, triggerBuild, createBuildLog]);
1371
2549
  useEffect4(() => {
1372
2550
  const viewerPath = getViewerPath();
1373
- void fileExists(join8(viewerPath, "index.html")).then((viewerBuilt) => {
2551
+ void fileExists(join11(viewerPath, "index.html")).then((viewerBuilt) => {
1374
2552
  if (!viewerBuilt) {
1375
2553
  setErrorMessage("Viewer not built. Run 'pnpm build:viewer' first.");
1376
2554
  setStatus("error");
@@ -1384,7 +2562,9 @@ var DevUI = ({
1384
2562
  activeProfileRef,
1385
2563
  outputDir,
1386
2564
  broadcast,
1387
- setProxyCacheSize
2565
+ setProxyCacheSize,
2566
+ lastPathRef,
2567
+ pluginDir
1388
2568
  );
1389
2569
  const server = createServer(handler);
1390
2570
  const wss = new WebSocketServer({ server });
@@ -1416,9 +2596,9 @@ var DevUI = ({
1416
2596
  server.close();
1417
2597
  };
1418
2598
  });
1419
- }, [port, createServerLog, createProxyLog, broadcast, outputDir]);
2599
+ }, [port, createServerLog, createProxyLog, broadcast, outputDir, pluginDir]);
1420
2600
  if (credMode === "editing") {
1421
- return /* @__PURE__ */ jsx6(
2601
+ return /* @__PURE__ */ jsx7(
1422
2602
  CredentialSetupUI,
1423
2603
  {
1424
2604
  ...credentialMode !== void 0 && { initialMode: credentialMode },
@@ -1432,80 +2612,80 @@ var DevUI = ({
1432
2612
  }
1433
2613
  const elapsedSeconds = lastBuilt !== null ? Math.round((Date.now() - lastBuilt.getTime()) / 1e3) : null;
1434
2614
  const elapsedLabel = elapsedSeconds === null ? "" : elapsedSeconds < 5 ? " (just now)" : ` (${String(elapsedSeconds)}s ago)`;
1435
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
1436
- /* @__PURE__ */ jsx6(AppHeader, { marginBottom: 1 }),
1437
- /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
1438
- /* @__PURE__ */ jsxs4(Box5, { gap: 2, marginBottom: 0, children: [
1439
- /* @__PURE__ */ jsx6(Text6, { bold: true, dimColor: true, children: "App Build" }),
1440
- buildStatus === "pending" && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "waiting\u2026" }),
1441
- buildStatus === "building" && /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
1442
- /* @__PURE__ */ jsx6(Spinner, {}),
1443
- /* @__PURE__ */ jsx6(Text6, { children: "Building plugin package\u2026" })
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" })
1444
2624
  ] }),
1445
- buildStatus === "done" && /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
1446
- /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713 Built" }),
1447
- lastBuilt !== null && /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
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: [
1448
2628
  "at ",
1449
2629
  lastBuilt.toLocaleTimeString(),
1450
2630
  elapsedLabel
1451
2631
  ] }),
1452
- lastBundleSize !== null && /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
2632
+ lastBundleSize !== null && /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
1453
2633
  "\xB7 bundle: ",
1454
2634
  formatBytes(lastBundleSize)
1455
2635
  ] })
1456
2636
  ] }),
1457
- buildStatus === "error" && /* @__PURE__ */ jsxs4(Text6, { color: "red", children: [
2637
+ buildStatus === "error" && /* @__PURE__ */ jsxs5(Text7, { color: "red", children: [
1458
2638
  "\u2717 ",
1459
2639
  buildError ?? "unknown error"
1460
2640
  ] })
1461
2641
  ] }),
1462
- /* @__PURE__ */ jsx6(
1463
- Box5,
2642
+ /* @__PURE__ */ jsx7(
2643
+ Box6,
1464
2644
  {
1465
2645
  flexDirection: "column",
1466
2646
  height: LOG_DISPLAY + 2,
1467
2647
  borderStyle: "single",
1468
2648
  borderColor: "gray",
1469
2649
  overflow: "hidden",
1470
- 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))
1471
2651
  }
1472
2652
  )
1473
2653
  ] }),
1474
- /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", children: [
1475
- /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
1476
- /* @__PURE__ */ jsx6(Text6, { bold: true, dimColor: true, children: "Web Server" }),
1477
- status === "starting" && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "starting\u2026" }),
1478
- status === "running" && /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
1479
- /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713 Running" }),
1480
- /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
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: [
1481
2661
  "\u25CF ",
1482
2662
  wsClientCount,
1483
2663
  " viewer",
1484
2664
  wsClientCount !== 1 ? "s" : ""
1485
2665
  ] })
1486
2666
  ] }),
1487
- status === "error" && /* @__PURE__ */ jsxs4(Text6, { color: "red", children: [
2667
+ status === "error" && /* @__PURE__ */ jsxs5(Text7, { color: "red", children: [
1488
2668
  "\u2717 ",
1489
2669
  errorMessage ?? "Server error"
1490
2670
  ] })
1491
2671
  ] }),
1492
- /* @__PURE__ */ jsx6(
1493
- Box5,
2672
+ /* @__PURE__ */ jsx7(
2673
+ Box6,
1494
2674
  {
1495
2675
  flexDirection: "column",
1496
2676
  height: LOG_DISPLAY + 2,
1497
2677
  borderStyle: "single",
1498
2678
  borderColor: "gray",
1499
2679
  overflow: "hidden",
1500
- 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))
1501
2681
  }
1502
2682
  )
1503
2683
  ] }),
1504
- /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", children: [
1505
- /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
1506
- /* @__PURE__ */ jsx6(Text6, { bold: true, dimColor: true, children: "Network Proxy" }),
1507
- /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713 Active" }),
1508
- /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
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: [
1509
2689
  "\u25CF ",
1510
2690
  proxyCacheSize,
1511
2691
  " key",
@@ -1518,26 +2698,47 @@ var DevUI = ({
1518
2698
  " miss"
1519
2699
  ] })
1520
2700
  ] }),
1521
- /* @__PURE__ */ jsx6(
1522
- Box5,
2701
+ /* @__PURE__ */ jsx7(
2702
+ Box6,
1523
2703
  {
1524
2704
  flexDirection: "column",
1525
2705
  height: LOG_DISPLAY + 2,
1526
2706
  borderStyle: "single",
1527
2707
  borderColor: "gray",
1528
2708
  overflow: "hidden",
1529
- children: proxyLogHistory.length === 0 ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " No requests yet." }) : proxyLogHistory.slice(-LOG_DISPLAY).map((log, i) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "truncate", children: log }, i))
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))
1530
2710
  }
1531
2711
  )
1532
2712
  ] }),
1533
- /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, gap: 1, children: [
1534
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "[" }),
1535
- /* @__PURE__ */ jsx6(Text6, { children: "q" }),
1536
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "] quit [" }),
1537
- /* @__PURE__ */ jsx6(Text6, { children: "c" }),
1538
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "] credentials [" }),
1539
- /* @__PURE__ */ jsx6(Text6, { children: "v" }),
1540
- /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
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: [
1541
2742
  "] ",
1542
2743
  wsClientCount === 0 ? "open viewer" : "relaunch viewer"
1543
2744
  ] })
@@ -1547,14 +2748,14 @@ var DevUI = ({
1547
2748
 
1548
2749
  // src/commands/dev.ts
1549
2750
  async function runSetupUI(initialMode) {
1550
- return new Promise((resolve) => {
2751
+ return new Promise((resolve2) => {
1551
2752
  let unmountFn = null;
1552
2753
  const handleComplete = (result) => {
1553
2754
  unmountFn?.();
1554
- resolve(result);
2755
+ resolve2(result);
1555
2756
  };
1556
- const { unmount } = render2(
1557
- createElement2(CredentialSetupUI, {
2757
+ const { unmount } = render3(
2758
+ createElement3(CredentialSetupUI, {
1558
2759
  ...initialMode !== void 0 && { initialMode },
1559
2760
  onComplete: handleComplete
1560
2761
  }),
@@ -1564,66 +2765,75 @@ async function runSetupUI(initialMode) {
1564
2765
  });
1565
2766
  }
1566
2767
  function devCommand(program2) {
1567
- 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) => {
1568
- const port = parseInt(options.port, 10);
1569
- const pluginDir = process.cwd();
1570
- const outputDir = `${pluginDir}/.kizenapp`;
1571
- ensureGitignore(pluginDir);
1572
- let credentials = null;
1573
- let credentialMode;
1574
- let activeCredentialProfile;
1575
- if (options.credentials) {
1576
- credentials = await loadCredentialsFromFile(options.credentials);
1577
- } else {
1578
- const config = await loadConfig(outputDir);
1579
- if (config.credentialMode === "local") {
1580
- credentialMode = "local";
1581
- } else if (config.credentialMode === "global") {
1582
- credentialMode = "global";
1583
- activeCredentialProfile = config.activeCredentialProfile;
1584
- if (activeCredentialProfile) {
1585
- credentials = await loadCredentialProfile(activeCredentialProfile);
1586
- 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 {
1587
2794
  credentials = await loadGlobalCredentials();
1588
- activeCredentialProfile = void 0;
2795
+ }
2796
+ if (!credentials) {
2797
+ const result = await runSetupUI("global");
2798
+ credentials = result.credentials;
2799
+ activeCredentialProfile = result.profileName;
1589
2800
  }
1590
2801
  } else {
1591
- credentials = await loadGlobalCredentials();
1592
- }
1593
- if (!credentials) {
1594
- const result = await runSetupUI("global");
2802
+ const result = await runSetupUI();
1595
2803
  credentials = result.credentials;
2804
+ credentialMode = result.mode;
1596
2805
  activeCredentialProfile = result.profileName;
2806
+ const profile = result.profileName;
2807
+ await saveConfig(outputDir, {
2808
+ credentialMode: result.mode,
2809
+ ...profile !== void 0 && { activeCredentialProfile: profile }
2810
+ });
1597
2811
  }
1598
- } else {
1599
- const result = await runSetupUI();
1600
- credentials = result.credentials;
1601
- credentialMode = result.mode;
1602
- activeCredentialProfile = result.profileName;
1603
- const profile = result.profileName;
1604
- await saveConfig(outputDir, {
1605
- credentialMode: result.mode,
1606
- ...profile !== void 0 && { activeCredentialProfile: profile }
1607
- });
1608
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();
1609
2830
  }
1610
- const { waitUntilExit } = render2(
1611
- createElement2(DevUI, {
1612
- port,
1613
- pluginDir,
1614
- outputDir,
1615
- credentials,
1616
- ...credentialMode !== void 0 && { credentialMode },
1617
- ...activeCredentialProfile !== void 0 && { activeCredentialProfile }
1618
- }),
1619
- { exitOnCtrlC: false }
1620
- );
1621
- await waitUntilExit();
1622
- });
2831
+ );
1623
2832
  }
1624
2833
 
1625
2834
  // src/index.ts
1626
2835
  program.name("appbuilder").description("Kizen plugin app builder").version("0.1.0");
2836
+ createCommand(program);
1627
2837
  buildCommand(program);
1628
2838
  devCommand(program);
1629
2839
  program.parse();