@kizenapps/cli 0.3.0 → 0.5.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 (74) hide show
  1. package/dist/chunk-4OYGMEFM.js +3675 -0
  2. package/dist/chunk-4OYGMEFM.js.map +1 -0
  3. package/dist/chunk-4Z6S6LWP.js +6134 -0
  4. package/dist/chunk-4Z6S6LWP.js.map +1 -0
  5. package/dist/chunk-5WPGAZLP.js +6136 -0
  6. package/dist/chunk-5WPGAZLP.js.map +1 -0
  7. package/dist/chunk-6XGFZ4W5.js +3673 -0
  8. package/dist/chunk-6XGFZ4W5.js.map +1 -0
  9. package/dist/chunk-KMKDY2I6.js +46 -0
  10. package/dist/chunk-KMKDY2I6.js.map +1 -0
  11. package/dist/chunk-LSHHXCC6.js +3639 -0
  12. package/dist/chunk-LSHHXCC6.js.map +1 -0
  13. package/dist/chunk-NNDSADQG.js +3641 -0
  14. package/dist/chunk-NNDSADQG.js.map +1 -0
  15. package/dist/chunk-SEGVTWSK.js +44 -0
  16. package/dist/chunk-SEGVTWSK.js.map +1 -0
  17. package/dist/devtools-HNR7DD7Q.js +68 -0
  18. package/dist/devtools-HNR7DD7Q.js.map +1 -0
  19. package/dist/devtools-HW3UQAHH.js +69 -0
  20. package/dist/devtools-HW3UQAHH.js.map +1 -0
  21. package/dist/devtools-KXIXZGBY.js +64 -0
  22. package/dist/devtools-KXIXZGBY.js.map +1 -0
  23. package/dist/devtools-PDXM3L35.js +67 -0
  24. package/dist/devtools-PDXM3L35.js.map +1 -0
  25. package/dist/devtools-SDEASRYI.js +66 -0
  26. package/dist/devtools-SDEASRYI.js.map +1 -0
  27. package/dist/devtools-XBEEGBQ4.js +66 -0
  28. package/dist/devtools-XBEEGBQ4.js.map +1 -0
  29. package/dist/dist-4P5P64FK.js +8474 -0
  30. package/dist/dist-4P5P64FK.js.map +1 -0
  31. package/dist/dist-AVHDSU4X.js +137 -0
  32. package/dist/dist-AVHDSU4X.js.map +1 -0
  33. package/dist/dist-NUJOSN7W.js +8472 -0
  34. package/dist/dist-NUJOSN7W.js.map +1 -0
  35. package/dist/dist-QHDMIBVE.js +135 -0
  36. package/dist/dist-QHDMIBVE.js.map +1 -0
  37. package/dist/electron/icon.png +0 -0
  38. package/dist/electron/main.js +65 -3
  39. package/dist/electron/main.js.map +1 -1
  40. package/dist/electron/preload.cjs +5 -0
  41. package/dist/gzip-size-K37OQCQ2.js +137 -0
  42. package/dist/gzip-size-K37OQCQ2.js.map +1 -0
  43. package/dist/gzip-size-SHZBT5GU.js +139 -0
  44. package/dist/gzip-size-SHZBT5GU.js.map +1 -0
  45. package/dist/index.js +588 -171
  46. package/dist/index.js.map +1 -1
  47. package/dist/viewer/assets/calendarSource.worker-C9iglnKD.js +24 -0
  48. package/dist/viewer/assets/expression.worker-Cin-WNVB.js +5 -0
  49. package/dist/viewer/assets/floatingFrame.worker-CWLtchq3.js +23 -0
  50. package/dist/viewer/assets/generic.worker-DRXu0-5B.js +23 -0
  51. package/dist/viewer/assets/index-B0d3If0o.js +582 -0
  52. package/dist/viewer/assets/index-B1mMv9H8.css +2 -0
  53. package/dist/viewer/assets/index-BTLnOfRu.css +2 -0
  54. package/dist/viewer/assets/index-B_uo-pdA.js +582 -0
  55. package/dist/viewer/assets/index-Bp6umXGR.js +582 -0
  56. package/dist/viewer/assets/index-Bsja5bOD.js +582 -0
  57. package/dist/viewer/assets/index-CAej9s9R.js +582 -0
  58. package/dist/viewer/assets/index-CCMFzDS-.css +2 -0
  59. package/dist/viewer/assets/index-Ci4aelpQ.js +582 -0
  60. package/dist/viewer/assets/index-CuVHD6vO.css +2 -0
  61. package/dist/viewer/assets/index-DQZ1fN2j.js +582 -0
  62. package/dist/viewer/assets/index-DSeA5UDI.js +582 -0
  63. package/dist/viewer/assets/index-w7OW3o4t.js +582 -0
  64. package/dist/viewer/assets/recordDetail.worker-BIHPFt8Y.js +23 -0
  65. package/dist/viewer/icon.png +0 -0
  66. package/dist/viewer/index.html +2 -2
  67. package/package.json +17 -13
  68. package/dist/viewer/assets/calendarSource.worker-DF0wAwJR.js +0 -24
  69. package/dist/viewer/assets/expression.worker-_W4VR2xe.js +0 -5
  70. package/dist/viewer/assets/floatingFrame.worker-DXcDBfqJ.js +0 -23
  71. package/dist/viewer/assets/generic.worker-CsqlYndL.js +0 -23
  72. package/dist/viewer/assets/index-CW_UqFf3.css +0 -1
  73. package/dist/viewer/assets/index-DaB01Qok.js +0 -637
  74. package/dist/viewer/assets/recordDetail.worker-Ccm59Np6.js +0 -23
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { render } from "ink";
9
9
 
10
10
  // src/ui/BuildUI.tsx
11
11
  import { useEffect, useState } from "react";
12
- import { Box, Text, useApp } from "ink";
12
+ import { Box as Box2, Text as Text2, useApp } from "ink";
13
13
 
14
14
  // src/lib/runBuild.ts
15
15
  import { mkdir, writeFile } from "fs/promises";
@@ -59,10 +59,11 @@ async function readLocalFiles(rootDir) {
59
59
  }
60
60
 
61
61
  // src/lib/runBuild.ts
62
- var serializePlugin = (plugin) => ({
62
+ var serializePlugin = (plugin, allReleaseNotes) => ({
63
63
  ...plugin,
64
64
  thumbnail: plugin.thumbnail ? Buffer.from(plugin.thumbnail).toString("base64") : null,
65
- kznFile: plugin.kznFile ? Buffer.from(plugin.kznFile).toString("base64") : null
65
+ kznFile: plugin.kznFile ? Buffer.from(plugin.kznFile).toString("base64") : null,
66
+ allReleaseNotes
66
67
  });
67
68
  async function runBuild(pluginDir, outputDir, onStep) {
68
69
  await mkdir(outputDir, { recursive: true });
@@ -79,12 +80,44 @@ async function runBuild(pluginDir, outputDir, onStep) {
79
80
  const packaged = packagePlugin(minified, manifests);
80
81
  const deployable = Object.values(packaged).map(transformDeployablePlugin);
81
82
  onStep?.("writing-bundle");
82
- const bundle = deployable.map(serializePlugin);
83
+ const bundle = deployable.map((plugin) => {
84
+ const notesDir = plugin.release_notes_directory?.replace(/\/$/, "");
85
+ const allReleaseNotes = notesDir ? files.filter((f) => f.path.startsWith(notesDir + "/") && f.path.endsWith(".md") && f.content).map((f) => ({
86
+ version: f.path.slice(f.path.lastIndexOf("/") + 1, -3),
87
+ notes: f.content
88
+ })).sort((a, b) => b.version.localeCompare(a.version, void 0, { numeric: true })) : [];
89
+ return serializePlugin(plugin, allReleaseNotes);
90
+ });
83
91
  await writeFile(join2(outputDir, "bundle.json"), JSON.stringify(bundle, null, 2), "utf-8");
84
92
  }
85
93
 
94
+ // src/ui/Logo.tsx
95
+ import { Box, Text } from "ink";
96
+ import { jsx } from "react/jsx-runtime";
97
+ var LOGO_LINES = [
98
+ " @@@ ",
99
+ " @@@@@ @@@@@@@ ",
100
+ " @@@@ @@@@@@@@@@ ",
101
+ " @@@@ @@@@@ ",
102
+ " @@@ @@@@ @@@@@ @@@@@@@ ",
103
+ " @@@@ @@@@ @@@@ @@@@@@@@@@@@@",
104
+ " @@@@ @@@@ @@@@@",
105
+ " @@@@ @@@ @@@@ @@@",
106
+ " @@@@ @@@@@@ ",
107
+ " @@@@@@ @@@@ ",
108
+ " @@@ @@@@@ @@@ @@@@ ",
109
+ " @@@@@ @@@@ @@@@ ",
110
+ " @@@@@@@@@@@@@ @@@ @@@ @@@ ",
111
+ " @@@@@@@@ @@@@ @@@ @@@@ ",
112
+ " @@@@@ @@@@ ",
113
+ " @@@@@@@@@@@ @@@@ ",
114
+ " @@@@@@@ @@@@@ ",
115
+ " @@@@ "
116
+ ];
117
+ var Logo = () => /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: LOGO_LINES.map((line, i) => /* @__PURE__ */ jsx(Text, { color: "cyan", children: line }, i)) });
118
+
86
119
  // src/ui/BuildUI.tsx
87
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
120
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
88
121
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
89
122
  var Spinner = () => {
90
123
  const [frame, setFrame] = useState(0);
@@ -96,7 +129,7 @@ var Spinner = () => {
96
129
  clearInterval(id);
97
130
  };
98
131
  }, []);
99
- return /* @__PURE__ */ jsx(Text, { color: "cyan", children: SPINNER_FRAMES[frame] ?? "\u280B" });
132
+ return /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: SPINNER_FRAMES[frame] ?? "\u280B" });
100
133
  };
101
134
  var STEPS = [
102
135
  "creating-dir",
@@ -113,57 +146,77 @@ var STEP_LABELS = {
113
146
  "writing-bundle": "Writing bundle.json"
114
147
  };
115
148
  var BuildUI = ({ outputDir, pluginDir }) => {
116
- const { exit } = useApp();
149
+ const app = useApp();
117
150
  const [step, setStep] = useState("creating-dir");
118
151
  const [errorMessage, setErrorMessage] = useState(null);
119
152
  useEffect(() => {
120
153
  void runBuild(pluginDir, outputDir, setStep).then(() => {
121
154
  setStep("done");
122
- exit();
155
+ app.exit();
123
156
  }).catch((err) => {
124
157
  const message = err instanceof Error ? err.message : String(err);
125
158
  setErrorMessage(message);
126
159
  setStep("error");
127
- exit(err instanceof Error ? err : new Error(message));
160
+ app.exit();
128
161
  });
129
- }, [outputDir, pluginDir, exit]);
162
+ }, [outputDir, pluginDir, app]);
130
163
  const currentIndex = STEPS.indexOf(step);
131
164
  const isError = step === "error";
132
165
  const isDone = step === "done";
133
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingY: 1, paddingX: 1, children: [
134
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
135
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "Kizen App Builder" }),
136
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(22) })
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) })
137
171
  ] }),
138
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, children: [
172
+ /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", gap: 0, children: [
139
173
  STEPS.map((s, i) => {
140
174
  const isActive = step === s;
141
175
  const isStepDone = isDone || currentIndex > i;
142
176
  const isFailed = isError && i === currentIndex;
143
- return /* @__PURE__ */ jsx(Box, { gap: 1, children: isFailed ? /* @__PURE__ */ jsxs(Text, { color: "red", children: [
177
+ return /* @__PURE__ */ jsx2(Box2, { gap: 1, children: isFailed ? /* @__PURE__ */ jsxs(Text2, { color: "red", children: [
144
178
  "\u2717 ",
145
179
  STEP_LABELS[s]
146
- ] }) : isStepDone ? /* @__PURE__ */ jsxs(Text, { color: "green", children: [
180
+ ] }) : isStepDone ? /* @__PURE__ */ jsxs(Text2, { color: "green", children: [
147
181
  "\u2713 ",
148
182
  STEP_LABELS[s]
149
183
  ] }) : isActive ? /* @__PURE__ */ jsxs(Fragment, { children: [
150
- /* @__PURE__ */ jsx(Spinner, {}),
151
- /* @__PURE__ */ jsx(Text, { children: STEP_LABELS[s] })
152
- ] }) : /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
153
- "\xB7 ",
184
+ /* @__PURE__ */ jsx2(Spinner, {}),
185
+ /* @__PURE__ */ jsx2(Text2, { children: STEP_LABELS[s] })
186
+ ] }) : /* @__PURE__ */ jsxs(Text2, { dimColor: true, children: [
187
+ " ",
154
188
  STEP_LABELS[s]
155
189
  ] }) }, s);
156
190
  }),
157
- errorMessage !== null && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "red", children: errorMessage }) })
191
+ errorMessage !== null && /* @__PURE__ */ jsx2(Box2, { marginTop: 1, borderStyle: "single", borderColor: "red", paddingX: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "red", children: errorMessage }) })
158
192
  ] })
159
193
  ] });
160
194
  };
161
195
 
196
+ // src/lib/gitignore.ts
197
+ import * as fs from "fs";
198
+ import * as path from "path";
199
+ function ensureGitignore(projectDir) {
200
+ const gitignorePath = path.join(projectDir, ".gitignore");
201
+ const entry = ".kizenapp/";
202
+ let contents = "";
203
+ if (fs.existsSync(gitignorePath)) {
204
+ contents = fs.readFileSync(gitignorePath, "utf8");
205
+ const lines = contents.split("\n").map((l) => l.trim());
206
+ if (lines.includes(entry) || lines.includes(".kizenapp")) {
207
+ return;
208
+ }
209
+ }
210
+ const addition = contents.endsWith("\n") ? entry + "\n" : "\n" + entry + "\n";
211
+ fs.writeFileSync(gitignorePath, contents + addition);
212
+ }
213
+
162
214
  // src/commands/build.ts
163
215
  function buildCommand(program2) {
164
216
  program2.command("build").description("Bundle the plugin app into .kizenapp directory").action(async () => {
165
- const outputDir = `${process.cwd()}/.kizenapp`;
166
217
  const pluginDir = process.cwd();
218
+ const outputDir = `${pluginDir}/.kizenapp`;
219
+ ensureGitignore(pluginDir);
167
220
  const { waitUntilExit } = render(createElement(BuildUI, { outputDir, pluginDir }));
168
221
  await waitUntilExit();
169
222
  });
@@ -174,24 +227,285 @@ import { createElement as createElement2 } from "react";
174
227
  import { render as render2 } from "ink";
175
228
 
176
229
  // src/ui/DevUI.tsx
177
- import { useCallback, useEffect as useEffect2, useRef, useState as useState2 } from "react";
178
- import { Box as Box2, Text as Text2, useInput } from "ink";
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";
179
232
  import { spawn } from "child_process";
180
- import {
181
- createReadStream,
182
- readFileSync,
183
- statSync,
184
- unlinkSync,
185
- watch,
186
- writeFileSync
187
- } from "fs";
188
- import { access, readFile as readFile2 } from "fs/promises";
233
+ import { createReadStream, watch } from "fs";
234
+ import { access, readFile as readFile4 } from "fs/promises";
189
235
  import { createServer } from "http";
190
236
  import { createRequire } from "module";
191
- import { dirname, extname, join as join3 } from "path";
237
+ import { dirname as dirname2, extname, join as join6 } from "path";
192
238
  import { fileURLToPath } from "url";
193
239
  import { WebSocketServer } from "ws";
194
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
240
+
241
+ // src/lib/config.ts
242
+ import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
243
+ import { join as join4 } from "path";
244
+ async function loadConfig(outputDir) {
245
+ try {
246
+ const content = await readFile2(join4(outputDir, "config.json"), "utf-8");
247
+ return JSON.parse(content);
248
+ } catch {
249
+ return {};
250
+ }
251
+ }
252
+ async function saveConfig(outputDir, config) {
253
+ await mkdir2(outputDir, { recursive: true });
254
+ await writeFile2(join4(outputDir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
255
+ }
256
+
257
+ // src/ui/CredentialSetupUI.tsx
258
+ import { useCallback, useEffect as useEffect2, useState as useState2 } from "react";
259
+ import { Box as Box3, Text as Text3, useInput } from "ink";
260
+
261
+ // src/lib/credentials.ts
262
+ import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
263
+ import { homedir } from "os";
264
+ import { dirname, join as join5 } from "path";
265
+ var ENVIRONMENTS = ["go", "fmo", "staging", "integration", "test1"];
266
+ var GLOBAL_CREDENTIALS_DIR = join5(homedir(), ".kizenappbuilder");
267
+ var GLOBAL_CREDENTIALS_PATH = join5(GLOBAL_CREDENTIALS_DIR, "credentials.json");
268
+ function isValidEnvironment(value) {
269
+ return ENVIRONMENTS.includes(value);
270
+ }
271
+ function parseCredentials(raw) {
272
+ if (typeof raw !== "object" || raw === null) {
273
+ throw new Error("Credentials must be a JSON object");
274
+ }
275
+ const obj = raw;
276
+ const env = obj.environment;
277
+ return {
278
+ apiKey: typeof obj.apiKey === "string" ? obj.apiKey : "",
279
+ userId: typeof obj.userId === "string" ? obj.userId : "",
280
+ businessId: typeof obj.businessId === "string" ? obj.businessId : "",
281
+ environment: isValidEnvironment(env) ? env : "go"
282
+ };
283
+ }
284
+ async function loadCredentialsFromFile(filePath) {
285
+ const content = await readFile3(filePath, "utf-8");
286
+ return parseCredentials(JSON.parse(content));
287
+ }
288
+ async function loadGlobalCredentials() {
289
+ try {
290
+ return await loadCredentialsFromFile(GLOBAL_CREDENTIALS_PATH);
291
+ } catch {
292
+ return null;
293
+ }
294
+ }
295
+ async function saveGlobalCredentials(credentials) {
296
+ await mkdir3(dirname(GLOBAL_CREDENTIALS_PATH), { recursive: true });
297
+ await writeFile3(GLOBAL_CREDENTIALS_PATH, JSON.stringify(credentials, null, 2), "utf-8");
298
+ }
299
+
300
+ // src/ui/CredentialSetupUI.tsx
301
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
302
+ var FIELDS = ["apiKey", "userId", "businessId"];
303
+ var FIELD_LABELS = {
304
+ apiKey: "API Key",
305
+ userId: "User ID",
306
+ businessId: "Business ID"
307
+ };
308
+ var Hint = ({ text }) => /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: text });
309
+ var CredentialSetupUI = ({
310
+ initialMode,
311
+ onComplete,
312
+ onCancel
313
+ }) => {
314
+ const [phase, setPhase] = useState2(
315
+ initialMode === "global" ? { type: "loading" } : { type: "mode-select", cursor: 0 }
316
+ );
317
+ const [inputBuffer, setInputBuffer] = useState2("");
318
+ const [error, setError] = useState2(null);
319
+ const handleModeChosen = useCallback(
320
+ async (mode) => {
321
+ if (mode === "local") {
322
+ onComplete({ mode: "local", credentials: null });
323
+ return;
324
+ }
325
+ setPhase({ type: "loading" });
326
+ const existing = await loadGlobalCredentials();
327
+ const envCursor2 = existing ? Math.max(0, ENVIRONMENTS.indexOf(existing.environment)) : 0;
328
+ setPhase({
329
+ type: "creds-entry",
330
+ field: 0,
331
+ values: existing ?? {},
332
+ envCursor: envCursor2
333
+ });
334
+ },
335
+ [onComplete]
336
+ );
337
+ const handleSave = useCallback(
338
+ async (values2, envCursor2) => {
339
+ const environment = ENVIRONMENTS[envCursor2] ?? "go";
340
+ const credentials = {
341
+ apiKey: values2.apiKey ?? "",
342
+ userId: values2.userId ?? "",
343
+ businessId: values2.businessId ?? "",
344
+ environment
345
+ };
346
+ setPhase({ type: "saving" });
347
+ try {
348
+ await saveGlobalCredentials(credentials);
349
+ onComplete({ mode: "global", credentials });
350
+ } catch (err) {
351
+ setError(err instanceof Error ? err.message : String(err));
352
+ setPhase({ type: "creds-entry", field: 0, values: values2, envCursor: envCursor2 });
353
+ }
354
+ },
355
+ [onComplete]
356
+ );
357
+ useInput((input, key) => {
358
+ if (key.ctrl && input === "c") {
359
+ process.exit(0);
360
+ }
361
+ if (key.escape) {
362
+ onCancel?.();
363
+ return;
364
+ }
365
+ if (phase.type === "mode-select") {
366
+ if (key.upArrow) {
367
+ setPhase({ type: "mode-select", cursor: 0 });
368
+ } else if (key.downArrow) {
369
+ setPhase({ type: "mode-select", cursor: 1 });
370
+ } else if (key.return) {
371
+ const mode = phase.cursor === 0 ? "global" : "local";
372
+ void handleModeChosen(mode);
373
+ }
374
+ return;
375
+ }
376
+ if (phase.type === "creds-entry") {
377
+ const { field, values: values2, envCursor: envCursor2 } = phase;
378
+ const isEnvField2 = field === FIELDS.length;
379
+ if (isEnvField2) {
380
+ if (key.leftArrow) {
381
+ setPhase({
382
+ ...phase,
383
+ envCursor: (envCursor2 - 1 + ENVIRONMENTS.length) % ENVIRONMENTS.length
384
+ });
385
+ } else if (key.rightArrow) {
386
+ setPhase({ ...phase, envCursor: (envCursor2 + 1) % ENVIRONMENTS.length });
387
+ } else if (key.upArrow) {
388
+ setPhase({ ...phase, field: field - 1 });
389
+ setInputBuffer(values2[FIELDS[field - 1]] ?? "");
390
+ } else if (key.return) {
391
+ void handleSave(values2, envCursor2);
392
+ }
393
+ return;
394
+ }
395
+ const fieldName = FIELDS[field];
396
+ if (!fieldName) {
397
+ return;
398
+ }
399
+ if (key.backspace || key.delete) {
400
+ setInputBuffer((prev) => prev.slice(0, -1));
401
+ } else if (key.upArrow && field > 0) {
402
+ const updatedValues = { ...values2, [fieldName]: inputBuffer };
403
+ const prevField = field - 1;
404
+ setPhase({ ...phase, field: prevField, values: updatedValues });
405
+ setInputBuffer(updatedValues[FIELDS[prevField]] ?? "");
406
+ } else if (key.return || key.tab || key.downArrow) {
407
+ const updatedValues = { ...values2, [fieldName]: inputBuffer };
408
+ const nextField = field + 1;
409
+ setPhase({ ...phase, field: nextField, values: updatedValues });
410
+ if (nextField < FIELDS.length) {
411
+ setInputBuffer(updatedValues[FIELDS[nextField]] ?? "");
412
+ } else {
413
+ setInputBuffer("");
414
+ }
415
+ } else if (input && !key.ctrl && !key.meta) {
416
+ setInputBuffer((prev) => prev + input);
417
+ }
418
+ }
419
+ });
420
+ useEffect2(() => {
421
+ if (initialMode === "global") {
422
+ void handleModeChosen("global");
423
+ }
424
+ }, []);
425
+ useEffect2(() => {
426
+ if (phase.type === "creds-entry" && phase.field === 0) {
427
+ setInputBuffer(phase.values.apiKey ?? "");
428
+ }
429
+ }, [phase.type]);
430
+ if (phase.type === "loading" || phase.type === "saving") {
431
+ return /* @__PURE__ */ jsx3(Box3, { paddingY: 1, paddingX: 2, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: phase.type === "loading" ? "Loading credentials\u2026" : "Saving credentials\u2026" }) });
432
+ }
433
+ if (phase.type === "done") {
434
+ return null;
435
+ }
436
+ if (phase.type === "mode-select") {
437
+ const options = [
438
+ { label: "Global", desc: `~/.kizenappbuilder/credentials.json`, mode: "global" },
439
+ { label: "Local", desc: "Enter credentials in the browser dev tools", mode: "local" }
440
+ ];
441
+ return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
442
+ /* @__PURE__ */ jsx3(Logo, {}),
443
+ /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, children: [
444
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "Kizen App Builder" }),
445
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2500".repeat(24) })
446
+ ] }),
447
+ /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", gap: 0, children: [
448
+ /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Credential mode" }),
449
+ options.map((opt, i) => {
450
+ const selected = phase.cursor === i;
451
+ return /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
452
+ /* @__PURE__ */ jsx3(Text3, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
453
+ /* @__PURE__ */ jsx3(Text3, { bold: selected, ...selected && { color: "cyan" }, children: opt.label }),
454
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: opt.desc })
455
+ ] }, opt.mode);
456
+ })
457
+ ] }),
458
+ /* @__PURE__ */ jsx3(Hint, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Ctrl+C to quit" })
459
+ ] });
460
+ }
461
+ const { field: activeField, values, envCursor } = phase;
462
+ const isEnvField = activeField === FIELDS.length;
463
+ return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
464
+ /* @__PURE__ */ jsx3(Logo, {}),
465
+ /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, children: [
466
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "Kizen App Builder" }),
467
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2500".repeat(24) })
468
+ ] }),
469
+ /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", gap: 0, children: [
470
+ /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Global credentials" }),
471
+ /* @__PURE__ */ jsxs2(Text3, { dimColor: true, children: [
472
+ "Saved to: ",
473
+ GLOBAL_CREDENTIALS_PATH
474
+ ] })
475
+ ] }),
476
+ error && /* @__PURE__ */ jsxs2(Text3, { color: "red", children: [
477
+ "Error: ",
478
+ error
479
+ ] }),
480
+ /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", gap: 0, children: [
481
+ FIELDS.map((name, i) => {
482
+ const isActive = activeField === i;
483
+ const displayValue = isActive ? inputBuffer : values[name] ?? "";
484
+ return /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
485
+ /* @__PURE__ */ jsx3(Box3, { width: 12, children: /* @__PURE__ */ jsx3(Text3, { bold: isActive, ...isActive && { color: "cyan" }, children: FIELD_LABELS[name] }) }),
486
+ /* @__PURE__ */ jsx3(Text3, { ...isActive && { color: "cyan" }, children: ">" }),
487
+ /* @__PURE__ */ jsx3(Text3, { children: displayValue }),
488
+ isActive && /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: "\u2588" })
489
+ ] }, name);
490
+ }),
491
+ /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
492
+ /* @__PURE__ */ jsx3(Box3, { width: 12, children: /* @__PURE__ */ jsx3(Text3, { bold: isEnvField, ...isEnvField && { color: "cyan" }, children: "Environment" }) }),
493
+ /* @__PURE__ */ jsx3(Text3, { ...isEnvField && { color: "cyan" }, children: ">" }),
494
+ /* @__PURE__ */ jsx3(Box3, { gap: 1, children: ENVIRONMENTS.map((env, i) => {
495
+ const isSelected = i === envCursor;
496
+ if (isEnvField) {
497
+ return /* @__PURE__ */ jsx3(Text3, { bold: isSelected, ...isSelected && { color: "cyan" }, children: isSelected ? `[${env}]` : env }, env);
498
+ }
499
+ return /* @__PURE__ */ jsx3(Text3, { dimColor: !isSelected, bold: isSelected, children: env }, env);
500
+ }) })
501
+ ] })
502
+ ] }),
503
+ 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" })
504
+ ] });
505
+ };
506
+
507
+ // src/ui/DevUI.tsx
508
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
195
509
  var SKIP_WATCH_PREFIXES = [".kizenapp", ".git"];
196
510
  var LOG_LIMIT = 50;
197
511
  var LOG_DISPLAY = 8;
@@ -212,8 +526,8 @@ var MIME_TYPES = {
212
526
  };
213
527
  var SPINNER_FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
214
528
  var Spinner2 = () => {
215
- const [frame, setFrame] = useState2(0);
216
- useEffect2(() => {
529
+ const [frame, setFrame] = useState3(0);
530
+ useEffect3(() => {
217
531
  const id = setInterval(() => {
218
532
  setFrame((prev) => (prev + 1) % SPINNER_FRAMES2.length);
219
533
  }, 80);
@@ -221,15 +535,15 @@ var Spinner2 = () => {
221
535
  clearInterval(id);
222
536
  };
223
537
  }, []);
224
- return /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: SPINNER_FRAMES2[frame] ?? "\u280B" });
538
+ return /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: SPINNER_FRAMES2[frame] ?? "\u280B" });
225
539
  };
226
540
  function getViewerPath() {
227
541
  const filename = fileURLToPath(import.meta.url);
228
- return join3(dirname(filename), "viewer");
542
+ return join6(dirname2(filename), "viewer");
229
543
  }
230
544
  function getElectronMainPath() {
231
545
  const filename = fileURLToPath(import.meta.url);
232
- return join3(dirname(filename), "electron", "main.js");
546
+ return join6(dirname2(filename), "electron", "main.js");
233
547
  }
234
548
  async function fileExists(filePath) {
235
549
  try {
@@ -239,83 +553,114 @@ async function fileExists(filePath) {
239
553
  return false;
240
554
  }
241
555
  }
242
- function createRequestHandler(viewerPath, createServerLog, createProxyLog) {
556
+ function createRequestHandler(viewerPath, createServerLog, createProxyLog, credentialsRef) {
243
557
  return (req, res) => {
244
558
  void (async () => {
245
559
  const url = req.url ?? "/";
246
- createServerLog(`Received request: ${url}`);
247
- if (url === "/api/bundle") {
248
- const bundlePath = join3(process.cwd(), ".kizenapp", "bundle.json");
249
- try {
250
- const content = await readFile2(bundlePath, "utf-8");
251
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
252
- res.end(content);
253
- } catch {
560
+ try {
561
+ createServerLog(`Received request: ${url}`);
562
+ if (url === "/api/credentials") {
254
563
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
255
- res.end("{}");
564
+ res.end(credentialsRef.current !== null ? JSON.stringify(credentialsRef.current) : "{}");
565
+ return;
256
566
  }
257
- return;
258
- }
259
- if (url.startsWith("/api/proxy")) {
260
- const proxyTarget = req.headers["x-proxy-target"];
261
- if (typeof proxyTarget !== "string") {
262
- res.writeHead(400);
263
- res.end("Missing x-proxy-target header");
567
+ if (url === "/api/bundle") {
568
+ const bundlePath = join6(process.cwd(), ".kizenapp", "bundle.json");
569
+ try {
570
+ const content = await readFile4(bundlePath, "utf-8");
571
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
572
+ res.end(content);
573
+ } catch {
574
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
575
+ res.end("{}");
576
+ }
577
+ return;
578
+ }
579
+ if (url.startsWith("/api/proxy")) {
580
+ const proxyTarget = req.headers["x-proxy-target"];
581
+ if (typeof proxyTarget !== "string") {
582
+ res.writeHead(400);
583
+ res.end("Missing x-proxy-target header");
584
+ return;
585
+ }
586
+ const upstreamPath = url.slice("/api/proxy".length) || "/";
587
+ const upstreamUrl = `${proxyTarget}${upstreamPath}`;
588
+ const chunks = [];
589
+ for await (const chunk of req) {
590
+ chunks.push(chunk);
591
+ }
592
+ const body = chunks.length > 0 ? Buffer.concat(chunks) : void 0;
593
+ const { host, "x-proxy-target": _drop, ...forwardHeaders } = req.headers;
594
+ const resolvedBody = body && body.length > 0 ? body : void 0;
595
+ const upstream = await fetch(upstreamUrl, {
596
+ ...req.method !== void 0 && { method: req.method },
597
+ headers: forwardHeaders,
598
+ ...resolvedBody !== void 0 && { body: resolvedBody }
599
+ });
600
+ createProxyLog(`${req.method ?? "GET"} ${upstreamPath} \u2192 ${String(upstream.status)}`);
601
+ const responseHeaders = Object.fromEntries(upstream.headers);
602
+ delete responseHeaders["content-encoding"];
603
+ delete responseHeaders["content-length"];
604
+ res.writeHead(upstream.status, responseHeaders);
605
+ res.end(Buffer.from(await upstream.arrayBuffer()));
264
606
  return;
265
607
  }
266
- const upstreamPath = url.slice("/api/proxy".length) || "/";
267
- const upstreamUrl = `${proxyTarget}${upstreamPath}`;
268
- const chunks = [];
269
- for await (const chunk of req) {
270
- chunks.push(chunk);
608
+ const rawPath = url === "/" ? "/index.html" : url;
609
+ const filePath = join6(viewerPath, rawPath);
610
+ const resolvedPath = await fileExists(filePath) ? filePath : join6(viewerPath, "index.html");
611
+ const ext = extname(resolvedPath);
612
+ const mimeType = MIME_TYPES[ext] ?? "application/octet-stream";
613
+ res.writeHead(200, { "Content-Type": mimeType });
614
+ createReadStream(resolvedPath).pipe(res);
615
+ } catch (err) {
616
+ createProxyLog(
617
+ `Error handling ${url}: ${err instanceof Error ? err.message : String(err)}`
618
+ );
619
+ if (!res.headersSent) {
620
+ res.writeHead(502);
621
+ res.end("Bad Gateway");
271
622
  }
272
- const body = chunks.length > 0 ? Buffer.concat(chunks) : void 0;
273
- const { host, "x-proxy-target": _drop, ...forwardHeaders } = req.headers;
274
- const resolvedBody = body && body.length > 0 ? body : void 0;
275
- const upstream = await fetch(upstreamUrl, {
276
- ...req.method !== void 0 && { method: req.method },
277
- headers: forwardHeaders,
278
- ...resolvedBody !== void 0 && { body: resolvedBody }
279
- });
280
- createProxyLog(`${req.method ?? "GET"} ${upstreamPath} \u2192 ${String(upstream.status)}`);
281
- const responseHeaders = Object.fromEntries(upstream.headers);
282
- delete responseHeaders["content-encoding"];
283
- delete responseHeaders["content-length"];
284
- res.writeHead(upstream.status, responseHeaders);
285
- res.end(Buffer.from(await upstream.arrayBuffer()));
286
- return;
287
623
  }
288
- const rawPath = url === "/" ? "/index.html" : url;
289
- const filePath = join3(viewerPath, rawPath);
290
- const resolvedPath = await fileExists(filePath) ? filePath : join3(viewerPath, "index.html");
291
- const ext = extname(resolvedPath);
292
- const mimeType = MIME_TYPES[ext] ?? "application/octet-stream";
293
- res.writeHead(200, { "Content-Type": mimeType });
294
- createReadStream(resolvedPath).pipe(res);
295
624
  })();
296
625
  };
297
626
  }
298
- var DevUI = ({ port, pluginDir, outputDir }) => {
299
- const [status, setStatus] = useState2("starting");
300
- const [errorMessage, setErrorMessage] = useState2(null);
301
- const [serverLogHistory, setServerLogHistory] = useState2([]);
302
- const [buildLogHistory, setBuildLogHistory] = useState2([]);
303
- const [proxyLogHistory, setProxyLogHistory] = useState2([]);
304
- const [buildStatus, setBuildStatus] = useState2("pending");
305
- const [buildError, setBuildError] = useState2(null);
306
- const [lastBuilt, setLastBuilt] = useState2(null);
307
- const [wsClientCount, setWsClientCount] = useState2(0);
627
+ var DevUI = ({
628
+ port,
629
+ pluginDir,
630
+ outputDir,
631
+ credentials: initialCredentials
632
+ }) => {
633
+ const credentialsRef = useRef(initialCredentials);
634
+ const [credMode, setCredMode] = useState3("main");
635
+ const [status, setStatus] = useState3("starting");
636
+ const [errorMessage, setErrorMessage] = useState3(null);
637
+ const [serverLogHistory, setServerLogHistory] = useState3([]);
638
+ const [buildLogHistory, setBuildLogHistory] = useState3([]);
639
+ const [proxyLogHistory, setProxyLogHistory] = useState3([]);
640
+ const [buildStatus, setBuildStatus] = useState3("pending");
641
+ const [buildError, setBuildError] = useState3(null);
642
+ const [lastBuilt, setLastBuilt] = useState3(null);
643
+ const [wsClientCount, setWsClientCount] = useState3(0);
308
644
  const buildingRef = useRef(false);
309
645
  const electronLaunchedRef = useRef(false);
310
646
  const debounceTimerRef = useRef(null);
311
647
  const wsClientsRef = useRef(/* @__PURE__ */ new Set());
312
648
  const pendingMessagesRef = useRef([]);
313
- useInput((input, key) => {
314
- if (input === "q" || key.ctrl && input === "c") {
315
- process.exit(0);
316
- }
317
- });
318
- useEffect2(() => {
649
+ useInput2(
650
+ (input, key) => {
651
+ if (input === "q" || key.ctrl && input === "c") {
652
+ process.exit(0);
653
+ }
654
+ if (input === "c") {
655
+ setCredMode("editing");
656
+ }
657
+ if (input === "d") {
658
+ broadcast({ type: "open-devtools" });
659
+ }
660
+ },
661
+ { isActive: credMode === "main" }
662
+ );
663
+ useEffect3(() => {
319
664
  if (status !== "running" || electronLaunchedRef.current) {
320
665
  return;
321
666
  }
@@ -323,27 +668,7 @@ var DevUI = ({ port, pluginDir, outputDir }) => {
323
668
  const _require = createRequire(import.meta.url);
324
669
  const electronBin = _require("electron");
325
670
  const electronMain = getElectronMainPath();
326
- const userDataDir = join3(outputDir, ".electron");
327
- if (process.platform === "darwin") {
328
- try {
329
- const plistPath = join3(dirname(dirname(electronBin)), "Info.plist");
330
- const plist = readFileSync(plistPath, "utf-8");
331
- if (!plist.includes(">Kizen Dev<")) {
332
- if (statSync(plistPath).nlink > 1) {
333
- unlinkSync(plistPath);
334
- writeFileSync(plistPath, plist);
335
- }
336
- writeFileSync(
337
- plistPath,
338
- plist.replace(/(<key>CFBundleName<\/key>\s*<string>)[^<]*(<\/string>)/, "$1Kizen Dev$2").replace(
339
- /(<key>CFBundleDisplayName<\/key>\s*<string>)[^<]*(<\/string>)/,
340
- "$1Kizen Dev$2"
341
- )
342
- );
343
- }
344
- } catch {
345
- }
346
- }
671
+ const userDataDir = join6(outputDir, ".electron");
347
672
  const proc = spawn(
348
673
  electronBin,
349
674
  [electronMain, `--port=${String(port)}`, `--user-data-dir=${userDataDir}`],
@@ -356,12 +681,12 @@ var DevUI = ({ port, pluginDir, outputDir }) => {
356
681
  proc.kill();
357
682
  });
358
683
  }, [status, port, outputDir]);
359
- const createServerLog = useCallback((message) => {
684
+ const createServerLog = useCallback2((message) => {
360
685
  setServerLogHistory(
361
686
  (h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-LOG_LIMIT)
362
687
  );
363
688
  }, []);
364
- const broadcast = useCallback((msg) => {
689
+ const broadcast = useCallback2((msg) => {
365
690
  const json = JSON.stringify(msg);
366
691
  if (wsClientsRef.current.size === 0) {
367
692
  pendingMessagesRef.current = [...pendingMessagesRef.current, json].slice(-LOG_LIMIT);
@@ -373,7 +698,16 @@ var DevUI = ({ port, pluginDir, outputDir }) => {
373
698
  }
374
699
  }
375
700
  }, []);
376
- const createProxyLog = useCallback(
701
+ const handleCredentialsDone = useCallback2(
702
+ (result) => {
703
+ credentialsRef.current = result.credentials;
704
+ setCredMode("main");
705
+ void saveConfig(outputDir, { credentialMode: result.mode });
706
+ broadcast({ type: "credentials-updated", credentials: result.credentials });
707
+ },
708
+ [outputDir, broadcast]
709
+ );
710
+ const createProxyLog = useCallback2(
377
711
  (message) => {
378
712
  setProxyLogHistory(
379
713
  (h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-LOG_LIMIT)
@@ -382,7 +716,7 @@ var DevUI = ({ port, pluginDir, outputDir }) => {
382
716
  },
383
717
  [broadcast]
384
718
  );
385
- const createBuildLog = useCallback(
719
+ const createBuildLog = useCallback2(
386
720
  (message) => {
387
721
  setBuildLogHistory(
388
722
  (h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-LOG_LIMIT)
@@ -391,7 +725,7 @@ var DevUI = ({ port, pluginDir, outputDir }) => {
391
725
  },
392
726
  [broadcast]
393
727
  );
394
- const triggerBuild = useCallback(() => {
728
+ const triggerBuild = useCallback2(() => {
395
729
  if (buildingRef.current) {
396
730
  return;
397
731
  }
@@ -417,7 +751,7 @@ var DevUI = ({ port, pluginDir, outputDir }) => {
417
751
  buildingRef.current = false;
418
752
  });
419
753
  }, [pluginDir, outputDir, createBuildLog]);
420
- useEffect2(() => {
754
+ useEffect3(() => {
421
755
  triggerBuild();
422
756
  const watcher = watch(pluginDir, { recursive: true }, (_, filename) => {
423
757
  if (!filename) {
@@ -440,15 +774,20 @@ var DevUI = ({ port, pluginDir, outputDir }) => {
440
774
  }
441
775
  };
442
776
  }, [pluginDir, triggerBuild, createBuildLog]);
443
- useEffect2(() => {
777
+ useEffect3(() => {
444
778
  const viewerPath = getViewerPath();
445
- void fileExists(join3(viewerPath, "index.html")).then((viewerBuilt) => {
779
+ void fileExists(join6(viewerPath, "index.html")).then((viewerBuilt) => {
446
780
  if (!viewerBuilt) {
447
781
  setErrorMessage("Viewer not built. Run 'pnpm build:viewer' first.");
448
782
  setStatus("error");
449
783
  return;
450
784
  }
451
- const handler = createRequestHandler(viewerPath, createServerLog, createProxyLog);
785
+ const handler = createRequestHandler(
786
+ viewerPath,
787
+ createServerLog,
788
+ createProxyLog,
789
+ credentialsRef
790
+ );
452
791
  const server = createServer(handler);
453
792
  const wss = new WebSocketServer({ server });
454
793
  wss.on("connection", (ws) => {
@@ -480,95 +819,173 @@ var DevUI = ({ port, pluginDir, outputDir }) => {
480
819
  };
481
820
  });
482
821
  }, [port, createServerLog, createProxyLog]);
822
+ if (credMode === "editing") {
823
+ return /* @__PURE__ */ jsx4(
824
+ CredentialSetupUI,
825
+ {
826
+ onComplete: handleCredentialsDone,
827
+ onCancel: () => {
828
+ setCredMode("main");
829
+ }
830
+ }
831
+ );
832
+ }
483
833
  const elapsedSeconds = lastBuilt !== null ? Math.round((Date.now() - lastBuilt.getTime()) / 1e3) : null;
484
834
  const elapsedLabel = elapsedSeconds === null ? "" : elapsedSeconds < 5 ? " (just now)" : ` (${String(elapsedSeconds)}s ago)`;
485
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingY: 1, paddingX: 1, children: [
486
- /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx2(Box2, { gap: 1, children: /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "Kizen App Builder" }) }) }),
487
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
488
- /* @__PURE__ */ jsxs2(Box2, { gap: 1, marginBottom: 0, children: [
489
- buildStatus === "pending" && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Build waiting..." }),
490
- buildStatus === "building" && /* @__PURE__ */ jsxs2(Fragment2, { children: [
491
- /* @__PURE__ */ jsx2(Spinner2, {}),
492
- /* @__PURE__ */ jsx2(Text2, { children: "Building Plugin Package..." })
835
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
836
+ /* @__PURE__ */ jsx4(Logo, {}),
837
+ /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [
838
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "Kizen App Builder" }),
839
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2500".repeat(24) })
840
+ ] }),
841
+ /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
842
+ /* @__PURE__ */ jsxs3(Box4, { gap: 2, marginBottom: 0, children: [
843
+ /* @__PURE__ */ jsx4(Text4, { bold: true, dimColor: true, children: "Build" }),
844
+ buildStatus === "pending" && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "waiting\u2026" }),
845
+ buildStatus === "building" && /* @__PURE__ */ jsxs3(Box4, { gap: 1, children: [
846
+ /* @__PURE__ */ jsx4(Spinner2, {}),
847
+ /* @__PURE__ */ jsx4(Text4, { children: "Building plugin package\u2026" })
493
848
  ] }),
494
- buildStatus === "done" && /* @__PURE__ */ jsxs2(Text2, { color: "green", children: [
495
- "\u2713 Built Plugin Package",
496
- lastBuilt !== null ? ` at ${lastBuilt.toLocaleTimeString()}` : "",
497
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: elapsedLabel })
849
+ buildStatus === "done" && /* @__PURE__ */ jsxs3(Box4, { gap: 1, children: [
850
+ /* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u2713 Built" }),
851
+ lastBuilt !== null && /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
852
+ "at ",
853
+ lastBuilt.toLocaleTimeString(),
854
+ elapsedLabel
855
+ ] })
498
856
  ] }),
499
- buildStatus === "error" && /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
500
- "\u2717 Build error: ",
857
+ buildStatus === "error" && /* @__PURE__ */ jsxs3(Text4, { color: "red", children: [
858
+ "\u2717 ",
501
859
  buildError ?? "unknown error"
502
860
  ] })
503
861
  ] }),
504
- /* @__PURE__ */ jsx2(
505
- Box2,
862
+ /* @__PURE__ */ jsx4(
863
+ Box4,
506
864
  {
507
865
  flexDirection: "column",
508
866
  height: LOG_DISPLAY,
509
- borderStyle: "round",
867
+ borderStyle: "single",
510
868
  borderColor: "gray",
511
869
  overflow: "hidden",
512
- children: buildLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate", children: log }, index))
870
+ children: buildLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx4(Text4, { dimColor: true, wrap: "truncate", children: log }, index))
513
871
  }
514
872
  )
515
873
  ] }),
516
- /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
517
- /* @__PURE__ */ jsxs2(Box2, { gap: 2, children: [
518
- status === "starting" && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Starting server..." }),
519
- status === "running" && /* @__PURE__ */ jsxs2(Fragment2, { children: [
520
- /* @__PURE__ */ jsx2(Text2, { color: "green", children: "\u2713 Dev Server running" }),
521
- /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
874
+ /* @__PURE__ */ jsxs3(Box4, { marginTop: 1, flexDirection: "column", children: [
875
+ /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
876
+ /* @__PURE__ */ jsx4(Text4, { bold: true, dimColor: true, children: "Server" }),
877
+ status === "starting" && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "starting\u2026" }),
878
+ status === "running" && /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
879
+ /* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u2713 Running" }),
880
+ /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
522
881
  "\u25CF ",
523
882
  wsClientCount,
524
883
  " viewer",
525
884
  wsClientCount !== 1 ? "s" : ""
526
885
  ] })
527
886
  ] }),
528
- status === "error" && /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
887
+ status === "error" && /* @__PURE__ */ jsxs3(Text4, { color: "red", children: [
529
888
  "\u2717 ",
530
889
  errorMessage ?? "Server error"
531
890
  ] })
532
891
  ] }),
533
- /* @__PURE__ */ jsx2(
534
- Box2,
892
+ /* @__PURE__ */ jsx4(
893
+ Box4,
535
894
  {
536
895
  flexDirection: "column",
537
896
  height: LOG_DISPLAY,
538
- borderStyle: "round",
897
+ borderStyle: "single",
539
898
  borderColor: "gray",
540
899
  overflow: "hidden",
541
- children: serverLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate", children: log }, index))
900
+ children: serverLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx4(Text4, { dimColor: true, wrap: "truncate", children: log }, index))
542
901
  }
543
902
  )
544
903
  ] }),
545
- /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
546
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Proxy" }),
547
- /* @__PURE__ */ jsx2(
548
- Box2,
904
+ /* @__PURE__ */ jsxs3(Box4, { marginTop: 1, flexDirection: "column", children: [
905
+ /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
906
+ /* @__PURE__ */ jsx4(Text4, { bold: true, dimColor: true, children: "Proxy" }),
907
+ /* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u2713 Active" })
908
+ ] }),
909
+ /* @__PURE__ */ jsx4(
910
+ Box4,
549
911
  {
550
912
  flexDirection: "column",
551
913
  height: LOG_DISPLAY,
552
- borderStyle: "round",
914
+ borderStyle: "single",
553
915
  borderColor: "gray",
554
916
  overflow: "hidden",
555
- children: proxyLogHistory.length === 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " No proxy requests yet." }) : proxyLogHistory.slice(-LOG_DISPLAY).map((log, i) => /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate", children: log }, i))
917
+ 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))
556
918
  }
557
919
  )
558
920
  ] }),
559
- /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "q to quit" }) })
921
+ /* @__PURE__ */ jsxs3(Box4, { marginTop: 1, gap: 1, children: [
922
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "[" }),
923
+ /* @__PURE__ */ jsx4(Text4, { children: "q" }),
924
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "] quit [" }),
925
+ /* @__PURE__ */ jsx4(Text4, { children: "c" }),
926
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "] credentials [" }),
927
+ /* @__PURE__ */ jsx4(Text4, { children: "d" }),
928
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "] devtools" })
929
+ ] })
560
930
  ] });
561
931
  };
562
932
 
563
933
  // src/commands/dev.ts
934
+ async function runSetupUI(initialMode) {
935
+ return new Promise((resolve) => {
936
+ let unmountFn = null;
937
+ const handleComplete = (result) => {
938
+ unmountFn?.();
939
+ resolve(result);
940
+ };
941
+ const { unmount } = render2(
942
+ createElement2(CredentialSetupUI, {
943
+ ...initialMode !== void 0 && { initialMode },
944
+ onComplete: handleComplete
945
+ }),
946
+ { exitOnCtrlC: false }
947
+ );
948
+ unmountFn = unmount;
949
+ });
950
+ }
564
951
  function devCommand(program2) {
565
- program2.command("dev").description("Start the plugin viewer dev server").option("-p, --port <port>", "port to listen on", "3121").action(async (options) => {
952
+ program2.command("dev").description("Start the plugin viewer dev server").option("-p, --port <port>", "port to listen on", "3121").option("-c, --credentials <path>", "path to a credentials JSON file").action(async (options) => {
566
953
  const port = parseInt(options.port, 10);
567
954
  const pluginDir = process.cwd();
568
955
  const outputDir = `${pluginDir}/.kizenapp`;
569
- const { waitUntilExit } = render2(createElement2(DevUI, { port, pluginDir, outputDir }), {
570
- exitOnCtrlC: false
571
- });
956
+ ensureGitignore(pluginDir);
957
+ let credentials = null;
958
+ let credentialMode;
959
+ if (options.credentials) {
960
+ credentials = await loadCredentialsFromFile(options.credentials);
961
+ } else {
962
+ const config = await loadConfig(outputDir);
963
+ if (config.credentialMode === "local") {
964
+ credentialMode = "local";
965
+ } else if (config.credentialMode === "global") {
966
+ credentialMode = "global";
967
+ credentials = await loadGlobalCredentials();
968
+ if (!credentials) {
969
+ const result = await runSetupUI("global");
970
+ credentials = result.credentials;
971
+ }
972
+ } else {
973
+ const result = await runSetupUI();
974
+ credentials = result.credentials;
975
+ credentialMode = result.mode;
976
+ await saveConfig(outputDir, { credentialMode: result.mode });
977
+ }
978
+ }
979
+ const { waitUntilExit } = render2(
980
+ createElement2(DevUI, {
981
+ port,
982
+ pluginDir,
983
+ outputDir,
984
+ credentials,
985
+ ...credentialMode !== void 0 && { credentialMode }
986
+ }),
987
+ { exitOnCtrlC: false }
988
+ );
572
989
  await waitUntilExit();
573
990
  });
574
991
  }