@kizenapps/cli 0.10.0 → 0.12.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 (103) hide show
  1. package/dist/chunk-4Z6S6LWP.js +6134 -0
  2. package/dist/chunk-4Z6S6LWP.js.map +1 -0
  3. package/dist/chunk-HJPLDI45.js +6134 -0
  4. package/dist/chunk-HJPLDI45.js.map +1 -0
  5. package/dist/chunk-LSHHXCC6.js +3639 -0
  6. package/dist/chunk-LSHHXCC6.js.map +1 -0
  7. package/dist/chunk-SEGVTWSK.js +44 -0
  8. package/dist/chunk-SEGVTWSK.js.map +1 -0
  9. package/dist/chunk-ZWE3DS7E.js +39 -0
  10. package/dist/chunk-ZWE3DS7E.js.map +1 -0
  11. package/dist/devtools-LZRAH4XS.js +90 -0
  12. package/dist/devtools-LZRAH4XS.js.map +1 -0
  13. package/dist/dist-C3F6UOWF.js +8472 -0
  14. package/dist/dist-C3F6UOWF.js.map +1 -0
  15. package/dist/dist-NUJOSN7W.js +8472 -0
  16. package/dist/dist-NUJOSN7W.js.map +1 -0
  17. package/dist/dist-QHDMIBVE.js +135 -0
  18. package/dist/dist-QHDMIBVE.js.map +1 -0
  19. package/dist/dist-ZIZQOSY3.js +135 -0
  20. package/dist/dist-ZIZQOSY3.js.map +1 -0
  21. package/dist/gzip-size-K37OQCQ2.js +137 -0
  22. package/dist/gzip-size-K37OQCQ2.js.map +1 -0
  23. package/dist/gzip-size-QRGIYYEQ.js +137 -0
  24. package/dist/gzip-size-QRGIYYEQ.js.map +1 -0
  25. package/dist/index.js +1046 -582
  26. package/dist/index.js.map +1 -1
  27. package/dist/viewer/assets/AppDetailPage-BPvWscWJ.js +1 -0
  28. package/dist/viewer/assets/AppDetailPage-DIuYKCTQ.js +1 -0
  29. package/dist/viewer/assets/AppDetailPage-r3jzO9Kf.js +1 -0
  30. package/dist/viewer/assets/BootstrapContext-CExPp3R2.js +1 -0
  31. package/dist/viewer/assets/BootstrapContext-CTQMWXf-.js +1 -0
  32. package/dist/viewer/assets/BootstrapContext-DbnbWmQT.js +1 -0
  33. package/dist/viewer/assets/Card-Bmn1HMWl.js +1 -0
  34. package/dist/viewer/assets/Card-DMz6T3VL.js +1 -0
  35. package/dist/viewer/assets/Card-DsqKctyM.js +1 -0
  36. package/dist/viewer/assets/CodeStepsPage-BHqLfCdV.js +4 -0
  37. package/dist/viewer/assets/CodeStepsPage-BymhEfzO.js +4 -0
  38. package/dist/viewer/assets/CodeStepsPage-CD_MhDwY.js +4 -0
  39. package/dist/viewer/assets/CodeStepsPage-CgI-LqEx.js +4 -0
  40. package/dist/viewer/assets/CodeStepsPage-Dak3saZE.js +5 -0
  41. package/dist/viewer/assets/CodeStepsPage-DdKuH37F.css +1 -0
  42. package/dist/viewer/assets/CodeStepsPage-Zj6DscGd.js +4 -0
  43. package/dist/viewer/assets/ConfigurationPage-Cjy9TH3N.js +1 -0
  44. package/dist/viewer/assets/ConfigurationPage-DmX-6MM-.js +1 -0
  45. package/dist/viewer/assets/ConfigurationPage-J56kCD_e.js +1 -0
  46. package/dist/viewer/assets/CredentialsContext-Xbse1T0q.js +1 -0
  47. package/dist/viewer/assets/CredentialsContext-zl2e9kpM.js +1 -0
  48. package/dist/viewer/assets/DevSidebar-B30mgxSn.js +1 -0
  49. package/dist/viewer/assets/{DevSidebar-BiROAoKF.js → DevSidebar-BhTD2bBO.js} +1 -1
  50. package/dist/viewer/assets/DevSidebar-CsEdCTej.js +1 -0
  51. package/dist/viewer/assets/IconReferencePage-B6j9wlte.js +1 -0
  52. package/dist/viewer/assets/IconReferencePage-CJa9yg9Z.js +1 -0
  53. package/dist/viewer/assets/IconReferencePage-Q5I-lMFQ.js +1 -0
  54. package/dist/viewer/assets/SandboxPage-C0h7GUkw.js +1 -0
  55. package/dist/viewer/assets/SandboxPage-GDcydJnJ.js +1 -0
  56. package/dist/viewer/assets/SandboxPage-JREBQ4id.js +1 -0
  57. package/dist/viewer/assets/SidebarContext-BPkYyxET.js +1 -0
  58. package/dist/viewer/assets/SidebarContext-DVDXFWJI.js +1 -0
  59. package/dist/viewer/assets/SidebarContext-HucmR353.js +1 -0
  60. package/dist/viewer/assets/VersionsPage-B76eugMm.js +1 -0
  61. package/dist/viewer/assets/VersionsPage-BIpB0iBz.js +1 -0
  62. package/dist/viewer/assets/VersionsPage-D0qenxK5.js +1 -0
  63. package/dist/viewer/assets/api-BOqcXygX.js +1 -0
  64. package/dist/viewer/assets/api-DqAiw0w9.js +1 -0
  65. package/dist/viewer/assets/bundleQuery-206rQR6g.js +1 -0
  66. package/dist/viewer/assets/bundleQuery-L4H4wq5q.js +1 -0
  67. package/dist/viewer/assets/{fa-core-mHW_3JUd.js → fa-core-DaD7O-yO.js} +1 -1
  68. package/dist/viewer/assets/fa-core-wsCxaNB7.js +558 -0
  69. package/dist/viewer/assets/fa-pro-icons-Dzkwxnip.js +1 -0
  70. package/dist/viewer/assets/index-BPiwld5D.js +5 -0
  71. package/dist/viewer/assets/index-BiyeMU_r.css +2 -0
  72. package/dist/viewer/assets/index-BlJv_543.js +5 -0
  73. package/dist/viewer/assets/index-CAYNux5J.js +5 -0
  74. package/dist/viewer/assets/index-D4AnoAXz.js +5 -0
  75. package/dist/viewer/assets/index-D8Koqlb-.js +5 -0
  76. package/dist/viewer/assets/index-DKrC0qkC.js +5 -0
  77. package/dist/viewer/assets/{react-D_K8ni3_.js → react-CkuOm_dq.js} +1 -1
  78. package/dist/viewer/assets/react-CyWK9rg5.js +9 -0
  79. package/dist/viewer/assets/useLocalStorage-BWwvt0nG.js +1 -0
  80. package/dist/viewer/assets/useLocalStorage-BlZWItfY.js +1 -0
  81. package/dist/viewer/assets/useLocalStorage-CQO0VGQ6.js +1 -0
  82. package/dist/viewer/assets/useNavigate-9J1ZzN1J.js +1 -0
  83. package/dist/viewer/assets/useNavigate-V8wjv5GQ.js +1 -0
  84. package/dist/viewer/assets/useObjectLookups-BAqe9wvu.js +14 -0
  85. package/dist/viewer/assets/useObjectLookups-C65zm-bM.js +14 -0
  86. package/dist/viewer/assets/useObjectLookups-CslM2vSA.js +14 -0
  87. package/dist/viewer/assets/{validIcons-TcFXfn-E.js → validIcons-Dqgxgpv-.js} +1 -1
  88. package/dist/viewer/assets/validIcons-UmfwFGTT.js +1 -0
  89. package/dist/viewer/index.html +10 -8
  90. package/dist/viewer/mark.svg +17 -0
  91. package/package.json +11 -10
  92. package/dist/viewer/assets/AppDetailPage-ObugMPkn.js +0 -1
  93. package/dist/viewer/assets/BootstrapContext-CEWgv2oI.js +0 -1
  94. package/dist/viewer/assets/Card-BSwG5koM.js +0 -1
  95. package/dist/viewer/assets/ConfigurationPage-SnFNzZgJ.js +0 -1
  96. package/dist/viewer/assets/CredentialsContext-BLZX6RwF.js +0 -1
  97. package/dist/viewer/assets/SandboxPage-BFVVe-i3.js +0 -1
  98. package/dist/viewer/assets/SidebarContext-Byo6PYOF.js +0 -1
  99. package/dist/viewer/assets/VersionsPage-DOd2Hv8M.js +0 -1
  100. package/dist/viewer/assets/configStorage-Bb-A9Nvj.js +0 -14
  101. package/dist/viewer/assets/index-B-4D4w_w.js +0 -5
  102. package/dist/viewer/assets/index-D6Un_G7k.css +0 -2
  103. /package/dist/viewer/assets/{fa-pro-icons-CIQGXozY.js → fa-pro-icons-CiTC899m.js} +0 -0
package/dist/index.js CHANGED
@@ -8,8 +8,8 @@ import { createElement } from "react";
8
8
  import { render } from "ink";
9
9
 
10
10
  // src/ui/BuildUI.tsx
11
- import { useEffect, useState } from "react";
12
- import { Box as Box2, Text as Text2, useApp } from "ink";
11
+ import { useEffect as useEffect2, useState as useState2 } from "react";
12
+ import { Box as Box3, Text as Text4, useApp } from "ink";
13
13
 
14
14
  // src/lib/runBuild.ts
15
15
  import { mkdir, writeFile } from "fs/promises";
@@ -88,36 +88,66 @@ async function runBuild(pluginDir, outputDir, onStep) {
88
88
  })).sort((a, b) => b.version.localeCompare(a.version, void 0, { numeric: true })) : [];
89
89
  return serializePlugin(plugin, allReleaseNotes);
90
90
  });
91
- await writeFile(join2(outputDir, "bundle.json"), JSON.stringify(bundle, null, 2), "utf-8");
91
+ const jsonStr = JSON.stringify(bundle, null, 2);
92
+ const bundleSize = Buffer.byteLength(jsonStr, "utf-8");
93
+ await writeFile(join2(outputDir, "bundle.json"), jsonStr, "utf-8");
94
+ return { bundleSize };
95
+ }
96
+
97
+ // src/lib/formatBytes.ts
98
+ function formatBytes(n) {
99
+ if (n < 1024) {
100
+ return `${String(n)} B`;
101
+ }
102
+ if (n < 1024 * 1024) {
103
+ return `${(n / 1024).toFixed(1)} KB`;
104
+ }
105
+ return `${(n / (1024 * 1024)).toFixed(2)} MB`;
92
106
  }
93
107
 
108
+ // src/ui/AppHeader.tsx
109
+ import { Box as Box2, Text as Text2 } from "ink";
110
+
94
111
  // src/ui/Logo.tsx
95
112
  import { Box, Text } from "ink";
96
113
  import { jsx } from "react/jsx-runtime";
97
114
  var LOGO_LINES = [
98
- " @@@ ",
99
- " @@@@@ @@@@@@@ ",
100
- " @@@@ @@@@@@@@@@ ",
101
- " @@@@ @@@@@ ",
102
- " @@@ @@@@ @@@@@ @@@@@@@ ",
103
- " @@@@ @@@@ @@@@ @@@@@@@@@@@@@",
104
- " @@@@ @@@@ @@@@@",
105
- " @@@@ @@@ @@@@ @@@",
106
- " @@@@ @@@@@@ ",
107
- " @@@@@@ @@@@ ",
108
- " @@@ @@@@@ @@@ @@@@ ",
109
- " @@@@@ @@@@ @@@@ ",
110
- " @@@@@@@@@@@@@ @@@ @@@ @@@ ",
111
- " @@@@@@@@ @@@@ @@@ @@@@ ",
112
- " @@@@@ @@@@ ",
113
- " @@@@@@@@@@@ @@@@ ",
114
- " @@@@@@@ @@@@@ ",
115
- " @@@@ "
115
+ " :::::::: ",
116
+ " :::::: ::::::::::: ",
117
+ " :::::: :::::::::: ",
118
+ " :::::: :::::: ::::::: ",
119
+ " ::::: :::::: ::::::::::::::::: ",
120
+ " ::::: ::::: ::::::::::::::::::: ",
121
+ " ::::: ::::: :::::: ::::: ",
122
+ ":::: ::::: ::: ::::: :: ",
123
+ "::::: :::::: :::::::::: ",
124
+ "::::: :::::: ::::::::::: ",
125
+ " ::::: ::: ::::::: ",
126
+ " ::::::: ::: ::::: ",
127
+ " ::::::::::: :::::: :::::",
128
+ " :::::::::: :::::: :::::",
129
+ " :: ::::: ::: ::::: ::::",
130
+ " ::::: :::::: ::::: ::::: ::: ",
131
+ " ::::::::::::::::::: ::::: ::::: ",
132
+ " ::::::::::::::::: ::::: ::::: ",
133
+ " ::::::::: :::::: :::::: ",
134
+ " :::::::::: :::::: ",
135
+ " ::::::::::: :::::: ",
136
+ " :::::::: "
116
137
  ];
117
138
  var Logo = () => /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: LOGO_LINES.map((line, i) => /* @__PURE__ */ jsx(Text, { color: "cyan", children: line }, i)) });
118
139
 
119
- // src/ui/BuildUI.tsx
140
+ // src/ui/AppHeader.tsx
120
141
  import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
142
+ var AppHeader = ({ marginBottom = 0 }) => /* @__PURE__ */ jsxs(Fragment, { children: [
143
+ /* @__PURE__ */ jsx2(Logo, {}),
144
+ /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, marginBottom, children: /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "Kizen App Development Toolkit" }) })
145
+ ] });
146
+
147
+ // src/ui/Spinner.tsx
148
+ import { useEffect, useState } from "react";
149
+ import { Text as Text3 } from "ink";
150
+ import { jsx as jsx3 } from "react/jsx-runtime";
121
151
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
122
152
  var Spinner = () => {
123
153
  const [frame, setFrame] = useState(0);
@@ -129,8 +159,11 @@ var Spinner = () => {
129
159
  clearInterval(id);
130
160
  };
131
161
  }, []);
132
- return /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: SPINNER_FRAMES[frame] ?? "\u280B" });
162
+ return /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: SPINNER_FRAMES[frame] ?? "\u280B" });
133
163
  };
164
+
165
+ // src/ui/BuildUI.tsx
166
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
134
167
  var STEPS = [
135
168
  "creating-dir",
136
169
  "reading-files",
@@ -147,10 +180,12 @@ var STEP_LABELS = {
147
180
  };
148
181
  var BuildUI = ({ outputDir, pluginDir }) => {
149
182
  const app = useApp();
150
- const [step, setStep] = useState("creating-dir");
151
- const [errorMessage, setErrorMessage] = useState(null);
152
- useEffect(() => {
153
- void runBuild(pluginDir, outputDir, setStep).then(() => {
183
+ const [step, setStep] = useState2("creating-dir");
184
+ const [errorMessage, setErrorMessage] = useState2(null);
185
+ const [bundleSize, setBundleSize] = useState2(null);
186
+ useEffect2(() => {
187
+ void runBuild(pluginDir, outputDir, setStep).then(({ bundleSize: size }) => {
188
+ setBundleSize(size);
154
189
  setStep("done");
155
190
  app.exit();
156
191
  }).catch((err) => {
@@ -163,32 +198,32 @@ var BuildUI = ({ outputDir, pluginDir }) => {
163
198
  const currentIndex = STEPS.indexOf(step);
164
199
  const isError = step === "error";
165
200
  const isDone = step === "done";
166
- return /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
167
- /* @__PURE__ */ jsx2(Logo, {}),
168
- /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [
169
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "Kizen App Builder" }),
170
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2500".repeat(24) })
171
- ] }),
172
- /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", gap: 0, children: [
201
+ return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
202
+ /* @__PURE__ */ jsx4(AppHeader, { marginBottom: 1 }),
203
+ /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", gap: 0, children: [
173
204
  STEPS.map((s, i) => {
174
205
  const isActive = step === s;
175
206
  const isStepDone = isDone || currentIndex > i;
176
207
  const isFailed = isError && i === currentIndex;
177
- return /* @__PURE__ */ jsx2(Box2, { gap: 1, children: isFailed ? /* @__PURE__ */ jsxs(Text2, { color: "red", children: [
208
+ return /* @__PURE__ */ jsx4(Box3, { gap: 1, children: isFailed ? /* @__PURE__ */ jsxs2(Text4, { color: "red", children: [
178
209
  "\u2717 ",
179
210
  STEP_LABELS[s]
180
- ] }) : isStepDone ? /* @__PURE__ */ jsxs(Text2, { color: "green", children: [
211
+ ] }) : isStepDone ? /* @__PURE__ */ jsxs2(Text4, { color: "green", children: [
181
212
  "\u2713 ",
182
213
  STEP_LABELS[s]
183
- ] }) : isActive ? /* @__PURE__ */ jsxs(Fragment, { children: [
184
- /* @__PURE__ */ jsx2(Spinner, {}),
185
- /* @__PURE__ */ jsx2(Text2, { children: STEP_LABELS[s] })
186
- ] }) : /* @__PURE__ */ jsxs(Text2, { dimColor: true, children: [
214
+ ] }) : isActive ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
215
+ /* @__PURE__ */ jsx4(Spinner, {}),
216
+ /* @__PURE__ */ jsx4(Text4, { children: STEP_LABELS[s] })
217
+ ] }) : /* @__PURE__ */ jsxs2(Text4, { dimColor: true, children: [
187
218
  " ",
188
219
  STEP_LABELS[s]
189
220
  ] }) }, s);
190
221
  }),
191
- errorMessage !== null && /* @__PURE__ */ jsx2(Box2, { marginTop: 1, borderStyle: "single", borderColor: "red", paddingX: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "red", children: errorMessage }) })
222
+ isDone && bundleSize !== null && /* @__PURE__ */ jsx4(Box3, { marginTop: 1, gap: 1, children: /* @__PURE__ */ jsxs2(Text4, { color: "green", children: [
223
+ "\u2713 bundle.json \u2014 ",
224
+ formatBytes(bundleSize)
225
+ ] }) }),
226
+ errorMessage !== null && /* @__PURE__ */ jsx4(Box3, { marginTop: 1, borderStyle: "single", borderColor: "red", paddingX: 1, children: /* @__PURE__ */ jsx4(Text4, { color: "red", children: errorMessage }) })
192
227
  ] })
193
228
  ] });
194
229
  };
@@ -222,27 +257,785 @@ function buildCommand(program2) {
222
257
  });
223
258
  }
224
259
 
225
- // src/commands/dev.ts
226
- import { createElement as createElement2 } from "react";
227
- import { render as render2 } from "ink";
228
-
229
- // src/ui/DevUI.tsx
230
- import { useCallback as useCallback2, useEffect as useEffect3, useRef, useState as useState3 } from "react";
231
- import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
232
- import { createReadStream, watch } from "fs";
233
- import { access, readFile as readFile5 } from "fs/promises";
234
- import { createServer } from "http";
235
- import { dirname as dirname3, extname, join as join7 } from "path";
236
- import { fileURLToPath as fileURLToPath2 } from "url";
237
- import { WebSocketServer } from "ws";
238
-
260
+ // src/commands/dev.ts
261
+ import { createElement as createElement2 } from "react";
262
+ import { render as render2 } from "ink";
263
+
264
+ // src/ui/DevUI.tsx
265
+ import { useCallback as useCallback3, useEffect as useEffect4, useRef, useState as useState5 } from "react";
266
+ import { Box as Box5, Text as Text6, useInput as useInput2 } from "ink";
267
+ import { watch } from "fs";
268
+ import { createServer } from "http";
269
+ import { join as join9 } from "path";
270
+ import { WebSocketServer } from "ws";
271
+
272
+ // src/server/requestHandler.ts
273
+ import { createReadStream } from "fs";
274
+ import { access as access2, readFile as readFile4 } from "fs/promises";
275
+ import { dirname as dirname2, extname, join as join7 } from "path";
276
+ import { fileURLToPath } from "url";
277
+
278
+ // src/lib/proxyCache.ts
279
+ function createProxyCache(options = {}) {
280
+ const cache = /* @__PURE__ */ new Map();
281
+ const { onChange } = options;
282
+ const emitChange = () => {
283
+ onChange?.(cache.size);
284
+ };
285
+ return {
286
+ get(key, fetcher) {
287
+ const existing = cache.get(key);
288
+ if (existing !== void 0) {
289
+ return existing.then((response) => ({ response, fromCache: true }));
290
+ }
291
+ const pending = fetcher().then(async (response) => {
292
+ const headers = Object.fromEntries(response.headers);
293
+ delete headers["content-encoding"];
294
+ delete headers["content-length"];
295
+ return {
296
+ status: response.status,
297
+ headers,
298
+ body: Buffer.from(await response.arrayBuffer())
299
+ };
300
+ }).catch((error) => {
301
+ cache.delete(key);
302
+ emitChange();
303
+ throw error;
304
+ });
305
+ cache.set(key, pending);
306
+ emitChange();
307
+ return pending.then((response) => {
308
+ if (response.status >= 400) {
309
+ cache.delete(key);
310
+ emitChange();
311
+ }
312
+ return { response, fromCache: false };
313
+ });
314
+ },
315
+ clear() {
316
+ cache.clear();
317
+ emitChange();
318
+ }
319
+ };
320
+ }
321
+
322
+ // src/lib/constants.ts
323
+ var LOG_LIMIT = 50;
324
+ var LOG_DISPLAY = 8;
325
+ var FILE_WATCH_DEBOUNCE_MS = 150;
326
+ var MIME_TYPES = {
327
+ ".html": "text/html; charset=utf-8",
328
+ ".js": "application/javascript; charset=utf-8",
329
+ ".mjs": "application/javascript; charset=utf-8",
330
+ ".css": "text/css; charset=utf-8",
331
+ ".png": "image/png",
332
+ ".jpg": "image/jpeg",
333
+ ".svg": "image/svg+xml",
334
+ ".ico": "image/x-icon",
335
+ ".json": "application/json; charset=utf-8",
336
+ ".woff": "font/woff",
337
+ ".woff2": "font/woff2",
338
+ ".ttf": "font/ttf",
339
+ ".map": "application/json"
340
+ };
341
+
342
+ // src/lib/credentials.ts
343
+ import { mkdir as mkdir2, readFile as readFile2, readdir as readdir2, writeFile as writeFile2 } from "fs/promises";
344
+ import { homedir } from "os";
345
+ import { dirname, join as join4 } from "path";
346
+ var ENVIRONMENTS = ["go", "fmo", "staging", "integration", "test1"];
347
+ var GLOBAL_CREDENTIALS_DIR = join4(homedir(), ".kizenappbuilder");
348
+ var GLOBAL_CREDENTIALS_PATH = join4(GLOBAL_CREDENTIALS_DIR, "credentials.json");
349
+ var DEFAULT_PROFILE_NAME = "credentials";
350
+ function isValidEnvironment(value) {
351
+ return ENVIRONMENTS.includes(value);
352
+ }
353
+ function parseCredentials(raw) {
354
+ if (typeof raw !== "object" || raw === null) {
355
+ throw new Error("Credentials must be a JSON object");
356
+ }
357
+ const obj = raw;
358
+ const env = obj.environment;
359
+ return {
360
+ apiKey: typeof obj.apiKey === "string" ? obj.apiKey : "",
361
+ userId: typeof obj.userId === "string" ? obj.userId : "",
362
+ businessId: typeof obj.businessId === "string" ? obj.businessId : "",
363
+ environment: isValidEnvironment(env) ? env : "go"
364
+ };
365
+ }
366
+ async function loadCredentialsFromFile(filePath) {
367
+ const content = await readFile2(filePath, "utf-8");
368
+ return parseCredentials(JSON.parse(content));
369
+ }
370
+ async function loadGlobalCredentials() {
371
+ try {
372
+ return await loadCredentialsFromFile(GLOBAL_CREDENTIALS_PATH);
373
+ } catch {
374
+ return null;
375
+ }
376
+ }
377
+ async function saveGlobalCredentials(credentials) {
378
+ await mkdir2(dirname(GLOBAL_CREDENTIALS_PATH), { recursive: true });
379
+ await writeFile2(GLOBAL_CREDENTIALS_PATH, JSON.stringify(credentials, null, 2), "utf-8");
380
+ }
381
+ function getProfilePath(name) {
382
+ return join4(GLOBAL_CREDENTIALS_DIR, `${name}.json`);
383
+ }
384
+ async function listCredentialProfiles() {
385
+ const profiles = [
386
+ { name: DEFAULT_PROFILE_NAME, path: GLOBAL_CREDENTIALS_PATH, isDefault: true }
387
+ ];
388
+ try {
389
+ const entries = await readdir2(GLOBAL_CREDENTIALS_DIR);
390
+ for (const entry of entries) {
391
+ if (!entry.endsWith(".json")) {
392
+ continue;
393
+ }
394
+ const name = entry.slice(0, -5);
395
+ if (name === DEFAULT_PROFILE_NAME) {
396
+ continue;
397
+ }
398
+ profiles.push({ name, path: join4(GLOBAL_CREDENTIALS_DIR, entry), isDefault: false });
399
+ }
400
+ } catch {
401
+ }
402
+ return profiles;
403
+ }
404
+ async function saveCredentialProfile(name, credentials) {
405
+ if (name === DEFAULT_PROFILE_NAME) {
406
+ await saveGlobalCredentials(credentials);
407
+ return;
408
+ }
409
+ await mkdir2(GLOBAL_CREDENTIALS_DIR, { recursive: true });
410
+ await writeFile2(getProfilePath(name), JSON.stringify(credentials, null, 2), "utf-8");
411
+ }
412
+ async function loadCredentialProfile(name) {
413
+ try {
414
+ return await loadCredentialsFromFile(getProfilePath(name));
415
+ } catch {
416
+ return null;
417
+ }
418
+ }
419
+
420
+ // src/lib/config.ts
421
+ import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
422
+ import { join as join5 } from "path";
423
+ async function loadConfig(outputDir) {
424
+ try {
425
+ const content = await readFile3(join5(outputDir, "config.json"), "utf-8");
426
+ return JSON.parse(content);
427
+ } catch {
428
+ return {};
429
+ }
430
+ }
431
+ async function saveConfig(outputDir, config) {
432
+ await mkdir3(outputDir, { recursive: true });
433
+ await writeFile3(join5(outputDir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
434
+ }
435
+
436
+ // src/server/pythonExecutor.ts
437
+ import { spawn, execFileSync } from "child_process";
438
+ import { writeFile as writeFile4, rm, mkdtemp, access } from "fs/promises";
439
+ import { join as join6 } from "path";
440
+ import { tmpdir } from "os";
441
+ var WRAPPER_SCRIPT = `
442
+ import sys, json, os, traceback
443
+
444
+ # --- Inputs proxy (attribute access) ---
445
+ class _InputsProxy:
446
+ def __init__(self, data):
447
+ object.__setattr__(self, '_data', data)
448
+
449
+ def __getattr__(self, name):
450
+ if name.startswith('_'):
451
+ raise AttributeError(name)
452
+ return self._data.get(name)
453
+
454
+ def __repr__(self):
455
+ return repr(self._data)
456
+
457
+ def __str__(self):
458
+ return str(self._data)
459
+
460
+ def __bool__(self):
461
+ return bool(self._data)
462
+
463
+ inputs = _InputsProxy(json.loads(os.environ.get('__KIZEN_INPUTS__', '{}')))
464
+
465
+ # --- Secrets ---
466
+ secrets = json.loads(os.environ.get('__KIZEN_SECRETS__', '{}'))
467
+
468
+ # --- Outputs proxy ---
469
+ class _OutputsProxy:
470
+ def __init__(self):
471
+ object.__setattr__(self, '_values', {})
472
+ object.__setattr__(self, '_logs', [])
473
+
474
+ def __setattr__(self, name, value):
475
+ if name.startswith('_'):
476
+ object.__setattr__(self, name, value)
477
+ else:
478
+ self._values[name] = value
479
+
480
+ def __getattr__(self, name):
481
+ if name.startswith('_'):
482
+ raise AttributeError(name)
483
+ return self._values.get(name)
484
+
485
+ def log(self, message):
486
+ self._logs.append(str(message))
487
+
488
+ outputs = _OutputsProxy()
489
+
490
+ # --- Execute user script ---
491
+ try:
492
+ exec(open(os.environ['__KIZEN_SCRIPT_PATH__'], encoding='utf-8').read())
493
+ result = {
494
+ 'success': True,
495
+ 'outputValues': outputs._values,
496
+ 'logs': outputs._logs,
497
+ 'error': None,
498
+ }
499
+ except Exception:
500
+ # Build a clean traceback that hides the wrapper internals.
501
+ # Keep only frames from the user script (shown as File "<string>")
502
+ # and re-label them with "script.py" for clarity.
503
+ exc_type, exc_value, exc_tb = sys.exc_info()
504
+ entries = traceback.extract_tb(exc_tb)
505
+ user_entries = [e for e in entries if e.filename != __file__]
506
+ for e in user_entries:
507
+ if e.filename == '<string>':
508
+ e.filename = 'script.py'
509
+ lines = ['Traceback (most recent call last):\\n']
510
+ lines += traceback.format_list(user_entries)
511
+ lines += traceback.format_exception_only(exc_type, exc_value)
512
+ result = {
513
+ 'success': False,
514
+ 'outputValues': outputs._values,
515
+ 'logs': outputs._logs,
516
+ 'error': ''.join(lines),
517
+ }
518
+
519
+ # Write results between markers so we can extract them reliably
520
+ # even if the user script wrote to stdout
521
+ sys.stdout.write('\\n__KIZEN_RESULT_START__\\n')
522
+ sys.stdout.write(json.dumps(result, ensure_ascii=False))
523
+ sys.stdout.write('\\n__KIZEN_RESULT_END__\\n')
524
+ sys.stdout.flush()
525
+ `.trim();
526
+ var DEFAULT_TIMEOUT = 3e4;
527
+ var VENV_DIR = join6(process.cwd(), ".kizenapp", "venv");
528
+ var VENV_PACKAGES = ["requests"];
529
+ var venvReady = null;
530
+ function resolvePythonBinary(runtime) {
531
+ const match = /^python-(\d+\.\d+)$/.exec(runtime);
532
+ if (match?.[1]) {
533
+ return `python${match[1]}`;
534
+ }
535
+ return "python3";
536
+ }
537
+ function ensureVenv(runtime) {
538
+ if (!venvReady) {
539
+ venvReady = (async () => {
540
+ const venvPython = join6(VENV_DIR, "bin", "python");
541
+ try {
542
+ await access(venvPython);
543
+ } catch {
544
+ const systemPython = resolvePythonBinary(runtime);
545
+ execFileSync(systemPython, ["-m", "venv", VENV_DIR], { stdio: "ignore" });
546
+ }
547
+ execFileSync(venvPython, ["-m", "pip", "install", "-q", ...VENV_PACKAGES], {
548
+ stdio: "ignore"
549
+ });
550
+ return venvPython;
551
+ })();
552
+ }
553
+ return venvReady;
554
+ }
555
+ function parseResult(stdout) {
556
+ const startMarker = "__KIZEN_RESULT_START__";
557
+ const endMarker = "__KIZEN_RESULT_END__";
558
+ const startIdx = stdout.indexOf(startMarker);
559
+ const endIdx = stdout.indexOf(endMarker);
560
+ if (startIdx === -1 || endIdx === -1) {
561
+ return { userStdout: stdout, result: null };
562
+ }
563
+ const userStdout = stdout.slice(0, startIdx).trimEnd();
564
+ const jsonStr = stdout.slice(startIdx + startMarker.length, endIdx).trim();
565
+ try {
566
+ const result = JSON.parse(jsonStr);
567
+ return { userStdout, result };
568
+ } catch {
569
+ return { userStdout: stdout, result: null };
570
+ }
571
+ }
572
+ async function executePythonStep(params) {
573
+ const timeout = params.timeout ?? DEFAULT_TIMEOUT;
574
+ const pythonBin = await ensureVenv(params.scriptRuntime);
575
+ const start = Date.now();
576
+ let tmpDir;
577
+ try {
578
+ tmpDir = await mkdtemp(join6(tmpdir(), "kizen-step-"));
579
+ const wrapperPath = join6(tmpDir, "wrapper.py");
580
+ const scriptPath = join6(tmpDir, "script.py");
581
+ await Promise.all([
582
+ writeFile4(wrapperPath, WRAPPER_SCRIPT, "utf-8"),
583
+ writeFile4(scriptPath, params.script, "utf-8")
584
+ ]);
585
+ const env = {
586
+ ...process.env,
587
+ __KIZEN_INPUTS__: JSON.stringify(params.inputs),
588
+ __KIZEN_SECRETS__: JSON.stringify(params.secrets),
589
+ __KIZEN_SCRIPT_PATH__: scriptPath
590
+ };
591
+ const { exitCode, stdout, stderr } = await new Promise((resolve) => {
592
+ const stdoutChunks = [];
593
+ const stderrChunks = [];
594
+ let settled = false;
595
+ const proc = spawn(pythonBin, [wrapperPath], {
596
+ env,
597
+ cwd: tmpDir,
598
+ stdio: ["ignore", "pipe", "pipe"]
599
+ });
600
+ const timer = setTimeout(() => {
601
+ proc.kill("SIGKILL");
602
+ }, timeout);
603
+ proc.stdout.on("data", (chunk) => {
604
+ stdoutChunks.push(chunk);
605
+ });
606
+ proc.stderr.on("data", (chunk) => {
607
+ stderrChunks.push(chunk);
608
+ });
609
+ const finish = (code) => {
610
+ if (settled) {
611
+ return;
612
+ }
613
+ settled = true;
614
+ clearTimeout(timer);
615
+ resolve({
616
+ exitCode: code ?? 1,
617
+ stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
618
+ stderr: Buffer.concat(stderrChunks).toString("utf-8")
619
+ });
620
+ };
621
+ proc.on("close", finish);
622
+ proc.on("error", (err) => {
623
+ if (settled) {
624
+ return;
625
+ }
626
+ settled = true;
627
+ clearTimeout(timer);
628
+ resolve({
629
+ exitCode: 1,
630
+ stdout: "",
631
+ stderr: err.message
632
+ });
633
+ });
634
+ });
635
+ const durationMs = Date.now() - start;
636
+ const { userStdout, result } = parseResult(stdout);
637
+ if (result) {
638
+ const base2 = {
639
+ success: result.success,
640
+ outputValues: result.outputValues,
641
+ logs: result.logs,
642
+ stdout: userStdout,
643
+ stderr,
644
+ exitCode,
645
+ durationMs
646
+ };
647
+ if (result.error !== null) {
648
+ base2.error = result.error;
649
+ }
650
+ return base2;
651
+ }
652
+ const base = {
653
+ success: false,
654
+ outputValues: {},
655
+ logs: [],
656
+ stdout: userStdout,
657
+ stderr,
658
+ exitCode,
659
+ durationMs
660
+ };
661
+ if (exitCode !== 0) {
662
+ base.error = `Process exited with code ${String(exitCode)}`;
663
+ }
664
+ return base;
665
+ } finally {
666
+ if (tmpDir) {
667
+ void rm(tmpDir, { recursive: true, force: true });
668
+ }
669
+ }
670
+ }
671
+
672
+ // src/server/remoteCodeRunner.ts
673
+ var BASE_URLS = {
674
+ go: "https://app.go.kizen.com/api",
675
+ fmo: "https://app.fmo.kizen.com/api",
676
+ staging: "https://staging.kizen.dev/api",
677
+ integration: "https://integration.kizen.dev/api",
678
+ test1: "https://test1.kizen.dev/api"
679
+ };
680
+ var SUPPORTED_RUNTIMES = ["python-3-13", "python-3-12"];
681
+ var DEFAULT_RUNTIME = "python-3-12";
682
+ var DATA_TYPE_TO_KIZEN = {
683
+ text: "s",
684
+ string: "s",
685
+ number: "n",
686
+ integer: "n",
687
+ boolean: "b",
688
+ date: "d",
689
+ datetime: "dt",
690
+ phone: "p",
691
+ phonenumber: "p",
692
+ uuid: "u",
693
+ file: "f",
694
+ list: "l"
695
+ };
696
+ function toKizenType(dataType) {
697
+ return DATA_TYPE_TO_KIZEN[dataType.toLowerCase()] ?? "s";
698
+ }
699
+ function resolveRuntime(runtime) {
700
+ if (SUPPORTED_RUNTIMES.includes(runtime)) {
701
+ return runtime;
702
+ }
703
+ return DEFAULT_RUNTIME;
704
+ }
705
+ function encodeInputs(inputs, inputTypes) {
706
+ const keys = Object.keys(inputs);
707
+ if (keys.length === 0) {
708
+ return null;
709
+ }
710
+ const encoded = {};
711
+ for (const key of keys) {
712
+ encoded[key] = {
713
+ t: toKizenType(inputTypes[key] ?? "string"),
714
+ v: inputs[key]
715
+ };
716
+ }
717
+ return encoded;
718
+ }
719
+ function encodeOutputTypes(outputTypes) {
720
+ const keys = Object.keys(outputTypes);
721
+ if (keys.length === 0) {
722
+ return null;
723
+ }
724
+ const encoded = {};
725
+ for (const key of keys) {
726
+ encoded[key] = toKizenType(outputTypes[key] ?? "string");
727
+ }
728
+ return encoded;
729
+ }
730
+ function decodeOutputValues(values) {
731
+ if (!values) {
732
+ return {};
733
+ }
734
+ const decoded = {};
735
+ for (const [key, encoded] of Object.entries(values)) {
736
+ decoded[key] = encoded.v;
737
+ }
738
+ return decoded;
739
+ }
740
+ async function executeRemoteStep(params, credentials) {
741
+ const baseUrl = BASE_URLS[credentials.environment];
742
+ const url = `${baseUrl}/coderunner/run`;
743
+ const start = Date.now();
744
+ const body = {
745
+ user_script: params.script,
746
+ runtime: resolveRuntime(params.scriptRuntime),
747
+ secrets: params.secretNames.length > 0 ? params.secretNames : void 0,
748
+ inputs: encodeInputs(params.inputs, params.inputTypes),
749
+ output_types: encodeOutputTypes(params.outputTypes)
750
+ };
751
+ try {
752
+ const response = await fetch(url, {
753
+ method: "POST",
754
+ headers: {
755
+ "Content-Type": "application/json",
756
+ "X-API-KEY": credentials.apiKey,
757
+ "X-USER-ID": credentials.userId,
758
+ "X-BUSINESS-ID": credentials.businessId
759
+ },
760
+ body: JSON.stringify(body)
761
+ });
762
+ const durationMs = Date.now() - start;
763
+ if (!response.ok) {
764
+ const text = await response.text();
765
+ return {
766
+ success: false,
767
+ outputValues: {},
768
+ logs: [],
769
+ stdout: "",
770
+ stderr: "",
771
+ error: `Remote runner returned ${String(response.status)}: ${text}`,
772
+ exitCode: 1,
773
+ durationMs
774
+ };
775
+ }
776
+ const data = await response.json();
777
+ const hasError = data.error !== null;
778
+ const result = {
779
+ success: !hasError,
780
+ outputValues: decodeOutputValues(data.values),
781
+ logs: data.logs ?? [],
782
+ stdout: "",
783
+ stderr: "",
784
+ exitCode: hasError ? 1 : 0,
785
+ durationMs: data.duration_ms ?? durationMs
786
+ };
787
+ if (hasError && data.error) {
788
+ result.error = data.error.detail ? `${data.error.error}
789
+ ${data.error.detail}` : data.error.error;
790
+ }
791
+ return result;
792
+ } catch (err) {
793
+ return {
794
+ success: false,
795
+ outputValues: {},
796
+ logs: [],
797
+ stdout: "",
798
+ stderr: "",
799
+ error: `Remote execution failed: ${err instanceof Error ? err.message : String(err)}`,
800
+ exitCode: 1,
801
+ durationMs: Date.now() - start
802
+ };
803
+ }
804
+ }
805
+
806
+ // src/server/requestHandler.ts
807
+ function proxyLogEntryToString(entry) {
808
+ if (entry.kind === "info") {
809
+ return entry.message;
810
+ }
811
+ const cache = entry.fromCache ? " [cache]" : "";
812
+ return `${entry.method} ${entry.url} \u2192 ${String(entry.status)}${cache}`;
813
+ }
814
+ function getViewerPath() {
815
+ const filename = fileURLToPath(import.meta.url);
816
+ return join7(dirname2(filename), "viewer");
817
+ }
818
+ async function fileExists(filePath) {
819
+ try {
820
+ await access2(filePath);
821
+ return true;
822
+ } catch {
823
+ return false;
824
+ }
825
+ }
826
+ function createRequestHandler(viewerPath, createServerLog, createProxyLog, credentialsRef, activeProfileRef, outputDir, broadcast, onProxyCacheChange, lastPathRef) {
827
+ const proxyCache = createProxyCache({ onChange: onProxyCacheChange });
828
+ return (req, res) => {
829
+ void (async () => {
830
+ const url = req.url ?? "/";
831
+ try {
832
+ createServerLog(`Received request: ${url}`);
833
+ if (url === "/api/credentials") {
834
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
835
+ res.end(credentialsRef.current !== null ? JSON.stringify(credentialsRef.current) : "{}");
836
+ return;
837
+ }
838
+ if (url === "/api/credential-profiles" && req.method === "GET") {
839
+ const profiles = await listCredentialProfiles();
840
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
841
+ res.end(JSON.stringify({ profiles, active: activeProfileRef.current }));
842
+ return;
843
+ }
844
+ if (url === "/api/credentials/switch" && req.method === "POST") {
845
+ const chunks = [];
846
+ for await (const chunk of req) {
847
+ chunks.push(chunk);
848
+ }
849
+ const body = JSON.parse(Buffer.concat(chunks).toString("utf-8") || "{}");
850
+ const profileName = body.profile;
851
+ const creds = profileName === void 0 || profileName === "" ? await loadGlobalCredentials() : await loadCredentialProfile(profileName);
852
+ if (creds !== null) {
853
+ credentialsRef.current = creds;
854
+ activeProfileRef.current = profileName ?? void 0;
855
+ const active = activeProfileRef.current;
856
+ await saveConfig(outputDir, {
857
+ credentialMode: "global",
858
+ ...active !== void 0 && { activeCredentialProfile: active }
859
+ });
860
+ broadcast({ type: "credentials-updated", credentials: creds });
861
+ }
862
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
863
+ res.end(JSON.stringify({ ok: creds !== null }));
864
+ return;
865
+ }
866
+ if (url === "/api/bundle") {
867
+ const bundlePath = join7(process.cwd(), ".kizenapp", "bundle.json");
868
+ try {
869
+ const content = await readFile4(bundlePath, "utf-8");
870
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
871
+ res.end(content);
872
+ } catch {
873
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
874
+ res.end("{}");
875
+ }
876
+ return;
877
+ }
878
+ if (url === "/api/last-path" && req.method === "POST") {
879
+ const chunks = [];
880
+ for await (const chunk of req) {
881
+ chunks.push(chunk);
882
+ }
883
+ const body = JSON.parse(Buffer.concat(chunks).toString("utf-8") || "{}");
884
+ if (typeof body.path === "string") {
885
+ lastPathRef.current = body.path;
886
+ const currentConfig = await loadConfig(outputDir);
887
+ await saveConfig(outputDir, { ...currentConfig, lastPath: body.path });
888
+ }
889
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
890
+ res.end('{"ok":true}');
891
+ return;
892
+ }
893
+ if (url === "/api/execute-step" && req.method === "POST") {
894
+ const chunks = [];
895
+ for await (const chunk of req) {
896
+ chunks.push(chunk);
897
+ }
898
+ const body = JSON.parse(Buffer.concat(chunks).toString("utf-8") || "{}");
899
+ if (typeof body.script !== "string") {
900
+ res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
901
+ res.end(JSON.stringify({ error: "Missing required field: script" }));
902
+ return;
903
+ }
904
+ let result;
905
+ if (body.mode === "remote") {
906
+ if (!credentialsRef.current) {
907
+ res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
908
+ res.end(JSON.stringify({ error: "Credentials are required for remote execution" }));
909
+ return;
910
+ }
911
+ result = await executeRemoteStep(
912
+ {
913
+ script: body.script,
914
+ scriptRuntime: body.scriptRuntime ?? "python-3-12",
915
+ inputs: body.inputs ?? {},
916
+ inputTypes: body.inputTypes ?? {},
917
+ outputTypes: body.outputTypes ?? {},
918
+ secretNames: body.secretNames ?? []
919
+ },
920
+ credentialsRef.current
921
+ );
922
+ } else {
923
+ const stepParams = {
924
+ script: body.script,
925
+ scriptRuntime: body.scriptRuntime ?? "python-3.9",
926
+ inputs: body.inputs ?? {},
927
+ secrets: body.secrets ?? {}
928
+ };
929
+ if (typeof body.timeout === "number") {
930
+ stepParams.timeout = body.timeout;
931
+ }
932
+ result = await executePythonStep(stepParams);
933
+ }
934
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
935
+ res.end(JSON.stringify(result));
936
+ return;
937
+ }
938
+ if (url === "/api/proxy-cache/clear") {
939
+ proxyCache.clear();
940
+ createProxyLog({ kind: "info", message: "Proxy cache cleared" });
941
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
942
+ res.end('{"ok":true}');
943
+ return;
944
+ }
945
+ if (url.startsWith("/api/proxy")) {
946
+ const proxyTarget = req.headers["x-proxy-target"];
947
+ if (typeof proxyTarget !== "string") {
948
+ res.writeHead(400);
949
+ res.end("Missing x-proxy-target header");
950
+ return;
951
+ }
952
+ const upstreamPath = url.slice("/api/proxy".length) || "/";
953
+ const upstreamUrl = `${proxyTarget}${upstreamPath}`;
954
+ const method = req.method ?? "GET";
955
+ const chunks = [];
956
+ for await (const chunk of req) {
957
+ chunks.push(chunk);
958
+ }
959
+ const { host, "x-proxy-target": _drop, ...forwardHeaders } = req.headers;
960
+ if (method === "GET") {
961
+ const cacheKey = `GET::${upstreamUrl}`;
962
+ const { response: cached, fromCache } = await proxyCache.get(
963
+ cacheKey,
964
+ () => fetch(upstreamUrl, {
965
+ method: "GET",
966
+ headers: forwardHeaders
967
+ })
968
+ );
969
+ createProxyLog({
970
+ kind: "request",
971
+ method: "GET",
972
+ status: cached.status,
973
+ fromCache,
974
+ url: upstreamPath
975
+ });
976
+ res.writeHead(cached.status, cached.headers);
977
+ res.end(cached.body);
978
+ return;
979
+ }
980
+ const body = chunks.length > 0 ? Buffer.concat(chunks) : void 0;
981
+ const resolvedBody = body && body.length > 0 ? body : void 0;
982
+ const upstream = await fetch(upstreamUrl, {
983
+ method,
984
+ headers: forwardHeaders,
985
+ ...resolvedBody !== void 0 && { body: resolvedBody }
986
+ });
987
+ createProxyLog({
988
+ kind: "request",
989
+ method,
990
+ status: upstream.status,
991
+ fromCache: false,
992
+ url: upstreamPath
993
+ });
994
+ const responseHeaders = Object.fromEntries(upstream.headers);
995
+ delete responseHeaders["content-encoding"];
996
+ delete responseHeaders["content-length"];
997
+ res.writeHead(upstream.status, responseHeaders);
998
+ res.end(Buffer.from(await upstream.arrayBuffer()));
999
+ return;
1000
+ }
1001
+ const rawPath = url === "/" ? "/index.html" : url;
1002
+ const filePath = join7(viewerPath, rawPath);
1003
+ const resolvedPath = await fileExists(filePath) ? filePath : join7(viewerPath, "index.html");
1004
+ const ext = extname(resolvedPath);
1005
+ const mimeType = MIME_TYPES[ext] ?? "application/octet-stream";
1006
+ res.writeHead(200, { "Content-Type": mimeType });
1007
+ createReadStream(resolvedPath).pipe(res);
1008
+ } catch (err) {
1009
+ if (url.startsWith("/api/proxy")) {
1010
+ createProxyLog({
1011
+ kind: "request",
1012
+ method: req.method ?? "GET",
1013
+ status: 502,
1014
+ fromCache: false,
1015
+ url: url.slice("/api/proxy".length) || "/"
1016
+ });
1017
+ } else {
1018
+ createProxyLog({
1019
+ kind: "info",
1020
+ message: `Error handling ${url}: ${err instanceof Error ? err.message : String(err)}`
1021
+ });
1022
+ }
1023
+ if (!res.headersSent) {
1024
+ res.writeHead(502);
1025
+ res.end("Bad Gateway");
1026
+ }
1027
+ }
1028
+ })();
1029
+ };
1030
+ }
1031
+
239
1032
  // src/chrome/launcher.ts
240
1033
  import { launch } from "chrome-launcher";
241
1034
  import { createRequire } from "module";
242
1035
  import { mkdirSync } from "fs";
243
- import { readFile as readFile2 } from "fs/promises";
244
- import { dirname, join as join4 } from "path";
245
- import { fileURLToPath } from "url";
1036
+ import { readFile as readFile5 } from "fs/promises";
1037
+ import { dirname as dirname3, join as join8 } from "path";
1038
+ import { fileURLToPath as fileURLToPath2 } from "url";
246
1039
  var CDP = createRequire(import.meta.url)("chrome-remote-interface");
247
1040
  var CSP_HEADERS = /* @__PURE__ */ new Set([
248
1041
  "content-security-policy",
@@ -320,18 +1113,19 @@ async function serializeArgAsync(client, arg) {
320
1113
  }
321
1114
  async function installDockIcon(client) {
322
1115
  try {
323
- const iconPath = join4(dirname(fileURLToPath(import.meta.url)), "viewer", "icon.png");
324
- const icon = await readFile2(iconPath);
1116
+ const iconPath = join8(dirname3(fileURLToPath2(import.meta.url)), "viewer", "icon.png");
1117
+ const icon = await readFile5(iconPath);
325
1118
  await client.Browser.setDockTile({ image: icon.toString("base64") });
326
1119
  } catch {
327
1120
  }
328
1121
  }
329
1122
  async function launchChromeViewer(opts) {
330
1123
  mkdirSync(opts.userDataDir, { recursive: true });
1124
+ const baseUrl = `http://localhost:${String(opts.port)}`;
331
1125
  const chrome = await launch({
332
- startingUrl: `http://localhost:${String(opts.port)}`,
1126
+ startingUrl: baseUrl,
333
1127
  chromeFlags: [
334
- `--app=http://localhost:${String(opts.port)}`,
1128
+ `--app=${baseUrl}`,
335
1129
  "--no-first-run",
336
1130
  "--no-default-browser-check",
337
1131
  "--disable-extensions"
@@ -347,6 +1141,9 @@ async function launchChromeViewer(opts) {
347
1141
  await new Promise((r) => setTimeout(r, 300));
348
1142
  try {
349
1143
  client = await CDP({ port: cdpPort });
1144
+ if (opts.path) {
1145
+ void client.send("Page.navigate", { url: `${baseUrl}${opts.path}` });
1146
+ }
350
1147
  break;
351
1148
  } catch {
352
1149
  if (attempt === 9) {
@@ -465,188 +1262,63 @@ async function launchChromeViewer(opts) {
465
1262
  c.on("Runtime.consoleAPICalled", (rawParams) => {
466
1263
  void (async () => {
467
1264
  const { type, args } = rawParams;
468
- if (args[0]?.type === "string" && String(args[0].value).includes("Chrome Security")) {
469
- return;
470
- }
471
- const level = type === "warning" ? "warn" : ["log", "warn", "error", "info"].includes(type) ? type : "log";
472
- const serialized = await Promise.all(args.map((arg) => serializeArgAsync(c, arg)));
473
- opts.broadcast({ type: "console-message", level, args: serialized });
474
- })().catch(() => {
475
- });
476
- });
477
- const kill = () => {
478
- clearInterval(cookieTimer);
479
- try {
480
- void c.close();
481
- } catch {
482
- }
483
- chrome.kill();
484
- };
485
- return { kill };
486
- }
487
-
488
- // src/lib/proxyCache.ts
489
- function createProxyCache(options = {}) {
490
- const cache = /* @__PURE__ */ new Map();
491
- const { onChange } = options;
492
- const emitChange = () => {
493
- onChange?.(cache.size);
494
- };
495
- return {
496
- get(key, fetcher) {
497
- const existing = cache.get(key);
498
- if (existing !== void 0) {
499
- return existing.then((response) => ({ response, fromCache: true }));
500
- }
501
- const pending = fetcher().then(async (response) => {
502
- const headers = Object.fromEntries(response.headers);
503
- delete headers["content-encoding"];
504
- delete headers["content-length"];
505
- return {
506
- status: response.status,
507
- headers,
508
- body: Buffer.from(await response.arrayBuffer())
509
- };
510
- }).catch((error) => {
511
- cache.delete(key);
512
- emitChange();
513
- throw error;
514
- });
515
- cache.set(key, pending);
516
- emitChange();
517
- return pending.then((response) => {
518
- if (response.status >= 400) {
519
- cache.delete(key);
520
- emitChange();
521
- }
522
- return { response, fromCache: false };
523
- });
524
- },
525
- clear() {
526
- cache.clear();
527
- emitChange();
528
- }
529
- };
530
- }
531
-
532
- // src/lib/credentials.ts
533
- import { mkdir as mkdir2, readFile as readFile3, readdir as readdir2, writeFile as writeFile2 } from "fs/promises";
534
- import { homedir } from "os";
535
- import { dirname as dirname2, join as join5 } from "path";
536
- var ENVIRONMENTS = ["go", "fmo", "staging", "integration", "test1"];
537
- var GLOBAL_CREDENTIALS_DIR = join5(homedir(), ".kizenappbuilder");
538
- var GLOBAL_CREDENTIALS_PATH = join5(GLOBAL_CREDENTIALS_DIR, "credentials.json");
539
- var DEFAULT_PROFILE_NAME = "credentials";
540
- function isValidEnvironment(value) {
541
- return ENVIRONMENTS.includes(value);
542
- }
543
- function parseCredentials(raw) {
544
- if (typeof raw !== "object" || raw === null) {
545
- throw new Error("Credentials must be a JSON object");
546
- }
547
- const obj = raw;
548
- const env = obj.environment;
549
- return {
550
- apiKey: typeof obj.apiKey === "string" ? obj.apiKey : "",
551
- userId: typeof obj.userId === "string" ? obj.userId : "",
552
- businessId: typeof obj.businessId === "string" ? obj.businessId : "",
553
- environment: isValidEnvironment(env) ? env : "go"
554
- };
555
- }
556
- async function loadCredentialsFromFile(filePath) {
557
- const content = await readFile3(filePath, "utf-8");
558
- return parseCredentials(JSON.parse(content));
559
- }
560
- async function loadGlobalCredentials() {
561
- try {
562
- return await loadCredentialsFromFile(GLOBAL_CREDENTIALS_PATH);
563
- } catch {
564
- return null;
565
- }
566
- }
567
- async function saveGlobalCredentials(credentials) {
568
- await mkdir2(dirname2(GLOBAL_CREDENTIALS_PATH), { recursive: true });
569
- await writeFile2(GLOBAL_CREDENTIALS_PATH, JSON.stringify(credentials, null, 2), "utf-8");
570
- }
571
- function getProfilePath(name) {
572
- return join5(GLOBAL_CREDENTIALS_DIR, `${name}.json`);
573
- }
574
- async function listCredentialProfiles() {
575
- const profiles = [
576
- { name: DEFAULT_PROFILE_NAME, path: GLOBAL_CREDENTIALS_PATH, isDefault: true }
577
- ];
578
- try {
579
- const entries = await readdir2(GLOBAL_CREDENTIALS_DIR);
580
- for (const entry of entries) {
581
- if (!entry.endsWith(".json")) {
582
- continue;
583
- }
584
- const name = entry.slice(0, -5);
585
- if (name === DEFAULT_PROFILE_NAME) {
586
- continue;
1265
+ if (args[0]?.type === "string" && String(args[0].value).includes("Chrome Security")) {
1266
+ return;
587
1267
  }
588
- profiles.push({ name, path: join5(GLOBAL_CREDENTIALS_DIR, entry), isDefault: false });
1268
+ const level = type === "warning" ? "warn" : ["log", "warn", "error", "info"].includes(type) ? type : "log";
1269
+ const serialized = await Promise.all(args.map((arg) => serializeArgAsync(c, arg)));
1270
+ opts.broadcast({ type: "console-message", level, args: serialized });
1271
+ })().catch(() => {
1272
+ });
1273
+ });
1274
+ const kill = () => {
1275
+ clearInterval(cookieTimer);
1276
+ try {
1277
+ void c.close();
1278
+ } catch {
589
1279
  }
590
- } catch {
591
- }
592
- return profiles;
593
- }
594
- async function saveCredentialProfile(name, credentials) {
595
- if (name === DEFAULT_PROFILE_NAME) {
596
- await saveGlobalCredentials(credentials);
597
- return;
598
- }
599
- await mkdir2(GLOBAL_CREDENTIALS_DIR, { recursive: true });
600
- await writeFile2(getProfilePath(name), JSON.stringify(credentials, null, 2), "utf-8");
601
- }
602
- async function loadCredentialProfile(name) {
603
- try {
604
- return await loadCredentialsFromFile(getProfilePath(name));
605
- } catch {
606
- return null;
607
- }
1280
+ chrome.kill();
1281
+ };
1282
+ return { kill };
608
1283
  }
609
1284
 
610
- // src/lib/config.ts
611
- import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
612
- import { join as join6 } from "path";
613
- async function loadConfig(outputDir) {
614
- try {
615
- const content = await readFile4(join6(outputDir, "config.json"), "utf-8");
616
- return JSON.parse(content);
617
- } catch {
618
- return {};
619
- }
620
- }
621
- async function saveConfig(outputDir, config) {
622
- await mkdir3(outputDir, { recursive: true });
623
- await writeFile3(join6(outputDir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
1285
+ // src/hooks/useLogHistory.ts
1286
+ import { useCallback, useState as useState3 } from "react";
1287
+ function useLogHistory(limit) {
1288
+ const [history, setHistory] = useState3([]);
1289
+ const append = useCallback(
1290
+ (message) => {
1291
+ setHistory((h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-limit));
1292
+ },
1293
+ [limit]
1294
+ );
1295
+ return [history, append];
624
1296
  }
625
1297
 
626
1298
  // src/ui/CredentialSetupUI.tsx
627
- import { useCallback, useEffect as useEffect2, useState as useState2 } from "react";
628
- import { Box as Box3, Text as Text3, useInput } from "ink";
629
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1299
+ import { useCallback as useCallback2, useEffect as useEffect3, useState as useState4 } from "react";
1300
+ import { Box as Box4, Text as Text5, useInput } from "ink";
1301
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
630
1302
  var FIELDS = ["apiKey", "userId", "businessId"];
631
1303
  var FIELD_LABELS = {
632
1304
  apiKey: "API Key",
633
1305
  userId: "User ID",
634
1306
  businessId: "Business ID"
635
1307
  };
636
- var Hint = ({ text }) => /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: text });
1308
+ var Hint = ({ text }) => /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: text });
637
1309
  var CredentialSetupUI = ({
638
1310
  initialMode,
639
1311
  showProfileManager,
640
1312
  onComplete,
641
1313
  onCancel
642
1314
  }) => {
643
- const [phase, setPhase] = useState2(
1315
+ const [phase, setPhase] = useState4(
644
1316
  initialMode === "global" && !showProfileManager ? { type: "loading" } : initialMode === "global" && showProfileManager ? { type: "profile-loading" } : { type: "mode-select", cursor: 0 }
645
1317
  );
646
- const [inputBuffer, setInputBuffer] = useState2("");
647
- const [error, setError] = useState2(null);
648
- const [activeProfileName, setActiveProfileName] = useState2(void 0);
649
- const handleModeChosen = useCallback(
1318
+ const [inputBuffer, setInputBuffer] = useState4("");
1319
+ const [error, setError] = useState4(null);
1320
+ const [activeProfileName, setActiveProfileName] = useState4(void 0);
1321
+ const handleModeChosen = useCallback2(
650
1322
  (mode) => {
651
1323
  if (mode === "local") {
652
1324
  onComplete({ mode: "local", credentials: null });
@@ -656,7 +1328,7 @@ var CredentialSetupUI = ({
656
1328
  },
657
1329
  [onComplete]
658
1330
  );
659
- const handleProfileChosen = useCallback(async (profile) => {
1331
+ const handleProfileChosen = useCallback2(async (profile) => {
660
1332
  if (profile === null) {
661
1333
  setPhase({ type: "profile-loading" });
662
1334
  const profiles = await listCredentialProfiles();
@@ -669,7 +1341,7 @@ var CredentialSetupUI = ({
669
1341
  const envCursor2 = existing ? Math.max(0, ENVIRONMENTS.indexOf(existing.environment)) : 0;
670
1342
  setPhase({ type: "creds-entry", field: 0, values: existing ?? {}, envCursor: envCursor2 });
671
1343
  }, []);
672
- const handleSave = useCallback(
1344
+ const handleSave = useCallback2(
673
1345
  async (values2, envCursor2) => {
674
1346
  const environment = ENVIRONMENTS[envCursor2] ?? "go";
675
1347
  const credentials = {
@@ -810,16 +1482,16 @@ var CredentialSetupUI = ({
810
1482
  }
811
1483
  }
812
1484
  });
813
- const loadProfileList = useCallback(async () => {
1485
+ const loadProfileList = useCallback2(async () => {
814
1486
  const profiles = await listCredentialProfiles();
815
1487
  setPhase({ type: "profile-select", profiles, cursor: 0 });
816
1488
  }, []);
817
- useEffect2(() => {
1489
+ useEffect3(() => {
818
1490
  if (phase.type === "profile-loading") {
819
1491
  void loadProfileList();
820
1492
  }
821
1493
  }, [phase.type, loadProfileList]);
822
- useEffect2(() => {
1494
+ useEffect3(() => {
823
1495
  if (initialMode === "global" && !showProfileManager) {
824
1496
  void (async () => {
825
1497
  const existing = await loadGlobalCredentials();
@@ -828,14 +1500,14 @@ var CredentialSetupUI = ({
828
1500
  })();
829
1501
  }
830
1502
  }, []);
831
- useEffect2(() => {
1503
+ useEffect3(() => {
832
1504
  if (phase.type === "creds-entry" && phase.field === 0) {
833
1505
  setInputBuffer(phase.values.apiKey ?? "");
834
1506
  }
835
1507
  }, [phase.type]);
836
1508
  if (phase.type === "loading" || phase.type === "saving" || phase.type === "profile-loading") {
837
1509
  const label = phase.type === "saving" ? "Saving credentials\u2026" : phase.type === "profile-loading" ? "Loading profiles\u2026" : "Loading credentials\u2026";
838
- return /* @__PURE__ */ jsx3(Box3, { paddingY: 1, paddingX: 2, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: label }) });
1510
+ return /* @__PURE__ */ jsx5(Box4, { paddingY: 1, paddingX: 2, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: label }) });
839
1511
  }
840
1512
  if (phase.type === "done") {
841
1513
  return null;
@@ -845,49 +1517,41 @@ var CredentialSetupUI = ({
845
1517
  { label: "Global", desc: `~/.kizenappbuilder/credentials.json`, mode: "global" },
846
1518
  { label: "Local", desc: "Enter credentials in the browser dev tools", mode: "local" }
847
1519
  ];
848
- return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
849
- /* @__PURE__ */ jsx3(Logo, {}),
850
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, children: [
851
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "Kizen App Builder" }),
852
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2500".repeat(24) })
853
- ] }),
854
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", gap: 0, children: [
855
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Credential mode" }),
1520
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
1521
+ /* @__PURE__ */ jsx5(AppHeader, {}),
1522
+ /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
1523
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Credential mode" }),
856
1524
  options.map((opt, i) => {
857
1525
  const selected = phase.cursor === i;
858
- return /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
859
- /* @__PURE__ */ jsx3(Text3, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
860
- /* @__PURE__ */ jsx3(Text3, { bold: selected, ...selected && { color: "cyan" }, children: opt.label }),
861
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: opt.desc })
1526
+ return /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1527
+ /* @__PURE__ */ jsx5(Text5, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
1528
+ /* @__PURE__ */ jsx5(Text5, { bold: selected, ...selected && { color: "cyan" }, children: opt.label }),
1529
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: opt.desc })
862
1530
  ] }, opt.mode);
863
1531
  })
864
1532
  ] }),
865
- /* @__PURE__ */ jsx3(Hint, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Ctrl+C to quit" })
1533
+ /* @__PURE__ */ jsx5(Hint, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Ctrl+C to quit" })
866
1534
  ] });
867
1535
  }
868
1536
  if (phase.type === "profile-select") {
869
1537
  const { profiles, cursor } = phase;
870
1538
  const addNewIdx = profiles.length;
871
- return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
872
- /* @__PURE__ */ jsx3(Logo, {}),
873
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, children: [
874
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "Kizen App Builder" }),
875
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2500".repeat(24) })
876
- ] }),
877
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", gap: 0, children: [
878
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Credential profiles" }),
1539
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
1540
+ /* @__PURE__ */ jsx5(AppHeader, {}),
1541
+ /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
1542
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Credential profiles" }),
879
1543
  profiles.map((profile, i) => {
880
1544
  const selected = cursor === i;
881
- return /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
882
- /* @__PURE__ */ jsx3(Text3, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
883
- /* @__PURE__ */ jsx3(Text3, { bold: selected, ...selected && { color: "cyan" }, children: profile.isDefault ? "Default" : profile.name }),
884
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: profile.path })
1545
+ return /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1546
+ /* @__PURE__ */ jsx5(Text5, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
1547
+ /* @__PURE__ */ jsx5(Text5, { bold: selected, ...selected && { color: "cyan" }, children: profile.isDefault ? "Default" : profile.name }),
1548
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: profile.path })
885
1549
  ] }, profile.name);
886
1550
  }),
887
- /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
888
- /* @__PURE__ */ jsx3(Text3, { ...cursor === addNewIdx && { color: "cyan" }, children: cursor === addNewIdx ? "\u276F" : " " }),
889
- /* @__PURE__ */ jsx3(
890
- Text3,
1551
+ /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1552
+ /* @__PURE__ */ jsx5(Text5, { ...cursor === addNewIdx && { color: "cyan" }, children: cursor === addNewIdx ? "\u276F" : " " }),
1553
+ /* @__PURE__ */ jsx5(
1554
+ Text5,
891
1555
  {
892
1556
  bold: cursor === addNewIdx,
893
1557
  ...cursor === addNewIdx && { color: "cyan" },
@@ -896,77 +1560,69 @@ var CredentialSetupUI = ({
896
1560
  )
897
1561
  ] })
898
1562
  ] }),
899
- /* @__PURE__ */ jsx3(Hint, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Esc to cancel" })
1563
+ /* @__PURE__ */ jsx5(Hint, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Esc to cancel" })
900
1564
  ] });
901
1565
  }
902
1566
  if (phase.type === "name-entry") {
903
1567
  const { nameBuffer, nameError } = phase;
904
- return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
905
- /* @__PURE__ */ jsx3(Logo, {}),
906
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, children: [
907
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "Kizen App Builder" }),
908
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2500".repeat(24) })
909
- ] }),
910
- /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", gap: 0, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: "New credential profile" }) }),
911
- nameError && /* @__PURE__ */ jsxs2(Text3, { color: "red", children: [
1568
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
1569
+ /* @__PURE__ */ jsx5(AppHeader, {}),
1570
+ /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", gap: 0, children: /* @__PURE__ */ jsx5(Text5, { bold: true, children: "New credential profile" }) }),
1571
+ nameError && /* @__PURE__ */ jsxs3(Text5, { color: "red", children: [
912
1572
  "Error: ",
913
1573
  nameError
914
1574
  ] }),
915
- /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
916
- /* @__PURE__ */ jsx3(Box3, { width: 12, children: /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "Profile name" }) }),
917
- /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: ">" }),
918
- /* @__PURE__ */ jsx3(Text3, { children: nameBuffer }),
919
- /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: "\u2588" })
1575
+ /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1576
+ /* @__PURE__ */ jsx5(Box4, { width: 12, children: /* @__PURE__ */ jsx5(Text5, { bold: true, color: "cyan", children: "Profile name" }) }),
1577
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: ">" }),
1578
+ /* @__PURE__ */ jsx5(Text5, { children: nameBuffer }),
1579
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u2588" })
920
1580
  ] }),
921
- /* @__PURE__ */ jsx3(Hint, { text: "Type a name \xB7 Enter to confirm \xB7 Esc to go back" })
1581
+ /* @__PURE__ */ jsx5(Hint, { text: "Type a name \xB7 Enter to confirm \xB7 Esc to go back" })
922
1582
  ] });
923
1583
  }
924
1584
  const { field: activeField, values, envCursor } = phase;
925
1585
  const isEnvField = activeField === FIELDS.length;
926
1586
  const profileLabel = activeProfileName ?? DEFAULT_PROFILE_NAME;
927
- return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
928
- /* @__PURE__ */ jsx3(Logo, {}),
929
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, children: [
930
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "Kizen App Builder" }),
931
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2500".repeat(24) })
932
- ] }),
933
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", gap: 0, children: [
934
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: activeProfileName !== void 0 ? `Profile: ${activeProfileName}` : "Global credentials" }),
935
- /* @__PURE__ */ jsxs2(Text3, { dimColor: true, children: [
1587
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
1588
+ /* @__PURE__ */ jsx5(AppHeader, {}),
1589
+ /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
1590
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: activeProfileName !== void 0 ? `Profile: ${activeProfileName}` : "Global credentials" }),
1591
+ /* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
936
1592
  "Saved to:",
937
1593
  " ",
938
1594
  activeProfileName !== void 0 ? `~/.kizenappbuilder/${activeProfileName}.json` : GLOBAL_CREDENTIALS_PATH
939
1595
  ] })
940
1596
  ] }),
941
- error && /* @__PURE__ */ jsxs2(Text3, { color: "red", children: [
1597
+ error && /* @__PURE__ */ jsxs3(Text5, { color: "red", children: [
942
1598
  "Error: ",
943
1599
  error
944
1600
  ] }),
945
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", gap: 0, children: [
1601
+ /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
946
1602
  FIELDS.map((name, i) => {
947
1603
  const isActive = activeField === i;
948
1604
  const displayValue = isActive ? inputBuffer : values[name] ?? "";
949
- return /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
950
- /* @__PURE__ */ jsx3(Box3, { width: 12, children: /* @__PURE__ */ jsx3(Text3, { bold: isActive, ...isActive && { color: "cyan" }, children: FIELD_LABELS[name] }) }),
951
- /* @__PURE__ */ jsx3(Text3, { ...isActive && { color: "cyan" }, children: ">" }),
952
- /* @__PURE__ */ jsx3(Text3, { children: displayValue }),
953
- isActive && /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: "\u2588" })
1605
+ return /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1606
+ /* @__PURE__ */ jsx5(Box4, { width: 12, children: /* @__PURE__ */ jsx5(Text5, { bold: isActive, ...isActive && { color: "cyan" }, children: FIELD_LABELS[name] }) }),
1607
+ /* @__PURE__ */ jsx5(Text5, { ...isActive && { color: "cyan" }, children: ">" }),
1608
+ /* @__PURE__ */ jsx5(Text5, { children: displayValue }),
1609
+ isActive && /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u2588" })
954
1610
  ] }, name);
955
1611
  }),
956
- /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
957
- /* @__PURE__ */ jsx3(Box3, { width: 12, children: /* @__PURE__ */ jsx3(Text3, { bold: isEnvField, ...isEnvField && { color: "cyan" }, children: "Environment" }) }),
958
- /* @__PURE__ */ jsx3(Text3, { ...isEnvField && { color: "cyan" }, children: ">" }),
959
- /* @__PURE__ */ jsx3(Box3, { gap: 1, children: ENVIRONMENTS.map((env, i) => {
1612
+ /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1613
+ /* @__PURE__ */ jsx5(Box4, { width: 12, children: /* @__PURE__ */ jsx5(Text5, { bold: isEnvField, ...isEnvField && { color: "cyan" }, children: "Environment" }) }),
1614
+ /* @__PURE__ */ jsx5(Text5, { ...isEnvField && { color: "cyan" }, children: ">" }),
1615
+ /* @__PURE__ */ jsx5(Box4, { gap: 1, children: ENVIRONMENTS.map((env, i) => {
960
1616
  const isSelected = i === envCursor;
961
1617
  if (isEnvField) {
962
- return /* @__PURE__ */ jsx3(Text3, { bold: isSelected, ...isSelected && { color: "cyan" }, children: isSelected ? `[${env}]` : env }, env);
1618
+ return /* @__PURE__ */ jsx5(Text5, { bold: isSelected, ...isSelected && { color: "cyan" }, children: isSelected ? `[${env}]` : env }, env);
963
1619
  }
964
- return /* @__PURE__ */ jsx3(Text3, { dimColor: !isSelected, bold: isSelected, children: env }, env);
1620
+ return /* @__PURE__ */ jsx5(Text5, { dimColor: !isSelected, bold: isSelected, children: env }, env);
965
1621
  }) })
966
1622
  ] })
967
1623
  ] }),
968
- isEnvField ? /* @__PURE__ */ jsx3(Hint, { text: "\u2190\u2192 to select \xB7 \u2191\u2193 to move \xB7 Enter to save \xB7 Esc to cancel" }) : /* @__PURE__ */ jsx3(Hint, { text: "\u2191\u2193 to move \xB7 Backspace to delete \xB7 Esc to cancel" }),
969
- /* @__PURE__ */ jsxs2(Text3, { dimColor: true, children: [
1624
+ isEnvField ? /* @__PURE__ */ jsx5(Hint, { text: "\u2190\u2192 to select \xB7 \u2191\u2193 to move \xB7 Enter to save \xB7 Esc to cancel" }) : /* @__PURE__ */ jsx5(Hint, { text: "\u2191\u2193 to move \xB7 Backspace to delete \xB7 Esc to cancel" }),
1625
+ /* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
970
1626
  "Profile: ",
971
1627
  profileLabel
972
1628
  ] })
@@ -974,235 +1630,44 @@ var CredentialSetupUI = ({
974
1630
  };
975
1631
 
976
1632
  // src/ui/DevUI.tsx
977
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
978
- function proxyLogEntryToString(entry) {
979
- if (entry.kind === "info") {
980
- return entry.message;
981
- }
982
- const cache = entry.fromCache ? " [cache]" : "";
983
- return `${entry.method} ${entry.url} \u2192 ${String(entry.status)}${cache}`;
984
- }
1633
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
985
1634
  var SKIP_WATCH_PREFIXES = [".kizenapp", ".git"];
986
- var LOG_LIMIT = 50;
987
- var LOG_DISPLAY = 8;
988
- var MIME_TYPES = {
989
- ".html": "text/html; charset=utf-8",
990
- ".js": "application/javascript; charset=utf-8",
991
- ".mjs": "application/javascript; charset=utf-8",
992
- ".css": "text/css; charset=utf-8",
993
- ".png": "image/png",
994
- ".jpg": "image/jpeg",
995
- ".svg": "image/svg+xml",
996
- ".ico": "image/x-icon",
997
- ".json": "application/json; charset=utf-8",
998
- ".woff": "font/woff",
999
- ".woff2": "font/woff2",
1000
- ".ttf": "font/ttf",
1001
- ".map": "application/json"
1002
- };
1003
- var SPINNER_FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1004
- var Spinner2 = () => {
1005
- const [frame, setFrame] = useState3(0);
1006
- useEffect3(() => {
1007
- const id = setInterval(() => {
1008
- setFrame((prev) => (prev + 1) % SPINNER_FRAMES2.length);
1009
- }, 80);
1010
- return () => {
1011
- clearInterval(id);
1012
- };
1013
- }, []);
1014
- return /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: SPINNER_FRAMES2[frame] ?? "\u280B" });
1015
- };
1016
- function getViewerPath() {
1017
- const filename = fileURLToPath2(import.meta.url);
1018
- return join7(dirname3(filename), "viewer");
1019
- }
1020
- async function fileExists(filePath) {
1021
- try {
1022
- await access(filePath);
1023
- return true;
1024
- } catch {
1025
- return false;
1026
- }
1027
- }
1028
- function createRequestHandler(viewerPath, createServerLog, createProxyLog, credentialsRef, activeProfileRef, outputDir, broadcast, onProxyCacheChange) {
1029
- const proxyCache = createProxyCache({ onChange: onProxyCacheChange });
1030
- return (req, res) => {
1031
- void (async () => {
1032
- const url = req.url ?? "/";
1033
- try {
1034
- createServerLog(`Received request: ${url}`);
1035
- if (url === "/api/credentials") {
1036
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1037
- res.end(credentialsRef.current !== null ? JSON.stringify(credentialsRef.current) : "{}");
1038
- return;
1039
- }
1040
- if (url === "/api/credential-profiles" && req.method === "GET") {
1041
- const profiles = await listCredentialProfiles();
1042
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1043
- res.end(JSON.stringify({ profiles, active: activeProfileRef.current }));
1044
- return;
1045
- }
1046
- if (url === "/api/credentials/switch" && req.method === "POST") {
1047
- const chunks = [];
1048
- for await (const chunk of req) {
1049
- chunks.push(chunk);
1050
- }
1051
- const body = JSON.parse(Buffer.concat(chunks).toString("utf-8") || "{}");
1052
- const profileName = body.profile;
1053
- const creds = profileName === void 0 || profileName === "" ? await loadGlobalCredentials() : await loadCredentialProfile(profileName);
1054
- if (creds !== null) {
1055
- credentialsRef.current = creds;
1056
- activeProfileRef.current = profileName ?? void 0;
1057
- const active = activeProfileRef.current;
1058
- await saveConfig(outputDir, {
1059
- credentialMode: "global",
1060
- ...active !== void 0 && { activeCredentialProfile: active }
1061
- });
1062
- broadcast({ type: "credentials-updated", credentials: creds });
1063
- }
1064
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1065
- res.end(JSON.stringify({ ok: creds !== null }));
1066
- return;
1067
- }
1068
- if (url === "/api/bundle") {
1069
- const bundlePath = join7(process.cwd(), ".kizenapp", "bundle.json");
1070
- try {
1071
- const content = await readFile5(bundlePath, "utf-8");
1072
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1073
- res.end(content);
1074
- } catch {
1075
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1076
- res.end("{}");
1077
- }
1078
- return;
1079
- }
1080
- if (url === "/api/proxy-cache/clear") {
1081
- proxyCache.clear();
1082
- createProxyLog({ kind: "info", message: "Proxy cache cleared" });
1083
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1084
- res.end('{"ok":true}');
1085
- return;
1086
- }
1087
- if (url.startsWith("/api/proxy")) {
1088
- const proxyTarget = req.headers["x-proxy-target"];
1089
- if (typeof proxyTarget !== "string") {
1090
- res.writeHead(400);
1091
- res.end("Missing x-proxy-target header");
1092
- return;
1093
- }
1094
- const upstreamPath = url.slice("/api/proxy".length) || "/";
1095
- const upstreamUrl = `${proxyTarget}${upstreamPath}`;
1096
- const method = req.method ?? "GET";
1097
- const chunks = [];
1098
- for await (const chunk of req) {
1099
- chunks.push(chunk);
1100
- }
1101
- const { host, "x-proxy-target": _drop, ...forwardHeaders } = req.headers;
1102
- if (method === "GET") {
1103
- const cacheKey = `GET::${upstreamUrl}`;
1104
- const { response: cached, fromCache } = await proxyCache.get(
1105
- cacheKey,
1106
- () => fetch(upstreamUrl, {
1107
- method: "GET",
1108
- headers: forwardHeaders
1109
- })
1110
- );
1111
- createProxyLog({
1112
- kind: "request",
1113
- method: "GET",
1114
- status: cached.status,
1115
- fromCache,
1116
- url: upstreamPath
1117
- });
1118
- res.writeHead(cached.status, cached.headers);
1119
- res.end(cached.body);
1120
- return;
1121
- }
1122
- const body = chunks.length > 0 ? Buffer.concat(chunks) : void 0;
1123
- const resolvedBody = body && body.length > 0 ? body : void 0;
1124
- const upstream = await fetch(upstreamUrl, {
1125
- method,
1126
- headers: forwardHeaders,
1127
- ...resolvedBody !== void 0 && { body: resolvedBody }
1128
- });
1129
- createProxyLog({
1130
- kind: "request",
1131
- method,
1132
- status: upstream.status,
1133
- fromCache: false,
1134
- url: upstreamPath
1135
- });
1136
- const responseHeaders = Object.fromEntries(upstream.headers);
1137
- delete responseHeaders["content-encoding"];
1138
- delete responseHeaders["content-length"];
1139
- res.writeHead(upstream.status, responseHeaders);
1140
- res.end(Buffer.from(await upstream.arrayBuffer()));
1141
- return;
1142
- }
1143
- const rawPath = url === "/" ? "/index.html" : url;
1144
- const filePath = join7(viewerPath, rawPath);
1145
- const resolvedPath = await fileExists(filePath) ? filePath : join7(viewerPath, "index.html");
1146
- const ext = extname(resolvedPath);
1147
- const mimeType = MIME_TYPES[ext] ?? "application/octet-stream";
1148
- res.writeHead(200, { "Content-Type": mimeType });
1149
- createReadStream(resolvedPath).pipe(res);
1150
- } catch (err) {
1151
- if (url.startsWith("/api/proxy")) {
1152
- createProxyLog({
1153
- kind: "request",
1154
- method: req.method ?? "GET",
1155
- status: 502,
1156
- fromCache: false,
1157
- url: url.slice("/api/proxy".length) || "/"
1158
- });
1159
- } else {
1160
- createProxyLog({
1161
- kind: "info",
1162
- message: `Error handling ${url}: ${err instanceof Error ? err.message : String(err)}`
1163
- });
1164
- }
1165
- if (!res.headersSent) {
1166
- res.writeHead(502);
1167
- res.end("Bad Gateway");
1168
- }
1169
- }
1170
- })();
1171
- };
1172
- }
1173
1635
  var DevUI = ({
1174
1636
  port,
1175
1637
  pluginDir,
1176
1638
  outputDir,
1177
1639
  credentials: initialCredentials,
1178
1640
  credentialMode: initialCredentialMode,
1179
- activeCredentialProfile: initialActiveProfile
1641
+ activeCredentialProfile: initialActiveProfile,
1642
+ lastPath: initialLastPath
1180
1643
  }) => {
1181
1644
  const credentialsRef = useRef(initialCredentials);
1182
1645
  const activeProfileRef = useRef(initialActiveProfile);
1183
- const [credMode, setCredMode] = useState3("main");
1184
- const [credentialMode, setCredentialMode] = useState3(
1646
+ const lastPathRef = useRef(initialLastPath);
1647
+ const [credMode, setCredMode] = useState5("main");
1648
+ const [credentialMode, setCredentialMode] = useState5(
1185
1649
  initialCredentialMode
1186
1650
  );
1187
- const [status, setStatus] = useState3("starting");
1188
- const [errorMessage, setErrorMessage] = useState3(null);
1189
- const [serverLogHistory, setServerLogHistory] = useState3([]);
1190
- const [buildLogHistory, setBuildLogHistory] = useState3([]);
1191
- const [proxyLogHistory, setProxyLogHistory] = useState3([]);
1192
- const [buildStatus, setBuildStatus] = useState3("pending");
1193
- const [buildError, setBuildError] = useState3(null);
1194
- const [lastBuilt, setLastBuilt] = useState3(null);
1195
- const [wsClientCount, setWsClientCount] = useState3(0);
1196
- const [proxyCacheSize, setProxyCacheSize] = useState3(0);
1197
- const [proxyCacheHits, setProxyCacheHits] = useState3(0);
1198
- const [proxyCacheMisses, setProxyCacheMisses] = useState3(0);
1651
+ const [status, setStatus] = useState5("starting");
1652
+ const [errorMessage, setErrorMessage] = useState5(null);
1653
+ const [serverLogHistory, appendServerLog] = useLogHistory(LOG_LIMIT);
1654
+ const [buildLogHistory, appendBuildLog] = useLogHistory(LOG_LIMIT);
1655
+ const [proxyLogHistory, setProxyLogHistory] = useState5([]);
1656
+ const [buildStatus, setBuildStatus] = useState5("pending");
1657
+ const [buildError, setBuildError] = useState5(null);
1658
+ const [lastBuilt, setLastBuilt] = useState5(null);
1659
+ const [lastBundleSize, setLastBundleSize] = useState5(null);
1660
+ const [wsClientCount, setWsClientCount] = useState5(0);
1661
+ const [proxyCacheSize, setProxyCacheSize] = useState5(0);
1662
+ const [proxyCacheHits, setProxyCacheHits] = useState5(0);
1663
+ const [proxyCacheMisses, setProxyCacheMisses] = useState5(0);
1199
1664
  const buildingRef = useRef(false);
1200
1665
  const viewerLaunchedRef = useRef(false);
1201
1666
  const chromeHandleRef = useRef(null);
1202
1667
  const debounceTimerRef = useRef(null);
1203
1668
  const wsClientsRef = useRef(/* @__PURE__ */ new Set());
1204
1669
  const pendingMessagesRef = useRef([]);
1205
- const broadcast = useCallback2((msg) => {
1670
+ const broadcast = useCallback3((msg) => {
1206
1671
  const json = JSON.stringify(msg);
1207
1672
  if (wsClientsRef.current.size === 0) {
1208
1673
  pendingMessagesRef.current = [...pendingMessagesRef.current, json].slice(-LOG_LIMIT);
@@ -1214,18 +1679,15 @@ var DevUI = ({
1214
1679
  }
1215
1680
  }
1216
1681
  }, []);
1217
- const createServerLog = useCallback2((message) => {
1218
- setServerLogHistory(
1219
- (h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-LOG_LIMIT)
1220
- );
1221
- }, []);
1222
- const launchChromeViewerCb = useCallback2(() => {
1682
+ const createServerLog = appendServerLog;
1683
+ const launchChromeViewerCb = useCallback3(() => {
1223
1684
  chromeHandleRef.current?.kill();
1224
1685
  chromeHandleRef.current = null;
1225
1686
  void launchChromeViewer({
1226
1687
  port,
1227
- userDataDir: join7(outputDir, ".chrome"),
1228
- broadcast
1688
+ userDataDir: join9(outputDir, ".chrome"),
1689
+ broadcast,
1690
+ ...lastPathRef.current !== void 0 && { path: lastPathRef.current }
1229
1691
  }).then((handle) => {
1230
1692
  chromeHandleRef.current = handle;
1231
1693
  process.on("exit", () => {
@@ -1247,14 +1709,14 @@ var DevUI = ({
1247
1709
  },
1248
1710
  { isActive: credMode === "main" }
1249
1711
  );
1250
- useEffect3(() => {
1712
+ useEffect4(() => {
1251
1713
  if (status !== "running" || viewerLaunchedRef.current) {
1252
1714
  return;
1253
1715
  }
1254
1716
  viewerLaunchedRef.current = true;
1255
1717
  launchChromeViewerCb();
1256
1718
  }, [status, launchChromeViewerCb]);
1257
- const handleCredentialsDone = useCallback2(
1719
+ const handleCredentialsDone = useCallback3(
1258
1720
  (result) => {
1259
1721
  credentialsRef.current = result.credentials;
1260
1722
  activeProfileRef.current = result.profileName;
@@ -1269,7 +1731,7 @@ var DevUI = ({
1269
1731
  },
1270
1732
  [outputDir, broadcast]
1271
1733
  );
1272
- const createProxyLog = useCallback2(
1734
+ const createProxyLog = useCallback3(
1273
1735
  (entry) => {
1274
1736
  setProxyLogHistory(
1275
1737
  (h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${proxyLogEntryToString(entry)}`].slice(
@@ -1287,16 +1749,14 @@ var DevUI = ({
1287
1749
  },
1288
1750
  [broadcast]
1289
1751
  );
1290
- const createBuildLog = useCallback2(
1752
+ const createBuildLog = useCallback3(
1291
1753
  (message) => {
1292
- setBuildLogHistory(
1293
- (h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-LOG_LIMIT)
1294
- );
1754
+ appendBuildLog(message);
1295
1755
  broadcast({ type: "log", message });
1296
1756
  },
1297
- [broadcast]
1757
+ [appendBuildLog, broadcast]
1298
1758
  );
1299
- const triggerBuild = useCallback2(() => {
1759
+ const triggerBuild = useCallback3(() => {
1300
1760
  if (buildingRef.current) {
1301
1761
  return;
1302
1762
  }
@@ -1304,10 +1764,11 @@ var DevUI = ({
1304
1764
  setBuildStatus("building");
1305
1765
  setBuildError(null);
1306
1766
  createBuildLog("Build started");
1307
- void runBuild(pluginDir, outputDir).then(() => {
1308
- createBuildLog("Build finished");
1767
+ void runBuild(pluginDir, outputDir).then(({ bundleSize }) => {
1768
+ createBuildLog(`Build finished \u2014 bundle: ${formatBytes(bundleSize)}`);
1309
1769
  setBuildStatus("done");
1310
1770
  setLastBuilt(/* @__PURE__ */ new Date());
1771
+ setLastBundleSize(bundleSize);
1311
1772
  createBuildLog("Notifying viewers to reload");
1312
1773
  for (const client of wsClientsRef.current) {
1313
1774
  if (client.readyState === 1) {
@@ -1322,7 +1783,7 @@ var DevUI = ({
1322
1783
  buildingRef.current = false;
1323
1784
  });
1324
1785
  }, [pluginDir, outputDir, createBuildLog]);
1325
- useEffect3(() => {
1786
+ useEffect4(() => {
1326
1787
  triggerBuild();
1327
1788
  const watcher = watch(pluginDir, { recursive: true }, (_, filename) => {
1328
1789
  if (!filename) {
@@ -1336,7 +1797,7 @@ var DevUI = ({
1336
1797
  if (debounceTimerRef.current !== null) {
1337
1798
  clearTimeout(debounceTimerRef.current);
1338
1799
  }
1339
- debounceTimerRef.current = setTimeout(triggerBuild, 150);
1800
+ debounceTimerRef.current = setTimeout(triggerBuild, FILE_WATCH_DEBOUNCE_MS);
1340
1801
  });
1341
1802
  return () => {
1342
1803
  watcher.close();
@@ -1345,9 +1806,9 @@ var DevUI = ({
1345
1806
  }
1346
1807
  };
1347
1808
  }, [pluginDir, triggerBuild, createBuildLog]);
1348
- useEffect3(() => {
1809
+ useEffect4(() => {
1349
1810
  const viewerPath = getViewerPath();
1350
- void fileExists(join7(viewerPath, "index.html")).then((viewerBuilt) => {
1811
+ void fileExists(join9(viewerPath, "index.html")).then((viewerBuilt) => {
1351
1812
  if (!viewerBuilt) {
1352
1813
  setErrorMessage("Viewer not built. Run 'pnpm build:viewer' first.");
1353
1814
  setStatus("error");
@@ -1361,7 +1822,8 @@ var DevUI = ({
1361
1822
  activeProfileRef,
1362
1823
  outputDir,
1363
1824
  broadcast,
1364
- setProxyCacheSize
1825
+ setProxyCacheSize,
1826
+ lastPathRef
1365
1827
  );
1366
1828
  const server = createServer(handler);
1367
1829
  const wss = new WebSocketServer({ server });
@@ -1395,7 +1857,7 @@ var DevUI = ({
1395
1857
  });
1396
1858
  }, [port, createServerLog, createProxyLog, broadcast, outputDir]);
1397
1859
  if (credMode === "editing") {
1398
- return /* @__PURE__ */ jsx4(
1860
+ return /* @__PURE__ */ jsx6(
1399
1861
  CredentialSetupUI,
1400
1862
  {
1401
1863
  ...credentialMode !== void 0 && { initialMode: credentialMode },
@@ -1409,80 +1871,80 @@ var DevUI = ({
1409
1871
  }
1410
1872
  const elapsedSeconds = lastBuilt !== null ? Math.round((Date.now() - lastBuilt.getTime()) / 1e3) : null;
1411
1873
  const elapsedLabel = elapsedSeconds === null ? "" : elapsedSeconds < 5 ? " (just now)" : ` (${String(elapsedSeconds)}s ago)`;
1412
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
1413
- /* @__PURE__ */ jsx4(Logo, {}),
1414
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [
1415
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "Kizen App Builder" }),
1416
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2500".repeat(24) })
1417
- ] }),
1418
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
1419
- /* @__PURE__ */ jsxs3(Box4, { gap: 2, marginBottom: 0, children: [
1420
- /* @__PURE__ */ jsx4(Text4, { bold: true, dimColor: true, children: "App Build" }),
1421
- buildStatus === "pending" && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "waiting\u2026" }),
1422
- buildStatus === "building" && /* @__PURE__ */ jsxs3(Box4, { gap: 1, children: [
1423
- /* @__PURE__ */ jsx4(Spinner2, {}),
1424
- /* @__PURE__ */ jsx4(Text4, { children: "Building plugin package\u2026" })
1874
+ return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
1875
+ /* @__PURE__ */ jsx6(AppHeader, { marginBottom: 1 }),
1876
+ /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
1877
+ /* @__PURE__ */ jsxs4(Box5, { gap: 2, marginBottom: 0, children: [
1878
+ /* @__PURE__ */ jsx6(Text6, { bold: true, dimColor: true, children: "App Build" }),
1879
+ buildStatus === "pending" && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "waiting\u2026" }),
1880
+ buildStatus === "building" && /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
1881
+ /* @__PURE__ */ jsx6(Spinner, {}),
1882
+ /* @__PURE__ */ jsx6(Text6, { children: "Building plugin package\u2026" })
1425
1883
  ] }),
1426
- buildStatus === "done" && /* @__PURE__ */ jsxs3(Box4, { gap: 1, children: [
1427
- /* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u2713 Built" }),
1428
- lastBuilt !== null && /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
1884
+ buildStatus === "done" && /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
1885
+ /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713 Built" }),
1886
+ lastBuilt !== null && /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
1429
1887
  "at ",
1430
1888
  lastBuilt.toLocaleTimeString(),
1431
1889
  elapsedLabel
1890
+ ] }),
1891
+ lastBundleSize !== null && /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
1892
+ "\xB7 bundle: ",
1893
+ formatBytes(lastBundleSize)
1432
1894
  ] })
1433
1895
  ] }),
1434
- buildStatus === "error" && /* @__PURE__ */ jsxs3(Text4, { color: "red", children: [
1896
+ buildStatus === "error" && /* @__PURE__ */ jsxs4(Text6, { color: "red", children: [
1435
1897
  "\u2717 ",
1436
1898
  buildError ?? "unknown error"
1437
1899
  ] })
1438
1900
  ] }),
1439
- /* @__PURE__ */ jsx4(
1440
- Box4,
1901
+ /* @__PURE__ */ jsx6(
1902
+ Box5,
1441
1903
  {
1442
1904
  flexDirection: "column",
1443
1905
  height: LOG_DISPLAY + 2,
1444
1906
  borderStyle: "single",
1445
1907
  borderColor: "gray",
1446
1908
  overflow: "hidden",
1447
- children: buildLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx4(Text4, { dimColor: true, wrap: "truncate", children: log }, index))
1909
+ children: buildLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "truncate", children: log }, index))
1448
1910
  }
1449
1911
  )
1450
1912
  ] }),
1451
- /* @__PURE__ */ jsxs3(Box4, { marginTop: 1, flexDirection: "column", children: [
1452
- /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1453
- /* @__PURE__ */ jsx4(Text4, { bold: true, dimColor: true, children: "Web Server" }),
1454
- status === "starting" && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "starting\u2026" }),
1455
- status === "running" && /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1456
- /* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u2713 Running" }),
1457
- /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
1913
+ /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", children: [
1914
+ /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
1915
+ /* @__PURE__ */ jsx6(Text6, { bold: true, dimColor: true, children: "Web Server" }),
1916
+ status === "starting" && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "starting\u2026" }),
1917
+ status === "running" && /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
1918
+ /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713 Running" }),
1919
+ /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
1458
1920
  "\u25CF ",
1459
1921
  wsClientCount,
1460
1922
  " viewer",
1461
1923
  wsClientCount !== 1 ? "s" : ""
1462
1924
  ] })
1463
1925
  ] }),
1464
- status === "error" && /* @__PURE__ */ jsxs3(Text4, { color: "red", children: [
1926
+ status === "error" && /* @__PURE__ */ jsxs4(Text6, { color: "red", children: [
1465
1927
  "\u2717 ",
1466
1928
  errorMessage ?? "Server error"
1467
1929
  ] })
1468
1930
  ] }),
1469
- /* @__PURE__ */ jsx4(
1470
- Box4,
1931
+ /* @__PURE__ */ jsx6(
1932
+ Box5,
1471
1933
  {
1472
1934
  flexDirection: "column",
1473
1935
  height: LOG_DISPLAY + 2,
1474
1936
  borderStyle: "single",
1475
1937
  borderColor: "gray",
1476
1938
  overflow: "hidden",
1477
- children: serverLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx4(Text4, { dimColor: true, wrap: "truncate", children: log }, index))
1939
+ children: serverLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "truncate", children: log }, index))
1478
1940
  }
1479
1941
  )
1480
1942
  ] }),
1481
- /* @__PURE__ */ jsxs3(Box4, { marginTop: 1, flexDirection: "column", children: [
1482
- /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
1483
- /* @__PURE__ */ jsx4(Text4, { bold: true, dimColor: true, children: "Network Proxy" }),
1484
- /* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u2713 Active" }),
1485
- /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
1943
+ /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", children: [
1944
+ /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
1945
+ /* @__PURE__ */ jsx6(Text6, { bold: true, dimColor: true, children: "Network Proxy" }),
1946
+ /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713 Active" }),
1947
+ /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
1486
1948
  "\u25CF ",
1487
1949
  proxyCacheSize,
1488
1950
  " key",
@@ -1495,26 +1957,26 @@ var DevUI = ({
1495
1957
  " miss"
1496
1958
  ] })
1497
1959
  ] }),
1498
- /* @__PURE__ */ jsx4(
1499
- Box4,
1960
+ /* @__PURE__ */ jsx6(
1961
+ Box5,
1500
1962
  {
1501
1963
  flexDirection: "column",
1502
1964
  height: LOG_DISPLAY + 2,
1503
1965
  borderStyle: "single",
1504
1966
  borderColor: "gray",
1505
1967
  overflow: "hidden",
1506
- children: proxyLogHistory.length === 0 ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " No requests yet." }) : proxyLogHistory.slice(-LOG_DISPLAY).map((log, i) => /* @__PURE__ */ jsx4(Text4, { dimColor: true, wrap: "truncate", children: log }, i))
1968
+ children: proxyLogHistory.length === 0 ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " No requests yet." }) : proxyLogHistory.slice(-LOG_DISPLAY).map((log, i) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "truncate", children: log }, i))
1507
1969
  }
1508
1970
  )
1509
1971
  ] }),
1510
- /* @__PURE__ */ jsxs3(Box4, { marginTop: 1, gap: 1, children: [
1511
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "[" }),
1512
- /* @__PURE__ */ jsx4(Text4, { children: "q" }),
1513
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "] quit [" }),
1514
- /* @__PURE__ */ jsx4(Text4, { children: "c" }),
1515
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "] credentials [" }),
1516
- /* @__PURE__ */ jsx4(Text4, { children: "v" }),
1517
- /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
1972
+ /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, gap: 1, children: [
1973
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "[" }),
1974
+ /* @__PURE__ */ jsx6(Text6, { children: "q" }),
1975
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "] quit [" }),
1976
+ /* @__PURE__ */ jsx6(Text6, { children: "c" }),
1977
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "] credentials [" }),
1978
+ /* @__PURE__ */ jsx6(Text6, { children: "v" }),
1979
+ /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
1518
1980
  "] ",
1519
1981
  wsClientCount === 0 ? "open viewer" : "relaunch viewer"
1520
1982
  ] })
@@ -1549,6 +2011,7 @@ function devCommand(program2) {
1549
2011
  let credentials = null;
1550
2012
  let credentialMode;
1551
2013
  let activeCredentialProfile;
2014
+ const { lastPath } = await loadConfig(outputDir);
1552
2015
  if (options.credentials) {
1553
2016
  credentials = await loadCredentialsFromFile(options.credentials);
1554
2017
  } else {
@@ -1591,7 +2054,8 @@ function devCommand(program2) {
1591
2054
  outputDir,
1592
2055
  credentials,
1593
2056
  ...credentialMode !== void 0 && { credentialMode },
1594
- ...activeCredentialProfile !== void 0 && { activeCredentialProfile }
2057
+ ...activeCredentialProfile !== void 0 && { activeCredentialProfile },
2058
+ ...lastPath !== void 0 && { lastPath }
1595
2059
  }),
1596
2060
  { exitOnCtrlC: false }
1597
2061
  );