@kizenapps/cli 0.13.0 → 0.15.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 (96) hide show
  1. package/LICENSE.md +674 -0
  2. package/README.md +42 -0
  3. package/dist/index.js +13 -2679
  4. package/dist/index.js.map +1 -1
  5. package/dist/viewer/assets/AppDetailPage-BorvgFPN.js +1 -0
  6. package/dist/viewer/assets/{BootstrapContext-CL_onTG7.js → BootstrapContext-D5ZnMrOp.js} +1 -1
  7. package/dist/viewer/assets/CodeStepsPage-BZ2IGkjP.js +1 -0
  8. package/dist/viewer/assets/ConfigurationPage-n8Uo_HL7.js +1 -0
  9. package/dist/viewer/assets/DevSidebar-DS8UU-G8.js +1 -0
  10. package/dist/viewer/assets/IconReferencePage-bTBpdJEd.js +1 -0
  11. package/dist/viewer/assets/SandboxPage-CB4FgqI3.js +1 -0
  12. package/dist/viewer/assets/SourceBrowserPage-BBZcr3MI.js +1 -0
  13. package/dist/viewer/assets/Tooltip-tpqNw0op.js +1 -0
  14. package/dist/viewer/assets/WhenBadge-y4k1WgBK.js +1 -0
  15. package/dist/viewer/assets/api-CT-MKz2h.js +1 -0
  16. package/dist/viewer/assets/configStorage-CNoQ0cy5.js +10 -0
  17. package/dist/viewer/assets/index-BMMO15ja.js +5 -0
  18. package/dist/viewer/assets/index-dW-Omils.css +2 -0
  19. package/dist/viewer/assets/storageKeys-C6c8YF5Y.js +1 -0
  20. package/dist/viewer/assets/useCriticalExceptionDialog-C3m9pLEk.js +5 -0
  21. package/dist/viewer/index.html +5 -7
  22. package/package.json +6 -3
  23. package/dist/chunk-4Z6S6LWP.js +0 -6134
  24. package/dist/chunk-4Z6S6LWP.js.map +0 -1
  25. package/dist/chunk-HJPLDI45.js +0 -6134
  26. package/dist/chunk-HJPLDI45.js.map +0 -1
  27. package/dist/chunk-LSHHXCC6.js +0 -3639
  28. package/dist/chunk-LSHHXCC6.js.map +0 -1
  29. package/dist/chunk-SEGVTWSK.js +0 -44
  30. package/dist/chunk-SEGVTWSK.js.map +0 -1
  31. package/dist/chunk-ZWE3DS7E.js +0 -39
  32. package/dist/chunk-ZWE3DS7E.js.map +0 -1
  33. package/dist/devtools-LZRAH4XS.js +0 -90
  34. package/dist/devtools-LZRAH4XS.js.map +0 -1
  35. package/dist/dist-C3F6UOWF.js +0 -8472
  36. package/dist/dist-C3F6UOWF.js.map +0 -1
  37. package/dist/dist-NUJOSN7W.js +0 -8472
  38. package/dist/dist-NUJOSN7W.js.map +0 -1
  39. package/dist/dist-QHDMIBVE.js +0 -135
  40. package/dist/dist-QHDMIBVE.js.map +0 -1
  41. package/dist/dist-ZIZQOSY3.js +0 -135
  42. package/dist/dist-ZIZQOSY3.js.map +0 -1
  43. package/dist/gzip-size-K37OQCQ2.js +0 -137
  44. package/dist/gzip-size-K37OQCQ2.js.map +0 -1
  45. package/dist/gzip-size-QRGIYYEQ.js +0 -137
  46. package/dist/gzip-size-QRGIYYEQ.js.map +0 -1
  47. package/dist/viewer/assets/AppDetailPage-Bh4FLKqJ.js +0 -1
  48. package/dist/viewer/assets/AppDetailPage-BoFB93TE.js +0 -1
  49. package/dist/viewer/assets/AppDetailPage-Ck_VyLCS.js +0 -1
  50. package/dist/viewer/assets/BootstrapContext-c_1rWAfc.js +0 -1
  51. package/dist/viewer/assets/Card-C7KVwKfm.js +0 -1
  52. package/dist/viewer/assets/CodeStepsPage-BHbbvjvr.js +0 -1
  53. package/dist/viewer/assets/CodeStepsPage-DnjVkPGB.js +0 -1
  54. package/dist/viewer/assets/CodeStepsPage-vHoGY4qA.js +0 -1
  55. package/dist/viewer/assets/CodeViewer--wbTev7Q.js +0 -4
  56. package/dist/viewer/assets/ConfigurationPage-DAVSSfuO.js +0 -1
  57. package/dist/viewer/assets/ConfigurationPage-_PScSEXP.js +0 -1
  58. package/dist/viewer/assets/ConfigurationPage-zSJDg1f4.js +0 -1
  59. package/dist/viewer/assets/CredentialsContext-Rkt9WDIb.js +0 -1
  60. package/dist/viewer/assets/CredentialsContext-k-q4M-VC.js +0 -1
  61. package/dist/viewer/assets/DevSidebar-CKdTotWS.js +0 -1
  62. package/dist/viewer/assets/DevSidebar-CcSTChgY.js +0 -1
  63. package/dist/viewer/assets/IconReferencePage-BhQv3kD4.js +0 -1
  64. package/dist/viewer/assets/IconReferencePage-C_I2Fxnr.js +0 -1
  65. package/dist/viewer/assets/PluginIcon-CWkWM-Eu.js +0 -1
  66. package/dist/viewer/assets/SandboxPage-BMz04QVs.js +0 -1
  67. package/dist/viewer/assets/SandboxPage-BWtQb8kY.js +0 -1
  68. package/dist/viewer/assets/SandboxPage-DOBUc8VL.js +0 -1
  69. package/dist/viewer/assets/SidebarContext-DMhpPBf_.js +0 -1
  70. package/dist/viewer/assets/SourceBrowserPage-C-IhTiL0.js +0 -1
  71. package/dist/viewer/assets/SourceBrowserPage-Cr6XOIdV.js +0 -1
  72. package/dist/viewer/assets/VersionsPage-Bpj4_jPC.js +0 -1
  73. package/dist/viewer/assets/VersionsPage-T8tsqgQx.js +0 -1
  74. package/dist/viewer/assets/api-DFDquUsn.js +0 -1
  75. package/dist/viewer/assets/api-bszpvP9L.js +0 -1
  76. package/dist/viewer/assets/bundleQuery-BQLpZ06V.js +0 -1
  77. package/dist/viewer/assets/fa-core-C4eeAd4v.js +0 -558
  78. package/dist/viewer/assets/fa-pro-icons-Dr3O0gTt.js +0 -1
  79. package/dist/viewer/assets/index-7CwR8Ogd.js +0 -5
  80. package/dist/viewer/assets/index-CCB9tECd.css +0 -2
  81. package/dist/viewer/assets/index-FEIsXxbf.js +0 -5
  82. package/dist/viewer/assets/index-fU82pXSW.js +0 -5
  83. package/dist/viewer/assets/react-CmC5kGa_.js +0 -9
  84. package/dist/viewer/assets/useCriticalExceptionDialog-BeVl0uaO.js +0 -14
  85. package/dist/viewer/assets/useCriticalExceptionDialog-DseJ_u9B.js +0 -14
  86. package/dist/viewer/assets/useCurrentApp-CWtSEVC4.js +0 -1
  87. package/dist/viewer/assets/useCurrentApp-x1p8URhM.js +0 -1
  88. package/dist/viewer/assets/useLocalStorage-HYhrPUxS.js +0 -1
  89. package/dist/viewer/assets/useNavigate-Ci7hClRo.js +0 -1
  90. package/dist/viewer/assets/useQuery-B3Ek2Yet.js +0 -1
  91. package/dist/viewer/assets/validIcons-CzxkK9y8.js +0 -1
  92. package/dist/viewer/manifest.json +0 -10
  93. /package/dist/viewer/assets/{CodeViewer-Cm7Th9pG.js → CodeViewer-C9aVhzG7.js} +0 -0
  94. /package/dist/viewer/assets/{VersionsPage-DAm4Ozs0.js → VersionsPage-D6QScnTb.js} +0 -0
  95. /package/dist/viewer/assets/{formatBytes-BCP0hIUu.js → formatBytes-C9lOTtsO.js} +0 -0
  96. /package/dist/viewer/assets/{useLocalStorage-eJ1PxgAP.js → useLocalStorage-CyZiO78q.js} +0 -0
package/dist/index.js CHANGED
@@ -1,863 +1,11 @@
1
1
  #!/usr/bin/env node
2
-
3
- // src/index.ts
4
- import { program } from "commander";
5
-
6
- // src/commands/build.ts
7
- import { createElement } from "react";
8
- import { render } from "ink";
9
-
10
- // src/ui/BuildUI.tsx
11
- import { useEffect as useEffect2, useState as useState2 } from "react";
12
- import { Box as Box3, Text as Text4, useApp } from "ink";
13
-
14
- // src/lib/runBuild.ts
15
- import { mkdir, writeFile } from "fs/promises";
16
- import { join as join2 } from "path";
17
- import { minifyFiles, packagePlugin, transformDeployablePlugin } from "@kizenapps/packager";
18
-
19
- // src/lib/readFiles.ts
20
- import { readdir, readFile } from "fs/promises";
21
- import { join, relative } from "path";
22
- var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".svg"]);
23
- var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([".kzn"]);
24
- var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".kizenapp", ".github"]);
25
- async function walk(dir, rootDir) {
26
- const entries = await readdir(dir, { withFileTypes: true });
27
- const paths = [];
28
- for (const entry of entries) {
29
- if (entry.isDirectory()) {
30
- if (SKIP_DIRS.has(entry.name)) {
31
- continue;
32
- }
33
- paths.push(...await walk(join(dir, entry.name), rootDir));
34
- } else if (entry.isFile()) {
35
- paths.push(join(dir, entry.name));
36
- }
37
- }
38
- return paths;
39
- }
40
- async function readLocalFiles(rootDir) {
41
- const absolutePaths = await walk(rootDir, rootDir);
42
- return Promise.all(
43
- absolutePaths.map(async (absPath) => {
44
- const relPath = relative(rootDir, absPath).split("\\").join("/");
45
- const dotIndex = relPath.lastIndexOf(".");
46
- const ext = dotIndex >= 0 ? relPath.slice(dotIndex).toLowerCase() : "";
47
- if (IMAGE_EXTENSIONS.has(ext)) {
48
- const buf = await readFile(absPath);
49
- return { path: relPath, content: "", base64Image: buf.toString("base64") };
50
- }
51
- if (BINARY_EXTENSIONS.has(ext)) {
52
- const buf = await readFile(absPath);
53
- return { path: relPath, content: "", binaryData: buf };
54
- }
55
- const content = await readFile(absPath, "utf-8");
56
- return { path: relPath, content };
57
- })
58
- );
59
- }
60
-
61
- // src/lib/runBuild.ts
62
- var serializePlugin = (plugin, allReleaseNotes) => ({
63
- ...plugin,
64
- thumbnail: plugin.thumbnail ? Buffer.from(plugin.thumbnail).toString("base64") : null,
65
- kznFile: plugin.kznFile ? Buffer.from(plugin.kznFile).toString("base64") : null,
66
- allReleaseNotes
67
- });
68
- async function runBuild(pluginDir, outputDir, onStep) {
69
- await mkdir(outputDir, { recursive: true });
70
- onStep?.("reading-files");
71
- const files = await readLocalFiles(pluginDir);
72
- onStep?.("minifying");
73
- const minified = await minifyFiles(files);
74
- onStep?.("packaging");
75
- const manifestFile = minified.find((f) => f.path === "kizen.json");
76
- if (!manifestFile) {
77
- throw new Error("kizen.json not found in plugin directory.");
78
- }
79
- const manifests = JSON.parse(manifestFile.content);
80
- const packaged = packagePlugin(minified, manifests);
81
- const deployable = Object.values(packaged).map(transformDeployablePlugin);
82
- onStep?.("writing-bundle");
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
- });
91
- const jsonStr = JSON.stringify(bundle, null, 2);
92
- const sizedBundle = bundle.map(({ allReleaseNotes: _notes, ...rest }) => rest);
93
- const bundleSize = Buffer.byteLength(JSON.stringify(sizedBundle, null, 2), "utf-8");
94
- await writeFile(join2(outputDir, "bundle.json"), jsonStr, "utf-8");
95
- return { bundleSize };
96
- }
97
-
98
- // src/lib/formatBytes.ts
99
- function formatBytes(n) {
100
- if (n < 1024) {
101
- return `${String(n)} B`;
102
- }
103
- if (n < 1024 * 1024) {
104
- return `${(n / 1024).toFixed(1)} KB`;
105
- }
106
- return `${(n / (1024 * 1024)).toFixed(2)} MB`;
107
- }
108
-
109
- // src/ui/AppHeader.tsx
110
- import { Box as Box2, Text as Text2 } from "ink";
111
-
112
- // src/ui/Logo.tsx
113
- import { Box, Text } from "ink";
114
- import { jsx } from "react/jsx-runtime";
115
- var LOGO_LINES = [
116
- " :::::::: ",
117
- " :::::: ::::::::::: ",
118
- " :::::: :::::::::: ",
119
- " :::::: :::::: ::::::: ",
120
- " ::::: :::::: ::::::::::::::::: ",
121
- " ::::: ::::: ::::::::::::::::::: ",
122
- " ::::: ::::: :::::: ::::: ",
123
- ":::: ::::: ::: ::::: :: ",
124
- "::::: :::::: :::::::::: ",
125
- "::::: :::::: ::::::::::: ",
126
- " ::::: ::: ::::::: ",
127
- " ::::::: ::: ::::: ",
128
- " ::::::::::: :::::: :::::",
129
- " :::::::::: :::::: :::::",
130
- " :: ::::: ::: ::::: ::::",
131
- " ::::: :::::: ::::: ::::: ::: ",
132
- " ::::::::::::::::::: ::::: ::::: ",
133
- " ::::::::::::::::: ::::: ::::: ",
134
- " ::::::::: :::::: :::::: ",
135
- " :::::::::: :::::: ",
136
- " ::::::::::: :::::: ",
137
- " :::::::: "
138
- ];
139
- var Logo = () => /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: LOGO_LINES.map((line, i) => /* @__PURE__ */ jsx(Text, { color: "cyan", children: line }, i)) });
140
-
141
- // src/ui/AppHeader.tsx
142
- import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
143
- var AppHeader = ({ marginBottom = 0 }) => /* @__PURE__ */ jsxs(Fragment, { children: [
144
- /* @__PURE__ */ jsx2(Logo, {}),
145
- /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, marginBottom, children: /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "Kizen App Development Toolkit" }) })
146
- ] });
147
-
148
- // src/ui/Spinner.tsx
149
- import { useEffect, useState } from "react";
150
- import { Text as Text3 } from "ink";
151
- import { jsx as jsx3 } from "react/jsx-runtime";
152
- var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
153
- var Spinner = () => {
154
- const [frame, setFrame] = useState(0);
155
- useEffect(() => {
156
- const id = setInterval(() => {
157
- setFrame((prev) => (prev + 1) % SPINNER_FRAMES.length);
158
- }, 80);
159
- return () => {
160
- clearInterval(id);
161
- };
162
- }, []);
163
- return /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: SPINNER_FRAMES[frame] ?? "\u280B" });
164
- };
165
-
166
- // src/ui/BuildUI.tsx
167
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
168
- var STEPS = [
169
- "creating-dir",
170
- "reading-files",
171
- "minifying",
172
- "packaging",
173
- "writing-bundle"
174
- ];
175
- var STEP_LABELS = {
176
- "creating-dir": "Creating .kizenapp directory",
177
- "reading-files": "Reading plugin files",
178
- minifying: "Minifying scripts",
179
- packaging: "Packaging plugin",
180
- "writing-bundle": "Writing bundle.json"
181
- };
182
- var BuildUI = ({ outputDir, pluginDir }) => {
183
- const app = useApp();
184
- const [step, setStep] = useState2("creating-dir");
185
- const [errorMessage, setErrorMessage] = useState2(null);
186
- const [bundleSize, setBundleSize] = useState2(null);
187
- useEffect2(() => {
188
- void runBuild(pluginDir, outputDir, setStep).then(({ bundleSize: size }) => {
189
- setBundleSize(size);
190
- setStep("done");
191
- app.exit();
192
- }).catch((err) => {
193
- const message = err instanceof Error ? err.message : String(err);
194
- setErrorMessage(message);
195
- setStep("error");
196
- app.exit();
197
- });
198
- }, [outputDir, pluginDir, app]);
199
- const currentIndex = STEPS.indexOf(step);
200
- const isError = step === "error";
201
- const isDone = step === "done";
202
- return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
203
- /* @__PURE__ */ jsx4(AppHeader, { marginBottom: 1 }),
204
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", gap: 0, children: [
205
- STEPS.map((s, i) => {
206
- const isActive = step === s;
207
- const isStepDone = isDone || currentIndex > i;
208
- const isFailed = isError && i === currentIndex;
209
- return /* @__PURE__ */ jsx4(Box3, { gap: 1, children: isFailed ? /* @__PURE__ */ jsxs2(Text4, { color: "red", children: [
210
- "\u2717 ",
211
- STEP_LABELS[s]
212
- ] }) : isStepDone ? /* @__PURE__ */ jsxs2(Text4, { color: "green", children: [
213
- "\u2713 ",
214
- STEP_LABELS[s]
215
- ] }) : isActive ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
216
- /* @__PURE__ */ jsx4(Spinner, {}),
217
- /* @__PURE__ */ jsx4(Text4, { children: STEP_LABELS[s] })
218
- ] }) : /* @__PURE__ */ jsxs2(Text4, { dimColor: true, children: [
219
- " ",
220
- STEP_LABELS[s]
221
- ] }) }, s);
222
- }),
223
- isDone && bundleSize !== null && /* @__PURE__ */ jsx4(Box3, { marginTop: 1, gap: 1, children: /* @__PURE__ */ jsxs2(Text4, { color: "green", children: [
224
- "\u2713 bundle \u2014 ",
225
- formatBytes(bundleSize)
226
- ] }) }),
227
- errorMessage !== null && /* @__PURE__ */ jsx4(Box3, { marginTop: 1, borderStyle: "single", borderColor: "red", paddingX: 1, children: /* @__PURE__ */ jsx4(Text4, { color: "red", children: errorMessage }) })
228
- ] })
229
- ] });
230
- };
231
-
232
- // src/lib/gitignore.ts
233
- import * as fs from "fs";
234
- import * as path from "path";
235
- function ensureGitignore(projectDir) {
236
- const gitignorePath = path.join(projectDir, ".gitignore");
237
- const entry = ".kizenapp/";
238
- let contents = "";
239
- if (fs.existsSync(gitignorePath)) {
240
- contents = fs.readFileSync(gitignorePath, "utf8");
241
- const lines = contents.split("\n").map((l) => l.trim());
242
- if (lines.includes(entry) || lines.includes(".kizenapp")) {
243
- return;
244
- }
245
- }
246
- const addition = contents.endsWith("\n") ? entry + "\n" : "\n" + entry + "\n";
247
- fs.writeFileSync(gitignorePath, contents + addition);
248
- }
249
-
250
- // src/commands/build.ts
251
- function buildCommand(program2) {
252
- program2.command("build").description("Bundle the plugin app into .kizenapp directory").action(async () => {
253
- const pluginDir = process.cwd();
254
- const outputDir = `${pluginDir}/.kizenapp`;
255
- ensureGitignore(pluginDir);
256
- const { waitUntilExit } = render(createElement(BuildUI, { outputDir, pluginDir }));
257
- await waitUntilExit();
258
- });
259
- }
260
-
261
- // src/commands/create.ts
262
- import { createElement as createElement2 } from "react";
263
- import { render as render2 } from "ink";
264
-
265
- // src/ui/CreateUI.tsx
266
- import { useCallback, useState as useState3 } from "react";
267
- import { Box as Box4, Text as Text5, useApp as useApp2, useInput } from "ink";
268
- import { join as join5 } from "path";
269
-
270
- // src/lib/createPlugin.ts
271
- import { access, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
272
- import { join as join4 } from "path";
273
- async function pathExists(path2) {
274
- try {
275
- await access(path2);
276
- return true;
277
- } catch {
278
- return false;
279
- }
280
- }
281
- async function precheckTargetDir(dir) {
282
- if (await pathExists(join4(dir, "kizen.json"))) {
283
- return "has-manifest";
284
- }
285
- if (await pathExists(join4(dir, ".kizenapp"))) {
286
- return "has-kizenapp";
287
- }
288
- return "ok";
289
- }
290
- async function findAvailableSubDir(parentDir, baseName) {
291
- const base = join4(parentDir, baseName);
292
- if (!await pathExists(base)) {
293
- return base;
294
- }
295
- for (let i = 1; i < 1e3; i++) {
296
- const candidate = join4(parentDir, `${baseName}-${String(i)}`);
297
- if (!await pathExists(candidate)) {
298
- return candidate;
299
- }
300
- }
301
- throw new Error(`Could not find an available directory name for "${baseName}"`);
302
- }
303
- function buildManifest(input) {
304
- return {
305
- name: input.name,
306
- version: "1.0.0",
307
- published: true,
308
- api_name: input.apiName,
309
- external_link: input.externalLink,
310
- description: input.description,
311
- entry: "src/",
312
- engine: "1.0.0",
313
- release_notes_directory: "releaseNotes/",
314
- release_environments: ["prod"],
315
- config_template: {},
316
- base_config: {},
317
- developer_business_id: input.developerBusinessId
318
- };
319
- }
320
- async function createPlugin(input) {
321
- await mkdir2(input.targetDir, { recursive: true });
322
- const manifest = buildManifest(input);
323
- await writeFile2(
324
- join4(input.targetDir, "kizen.json"),
325
- JSON.stringify(manifest, null, 2) + "\n",
326
- "utf-8"
327
- );
328
- await mkdir2(join4(input.targetDir, "src"), { recursive: true });
329
- await mkdir2(join4(input.targetDir, "releaseNotes"), { recursive: true });
330
- ensureGitignore(input.targetDir);
331
- }
332
-
333
- // src/ui/CreateUI.tsx
334
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
335
- var FIELDS = ["name", "apiName", "externalLink", "description", "developerBusinessId"];
336
- var FIELD_LABELS = {
337
- name: "Name",
338
- apiName: "API name",
339
- externalLink: "External link",
340
- description: "Description",
341
- developerBusinessId: "Business ID"
342
- };
343
- var REQUIRED_FIELDS = ["name", "apiName"];
344
- function inferApiName(name) {
345
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
346
- }
347
- var Hint = ({ text }) => /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: text });
348
- function emptyValues(defaultBusinessId) {
349
- return {
350
- name: "",
351
- apiName: "",
352
- externalLink: "",
353
- description: "",
354
- developerBusinessId: defaultBusinessId
355
- };
356
- }
357
- var CreateUI = ({ parentDir, defaultBusinessId }) => {
358
- const app = useApp2();
359
- const [phase, setPhase] = useState3({ type: "target-select", cursor: 0 });
360
- const [mode, setMode] = useState3("in-place");
361
- const [values, setValues] = useState3(() => emptyValues(defaultBusinessId));
362
- const [inputBuffer, setInputBuffer] = useState3("");
363
- const [targetDir, setTargetDir] = useState3(parentDir);
364
- const enterFieldEntry = useCallback((nextMode, initialValues) => {
365
- setMode(nextMode);
366
- setInputBuffer(initialValues.name);
367
- setPhase({ type: "field-entry", field: 0, values: initialValues });
368
- }, []);
369
- const runPrecheck = useCallback(
370
- async (dir) => {
371
- const result = await precheckTargetDir(dir);
372
- if (result !== "ok") {
373
- setPhase({ type: "precheck-error", targetDir: dir, reason: result });
374
- return;
375
- }
376
- setTargetDir(dir);
377
- enterFieldEntry("in-place", values);
378
- },
379
- [values, enterFieldEntry]
380
- );
381
- const resolveSubDirAndConfirm = useCallback(
382
- async (finalValues) => {
383
- try {
384
- const dir = await findAvailableSubDir(parentDir, finalValues.apiName);
385
- setTargetDir(dir);
386
- setPhase({ type: "confirm", values: finalValues });
387
- } catch (err) {
388
- setPhase({
389
- type: "error",
390
- message: err instanceof Error ? err.message : String(err)
391
- });
392
- }
393
- },
394
- [parentDir]
395
- );
396
- const runWrite = useCallback(async (finalValues, dir) => {
397
- try {
398
- await createPlugin({
399
- targetDir: dir,
400
- name: finalValues.name,
401
- apiName: finalValues.apiName,
402
- externalLink: finalValues.externalLink,
403
- description: finalValues.description,
404
- developerBusinessId: finalValues.developerBusinessId
405
- });
406
- setPhase({ type: "done", targetDir: dir });
407
- } catch (err) {
408
- setPhase({
409
- type: "error",
410
- message: err instanceof Error ? err.message : String(err)
411
- });
412
- }
413
- }, []);
414
- useInput((input, key) => {
415
- if (key.ctrl && input === "c") {
416
- app.exit();
417
- return;
418
- }
419
- if (phase.type === "target-select") {
420
- if (key.upArrow) {
421
- setPhase({ type: "target-select", cursor: 0 });
422
- } else if (key.downArrow) {
423
- setPhase({ type: "target-select", cursor: 1 });
424
- } else if (key.return) {
425
- if (phase.cursor === 0) {
426
- void runPrecheck(parentDir);
427
- } else {
428
- enterFieldEntry("sub-dir", values);
429
- }
430
- } else if (key.escape) {
431
- app.exit();
432
- }
433
- return;
434
- }
435
- if (phase.type === "precheck-error") {
436
- if (key.return || key.escape) {
437
- app.exit();
438
- }
439
- return;
440
- }
441
- if (phase.type === "field-entry") {
442
- const { field, values: current } = phase;
443
- const fieldName = FIELDS[field];
444
- if (!fieldName) {
445
- return;
446
- }
447
- if (key.escape) {
448
- app.exit();
449
- return;
450
- }
451
- if (key.backspace || key.delete) {
452
- setInputBuffer((prev) => prev.slice(0, -1));
453
- return;
454
- }
455
- if (key.upArrow && field > 0) {
456
- const updated = { ...current, [fieldName]: inputBuffer };
457
- const prevField = field - 1;
458
- const prevName = FIELDS[prevField];
459
- setPhase({ type: "field-entry", field: prevField, values: updated });
460
- setValues(updated);
461
- setInputBuffer(prevName ? updated[prevName] : "");
462
- return;
463
- }
464
- if (key.return || key.tab || key.downArrow) {
465
- const buffer = inputBuffer.trim();
466
- const isRequired = REQUIRED_FIELDS.includes(fieldName);
467
- if (isRequired && !buffer) {
468
- setPhase({
469
- type: "field-entry",
470
- field,
471
- values: current,
472
- fieldError: `${FIELD_LABELS[fieldName]} is required`
473
- });
474
- return;
475
- }
476
- const valueToStore = fieldName === "description" ? inputBuffer : buffer;
477
- const updated = { ...current, [fieldName]: valueToStore };
478
- setValues(updated);
479
- const nextField = field + 1;
480
- if (nextField >= FIELDS.length) {
481
- setInputBuffer("");
482
- if (mode === "sub-dir") {
483
- setPhase({ type: "resolving-target" });
484
- void resolveSubDirAndConfirm(updated);
485
- } else {
486
- setPhase({ type: "confirm", values: updated });
487
- }
488
- } else {
489
- const nextName = FIELDS[nextField];
490
- const existing = nextName ? updated[nextName] : "";
491
- const nextBuffer = nextName === "apiName" && !existing ? inferApiName(updated.name) : existing;
492
- setPhase({ type: "field-entry", field: nextField, values: updated });
493
- setInputBuffer(nextBuffer);
494
- }
495
- return;
496
- }
497
- if (input && !key.ctrl && !key.meta) {
498
- setInputBuffer((prev) => prev + input);
499
- }
500
- return;
501
- }
502
- if (phase.type === "confirm") {
503
- if (key.escape) {
504
- const lastField = FIELDS.length - 1;
505
- const lastName = FIELDS[lastField];
506
- setPhase({ type: "field-entry", field: lastField, values: phase.values });
507
- setInputBuffer(lastName ? phase.values[lastName] : "");
508
- return;
509
- }
510
- if (key.return) {
511
- setPhase({ type: "writing" });
512
- void runWrite(phase.values, targetDir);
513
- }
514
- return;
515
- }
516
- if (phase.type === "done" || phase.type === "error") {
517
- if (key.return || key.escape) {
518
- app.exit();
519
- }
520
- }
521
- });
522
- if (phase.type === "target-select") {
523
- const options = [
524
- { label: "Use current directory", desc: parentDir },
525
- { label: "Create new sub-directory", desc: `${parentDir}/<inferred from API name>` }
526
- ];
527
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
528
- /* @__PURE__ */ jsx5(AppHeader, {}),
529
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
530
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Where should the new plugin live?" }),
531
- options.map((opt, i) => {
532
- const selected = phase.cursor === i;
533
- return /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
534
- /* @__PURE__ */ jsx5(Text5, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
535
- /* @__PURE__ */ jsx5(Text5, { bold: selected, ...selected && { color: "cyan" }, children: opt.label }),
536
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: opt.desc })
537
- ] }, opt.label);
538
- })
539
- ] }),
540
- /* @__PURE__ */ jsx5(Hint, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Esc to cancel" })
541
- ] });
542
- }
543
- if (phase.type === "precheck-error") {
544
- const { targetDir: dir, reason } = phase;
545
- const conflict = reason === "has-manifest" ? "kizen.json" : ".kizenapp/";
546
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
547
- /* @__PURE__ */ jsx5(AppHeader, {}),
548
- /* @__PURE__ */ jsx5(Text5, { color: "red", bold: true, children: "Cannot create plugin" }),
549
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
550
- /* @__PURE__ */ jsxs3(Text5, { children: [
551
- dir,
552
- " already contains ",
553
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: conflict }),
554
- "."
555
- ] }),
556
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Remove the conflicting file/directory or choose a different target to avoid overwriting an existing plugin." })
557
- ] }),
558
- /* @__PURE__ */ jsx5(Hint, { text: "Enter or Esc to exit" })
559
- ] });
560
- }
561
- if (phase.type === "field-entry") {
562
- const { field: activeField, fieldError } = phase;
563
- const targetLine = mode === "sub-dir" ? `Target: sub-directory of ${parentDir} (inferred from API name)` : `Target: ${targetDir}`;
564
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
565
- /* @__PURE__ */ jsx5(AppHeader, {}),
566
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
567
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Plugin details" }),
568
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: targetLine })
569
- ] }),
570
- fieldError !== void 0 && /* @__PURE__ */ jsxs3(Text5, { color: "red", children: [
571
- "Error: ",
572
- fieldError
573
- ] }),
574
- /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", gap: 0, children: FIELDS.map((name, i) => {
575
- const isActive = activeField === i;
576
- const displayValue = isActive ? inputBuffer : values[name];
577
- const isRequired = REQUIRED_FIELDS.includes(name);
578
- const label = `${FIELD_LABELS[name]}${isRequired ? "" : " (optional)"}`;
579
- return /* @__PURE__ */ jsxs3(Box4, { gap: 2, children: [
580
- /* @__PURE__ */ jsx5(Box4, { width: 24, children: /* @__PURE__ */ jsx5(Text5, { bold: isActive, ...isActive && { color: "cyan" }, children: label }) }),
581
- /* @__PURE__ */ jsx5(Text5, { ...isActive && { color: "cyan" }, children: ">" }),
582
- /* @__PURE__ */ jsx5(Text5, { children: displayValue }),
583
- isActive && /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u2588" })
584
- ] }, name);
585
- }) }),
586
- /* @__PURE__ */ jsx5(Hint, { text: "Enter/Tab/\u2193 next \xB7 \u2191 previous \xB7 Backspace delete \xB7 Esc cancel" })
587
- ] });
588
- }
589
- if (phase.type === "confirm") {
590
- const manifest = buildManifest({
591
- targetDir,
592
- name: phase.values.name,
593
- apiName: phase.values.apiName,
594
- externalLink: phase.values.externalLink,
595
- description: phase.values.description,
596
- developerBusinessId: phase.values.developerBusinessId
597
- });
598
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
599
- /* @__PURE__ */ jsx5(AppHeader, {}),
600
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
601
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Ready to create" }),
602
- /* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
603
- "Target: ",
604
- targetDir
605
- ] })
606
- ] }),
607
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
608
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "kizen.json preview" }),
609
- /* @__PURE__ */ jsx5(Text5, { children: JSON.stringify(manifest, null, 2) })
610
- ] }),
611
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
612
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Also creates:" }),
613
- /* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
614
- " ",
615
- join5(targetDir, "src/")
616
- ] }),
617
- /* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
618
- " ",
619
- join5(targetDir, "releaseNotes/")
620
- ] }),
621
- /* @__PURE__ */ jsxs3(Text5, { dimColor: true, children: [
622
- " ",
623
- join5(targetDir, ".gitignore"),
624
- " (adds .kizenapp/)"
625
- ] })
626
- ] }),
627
- /* @__PURE__ */ jsx5(Hint, { text: "Enter to create \xB7 Esc to go back" })
628
- ] });
629
- }
630
- if (phase.type === "resolving-target") {
631
- return /* @__PURE__ */ jsx5(Box4, { paddingY: 1, paddingX: 2, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Finding available sub-directory\u2026" }) });
632
- }
633
- if (phase.type === "writing") {
634
- return /* @__PURE__ */ jsx5(Box4, { paddingY: 1, paddingX: 2, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Creating plugin\u2026" }) });
635
- }
636
- if (phase.type === "done") {
637
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
638
- /* @__PURE__ */ jsx5(AppHeader, {}),
639
- /* @__PURE__ */ jsxs3(Text5, { color: "green", bold: true, children: [
640
- "Plugin created at ",
641
- phase.targetDir
642
- ] }),
643
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 0, children: [
644
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Next steps:" }),
645
- /* @__PURE__ */ jsxs3(Text5, { children: [
646
- " ",
647
- "cd ",
648
- phase.targetDir
649
- ] }),
650
- /* @__PURE__ */ jsxs3(Text5, { children: [
651
- " ",
652
- "appbuilder dev"
653
- ] })
654
- ] }),
655
- /* @__PURE__ */ jsx5(Hint, { text: "Enter or Esc to exit" })
656
- ] });
657
- }
658
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
659
- /* @__PURE__ */ jsx5(AppHeader, {}),
660
- /* @__PURE__ */ jsx5(Text5, { color: "red", bold: true, children: "Failed to create plugin" }),
661
- /* @__PURE__ */ jsx5(Text5, { children: phase.message }),
662
- /* @__PURE__ */ jsx5(Hint, { text: "Enter or Esc to exit" })
663
- ] });
664
- };
665
-
666
- // src/lib/credentials.ts
667
- import { mkdir as mkdir3, readFile as readFile2, readdir as readdir2, writeFile as writeFile3 } from "fs/promises";
668
- import { homedir } from "os";
669
- import { dirname, join as join6 } from "path";
670
- var ENVIRONMENTS = ["go", "fmo", "staging", "integration", "test1"];
671
- var GLOBAL_CREDENTIALS_DIR = join6(homedir(), ".kizenappbuilder");
672
- var GLOBAL_CREDENTIALS_PATH = join6(GLOBAL_CREDENTIALS_DIR, "credentials.json");
673
- var DEFAULT_PROFILE_NAME = "credentials";
674
- function isValidEnvironment(value) {
675
- return ENVIRONMENTS.includes(value);
676
- }
677
- function parseCredentials(raw) {
678
- if (typeof raw !== "object" || raw === null) {
679
- throw new Error("Credentials must be a JSON object");
680
- }
681
- const obj = raw;
682
- const env = obj.environment;
683
- return {
684
- apiKey: typeof obj.apiKey === "string" ? obj.apiKey : "",
685
- userId: typeof obj.userId === "string" ? obj.userId : "",
686
- businessId: typeof obj.businessId === "string" ? obj.businessId : "",
687
- environment: isValidEnvironment(env) ? env : "go"
688
- };
689
- }
690
- async function loadCredentialsFromFile(filePath) {
691
- const content = await readFile2(filePath, "utf-8");
692
- return parseCredentials(JSON.parse(content));
693
- }
694
- async function loadGlobalCredentials() {
695
- try {
696
- return await loadCredentialsFromFile(GLOBAL_CREDENTIALS_PATH);
697
- } catch {
698
- return null;
699
- }
700
- }
701
- async function saveGlobalCredentials(credentials) {
702
- await mkdir3(dirname(GLOBAL_CREDENTIALS_PATH), { recursive: true });
703
- await writeFile3(GLOBAL_CREDENTIALS_PATH, JSON.stringify(credentials, null, 2), "utf-8");
704
- }
705
- function getProfilePath(name) {
706
- return join6(GLOBAL_CREDENTIALS_DIR, `${name}.json`);
707
- }
708
- async function listCredentialProfiles() {
709
- const profiles = [
710
- { name: DEFAULT_PROFILE_NAME, path: GLOBAL_CREDENTIALS_PATH, isDefault: true }
711
- ];
712
- try {
713
- const entries = await readdir2(GLOBAL_CREDENTIALS_DIR);
714
- for (const entry of entries) {
715
- if (!entry.endsWith(".json")) {
716
- continue;
717
- }
718
- const name = entry.slice(0, -5);
719
- if (name === DEFAULT_PROFILE_NAME) {
720
- continue;
721
- }
722
- profiles.push({ name, path: join6(GLOBAL_CREDENTIALS_DIR, entry), isDefault: false });
723
- }
724
- } catch {
725
- }
726
- return profiles;
727
- }
728
- async function saveCredentialProfile(name, credentials) {
729
- if (name === DEFAULT_PROFILE_NAME) {
730
- await saveGlobalCredentials(credentials);
731
- return;
732
- }
733
- await mkdir3(GLOBAL_CREDENTIALS_DIR, { recursive: true });
734
- await writeFile3(getProfilePath(name), JSON.stringify(credentials, null, 2), "utf-8");
735
- }
736
- async function loadCredentialProfile(name) {
737
- try {
738
- return await loadCredentialsFromFile(getProfilePath(name));
739
- } catch {
740
- return null;
741
- }
742
- }
743
-
744
- // src/commands/create.ts
745
- function createCommand(program2) {
746
- program2.command("create").description("Scaffold a new Kizen plugin project").action(async () => {
747
- const parentDir = process.cwd();
748
- const globalCreds = await loadGlobalCredentials();
749
- const defaultBusinessId = globalCreds?.businessId ?? "";
750
- const { waitUntilExit } = render2(createElement2(CreateUI, { parentDir, defaultBusinessId }), {
751
- exitOnCtrlC: false
752
- });
753
- await waitUntilExit();
754
- });
755
- }
756
-
757
- // src/commands/dev.ts
758
- import { createElement as createElement3 } from "react";
759
- import { render as render3 } from "ink";
760
-
761
- // src/ui/DevUI.tsx
762
- import { useCallback as useCallback4, useEffect as useEffect4, useRef, useState as useState6 } from "react";
763
- import { Box as Box6, Text as Text7, useInput as useInput3 } from "ink";
764
- import { watch } from "fs";
765
- import { createServer } from "http";
766
- import { join as join11 } from "path";
767
- import { WebSocketServer } from "ws";
768
-
769
- // src/server/requestHandler.ts
770
- import { createReadStream } from "fs";
771
- import { access as access3, readdir as readdir3, readFile as readFile4, stat } from "fs/promises";
772
- import { dirname as dirname2, extname, join as join9, resolve, sep } from "path";
773
- import { fileURLToPath } from "url";
774
-
775
- // src/lib/proxyCache.ts
776
- function createProxyCache(options = {}) {
777
- const cache = /* @__PURE__ */ new Map();
778
- const { onChange } = options;
779
- const emitChange = () => {
780
- onChange?.(cache.size);
781
- };
782
- return {
783
- get(key, fetcher) {
784
- const existing = cache.get(key);
785
- if (existing !== void 0) {
786
- return existing.then((response) => ({ response, fromCache: true }));
787
- }
788
- const pending = fetcher().then(async (response) => {
789
- const headers = Object.fromEntries(response.headers);
790
- delete headers["content-encoding"];
791
- delete headers["content-length"];
792
- return {
793
- status: response.status,
794
- headers,
795
- body: Buffer.from(await response.arrayBuffer())
796
- };
797
- }).catch((error) => {
798
- cache.delete(key);
799
- emitChange();
800
- throw error;
801
- });
802
- cache.set(key, pending);
803
- emitChange();
804
- return pending.then((response) => {
805
- if (response.status >= 400) {
806
- cache.delete(key);
807
- emitChange();
808
- }
809
- return { response, fromCache: false };
810
- });
811
- },
812
- clear() {
813
- cache.clear();
814
- emitChange();
815
- }
816
- };
817
- }
818
-
819
- // src/lib/constants.ts
820
- var LOG_LIMIT = 50;
821
- var LOG_DISPLAY = 8;
822
- var FILE_WATCH_DEBOUNCE_MS = 150;
823
- var MIME_TYPES = {
824
- ".html": "text/html; charset=utf-8",
825
- ".js": "application/javascript; charset=utf-8",
826
- ".mjs": "application/javascript; charset=utf-8",
827
- ".css": "text/css; charset=utf-8",
828
- ".png": "image/png",
829
- ".jpg": "image/jpeg",
830
- ".svg": "image/svg+xml",
831
- ".ico": "image/x-icon",
832
- ".json": "application/json; charset=utf-8",
833
- ".woff": "font/woff",
834
- ".woff2": "font/woff2",
835
- ".ttf": "font/ttf",
836
- ".map": "application/json"
837
- };
838
-
839
- // src/lib/config.ts
840
- import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
841
- import { join as join7 } from "path";
842
- async function loadConfig(outputDir) {
843
- try {
844
- const content = await readFile3(join7(outputDir, "config.json"), "utf-8");
845
- return JSON.parse(content);
846
- } catch {
847
- return {};
848
- }
849
- }
850
- async function saveConfig(outputDir, config) {
851
- await mkdir4(outputDir, { recursive: true });
852
- await writeFile4(join7(outputDir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
853
- }
854
-
855
- // src/server/pythonExecutor.ts
856
- import { spawn } from "child_process";
857
- import { writeFile as writeFile5, rm, mkdtemp, access as access2 } from "fs/promises";
858
- import { join as join8 } from "path";
859
- import { tmpdir } from "os";
860
- var WRAPPER_SCRIPT = `
2
+ import{readFileSync as go}from"fs";import{dirname as yo,join as ho}from"path";import{fileURLToPath as xo}from"url";import{program as Le}from"commander";import{createElement as en}from"react";import{render as tn}from"ink";import{useEffect as Yr,useState as ct}from"react";import{Box as Pe,Text as me,useApp as Zr}from"ink";import{mkdir as Dr,writeFile as Rr}from"fs/promises";import{join as Nr}from"path";import{minifyFiles as Lr,packagePlugin as jr,transformDeployablePlugin as Fr}from"@kizenapps/packager";import{readdir as Er,readFile as at}from"fs/promises";import{join as _t,relative as kr}from"path";var Ir=new Set([".png",".svg"]),Br=new Set([".kzn"]),$e=new Set(["node_modules",".git",".kizenapp",".github"]);async function Et(e,t){let i=await Er(e,{withFileTypes:!0}),n=[];for(let r of i)if(r.isDirectory()){if($e.has(r.name))continue;n.push(...await Et(_t(e,r.name),t))}else r.isFile()&&n.push(_t(e,r.name));return n}async function kt(e){let t=await Et(e,e);return Promise.all(t.map(async i=>{let n=kr(e,i).split("\\").join("/"),r=n.lastIndexOf("."),c=r>=0?n.slice(r).toLowerCase():"";if(Ir.has(c)){let y=await at(i);return{path:n,content:"",base64Image:y.toString("base64")}}if(Br.has(c)){let y=await at(i);return{path:n,content:"",binaryData:y}}let S=await at(i,"utf-8");return{path:n,content:S}}))}var Ar=["name","api_name","version","entry"];function It(e,t){let i=t===void 0?"kizen.json":`kizen.json entry #${String(t+1)}`;for(let n of Ar){let r=e[n];if(typeof r!="string"||r==="")throw new Error(`${i} is missing required field "${n}" (expected a non-empty string).`)}}function Or(e){let t;try{t=JSON.parse(e)}catch(i){let n=i instanceof Error?i.message:String(i);throw new Error(`kizen.json is not valid JSON: ${n}`)}if(Array.isArray(t))return t.forEach((i,n)=>{if(typeof i!="object"||i===null)throw new Error(`kizen.json entry #${String(n+1)} is not a JSON object.`);It(i,n)}),t;if(typeof t!="object"||t===null)throw new Error("kizen.json must be a JSON object (or array of objects).");return It(t),t}var $r=(e,t)=>({...e,thumbnail:e.thumbnail?Buffer.from(e.thumbnail).toString("base64"):null,kznFile:e.kznFile?Buffer.from(e.kznFile).toString("base64"):null,allReleaseNotes:t});async function ze(e,t,i){await Dr(t,{recursive:!0}),i?.("reading-files");let n=await kt(e);i?.("minifying");let r=await Lr(n);i?.("packaging");let c=r.find(a=>a.path==="kizen.json");if(!c)throw new Error("kizen.json not found in plugin directory.");let S=Or(c.content),y=jr(r,S),_=Object.values(y).map(Fr);i?.("writing-bundle");let b=_.map(a=>{let v=a.release_notes_directory?.replace(/\/$/,""),N=v?n.filter(E=>E.path.startsWith(v+"/")&&E.path.endsWith(".md")&&E.content).map(E=>({version:E.path.slice(E.path.lastIndexOf("/")+1,-3),notes:E.content})).sort((E,C)=>C.version.localeCompare(E.version,void 0,{numeric:!0})):[];return $r(a,N)}),h=JSON.stringify(b,null,2),g=(a,v)=>a==="allReleaseNotes"?void 0:v,f=Buffer.byteLength(JSON.stringify(b,g,2),"utf-8");return await Rr(Nr(t,"bundle.json"),h,"utf-8"),{bundleSize:f}}function Se(e){return e<1024?`${String(e)} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(2)} MB`}import{Box as Ur,Text as qr}from"ink";import{Box as zr,Text as Mr}from"ink";import{jsx as Bt}from"react/jsx-runtime";var Hr=[" :::::::: "," :::::: ::::::::::: "," :::::: :::::::::: "," :::::: :::::: ::::::: "," ::::: :::::: ::::::::::::::::: "," ::::: ::::: ::::::::::::::::::: "," ::::: ::::: :::::: ::::: ",":::: ::::: ::: ::::: :: ","::::: :::::: :::::::::: ","::::: :::::: ::::::::::: "," ::::: ::: ::::::: "," ::::::: ::: ::::: "," ::::::::::: :::::: :::::"," :::::::::: :::::: :::::"," :: ::::: ::: ::::: ::::"," ::::: :::::: ::::: ::::: ::: "," ::::::::::::::::::: ::::: ::::: "," ::::::::::::::::: ::::: ::::: "," ::::::::: :::::: :::::: "," :::::::::: :::::: "," ::::::::::: :::::: "," :::::::: "],Dt=()=>Bt(zr,{flexDirection:"column",children:Hr.map((e,t)=>Bt(Mr,{color:"cyan",children:e},t))});import{Fragment as Vr,jsx as lt,jsxs as Jr}from"react/jsx-runtime";var V=({marginBottom:e=0})=>Jr(Vr,{children:[lt(Dt,{}),lt(Ur,{flexDirection:"column",marginTop:1,marginBottom:e,children:lt(qr,{bold:!0,color:"cyan",children:"Kizen App Development Toolkit"})})]});import{useEffect as Wr,useState as Kr}from"react";import{Text as Gr}from"ink";import{jsx as Xr}from"react/jsx-runtime";var Rt=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],Me=()=>{let[e,t]=Kr(0);return Wr(()=>{let i=setInterval(()=>{t(n=>(n+1)%Rt.length)},80);return()=>{clearInterval(i)}},[]),Xr(Gr,{color:"cyan",children:Rt[e]??"\u280B"})};import{Fragment as Qr,jsx as ae,jsxs as le}from"react/jsx-runtime";var Nt=["creating-dir","reading-files","minifying","packaging","writing-bundle"],He={"creating-dir":"Creating .kizenapp directory","reading-files":"Reading plugin files",minifying:"Minifying scripts",packaging:"Packaging plugin","writing-bundle":"Writing bundle.json"},Lt=({outputDir:e,pluginDir:t})=>{let i=Zr(),[n,r]=ct("creating-dir"),[c,S]=ct(null),[y,_]=ct(null);Yr(()=>{ze(t,e,r).then(({bundleSize:f})=>{_(f),r("done"),i.exit()}).catch(f=>{let a=f instanceof Error?f.message:String(f);S(a),r("error"),i.exit()})},[e,t,i]);let b=Nt.indexOf(n),h=n==="error",g=n==="done";return le(Pe,{flexDirection:"column",paddingY:1,paddingX:2,children:[ae(V,{marginBottom:1}),le(Pe,{flexDirection:"column",gap:0,children:[Nt.map((f,a)=>{let v=n===f,N=g||b>a;return ae(Pe,{gap:1,children:h&&a===b?le(me,{color:"red",children:["\u2717 ",He[f]]}):N?le(me,{color:"green",children:["\u2713 ",He[f]]}):v?le(Qr,{children:[ae(Me,{}),ae(me,{children:He[f]})]}):le(me,{dimColor:!0,children:[" ",He[f]]})},f)}),g&&y!==null&&ae(Pe,{marginTop:1,gap:1,children:le(me,{color:"green",children:["\u2713 bundle \u2014 ",Se(y)]})}),c!==null&&ae(Pe,{marginTop:1,borderStyle:"single",borderColor:"red",paddingX:1,children:ae(me,{color:"red",children:c})})]})]})};import*as ge from"fs";import*as jt from"path";function ye(e){let t=jt.join(e,".gitignore"),i=".kizenapp/",n="";if(ge.existsSync(t)){n=ge.readFileSync(t,"utf8");let c=n.split(`
3
+ `).map(S=>S.trim());if(c.includes(i)||c.includes(".kizenapp"))return}let r=n.endsWith(`
4
+ `)?i+`
5
+ `:`
6
+ `+i+`
7
+ `;ge.writeFileSync(t,n+r)}function Ft(e){e.command("build").description("Bundle the plugin app into .kizenapp directory").action(async()=>{let t=process.cwd(),i=`${t}/.kizenapp`;ye(t);let{waitUntilExit:n}=tn(en(Lt,{outputDir:i,pluginDir:t}));await n()})}import{createElement as yn}from"react";import{render as hn}from"ink";import{useCallback as qe,useState as Te}from"react";import{Box as M,Text as B,useApp as on,useInput as sn}from"ink";import{join as pt}from"path";import{access as rn,mkdir as dt,writeFile as nn}from"fs/promises";import{join as ce}from"path";async function Ue(e){try{return await rn(e),!0}catch{return!1}}async function At(e){return await Ue(ce(e,"kizen.json"))?"has-manifest":await Ue(ce(e,".kizenapp"))?"has-kizenapp":"ok"}async function Ot(e,t){let i=ce(e,t);if(!await Ue(i))return i;for(let n=1;n<1e3;n++){let r=ce(e,`${t}-${String(n)}`);if(!await Ue(r))return r}throw new Error(`Could not find an available directory name for "${t}"`)}function ut(e){return{name:e.name,version:"1.0.0",published:!0,api_name:e.apiName,external_link:e.externalLink,description:e.description,entry:"src/",engine:"1.0.0",release_notes_directory:"releaseNotes/",release_environments:["prod"],config_template:{},base_config:{},developer_business_id:e.developerBusinessId}}async function $t(e){await dt(e.targetDir,{recursive:!0});let t=ut(e);await nn(ce(e.targetDir,"kizen.json"),JSON.stringify(t,null,2)+`
8
+ `,"utf-8"),await dt(ce(e.targetDir,"src"),{recursive:!0}),await dt(ce(e.targetDir,"releaseNotes"),{recursive:!0}),ye(e.targetDir)}import{jsx as T,jsxs as A}from"react/jsx-runtime";var de=["name","apiName","externalLink","description","developerBusinessId"],zt={name:"Name",apiName:"API name",externalLink:"External link",description:"Description",developerBusinessId:"Business ID"},Mt=["name","apiName"];function an(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"")}var he=({text:e})=>T(B,{dimColor:!0,children:e});function ln(e){return{name:"",apiName:"",externalLink:"",description:"",developerBusinessId:e}}var Ht=({parentDir:e,defaultBusinessId:t})=>{let i=on(),[n,r]=Te({type:"target-select",cursor:0}),[c,S]=Te("in-place"),[y,_]=Te(()=>ln(t)),[b,h]=Te(""),[g,f]=Te(e),a=qe((C,m)=>{S(C),h(m.name),r({type:"field-entry",field:0,values:m})},[]),v=qe(async C=>{let m=await At(C);if(m!=="ok"){r({type:"precheck-error",targetDir:C,reason:m});return}f(C),a("in-place",y)},[y,a]),N=qe(async C=>{try{let m=await Ot(e,C.apiName);f(m),r({type:"confirm",values:C})}catch(m){r({type:"error",message:m instanceof Error?m.message:String(m)})}},[e]),E=qe(async(C,m)=>{try{await $t({targetDir:m,name:C.name,apiName:C.apiName,externalLink:C.externalLink,description:C.description,developerBusinessId:C.developerBusinessId}),r({type:"done",targetDir:m})}catch(u){r({type:"error",message:u instanceof Error?u.message:String(u)})}},[]);if(sn((C,m)=>{if(m.ctrl&&C==="c"){i.exit();return}if(n.type==="target-select"){m.upArrow?r({type:"target-select",cursor:0}):m.downArrow?r({type:"target-select",cursor:1}):m.return?n.cursor===0?v(e):a("sub-dir",y):m.escape&&i.exit();return}if(n.type==="precheck-error"){(m.return||m.escape)&&i.exit();return}if(n.type==="field-entry"){let{field:u,values:w}=n,s=de[u];if(!s)return;if(m.escape){i.exit();return}if(m.backspace||m.delete){h(o=>o.slice(0,-1));return}if(m.upArrow&&u>0){let o={...w,[s]:b},l=u-1,d=de[l];r({type:"field-entry",field:l,values:o}),_(o),h(d?o[d]:"");return}if(m.return||m.tab||m.downArrow){let o=b.trim();if(Mt.includes(s)&&!o){r({type:"field-entry",field:u,values:w,fieldError:`${zt[s]} is required`});return}let d=s==="description"?b:o,p={...w,[s]:d};_(p);let j=u+1;if(j>=de.length)h(""),c==="sub-dir"?(r({type:"resolving-target"}),N(p)):r({type:"confirm",values:p});else{let k=de[j],I=k?p[k]:"",O=k==="apiName"&&!I?an(p.name):I;r({type:"field-entry",field:j,values:p}),h(O)}return}C&&!m.ctrl&&!m.meta&&h(o=>o+C);return}if(n.type==="confirm"){if(m.escape){let u=de.length-1,w=de[u];r({type:"field-entry",field:u,values:n.values}),h(w?n.values[w]:"");return}m.return&&(r({type:"writing"}),E(n.values,g));return}(n.type==="done"||n.type==="error")&&(m.return||m.escape)&&i.exit()}),n.type==="target-select"){let C=[{label:"Use current directory",desc:e},{label:"Create new sub-directory",desc:`${e}/<inferred from API name>`}];return A(M,{flexDirection:"column",paddingY:1,paddingX:2,gap:1,children:[T(V,{}),A(M,{flexDirection:"column",gap:0,children:[T(B,{bold:!0,children:"Where should the new plugin live?"}),C.map((m,u)=>{let w=n.cursor===u;return A(M,{gap:2,children:[T(B,{...w&&{color:"cyan"},children:w?"\u276F":" "}),T(B,{bold:w,...w&&{color:"cyan"},children:m.label}),T(B,{dimColor:!0,children:m.desc})]},m.label)})]}),T(he,{text:"\u2191\u2193 to move \xB7 Enter to select \xB7 Esc to cancel"})]})}if(n.type==="precheck-error"){let{targetDir:C,reason:m}=n;return A(M,{flexDirection:"column",paddingY:1,paddingX:2,gap:1,children:[T(V,{}),T(B,{color:"red",bold:!0,children:"Cannot create plugin"}),A(M,{flexDirection:"column",gap:0,children:[A(B,{children:[C," already contains ",T(B,{bold:!0,children:m==="has-manifest"?"kizen.json":".kizenapp/"}),"."]}),T(B,{dimColor:!0,children:"Remove the conflicting file/directory or choose a different target to avoid overwriting an existing plugin."})]}),T(he,{text:"Enter or Esc to exit"})]})}if(n.type==="field-entry"){let{field:C,fieldError:m}=n,u=c==="sub-dir"?`Target: sub-directory of ${e} (inferred from API name)`:`Target: ${g}`;return A(M,{flexDirection:"column",paddingY:1,paddingX:2,gap:1,children:[T(V,{}),A(M,{flexDirection:"column",gap:0,children:[T(B,{bold:!0,children:"Plugin details"}),T(B,{dimColor:!0,children:u})]}),m!==void 0&&A(B,{color:"red",children:["Error: ",m]}),T(M,{flexDirection:"column",gap:0,children:de.map((w,s)=>{let o=C===s,l=o?b:y[w],d=Mt.includes(w),p=`${zt[w]}${d?"":" (optional)"}`;return A(M,{gap:2,children:[T(M,{width:24,children:T(B,{bold:o,...o&&{color:"cyan"},children:p})}),T(B,{...o&&{color:"cyan"},children:">"}),T(B,{children:l}),o&&T(B,{color:"cyan",children:"\u2588"})]},w)})}),T(he,{text:"Enter/Tab/\u2193 next \xB7 \u2191 previous \xB7 Backspace delete \xB7 Esc cancel"})]})}if(n.type==="confirm"){let C=ut({targetDir:g,name:n.values.name,apiName:n.values.apiName,externalLink:n.values.externalLink,description:n.values.description,developerBusinessId:n.values.developerBusinessId});return A(M,{flexDirection:"column",paddingY:1,paddingX:2,gap:1,children:[T(V,{}),A(M,{flexDirection:"column",gap:0,children:[T(B,{bold:!0,children:"Ready to create"}),A(B,{dimColor:!0,children:["Target: ",g]})]}),A(M,{flexDirection:"column",gap:0,children:[T(B,{bold:!0,children:"kizen.json preview"}),T(B,{children:JSON.stringify(C,null,2)})]}),A(M,{flexDirection:"column",gap:0,children:[T(B,{dimColor:!0,children:"Also creates:"}),A(B,{dimColor:!0,children:[" ",pt(g,"src/")]}),A(B,{dimColor:!0,children:[" ",pt(g,"releaseNotes/")]}),A(B,{dimColor:!0,children:[" ",pt(g,".gitignore")," (adds .kizenapp/)"]})]}),T(he,{text:"Enter to create \xB7 Esc to go back"})]})}return n.type==="resolving-target"?T(M,{paddingY:1,paddingX:2,children:T(B,{dimColor:!0,children:"Finding available sub-directory\u2026"})}):n.type==="writing"?T(M,{paddingY:1,paddingX:2,children:T(B,{dimColor:!0,children:"Creating plugin\u2026"})}):n.type==="done"?A(M,{flexDirection:"column",paddingY:1,paddingX:2,gap:1,children:[T(V,{}),A(B,{color:"green",bold:!0,children:["Plugin created at ",n.targetDir]}),A(M,{flexDirection:"column",gap:0,children:[T(B,{dimColor:!0,children:"Next steps:"}),A(B,{children:[" ","cd ",n.targetDir]}),A(B,{children:[" ","appbuilder dev"]})]}),T(he,{text:"Enter or Esc to exit"})]}):A(M,{flexDirection:"column",paddingY:1,paddingX:2,gap:1,children:[T(V,{}),T(B,{color:"red",bold:!0,children:"Failed to create plugin"}),T(B,{children:n.message}),T(he,{text:"Enter or Esc to exit"})]})};import{mkdir as Ut,readFile as cn,readdir as dn,writeFile as qt}from"fs/promises";import{homedir as un}from"os";import{dirname as pn,join as Ve}from"path";var G=["go","fmo","staging","integration","test1"];var _e=Ve(un(),".kizenappbuilder"),xe=Ve(_e,"credentials.json"),re="credentials";function fn(e){return G.includes(e)}function mn(e){if(typeof e!="object"||e===null)throw new Error("Credentials must be a JSON object");let t=e,i=t.environment;return{apiKey:typeof t.apiKey=="string"?t.apiKey:"",userId:typeof t.userId=="string"?t.userId:"",businessId:typeof t.businessId=="string"?t.businessId:"",environment:fn(i)?i:"go"}}async function Je(e){let t=await cn(e,"utf-8");return mn(JSON.parse(t))}async function Z(){try{return await Je(xe)}catch{return null}}async function gn(e){await Ut(pn(xe),{recursive:!0,mode:448}),await qt(xe,JSON.stringify(e,null,2),{encoding:"utf-8",mode:384})}function Vt(e){return Ve(_e,`${e}.json`)}async function Ee(){let e=[{name:re,path:xe,isDefault:!0}];try{let t=await dn(_e);for(let i of t){if(!i.endsWith(".json"))continue;let n=i.slice(0,-5);n!==re&&e.push({name:n,path:Ve(_e,i),isDefault:!1})}}catch{}return e}async function Jt(e,t){if(e===re){await gn(t);return}await Ut(_e,{recursive:!0,mode:448}),await qt(Vt(e),JSON.stringify(t,null,2),{encoding:"utf-8",mode:384})}async function be(e){try{return await Je(Vt(e))}catch{return null}}function Wt(e){e.command("create").description("Scaffold a new Kizen plugin project").action(async()=>{let t=process.cwd(),n=(await Z())?.businessId??"",{waitUntilExit:r}=hn(yn(Ht,{parentDir:t,defaultBusinessId:n}),{exitOnCtrlC:!1});await r()})}import{createElement as yr}from"react";import{render as hr}from"ink";import{useCallback as ve,useEffect as et,useRef as Q,useState as W}from"react";import{Box as U,Text as R,useInput as co}from"ink";import{watch as uo}from"fs";import{createServer as po}from"http";import{join as fr}from"path";import{WebSocketServer as fo}from"ws";import{createReadStream as rr,realpathSync as Nn}from"fs";import{access as Ln,readdir as jn,readFile as Fn,realpath as An,stat as nr}from"fs/promises";import{dirname as On,extname as or,join as De,resolve as $n,sep as vt}from"path";import{fileURLToPath as zn}from"url";var xn=new Set(["set-cookie","strict-transport-security","content-security-policy","content-security-policy-report-only","x-frame-options","content-encoding","content-length"]);function ft(e){let t={};for(let[i,n]of Object.entries(e)){let r=i.toLowerCase();xn.has(r)||r.startsWith("access-control-")||(t[i]=n)}return t}var mt=10*1024*1024,bn=6e4,vn=200,ke=class extends Error{bytesRead;limit;constructor(t,i){super(`Upstream response exceeded ${String(i)} bytes (read ${String(t)}).`),this.name="ProxyResponseTooLargeError",this.bytesRead=t,this.limit=i}};async function gt(e,t){let i=e.body;if(i===null)return Buffer.alloc(0);let n=[],r=0;for await(let c of i){if(r+=c.length,r>t)throw new ke(r,t);n.push(c)}return Buffer.concat(n)}function Kt(e={}){let t=new Map,{onChange:i}=e,n=()=>{i?.(t.size)},r=c=>{t.delete(c)&&n()};return{get(c,S){let y=t.get(c);if(y!==void 0){if(Date.now()-y.insertedAt<bn)return y.promise.then(b=>({response:b,fromCache:!0}));r(c)}if(t.size>=vn){let b=t.keys().next().value;b!==void 0&&r(b)}let _=S().then(async b=>{let h=ft(Object.fromEntries(b.headers)),g=await gt(b,mt);return{status:b.status,headers:h,body:g}}).catch(b=>{throw r(c),b});return t.set(c,{insertedAt:Date.now(),promise:_}),n(),_.then(b=>((b.status<200||b.status>=300)&&r(c),{response:b,fromCache:!1}))},clear(){t.clear(),n()}}}var yt={".html":"text/html; charset=utf-8",".js":"application/javascript; charset=utf-8",".mjs":"application/javascript; charset=utf-8",".css":"text/css; charset=utf-8",".png":"image/png",".jpg":"image/jpeg",".svg":"image/svg+xml",".ico":"image/x-icon",".json":"application/json; charset=utf-8",".woff":"font/woff",".woff2":"font/woff2",".ttf":"font/ttf",".map":"application/json"};import{mkdir as Cn,readFile as wn,writeFile as Sn}from"fs/promises";import{join as Gt}from"path";async function Ie(e){try{let t=await wn(Gt(e,"config.json"),"utf-8");return JSON.parse(t)}catch{return{}}}async function ue(e,t){await Cn(e,{recursive:!0}),await Sn(Gt(e,"config.json"),JSON.stringify(t,null,2),"utf-8")}import{spawn as Zt}from"child_process";import{writeFile as Xt,rm as Qt,mkdtemp as Pn,access as Tn}from"fs/promises";import{join as Be}from"path";import{tmpdir as _n}from"os";var En=`
861
9
  import sys, json, os, traceback
862
10
 
863
11
  # --- Inputs proxy (attribute access) ---
@@ -1017,1824 +165,10 @@ sys.stdout.write('\\n__KIZEN_RESULT_START__\\n')
1017
165
  sys.stdout.write(json.dumps(result, ensure_ascii=False))
1018
166
  sys.stdout.write('\\n__KIZEN_RESULT_END__\\n')
1019
167
  sys.stdout.flush()
1020
- `.trim();
1021
- var DEFAULT_TIMEOUT = 3e4;
1022
- var VENV_DIR = join8(process.cwd(), ".kizenapp", "venv");
1023
- var VENV_PACKAGES = [
1024
- "awslambdaric",
1025
- "bcrypt",
1026
- "fpdf2",
1027
- "lxml",
1028
- "msgpack",
1029
- "numpy",
1030
- "paramiko",
1031
- "pyjwt",
1032
- "pypdf",
1033
- "pytz",
1034
- "requests",
1035
- "tzdata"
1036
- ];
1037
- var venvReady = null;
1038
- function resolvePythonBinary(runtime) {
1039
- const match = /^python-(\d+\.\d+)$/.exec(runtime);
1040
- if (match?.[1]) {
1041
- return `python${match[1]}`;
1042
- }
1043
- return "python3";
1044
- }
1045
- function runCommand(bin, args, onLine) {
1046
- return new Promise((resolve2, reject) => {
1047
- const proc = spawn(bin, args, { stdio: ["ignore", "pipe", "pipe"] });
1048
- const pipeLines = (source, stream) => {
1049
- const captured = [];
1050
- let buffer = "";
1051
- source.on("data", (chunk) => {
1052
- buffer += chunk.toString("utf-8");
1053
- let idx = buffer.indexOf("\n");
1054
- while (idx >= 0) {
1055
- const line = buffer.slice(0, idx).replace(/\r$/, "");
1056
- buffer = buffer.slice(idx + 1);
1057
- captured.push(line);
1058
- onLine?.(line, stream);
1059
- idx = buffer.indexOf("\n");
1060
- }
1061
- });
1062
- source.on("end", () => {
1063
- if (buffer.length > 0) {
1064
- const line = buffer.replace(/\r$/, "");
1065
- captured.push(line);
1066
- onLine?.(line, stream);
1067
- }
1068
- });
1069
- return captured;
1070
- };
1071
- const stderrCaptured = pipeLines(proc.stderr, "stderr");
1072
- pipeLines(proc.stdout, "stdout");
1073
- proc.on("error", reject);
1074
- proc.on("close", (code) => {
1075
- if (code === 0) {
1076
- resolve2();
1077
- } else {
1078
- const tail = stderrCaptured.slice(-10).join("\n").trim();
1079
- const detail = tail.length > 0 ? `: ${tail}` : "";
1080
- reject(new Error(`${bin} exited with code ${String(code)}${detail}`));
1081
- }
1082
- });
1083
- });
1084
- }
1085
- var installListeners = /* @__PURE__ */ new Set();
1086
- function emitInstall(event) {
1087
- for (const listener of installListeners) {
1088
- listener(event);
1089
- }
1090
- }
1091
- function ensureVenv(runtime, listener) {
1092
- if (listener) {
1093
- installListeners.add(listener);
1094
- }
1095
- if (!venvReady) {
1096
- venvReady = (async () => {
1097
- const venvPython = join8(VENV_DIR, "bin", "python");
1098
- try {
1099
- await access2(venvPython);
1100
- } catch {
1101
- const systemPython = resolvePythonBinary(runtime);
1102
- await runCommand(systemPython, ["-m", "venv", VENV_DIR]);
1103
- }
1104
- emitInstall({ kind: "start" });
1105
- try {
1106
- await runCommand(
1107
- venvPython,
1108
- ["-m", "pip", "install", "--disable-pip-version-check", ...VENV_PACKAGES],
1109
- (line, stream) => {
1110
- emitInstall({ kind: "log", line, stream });
1111
- }
1112
- );
1113
- emitInstall({ kind: "complete" });
1114
- } catch (err) {
1115
- const message = err instanceof Error ? err.message : String(err);
1116
- emitInstall({ kind: "error", message });
1117
- throw err;
1118
- } finally {
1119
- installListeners.clear();
1120
- }
1121
- return venvPython;
1122
- })();
1123
- }
1124
- return venvReady;
1125
- }
1126
- function parseResult(stdout) {
1127
- const startMarker = "__KIZEN_RESULT_START__";
1128
- const endMarker = "__KIZEN_RESULT_END__";
1129
- const startIdx = stdout.indexOf(startMarker);
1130
- const endIdx = stdout.indexOf(endMarker);
1131
- if (startIdx === -1 || endIdx === -1) {
1132
- return { userStdout: stdout, result: null };
1133
- }
1134
- const userStdout = stdout.slice(0, startIdx).trimEnd();
1135
- const jsonStr = stdout.slice(startIdx + startMarker.length, endIdx).trim();
1136
- try {
1137
- const result = JSON.parse(jsonStr);
1138
- return { userStdout, result };
1139
- } catch {
1140
- return { userStdout: stdout, result: null };
1141
- }
1142
- }
1143
- async function executePythonStep(params) {
1144
- const timeout = params.timeout ?? DEFAULT_TIMEOUT;
1145
- const pythonBin = await ensureVenv(params.scriptRuntime, params.onInstallProgress);
1146
- const start = Date.now();
1147
- let tmpDir;
1148
- try {
1149
- tmpDir = await mkdtemp(join8(tmpdir(), "kizen-step-"));
1150
- const wrapperPath = join8(tmpDir, "wrapper.py");
1151
- const scriptPath = join8(tmpDir, "script.py");
1152
- await Promise.all([
1153
- writeFile5(wrapperPath, WRAPPER_SCRIPT, "utf-8"),
1154
- writeFile5(scriptPath, params.script, "utf-8")
1155
- ]);
1156
- const env = {
1157
- ...process.env,
1158
- __KIZEN_INPUTS__: JSON.stringify(params.inputs),
1159
- __KIZEN_SECRETS__: JSON.stringify(params.secrets),
1160
- __KIZEN_SCRIPT_PATH__: scriptPath
1161
- };
1162
- const { exitCode, stdout, stderr } = await new Promise((resolve2) => {
1163
- const stdoutChunks = [];
1164
- const stderrChunks = [];
1165
- let settled = false;
1166
- const proc = spawn(pythonBin, [wrapperPath], {
1167
- env,
1168
- cwd: tmpDir,
1169
- stdio: ["ignore", "pipe", "pipe"]
1170
- });
1171
- const timer = setTimeout(() => {
1172
- proc.kill("SIGKILL");
1173
- }, timeout);
1174
- proc.stdout.on("data", (chunk) => {
1175
- stdoutChunks.push(chunk);
1176
- });
1177
- proc.stderr.on("data", (chunk) => {
1178
- stderrChunks.push(chunk);
1179
- });
1180
- const finish = (code) => {
1181
- if (settled) {
1182
- return;
1183
- }
1184
- settled = true;
1185
- clearTimeout(timer);
1186
- resolve2({
1187
- exitCode: code ?? 1,
1188
- stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
1189
- stderr: Buffer.concat(stderrChunks).toString("utf-8")
1190
- });
1191
- };
1192
- proc.on("close", finish);
1193
- proc.on("error", (err) => {
1194
- if (settled) {
1195
- return;
1196
- }
1197
- settled = true;
1198
- clearTimeout(timer);
1199
- resolve2({
1200
- exitCode: 1,
1201
- stdout: "",
1202
- stderr: err.message
1203
- });
1204
- });
1205
- });
1206
- const durationMs = Date.now() - start;
1207
- const { userStdout, result } = parseResult(stdout);
1208
- if (result) {
1209
- const base2 = {
1210
- success: result.success,
1211
- outputValues: result.outputValues,
1212
- logs: result.logs,
1213
- stdout: userStdout,
1214
- stderr,
1215
- exitCode,
1216
- durationMs
1217
- };
1218
- if (result.error !== null) {
1219
- base2.error = result.error;
1220
- }
1221
- if (result.httpRequests) {
1222
- base2.httpRequests = result.httpRequests;
1223
- }
1224
- return base2;
1225
- }
1226
- const base = {
1227
- success: false,
1228
- outputValues: {},
1229
- logs: [],
1230
- stdout: userStdout,
1231
- stderr,
1232
- exitCode,
1233
- durationMs
1234
- };
1235
- if (exitCode !== 0) {
1236
- base.error = `Process exited with code ${String(exitCode)}`;
1237
- }
1238
- return base;
1239
- } finally {
1240
- if (tmpDir) {
1241
- void rm(tmpDir, { recursive: true, force: true });
1242
- }
1243
- }
1244
- }
1245
-
1246
- // src/server/requestHandler.ts
1247
- var SOURCE_FILE_MAX_BYTES = 2 * 1024 * 1024;
1248
- var SKIP_FILES = /* @__PURE__ */ new Set([".DS_Store", ".gitignore"]);
1249
- var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
1250
- ".ts",
1251
- ".tsx",
1252
- ".js",
1253
- ".jsx",
1254
- ".mjs",
1255
- ".cjs",
1256
- ".json",
1257
- ".md",
1258
- ".mdx",
1259
- ".yml",
1260
- ".yaml",
1261
- ".toml",
1262
- ".css",
1263
- ".scss",
1264
- ".html",
1265
- ".htm",
1266
- ".xml",
1267
- ".svg",
1268
- ".txt",
1269
- ".sh",
1270
- ".py",
1271
- ".env",
1272
- ".gitignore",
1273
- ".prettierrc",
1274
- ".eslintrc"
1275
- ]);
1276
- function resolveSafePath(pluginDir, relParam) {
1277
- if (relParam.includes("\0")) {
1278
- return null;
1279
- }
1280
- const rel = relParam.replace(/^\/+/, "");
1281
- const abs = resolve(pluginDir, rel);
1282
- if (abs !== pluginDir && !abs.startsWith(pluginDir + sep)) {
1283
- return null;
1284
- }
1285
- return abs;
1286
- }
1287
- function isInsideSkippedDir(pluginDir, abs) {
1288
- const rel = abs === pluginDir ? "" : abs.slice(pluginDir.length + 1);
1289
- if (rel === "") {
1290
- return false;
1291
- }
1292
- const segments = rel.split(sep);
1293
- return segments.some((segment) => SKIP_DIRS.has(segment));
1294
- }
1295
- function proxyLogEntryToString(entry) {
1296
- if (entry.kind === "info") {
1297
- return entry.message;
1298
- }
1299
- const cache = entry.fromCache ? " [cache]" : "";
1300
- return `${entry.method} ${entry.url} \u2192 ${String(entry.status)}${cache}`;
1301
- }
1302
- function getViewerPath() {
1303
- const filename = fileURLToPath(import.meta.url);
1304
- return join9(dirname2(filename), "viewer");
1305
- }
1306
- async function fileExists(filePath) {
1307
- try {
1308
- await access3(filePath);
1309
- return true;
1310
- } catch {
1311
- return false;
1312
- }
1313
- }
1314
- function createRequestHandler(viewerPath, createServerLog, createProxyLog, credentialsRef, activeProfileRef, outputDir, broadcast, onProxyCacheChange, lastPathRef, pluginDir) {
1315
- const proxyCache = createProxyCache({ onChange: onProxyCacheChange });
1316
- return (req, res) => {
1317
- void (async () => {
1318
- const url = req.url ?? "/";
1319
- try {
1320
- createServerLog(`Received request: ${url}`);
1321
- if (url === "/api/credentials") {
1322
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1323
- res.end(credentialsRef.current !== null ? JSON.stringify(credentialsRef.current) : "{}");
1324
- return;
1325
- }
1326
- if (url === "/api/credential-profiles" && req.method === "GET") {
1327
- const profiles = await listCredentialProfiles();
1328
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1329
- res.end(JSON.stringify({ profiles, active: activeProfileRef.current }));
1330
- return;
1331
- }
1332
- if (url === "/api/credentials/switch" && req.method === "POST") {
1333
- const chunks = [];
1334
- for await (const chunk of req) {
1335
- chunks.push(chunk);
1336
- }
1337
- const body = JSON.parse(Buffer.concat(chunks).toString("utf-8") || "{}");
1338
- const profileName = body.profile;
1339
- const creds = profileName === void 0 || profileName === "" ? await loadGlobalCredentials() : await loadCredentialProfile(profileName);
1340
- if (creds !== null) {
1341
- credentialsRef.current = creds;
1342
- activeProfileRef.current = profileName ?? void 0;
1343
- const active = activeProfileRef.current;
1344
- await saveConfig(outputDir, {
1345
- credentialMode: "global",
1346
- ...active !== void 0 && { activeCredentialProfile: active }
1347
- });
1348
- broadcast({ type: "credentials-updated", credentials: creds });
1349
- }
1350
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1351
- res.end(JSON.stringify({ ok: creds !== null }));
1352
- return;
1353
- }
1354
- if (url === "/api/bundle") {
1355
- const bundlePath = join9(process.cwd(), ".kizenapp", "bundle.json");
1356
- try {
1357
- const content = await readFile4(bundlePath, "utf-8");
1358
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1359
- res.end(content);
1360
- } catch {
1361
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1362
- res.end("{}");
1363
- }
1364
- return;
1365
- }
1366
- if (url === "/api/last-path" && req.method === "POST") {
1367
- const chunks = [];
1368
- for await (const chunk of req) {
1369
- chunks.push(chunk);
1370
- }
1371
- const body = JSON.parse(Buffer.concat(chunks).toString("utf-8") || "{}");
1372
- if (typeof body.path === "string") {
1373
- lastPathRef.current = body.path;
1374
- const currentConfig = await loadConfig(outputDir);
1375
- await saveConfig(outputDir, { ...currentConfig, lastPath: body.path });
1376
- }
1377
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1378
- res.end('{"ok":true}');
1379
- return;
1380
- }
1381
- if (url === "/api/execute-step" && req.method === "POST") {
1382
- const chunks = [];
1383
- for await (const chunk of req) {
1384
- chunks.push(chunk);
1385
- }
1386
- const body = JSON.parse(Buffer.concat(chunks).toString("utf-8") || "{}");
1387
- if (typeof body.script !== "string") {
1388
- res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
1389
- res.end(JSON.stringify({ error: "Missing required field: script" }));
1390
- return;
1391
- }
1392
- const stepParams = {
1393
- script: body.script,
1394
- scriptRuntime: body.scriptRuntime ?? "python-3.9",
1395
- inputs: body.inputs ?? {},
1396
- secrets: body.secrets ?? {},
1397
- onInstallProgress: (event) => {
1398
- if (event.kind === "start") {
1399
- broadcast({ type: "venv-install-start" });
1400
- } else if (event.kind === "log") {
1401
- broadcast({
1402
- type: "venv-install-log",
1403
- line: event.line,
1404
- stream: event.stream
1405
- });
1406
- } else if (event.kind === "complete") {
1407
- broadcast({ type: "venv-install-complete" });
1408
- } else {
1409
- broadcast({ type: "venv-install-error", message: event.message });
1410
- }
1411
- }
1412
- };
1413
- if (typeof body.timeout === "number") {
1414
- stepParams.timeout = body.timeout;
1415
- }
1416
- const result = await executePythonStep(stepParams);
1417
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1418
- res.end(JSON.stringify(result));
1419
- return;
1420
- }
1421
- if (url === "/api/proxy-cache/clear") {
1422
- proxyCache.clear();
1423
- createProxyLog({ kind: "info", message: "Proxy cache cleared" });
1424
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1425
- res.end('{"ok":true}');
1426
- return;
1427
- }
1428
- if (url.startsWith("/api/proxy")) {
1429
- const proxyTarget = req.headers["x-proxy-target"];
1430
- if (typeof proxyTarget !== "string") {
1431
- res.writeHead(400);
1432
- res.end("Missing x-proxy-target header");
1433
- return;
1434
- }
1435
- const upstreamPath = url.slice("/api/proxy".length) || "/";
1436
- const upstreamUrl = `${proxyTarget}${upstreamPath}`;
1437
- const method = req.method ?? "GET";
1438
- const chunks = [];
1439
- for await (const chunk of req) {
1440
- chunks.push(chunk);
1441
- }
1442
- const { host, "x-proxy-target": _drop, ...forwardHeaders } = req.headers;
1443
- if (method === "GET") {
1444
- const cacheKey = `GET::${upstreamUrl}`;
1445
- const { response: cached, fromCache } = await proxyCache.get(
1446
- cacheKey,
1447
- () => fetch(upstreamUrl, {
1448
- method: "GET",
1449
- headers: forwardHeaders
1450
- })
1451
- );
1452
- createProxyLog({
1453
- kind: "request",
1454
- method: "GET",
1455
- status: cached.status,
1456
- fromCache,
1457
- url: upstreamPath
1458
- });
1459
- res.writeHead(cached.status, cached.headers);
1460
- res.end(cached.body);
1461
- return;
1462
- }
1463
- const body = chunks.length > 0 ? Buffer.concat(chunks) : void 0;
1464
- const resolvedBody = body && body.length > 0 ? body : void 0;
1465
- const upstream = await fetch(upstreamUrl, {
1466
- method,
1467
- headers: forwardHeaders,
1468
- ...resolvedBody !== void 0 && { body: resolvedBody }
1469
- });
1470
- createProxyLog({
1471
- kind: "request",
1472
- method,
1473
- status: upstream.status,
1474
- fromCache: false,
1475
- url: upstreamPath
1476
- });
1477
- const responseHeaders = Object.fromEntries(upstream.headers);
1478
- delete responseHeaders["content-encoding"];
1479
- delete responseHeaders["content-length"];
1480
- res.writeHead(upstream.status, responseHeaders);
1481
- res.end(Buffer.from(await upstream.arrayBuffer()));
1482
- return;
1483
- }
1484
- if (url.startsWith("/api/source/tree") && req.method === "GET") {
1485
- const query = new URL(url, "http://localhost").searchParams;
1486
- const relParam = query.get("path") ?? "";
1487
- const abs = resolveSafePath(pluginDir, relParam);
1488
- if (abs === null) {
1489
- res.writeHead(403, { "Content-Type": "application/json; charset=utf-8" });
1490
- res.end(JSON.stringify({ error: "Invalid path" }));
1491
- return;
1492
- }
1493
- if (isInsideSkippedDir(pluginDir, abs)) {
1494
- res.writeHead(403, { "Content-Type": "application/json; charset=utf-8" });
1495
- res.end(JSON.stringify({ error: "Path is excluded" }));
1496
- return;
1497
- }
1498
- try {
1499
- const dirEntries = await readdir3(abs, { withFileTypes: true });
1500
- const entries = await Promise.all(
1501
- dirEntries.filter((entry) => {
1502
- if (entry.isDirectory()) {
1503
- return !SKIP_DIRS.has(entry.name);
1504
- }
1505
- if (entry.isFile()) {
1506
- return !SKIP_FILES.has(entry.name);
1507
- }
1508
- return false;
1509
- }).map(async (entry) => {
1510
- const kind = entry.isDirectory() ? "dir" : "file";
1511
- const result = {
1512
- name: entry.name,
1513
- kind
1514
- };
1515
- if (kind === "file") {
1516
- try {
1517
- const stats = await stat(join9(abs, entry.name));
1518
- result.size = stats.size;
1519
- } catch {
1520
- }
1521
- }
1522
- return result;
1523
- })
1524
- );
1525
- entries.sort((a, b) => {
1526
- if (a.kind !== b.kind) {
1527
- return a.kind === "dir" ? -1 : 1;
1528
- }
1529
- return a.name.localeCompare(b.name);
1530
- });
1531
- const normalizedPath = abs === pluginDir ? "" : abs.slice(pluginDir.length + 1);
1532
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1533
- res.end(JSON.stringify({ path: normalizedPath, entries }));
1534
- } catch (err) {
1535
- const message = err instanceof Error ? err.message : String(err);
1536
- res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
1537
- res.end(JSON.stringify({ error: message }));
1538
- }
1539
- return;
1540
- }
1541
- if (url.startsWith("/api/source/file") && req.method === "GET") {
1542
- const query = new URL(url, "http://localhost").searchParams;
1543
- const relParam = query.get("path") ?? "";
1544
- if (relParam === "") {
1545
- res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
1546
- res.end(JSON.stringify({ error: "Missing path" }));
1547
- return;
1548
- }
1549
- const abs = resolveSafePath(pluginDir, relParam);
1550
- if (abs === null) {
1551
- res.writeHead(403, { "Content-Type": "application/json; charset=utf-8" });
1552
- res.end(JSON.stringify({ error: "Invalid path" }));
1553
- return;
1554
- }
1555
- if (isInsideSkippedDir(pluginDir, abs)) {
1556
- res.writeHead(403, { "Content-Type": "application/json; charset=utf-8" });
1557
- res.end(JSON.stringify({ error: "Path is excluded" }));
1558
- return;
1559
- }
1560
- try {
1561
- const stats = await stat(abs);
1562
- if (!stats.isFile()) {
1563
- res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
1564
- res.end(JSON.stringify({ error: "Not a file" }));
1565
- return;
1566
- }
1567
- if (stats.size > SOURCE_FILE_MAX_BYTES) {
1568
- res.writeHead(413, { "Content-Type": "application/json; charset=utf-8" });
1569
- res.end(JSON.stringify({ error: "File too large", size: stats.size }));
1570
- return;
1571
- }
1572
- const ext2 = extname(abs).toLowerCase();
1573
- const explicitMime = MIME_TYPES[ext2];
1574
- const isKnownText = TEXT_EXTENSIONS.has(ext2);
1575
- const contentType = explicitMime ?? (isKnownText ? "text/plain; charset=utf-8" : "application/octet-stream");
1576
- res.writeHead(200, {
1577
- "Content-Type": contentType,
1578
- "Content-Length": String(stats.size)
1579
- });
1580
- createReadStream(abs).pipe(res);
1581
- } catch (err) {
1582
- const message = err instanceof Error ? err.message : String(err);
1583
- res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
1584
- res.end(JSON.stringify({ error: message }));
1585
- }
1586
- return;
1587
- }
1588
- const rawPath = url === "/" ? "/index.html" : url;
1589
- const filePath = join9(viewerPath, rawPath);
1590
- const resolvedPath = await fileExists(filePath) ? filePath : join9(viewerPath, "index.html");
1591
- const ext = extname(resolvedPath);
1592
- const mimeType = MIME_TYPES[ext] ?? "application/octet-stream";
1593
- res.writeHead(200, { "Content-Type": mimeType });
1594
- createReadStream(resolvedPath).pipe(res);
1595
- } catch (err) {
1596
- if (url.startsWith("/api/proxy")) {
1597
- createProxyLog({
1598
- kind: "request",
1599
- method: req.method ?? "GET",
1600
- status: 502,
1601
- fromCache: false,
1602
- url: url.slice("/api/proxy".length) || "/"
1603
- });
1604
- } else {
1605
- createProxyLog({
1606
- kind: "info",
1607
- message: `Error handling ${url}: ${err instanceof Error ? err.message : String(err)}`
1608
- });
1609
- }
1610
- if (!res.headersSent) {
1611
- res.writeHead(502);
1612
- res.end("Bad Gateway");
1613
- }
1614
- }
1615
- })();
1616
- };
1617
- }
1618
-
1619
- // src/chrome/launcher.ts
1620
- import { launch } from "chrome-launcher";
1621
- import { createRequire } from "module";
1622
- import { mkdirSync } from "fs";
1623
- import { readFile as readFile5 } from "fs/promises";
1624
- import { dirname as dirname3, join as join10 } from "path";
1625
- import { fileURLToPath as fileURLToPath2 } from "url";
1626
- var CDP = createRequire(import.meta.url)("chrome-remote-interface");
1627
- var CSP_HEADERS = /* @__PURE__ */ new Set([
1628
- "content-security-policy",
1629
- "content-security-policy-report-only",
1630
- "x-frame-options"
1631
- ]);
1632
- var KIZEN_DOMAINS = ["kizen.dev", "kizen.com"];
1633
- function isKizenUrl(url) {
1634
- try {
1635
- const { hostname } = new URL(url);
1636
- return KIZEN_DOMAINS.some((d) => hostname === d || hostname.endsWith("." + d));
1637
- } catch {
1638
- return false;
1639
- }
1640
- }
1641
- function parseProp(prop) {
1642
- if (prop.type === "number") {
1643
- return Number(prop.value);
1644
- }
1645
- if (prop.type === "boolean") {
1646
- return prop.value === "true";
1647
- }
1648
- if (prop.type === "undefined") {
1649
- return void 0;
1650
- }
1651
- if (prop.type === "object" && prop.value === "null") {
1652
- return null;
1653
- }
1654
- if (prop.type === "object" && prop.valuePreview) {
1655
- const { valuePreview } = prop;
1656
- if (valuePreview.subtype === "array") {
1657
- return valuePreview.properties.filter((p) => /^\d+$/.test(p.name)).sort((a, b) => parseInt(a.name) - parseInt(b.name)).map(parseProp);
1658
- }
1659
- const obj = {};
1660
- for (const p of valuePreview.properties) {
1661
- obj[p.name] = parseProp(p);
1662
- }
1663
- return obj;
1664
- }
1665
- return prop.value ?? `[${prop.type}]`;
1666
- }
1667
- function serializeArg(arg) {
1668
- if (arg.value !== void 0) {
1669
- return arg.value;
1670
- }
1671
- if (arg.preview) {
1672
- const { preview } = arg;
1673
- if (preview.subtype === "array") {
1674
- return preview.properties.filter((p) => /^\d+$/.test(p.name)).sort((a, b) => parseInt(a.name) - parseInt(b.name)).map(parseProp);
1675
- }
1676
- const obj = {};
1677
- for (const prop of preview.properties) {
1678
- obj[prop.name] = parseProp(prop);
1679
- }
1680
- return obj;
1681
- }
1682
- return arg.description ?? `[${arg.type}]`;
1683
- }
1684
- async function serializeArgAsync(client, arg) {
1685
- if (arg.type === "object" && arg.objectId) {
1686
- try {
1687
- const result = await client.Runtime.callFunctionOn({
1688
- objectId: arg.objectId,
1689
- functionDeclaration: 'function() { const seen = new WeakSet(); return JSON.stringify(this, function(k,v) { if (typeof v === "object" && v !== null) { if (seen.has(v)) return "[Circular]"; seen.add(v); } return v; }); }',
1690
- returnByValue: true
1691
- });
1692
- const val = result.result.value;
1693
- if (typeof val === "string") {
1694
- return JSON.parse(val);
1695
- }
1696
- } catch {
1697
- }
1698
- }
1699
- return serializeArg(arg);
1700
- }
1701
- async function installDockIcon(client) {
1702
- try {
1703
- const iconPath = join10(dirname3(fileURLToPath2(import.meta.url)), "viewer", "icon.png");
1704
- const icon = await readFile5(iconPath);
1705
- await client.Browser.setDockTile({ image: icon.toString("base64") });
1706
- } catch {
1707
- }
1708
- }
1709
- async function launchChromeViewer(opts) {
1710
- mkdirSync(opts.userDataDir, { recursive: true });
1711
- const baseUrl = `http://localhost:${String(opts.port)}`;
1712
- const chrome = await launch({
1713
- startingUrl: baseUrl,
1714
- chromeFlags: [
1715
- `--app=${baseUrl}`,
1716
- "--no-first-run",
1717
- "--no-default-browser-check",
1718
- "--disable-extensions"
1719
- ],
1720
- userDataDir: opts.userDataDir,
1721
- handleSIGINT: false,
1722
- logLevel: "silent"
1723
- });
1724
- const cdpPort = chrome.port;
1725
- let client = null;
1726
- let cookieTimer = null;
1727
- let mainTargetId = null;
1728
- let reconnectTimer = null;
1729
- let reconnectAttempt = 0;
1730
- let killed = false;
1731
- const dbg = (msg) => {
1732
- opts.debugLog?.(msg);
1733
- };
1734
- const vb = (msg) => {
1735
- if (opts.verbose) {
1736
- opts.debugLog?.(msg);
1737
- }
1738
- };
1739
- const clearCookieTimer = () => {
1740
- if (cookieTimer !== null) {
1741
- clearInterval(cookieTimer);
1742
- cookieTimer = null;
1743
- }
1744
- };
1745
- const isNewTabUrl = (url) => url === "chrome://new-tab-page/" || url === "chrome://newtab/";
1746
- const setupClient = async (c) => {
1747
- clearCookieTimer();
1748
- mainTargetId = null;
1749
- await installDockIcon(c);
1750
- await c.Fetch.enable({
1751
- patterns: KIZEN_DOMAINS.flatMap((d) => [
1752
- { urlPattern: `*://${d}/*`, requestStage: "Response" },
1753
- { urlPattern: `*://*.${d}/*`, requestStage: "Response" }
1754
- ])
1755
- });
1756
- c.on("Fetch.requestPaused", (rawParams) => {
1757
- void (async () => {
1758
- const params = rawParams;
1759
- const { requestId, request, responseHeaders, responseStatusCode } = params;
1760
- const url = request.url;
1761
- vb(`Fetch ${String(responseStatusCode ?? "?")} ${url}`);
1762
- const filtered = [];
1763
- let rewroteSetCookie = false;
1764
- for (const h of responseHeaders ?? []) {
1765
- if (CSP_HEADERS.has(h.name.toLowerCase())) {
1766
- continue;
1767
- }
1768
- if (h.name.toLowerCase() === "set-cookie" && isKizenUrl(url)) {
1769
- const patched = h.value.split("\n").map((cookie) => {
1770
- if (!/SameSite=/i.test(cookie)) {
1771
- return cookie + "; SameSite=None; Secure";
1772
- }
1773
- return cookie.replace(/SameSite=\w+/i, "SameSite=None");
1774
- }).join("\n");
1775
- filtered.push({ name: h.name, value: patched });
1776
- rewroteSetCookie = true;
1777
- } else {
1778
- filtered.push(h);
1779
- }
1780
- }
1781
- if (rewroteSetCookie) {
1782
- vb(` rewrote Set-Cookie SameSite on ${url}`);
1783
- }
1784
- try {
1785
- const { body, base64Encoded } = await c.Fetch.getResponseBody({ requestId });
1786
- await c.Fetch.fulfillRequest({
1787
- requestId,
1788
- responseCode: responseStatusCode ?? 200,
1789
- responseHeaders: filtered,
1790
- body,
1791
- ...base64Encoded && { base64Encoded: true }
1792
- });
1793
- } catch (e) {
1794
- vb(` fulfillRequest failed (${e.message}), falling through`);
1795
- try {
1796
- await c.Fetch.continueRequest({ requestId });
1797
- } catch (e2) {
1798
- vb(` continueRequest also failed (${e2.message})`);
1799
- }
1800
- }
1801
- })();
1802
- });
1803
- cookieTimer = setInterval(() => {
1804
- void (async () => {
1805
- try {
1806
- const { cookies } = await c.Network.getAllCookies();
1807
- let patched = 0;
1808
- for (const cookie of cookies) {
1809
- if (!isKizenUrl(`https://${cookie.domain ?? ""}`)) {
1810
- continue;
1811
- }
1812
- if (cookie.sameSite === "None") {
1813
- continue;
1814
- }
1815
- const raw = cookie.domain ?? "";
1816
- const bare = raw.startsWith(".") ? raw.slice(1) : raw;
1817
- await c.Network.setCookie({
1818
- url: `https://${bare}`,
1819
- name: cookie.name,
1820
- value: cookie.value,
1821
- domain: raw,
1822
- path: cookie.path ?? "/",
1823
- secure: true,
1824
- ...cookie.httpOnly !== void 0 && { httpOnly: cookie.httpOnly },
1825
- ...cookie.expirationDate !== void 0 && {
1826
- expires: cookie.expirationDate
1827
- },
1828
- sameSite: "None"
1829
- });
1830
- patched++;
1831
- }
1832
- if (patched > 0) {
1833
- vb(`cookie-patch cycle: updated ${String(patched)} cookie(s) to SameSite=None`);
1834
- }
1835
- } catch (e) {
1836
- vb(`cookie-patch cycle failed: ${e.message}`);
1837
- }
1838
- })();
1839
- }, 2e3);
1840
- await c.send("Runtime.enable", { generatePreview: true });
1841
- c.on("Target.attachedToTarget", (rawParams) => {
1842
- const params = rawParams;
1843
- const { sessionId, targetInfo } = params;
1844
- vb(`attached session ${sessionId} (${targetInfo.type} ${targetInfo.url})`);
1845
- void c.send("Runtime.enable", { generatePreview: true }, sessionId).catch((e) => {
1846
- vb(` Runtime.enable on session ${sessionId} failed: ${e.message}`);
1847
- });
1848
- });
1849
- await c.Target.setAutoAttach({
1850
- autoAttach: true,
1851
- waitForDebuggerOnStart: false,
1852
- flatten: true
1853
- });
1854
- await c.Target.setDiscoverTargets({ discover: true });
1855
- const handleTarget = (targetInfo) => {
1856
- if (targetInfo.type !== "page") {
1857
- return;
1858
- }
1859
- if (isNewTabUrl(targetInfo.url)) {
1860
- dbg(`closed stray new-tab-page target ${targetInfo.targetId}`);
1861
- if (mainTargetId !== null) {
1862
- void c.Target.activateTarget({ targetId: mainTargetId }).catch(() => {
1863
- });
1864
- }
1865
- void c.Target.closeTarget({ targetId: targetInfo.targetId }).catch(() => {
1866
- });
1867
- return;
1868
- }
1869
- if (mainTargetId === null) {
1870
- mainTargetId = targetInfo.targetId;
1871
- dbg(`captured main target ${targetInfo.targetId} (${targetInfo.url})`);
1872
- }
1873
- };
1874
- c.on("Target.targetCreated", (rawParams) => {
1875
- const { targetInfo } = rawParams;
1876
- vb(`target +${targetInfo.type} ${targetInfo.targetId} ${targetInfo.url}`);
1877
- handleTarget(targetInfo);
1878
- });
1879
- c.on("Target.targetInfoChanged", (rawParams) => {
1880
- const { targetInfo } = rawParams;
1881
- vb(`target ~${targetInfo.type} ${targetInfo.targetId} ${targetInfo.url}`);
1882
- handleTarget(targetInfo);
1883
- });
1884
- c.on("Runtime.consoleAPICalled", (rawParams) => {
1885
- void (async () => {
1886
- const { type, args } = rawParams;
1887
- if (args[0]?.type === "string" && String(args[0].value).includes("Chrome Security")) {
1888
- return;
1889
- }
1890
- const level = type === "warning" ? "warn" : ["log", "warn", "error", "info"].includes(type) ? type : "log";
1891
- vb(`console.${level} (${String(args.length)} arg(s))`);
1892
- const serialized = await Promise.all(args.map((arg) => serializeArgAsync(c, arg)));
1893
- opts.broadcast({ type: "console-message", level, args: serialized });
1894
- })().catch((e) => {
1895
- vb(`console serialization failed: ${e.message}`);
1896
- });
1897
- });
1898
- c.on("disconnect", () => {
1899
- if (killed || client !== c) {
1900
- return;
1901
- }
1902
- dbg("CDP disconnected");
1903
- client = null;
1904
- clearCookieTimer();
1905
- scheduleReconnect();
1906
- });
1907
- };
1908
- const scheduleReconnect = () => {
1909
- if (killed || reconnectTimer !== null) {
1910
- return;
1911
- }
1912
- const delay = Math.min(1e3 * 2 ** reconnectAttempt, 1e4);
1913
- const attemptNumber = reconnectAttempt + 1;
1914
- reconnectAttempt++;
1915
- dbg(`CDP reconnect scheduled in ${String(delay)}ms (attempt #${String(attemptNumber)})`);
1916
- reconnectTimer = setTimeout(() => {
1917
- reconnectTimer = null;
1918
- void (async () => {
1919
- let c;
1920
- try {
1921
- c = await CDP({ port: cdpPort });
1922
- } catch {
1923
- dbg(`CDP reconnect attempt #${String(attemptNumber)} failed`);
1924
- scheduleReconnect();
1925
- return;
1926
- }
1927
- if (killed) {
1928
- try {
1929
- await c.close();
1930
- } catch {
1931
- }
1932
- return;
1933
- }
1934
- client = c;
1935
- try {
1936
- await setupClient(c);
1937
- dbg(`CDP reconnected after ${String(attemptNumber)} attempt(s)`);
1938
- reconnectAttempt = 0;
1939
- } catch {
1940
- if (client === c) {
1941
- client = null;
1942
- }
1943
- try {
1944
- await c.close();
1945
- } catch {
1946
- }
1947
- dbg(`CDP reconnect attempt #${String(attemptNumber)} failed during setup`);
1948
- scheduleReconnect();
1949
- }
1950
- })();
1951
- }, delay);
1952
- };
1953
- for (let attempt = 0; attempt < 10; attempt++) {
1954
- await new Promise((r) => setTimeout(r, 300));
1955
- dbg(`CDP connect attempt ${String(attempt + 1)}/10`);
1956
- try {
1957
- const c = await CDP({ port: cdpPort });
1958
- client = c;
1959
- if (opts.path) {
1960
- void c.send("Page.navigate", { url: `${baseUrl}${opts.path}` });
1961
- }
1962
- try {
1963
- await setupClient(c);
1964
- dbg("CDP connected");
1965
- } catch {
1966
- if (client === c) {
1967
- client = null;
1968
- }
1969
- try {
1970
- await c.close();
1971
- } catch {
1972
- }
1973
- dbg("CDP setup failed after connect");
1974
- }
1975
- break;
1976
- } catch {
1977
- if (attempt === 9) {
1978
- dbg("CDP unavailable after 10 attempts \u2014 viewer will run without interception");
1979
- return {
1980
- kill: () => {
1981
- chrome.kill();
1982
- }
1983
- };
1984
- }
1985
- }
1986
- }
1987
- if (!client) {
1988
- return {
1989
- kill: () => {
1990
- chrome.kill();
1991
- }
1992
- };
1993
- }
1994
- const kill = () => {
1995
- dbg("CDP kill requested \u2014 tearing down");
1996
- killed = true;
1997
- if (reconnectTimer !== null) {
1998
- clearTimeout(reconnectTimer);
1999
- reconnectTimer = null;
2000
- }
2001
- clearCookieTimer();
2002
- if (client !== null) {
2003
- try {
2004
- void client.close();
2005
- } catch {
2006
- }
2007
- client = null;
2008
- }
2009
- chrome.kill();
2010
- };
2011
- return { kill };
2012
- }
2013
-
2014
- // src/hooks/useLogHistory.ts
2015
- import { useCallback as useCallback2, useState as useState4 } from "react";
2016
- function useLogHistory(limit) {
2017
- const [history, setHistory] = useState4([]);
2018
- const append = useCallback2(
2019
- (message) => {
2020
- setHistory((h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-limit));
2021
- },
2022
- [limit]
2023
- );
2024
- return [history, append];
2025
- }
2026
-
2027
- // src/ui/CredentialSetupUI.tsx
2028
- import { useCallback as useCallback3, useEffect as useEffect3, useState as useState5 } from "react";
2029
- import { Box as Box5, Text as Text6, useInput as useInput2 } from "ink";
2030
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
2031
- var FIELDS2 = ["apiKey", "userId", "businessId"];
2032
- var FIELD_LABELS2 = {
2033
- apiKey: "API Key",
2034
- userId: "User ID",
2035
- businessId: "Business ID"
2036
- };
2037
- var Hint2 = ({ text }) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: text });
2038
- var CredentialSetupUI = ({
2039
- initialMode,
2040
- showProfileManager,
2041
- onComplete,
2042
- onCancel
2043
- }) => {
2044
- const [phase, setPhase] = useState5(
2045
- initialMode === "global" && !showProfileManager ? { type: "loading" } : initialMode === "global" && showProfileManager ? { type: "profile-loading" } : { type: "mode-select", cursor: 0 }
2046
- );
2047
- const [inputBuffer, setInputBuffer] = useState5("");
2048
- const [error, setError] = useState5(null);
2049
- const [activeProfileName, setActiveProfileName] = useState5(void 0);
2050
- const handleModeChosen = useCallback3(
2051
- (mode) => {
2052
- if (mode === "local") {
2053
- onComplete({ mode: "local", credentials: null });
2054
- return;
2055
- }
2056
- setPhase({ type: "profile-loading" });
2057
- },
2058
- [onComplete]
2059
- );
2060
- const handleProfileChosen = useCallback3(async (profile) => {
2061
- if (profile === null) {
2062
- setPhase({ type: "profile-loading" });
2063
- const profiles = await listCredentialProfiles();
2064
- setPhase({ type: "name-entry", nameBuffer: "", existingProfiles: profiles });
2065
- return;
2066
- }
2067
- setActiveProfileName(profile.isDefault ? void 0 : profile.name);
2068
- setPhase({ type: "loading" });
2069
- const existing = profile.isDefault ? await loadGlobalCredentials() : await loadCredentialProfile(profile.name);
2070
- const envCursor2 = existing ? Math.max(0, ENVIRONMENTS.indexOf(existing.environment)) : 0;
2071
- setPhase({ type: "creds-entry", field: 0, values: existing ?? {}, envCursor: envCursor2 });
2072
- }, []);
2073
- const handleSave = useCallback3(
2074
- async (values2, envCursor2) => {
2075
- const environment = ENVIRONMENTS[envCursor2] ?? "go";
2076
- const credentials = {
2077
- apiKey: values2.apiKey ?? "",
2078
- userId: values2.userId ?? "",
2079
- businessId: values2.businessId ?? "",
2080
- environment
2081
- };
2082
- setPhase({ type: "saving" });
2083
- try {
2084
- const profileName = activeProfileName ?? DEFAULT_PROFILE_NAME;
2085
- await saveCredentialProfile(profileName, credentials);
2086
- onComplete({
2087
- mode: "global",
2088
- credentials,
2089
- ...activeProfileName !== void 0 && { profileName: activeProfileName }
2090
- });
2091
- } catch (err) {
2092
- setError(err instanceof Error ? err.message : String(err));
2093
- setPhase({ type: "creds-entry", field: 0, values: values2, envCursor: envCursor2 });
2094
- }
2095
- },
2096
- [onComplete, activeProfileName]
2097
- );
2098
- useInput2((input, key) => {
2099
- if (key.ctrl && input === "c") {
2100
- process.exit(0);
2101
- }
2102
- if (key.escape) {
2103
- if (phase.type === "creds-entry" || phase.type === "name-entry") {
2104
- if (initialMode === "global" && showProfileManager) {
2105
- setPhase({ type: "profile-loading" });
2106
- void loadProfileList();
2107
- return;
2108
- }
2109
- }
2110
- onCancel?.();
2111
- return;
2112
- }
2113
- if (phase.type === "mode-select") {
2114
- if (key.upArrow) {
2115
- setPhase({ type: "mode-select", cursor: 0 });
2116
- } else if (key.downArrow) {
2117
- setPhase({ type: "mode-select", cursor: 1 });
2118
- } else if (key.return) {
2119
- const mode = phase.cursor === 0 ? "global" : "local";
2120
- handleModeChosen(mode);
2121
- }
2122
- return;
2123
- }
2124
- if (phase.type === "profile-select") {
2125
- const { profiles, cursor } = phase;
2126
- const addNewIdx = profiles.length;
2127
- const totalItems = profiles.length + 1;
2128
- if (key.upArrow) {
2129
- setPhase({ ...phase, cursor: (cursor - 1 + totalItems) % totalItems });
2130
- } else if (key.downArrow) {
2131
- setPhase({ ...phase, cursor: (cursor + 1) % totalItems });
2132
- } else if (key.return) {
2133
- if (cursor === addNewIdx) {
2134
- setPhase({ type: "name-entry", nameBuffer: "", existingProfiles: profiles });
2135
- } else {
2136
- const selected = profiles[cursor] ?? profiles[0];
2137
- if (selected) {
2138
- void handleProfileChosen(selected);
2139
- }
2140
- }
2141
- }
2142
- return;
2143
- }
2144
- if (phase.type === "name-entry") {
2145
- const { nameBuffer, existingProfiles } = phase;
2146
- if (key.backspace || key.delete) {
2147
- setPhase({ type: "name-entry", nameBuffer: nameBuffer.slice(0, -1), existingProfiles });
2148
- } else if (key.return) {
2149
- const trimmed = nameBuffer.trim();
2150
- if (!trimmed) {
2151
- setPhase({ ...phase, nameError: "Name cannot be empty" });
2152
- return;
2153
- }
2154
- if (trimmed === DEFAULT_PROFILE_NAME) {
2155
- setPhase({ ...phase, nameError: `"${DEFAULT_PROFILE_NAME}" is reserved` });
2156
- return;
2157
- }
2158
- if (existingProfiles.some((p) => p.name === trimmed)) {
2159
- setPhase({ ...phase, nameError: `Profile "${trimmed}" already exists` });
2160
- return;
2161
- }
2162
- setActiveProfileName(trimmed);
2163
- setPhase({ type: "creds-entry", field: 0, values: {}, envCursor: 0 });
2164
- setInputBuffer("");
2165
- } else if (input && !key.ctrl && !key.meta) {
2166
- setPhase({ type: "name-entry", nameBuffer: nameBuffer + input, existingProfiles });
2167
- }
2168
- return;
2169
- }
2170
- if (phase.type === "creds-entry") {
2171
- const { field, values: values2, envCursor: envCursor2 } = phase;
2172
- const isEnvField2 = field === FIELDS2.length;
2173
- if (isEnvField2) {
2174
- if (key.leftArrow) {
2175
- setPhase({
2176
- ...phase,
2177
- envCursor: (envCursor2 - 1 + ENVIRONMENTS.length) % ENVIRONMENTS.length
2178
- });
2179
- } else if (key.rightArrow) {
2180
- setPhase({ ...phase, envCursor: (envCursor2 + 1) % ENVIRONMENTS.length });
2181
- } else if (key.upArrow) {
2182
- setPhase({ ...phase, field: field - 1 });
2183
- setInputBuffer(values2[FIELDS2[field - 1]] ?? "");
2184
- } else if (key.return) {
2185
- void handleSave(values2, envCursor2);
2186
- }
2187
- return;
2188
- }
2189
- const fieldName = FIELDS2[field];
2190
- if (!fieldName) {
2191
- return;
2192
- }
2193
- if (key.backspace || key.delete) {
2194
- setInputBuffer((prev) => prev.slice(0, -1));
2195
- } else if (key.upArrow && field > 0) {
2196
- const updatedValues = { ...values2, [fieldName]: inputBuffer };
2197
- const prevField = field - 1;
2198
- setPhase({ ...phase, field: prevField, values: updatedValues });
2199
- setInputBuffer(updatedValues[FIELDS2[prevField]] ?? "");
2200
- } else if (key.return || key.tab || key.downArrow) {
2201
- const updatedValues = { ...values2, [fieldName]: inputBuffer };
2202
- const nextField = field + 1;
2203
- setPhase({ ...phase, field: nextField, values: updatedValues });
2204
- if (nextField < FIELDS2.length) {
2205
- setInputBuffer(updatedValues[FIELDS2[nextField]] ?? "");
2206
- } else {
2207
- setInputBuffer("");
2208
- }
2209
- } else if (input && !key.ctrl && !key.meta) {
2210
- setInputBuffer((prev) => prev + input);
2211
- }
2212
- }
2213
- });
2214
- const loadProfileList = useCallback3(async () => {
2215
- const profiles = await listCredentialProfiles();
2216
- setPhase({ type: "profile-select", profiles, cursor: 0 });
2217
- }, []);
2218
- useEffect3(() => {
2219
- if (phase.type === "profile-loading") {
2220
- void loadProfileList();
2221
- }
2222
- }, [phase.type, loadProfileList]);
2223
- useEffect3(() => {
2224
- if (initialMode === "global" && !showProfileManager) {
2225
- void (async () => {
2226
- const existing = await loadGlobalCredentials();
2227
- const envCursor2 = existing ? Math.max(0, ENVIRONMENTS.indexOf(existing.environment)) : 0;
2228
- setPhase({ type: "creds-entry", field: 0, values: existing ?? {}, envCursor: envCursor2 });
2229
- })();
2230
- }
2231
- }, []);
2232
- useEffect3(() => {
2233
- if (phase.type === "creds-entry" && phase.field === 0) {
2234
- setInputBuffer(phase.values.apiKey ?? "");
2235
- }
2236
- }, [phase.type]);
2237
- if (phase.type === "loading" || phase.type === "saving" || phase.type === "profile-loading") {
2238
- const label = phase.type === "saving" ? "Saving credentials\u2026" : phase.type === "profile-loading" ? "Loading profiles\u2026" : "Loading credentials\u2026";
2239
- return /* @__PURE__ */ jsx6(Box5, { paddingY: 1, paddingX: 2, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: label }) });
2240
- }
2241
- if (phase.type === "done") {
2242
- return null;
2243
- }
2244
- if (phase.type === "mode-select") {
2245
- const options = [
2246
- { label: "Global", desc: `~/.kizenappbuilder/credentials.json`, mode: "global" },
2247
- { label: "Local", desc: "Enter credentials in the browser dev tools", mode: "local" }
2248
- ];
2249
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
2250
- /* @__PURE__ */ jsx6(AppHeader, {}),
2251
- /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 0, children: [
2252
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Credential mode" }),
2253
- options.map((opt, i) => {
2254
- const selected = phase.cursor === i;
2255
- return /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
2256
- /* @__PURE__ */ jsx6(Text6, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
2257
- /* @__PURE__ */ jsx6(Text6, { bold: selected, ...selected && { color: "cyan" }, children: opt.label }),
2258
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: opt.desc })
2259
- ] }, opt.mode);
2260
- })
2261
- ] }),
2262
- /* @__PURE__ */ jsx6(Hint2, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Ctrl+C to quit" })
2263
- ] });
2264
- }
2265
- if (phase.type === "profile-select") {
2266
- const { profiles, cursor } = phase;
2267
- const addNewIdx = profiles.length;
2268
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
2269
- /* @__PURE__ */ jsx6(AppHeader, {}),
2270
- /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 0, children: [
2271
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Credential profiles" }),
2272
- profiles.map((profile, i) => {
2273
- const selected = cursor === i;
2274
- return /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
2275
- /* @__PURE__ */ jsx6(Text6, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
2276
- /* @__PURE__ */ jsx6(Text6, { bold: selected, ...selected && { color: "cyan" }, children: profile.isDefault ? "Default" : profile.name }),
2277
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: profile.path })
2278
- ] }, profile.name);
2279
- }),
2280
- /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
2281
- /* @__PURE__ */ jsx6(Text6, { ...cursor === addNewIdx && { color: "cyan" }, children: cursor === addNewIdx ? "\u276F" : " " }),
2282
- /* @__PURE__ */ jsx6(
2283
- Text6,
2284
- {
2285
- bold: cursor === addNewIdx,
2286
- ...cursor === addNewIdx && { color: "cyan" },
2287
- children: "+ Add new profile"
2288
- }
2289
- )
2290
- ] })
2291
- ] }),
2292
- /* @__PURE__ */ jsx6(Hint2, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Esc to cancel" })
2293
- ] });
2294
- }
2295
- if (phase.type === "name-entry") {
2296
- const { nameBuffer, nameError } = phase;
2297
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
2298
- /* @__PURE__ */ jsx6(AppHeader, {}),
2299
- /* @__PURE__ */ jsx6(Box5, { flexDirection: "column", gap: 0, children: /* @__PURE__ */ jsx6(Text6, { bold: true, children: "New credential profile" }) }),
2300
- nameError && /* @__PURE__ */ jsxs4(Text6, { color: "red", children: [
2301
- "Error: ",
2302
- nameError
2303
- ] }),
2304
- /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
2305
- /* @__PURE__ */ jsx6(Box5, { width: 12, children: /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: "Profile name" }) }),
2306
- /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: ">" }),
2307
- /* @__PURE__ */ jsx6(Text6, { children: nameBuffer }),
2308
- /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "\u2588" })
2309
- ] }),
2310
- /* @__PURE__ */ jsx6(Hint2, { text: "Type a name \xB7 Enter to confirm \xB7 Esc to go back" })
2311
- ] });
2312
- }
2313
- const { field: activeField, values, envCursor } = phase;
2314
- const isEnvField = activeField === FIELDS2.length;
2315
- const profileLabel = activeProfileName ?? DEFAULT_PROFILE_NAME;
2316
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
2317
- /* @__PURE__ */ jsx6(AppHeader, {}),
2318
- /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 0, children: [
2319
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: activeProfileName !== void 0 ? `Profile: ${activeProfileName}` : "Global credentials" }),
2320
- /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
2321
- "Saved to:",
2322
- " ",
2323
- activeProfileName !== void 0 ? `~/.kizenappbuilder/${activeProfileName}.json` : GLOBAL_CREDENTIALS_PATH
2324
- ] })
2325
- ] }),
2326
- error && /* @__PURE__ */ jsxs4(Text6, { color: "red", children: [
2327
- "Error: ",
2328
- error
2329
- ] }),
2330
- /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 0, children: [
2331
- FIELDS2.map((name, i) => {
2332
- const isActive = activeField === i;
2333
- const displayValue = isActive ? inputBuffer : values[name] ?? "";
2334
- return /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
2335
- /* @__PURE__ */ jsx6(Box5, { width: 12, children: /* @__PURE__ */ jsx6(Text6, { bold: isActive, ...isActive && { color: "cyan" }, children: FIELD_LABELS2[name] }) }),
2336
- /* @__PURE__ */ jsx6(Text6, { ...isActive && { color: "cyan" }, children: ">" }),
2337
- /* @__PURE__ */ jsx6(Text6, { children: displayValue }),
2338
- isActive && /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "\u2588" })
2339
- ] }, name);
2340
- }),
2341
- /* @__PURE__ */ jsxs4(Box5, { gap: 2, children: [
2342
- /* @__PURE__ */ jsx6(Box5, { width: 12, children: /* @__PURE__ */ jsx6(Text6, { bold: isEnvField, ...isEnvField && { color: "cyan" }, children: "Environment" }) }),
2343
- /* @__PURE__ */ jsx6(Text6, { ...isEnvField && { color: "cyan" }, children: ">" }),
2344
- /* @__PURE__ */ jsx6(Box5, { gap: 1, children: ENVIRONMENTS.map((env, i) => {
2345
- const isSelected = i === envCursor;
2346
- if (isEnvField) {
2347
- return /* @__PURE__ */ jsx6(Text6, { bold: isSelected, ...isSelected && { color: "cyan" }, children: isSelected ? `[${env}]` : env }, env);
2348
- }
2349
- return /* @__PURE__ */ jsx6(Text6, { dimColor: !isSelected, bold: isSelected, children: env }, env);
2350
- }) })
2351
- ] })
2352
- ] }),
2353
- isEnvField ? /* @__PURE__ */ jsx6(Hint2, { text: "\u2190\u2192 to select \xB7 \u2191\u2193 to move \xB7 Enter to save \xB7 Esc to cancel" }) : /* @__PURE__ */ jsx6(Hint2, { text: "\u2191\u2193 to move \xB7 Backspace to delete \xB7 Esc to cancel" }),
2354
- /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
2355
- "Profile: ",
2356
- profileLabel
2357
- ] })
2358
- ] });
2359
- };
2360
-
2361
- // src/ui/DevUI.tsx
2362
- import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
2363
- var SKIP_WATCH_PREFIXES = [".kizenapp", ".git"];
2364
- var DevUI = ({
2365
- port,
2366
- pluginDir,
2367
- outputDir,
2368
- credentials: initialCredentials,
2369
- credentialMode: initialCredentialMode,
2370
- activeCredentialProfile: initialActiveProfile,
2371
- lastPath: initialLastPath,
2372
- debug = false,
2373
- verbose = false
2374
- }) => {
2375
- const credentialsRef = useRef(initialCredentials);
2376
- const activeProfileRef = useRef(initialActiveProfile);
2377
- const lastPathRef = useRef(initialLastPath);
2378
- const [credMode, setCredMode] = useState6("main");
2379
- const [credentialMode, setCredentialMode] = useState6(
2380
- initialCredentialMode
2381
- );
2382
- const [status, setStatus] = useState6("starting");
2383
- const [errorMessage, setErrorMessage] = useState6(null);
2384
- const [serverLogHistory, appendServerLog] = useLogHistory(LOG_LIMIT);
2385
- const [buildLogHistory, appendBuildLog] = useLogHistory(LOG_LIMIT);
2386
- const [cdpLogHistory, appendCdpLog] = useLogHistory(LOG_LIMIT);
2387
- const [proxyLogHistory, setProxyLogHistory] = useState6([]);
2388
- const [buildStatus, setBuildStatus] = useState6("pending");
2389
- const [buildError, setBuildError] = useState6(null);
2390
- const [lastBuilt, setLastBuilt] = useState6(null);
2391
- const [lastBundleSize, setLastBundleSize] = useState6(null);
2392
- const [wsClientCount, setWsClientCount] = useState6(0);
2393
- const [proxyCacheSize, setProxyCacheSize] = useState6(0);
2394
- const [proxyCacheHits, setProxyCacheHits] = useState6(0);
2395
- const [proxyCacheMisses, setProxyCacheMisses] = useState6(0);
2396
- const buildingRef = useRef(false);
2397
- const viewerLaunchedRef = useRef(false);
2398
- const chromeHandleRef = useRef(null);
2399
- const debounceTimerRef = useRef(null);
2400
- const wsClientsRef = useRef(/* @__PURE__ */ new Set());
2401
- const pendingMessagesRef = useRef([]);
2402
- const broadcast = useCallback4((msg) => {
2403
- const json = JSON.stringify(msg);
2404
- if (wsClientsRef.current.size === 0) {
2405
- pendingMessagesRef.current = [...pendingMessagesRef.current, json].slice(-LOG_LIMIT);
2406
- return;
2407
- }
2408
- for (const client of wsClientsRef.current) {
2409
- if (client.readyState === 1) {
2410
- client.send(json);
2411
- }
2412
- }
2413
- }, []);
2414
- const createServerLog = appendServerLog;
2415
- const launchChromeViewerCb = useCallback4(() => {
2416
- chromeHandleRef.current?.kill();
2417
- chromeHandleRef.current = null;
2418
- void launchChromeViewer({
2419
- port,
2420
- userDataDir: join11(outputDir, ".chrome"),
2421
- broadcast,
2422
- ...lastPathRef.current !== void 0 && { path: lastPathRef.current },
2423
- ...debug && { debugLog: appendCdpLog },
2424
- ...verbose && { verbose: true }
2425
- }).then((handle) => {
2426
- chromeHandleRef.current = handle;
2427
- });
2428
- }, [port, outputDir, broadcast, debug, verbose, appendCdpLog]);
2429
- useEffect4(() => {
2430
- const onExit = () => {
2431
- chromeHandleRef.current?.kill();
2432
- };
2433
- process.on("exit", onExit);
2434
- return () => {
2435
- process.off("exit", onExit);
2436
- };
2437
- }, []);
2438
- useInput3(
2439
- (input, key) => {
2440
- if (input === "q" || key.ctrl && input === "c") {
2441
- process.exit(0);
2442
- }
2443
- if (input === "c") {
2444
- setCredMode("editing");
2445
- }
2446
- if (input === "v") {
2447
- launchChromeViewerCb();
2448
- }
2449
- },
2450
- { isActive: credMode === "main" }
2451
- );
2452
- useEffect4(() => {
2453
- if (status !== "running" || viewerLaunchedRef.current) {
2454
- return;
2455
- }
2456
- viewerLaunchedRef.current = true;
2457
- launchChromeViewerCb();
2458
- }, [status, launchChromeViewerCb]);
2459
- const handleCredentialsDone = useCallback4(
2460
- (result) => {
2461
- credentialsRef.current = result.credentials;
2462
- activeProfileRef.current = result.profileName;
2463
- setCredMode("main");
2464
- setCredentialMode(result.mode);
2465
- const profile = result.profileName;
2466
- void saveConfig(outputDir, {
2467
- credentialMode: result.mode,
2468
- ...profile !== void 0 && { activeCredentialProfile: profile }
2469
- });
2470
- broadcast({ type: "credentials-updated", credentials: result.credentials });
2471
- },
2472
- [outputDir, broadcast]
2473
- );
2474
- const createProxyLog = useCallback4(
2475
- (entry) => {
2476
- setProxyLogHistory(
2477
- (h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${proxyLogEntryToString(entry)}`].slice(
2478
- -LOG_LIMIT
2479
- )
2480
- );
2481
- if (entry.kind === "request") {
2482
- if (entry.fromCache) {
2483
- setProxyCacheHits((n) => n + 1);
2484
- } else {
2485
- setProxyCacheMisses((n) => n + 1);
2486
- }
2487
- }
2488
- broadcast({ type: "proxy-log", entry });
2489
- },
2490
- [broadcast]
2491
- );
2492
- const createBuildLog = useCallback4(
2493
- (message) => {
2494
- appendBuildLog(message);
2495
- broadcast({ type: "log", message });
2496
- },
2497
- [appendBuildLog, broadcast]
2498
- );
2499
- const triggerBuild = useCallback4(() => {
2500
- if (buildingRef.current) {
2501
- return;
2502
- }
2503
- buildingRef.current = true;
2504
- setBuildStatus("building");
2505
- setBuildError(null);
2506
- createBuildLog("Build started");
2507
- void runBuild(pluginDir, outputDir).then(({ bundleSize }) => {
2508
- createBuildLog(`Build finished \u2014 bundle: ${formatBytes(bundleSize)}`);
2509
- setBuildStatus("done");
2510
- setLastBuilt(/* @__PURE__ */ new Date());
2511
- setLastBundleSize(bundleSize);
2512
- createBuildLog("Notifying viewers to reload");
2513
- for (const client of wsClientsRef.current) {
2514
- if (client.readyState === 1) {
2515
- client.send(JSON.stringify({ type: "rebuild" }));
2516
- }
2517
- }
2518
- }).catch((err) => {
2519
- const message = err instanceof Error ? err.message : String(err);
2520
- setBuildError(message);
2521
- setBuildStatus("error");
2522
- }).finally(() => {
2523
- buildingRef.current = false;
2524
- });
2525
- }, [pluginDir, outputDir, createBuildLog]);
2526
- useEffect4(() => {
2527
- triggerBuild();
2528
- const watcher = watch(pluginDir, { recursive: true }, (_, filename) => {
2529
- if (!filename) {
2530
- return;
2531
- }
2532
- const normalized = filename.replace(/\\/g, "/");
2533
- if (SKIP_WATCH_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
2534
- return;
2535
- }
2536
- createBuildLog(`File change detected: ${filename}`);
2537
- if (debounceTimerRef.current !== null) {
2538
- clearTimeout(debounceTimerRef.current);
2539
- }
2540
- debounceTimerRef.current = setTimeout(triggerBuild, FILE_WATCH_DEBOUNCE_MS);
2541
- });
2542
- return () => {
2543
- watcher.close();
2544
- if (debounceTimerRef.current !== null) {
2545
- clearTimeout(debounceTimerRef.current);
2546
- }
2547
- };
2548
- }, [pluginDir, triggerBuild, createBuildLog]);
2549
- useEffect4(() => {
2550
- const viewerPath = getViewerPath();
2551
- void fileExists(join11(viewerPath, "index.html")).then((viewerBuilt) => {
2552
- if (!viewerBuilt) {
2553
- setErrorMessage("Viewer not built. Run 'pnpm build:viewer' first.");
2554
- setStatus("error");
2555
- return;
2556
- }
2557
- const handler = createRequestHandler(
2558
- viewerPath,
2559
- createServerLog,
2560
- createProxyLog,
2561
- credentialsRef,
2562
- activeProfileRef,
2563
- outputDir,
2564
- broadcast,
2565
- setProxyCacheSize,
2566
- lastPathRef,
2567
- pluginDir
2568
- );
2569
- const server = createServer(handler);
2570
- const wss = new WebSocketServer({ server });
2571
- wss.on("connection", (ws) => {
2572
- for (const json of pendingMessagesRef.current) {
2573
- if (ws.readyState === 1) {
2574
- ws.send(json);
2575
- }
2576
- }
2577
- pendingMessagesRef.current = [];
2578
- createServerLog("Viewer connected for live reload");
2579
- wsClientsRef.current.add(ws);
2580
- setWsClientCount(wsClientsRef.current.size);
2581
- ws.on("close", () => {
2582
- wsClientsRef.current.delete(ws);
2583
- setWsClientCount(wsClientsRef.current.size);
2584
- });
2585
- });
2586
- server.listen(port, () => {
2587
- setStatus("running");
2588
- createServerLog(`Server started on port ${String(port)}`);
2589
- });
2590
- server.on("error", (err) => {
2591
- setErrorMessage(err.message);
2592
- setStatus("error");
2593
- });
2594
- return () => {
2595
- wss.close();
2596
- server.close();
2597
- };
2598
- });
2599
- }, [port, createServerLog, createProxyLog, broadcast, outputDir, pluginDir]);
2600
- if (credMode === "editing") {
2601
- return /* @__PURE__ */ jsx7(
2602
- CredentialSetupUI,
2603
- {
2604
- ...credentialMode !== void 0 && { initialMode: credentialMode },
2605
- showProfileManager: credentialMode === "global",
2606
- onComplete: handleCredentialsDone,
2607
- onCancel: () => {
2608
- setCredMode("main");
2609
- }
2610
- }
2611
- );
2612
- }
2613
- const elapsedSeconds = lastBuilt !== null ? Math.round((Date.now() - lastBuilt.getTime()) / 1e3) : null;
2614
- const elapsedLabel = elapsedSeconds === null ? "" : elapsedSeconds < 5 ? " (just now)" : ` (${String(elapsedSeconds)}s ago)`;
2615
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", paddingY: 1, paddingX: 2, children: [
2616
- /* @__PURE__ */ jsx7(AppHeader, { marginBottom: 1 }),
2617
- /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
2618
- /* @__PURE__ */ jsxs5(Box6, { gap: 2, marginBottom: 0, children: [
2619
- /* @__PURE__ */ jsx7(Text7, { bold: true, dimColor: true, children: "App Build" }),
2620
- buildStatus === "pending" && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "waiting\u2026" }),
2621
- buildStatus === "building" && /* @__PURE__ */ jsxs5(Box6, { gap: 1, children: [
2622
- /* @__PURE__ */ jsx7(Spinner, {}),
2623
- /* @__PURE__ */ jsx7(Text7, { children: "Building plugin package\u2026" })
2624
- ] }),
2625
- buildStatus === "done" && /* @__PURE__ */ jsxs5(Box6, { gap: 1, children: [
2626
- /* @__PURE__ */ jsx7(Text7, { color: "green", children: "\u2713 Built" }),
2627
- lastBuilt !== null && /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
2628
- "at ",
2629
- lastBuilt.toLocaleTimeString(),
2630
- elapsedLabel
2631
- ] }),
2632
- lastBundleSize !== null && /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
2633
- "\xB7 bundle: ",
2634
- formatBytes(lastBundleSize)
2635
- ] })
2636
- ] }),
2637
- buildStatus === "error" && /* @__PURE__ */ jsxs5(Text7, { color: "red", children: [
2638
- "\u2717 ",
2639
- buildError ?? "unknown error"
2640
- ] })
2641
- ] }),
2642
- /* @__PURE__ */ jsx7(
2643
- Box6,
2644
- {
2645
- flexDirection: "column",
2646
- height: LOG_DISPLAY + 2,
2647
- borderStyle: "single",
2648
- borderColor: "gray",
2649
- overflow: "hidden",
2650
- children: buildLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx7(Text7, { dimColor: true, wrap: "truncate", children: log }, index))
2651
- }
2652
- )
2653
- ] }),
2654
- /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
2655
- /* @__PURE__ */ jsxs5(Box6, { gap: 2, children: [
2656
- /* @__PURE__ */ jsx7(Text7, { bold: true, dimColor: true, children: "Web Server" }),
2657
- status === "starting" && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "starting\u2026" }),
2658
- status === "running" && /* @__PURE__ */ jsxs5(Box6, { gap: 2, children: [
2659
- /* @__PURE__ */ jsx7(Text7, { color: "green", children: "\u2713 Running" }),
2660
- /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
2661
- "\u25CF ",
2662
- wsClientCount,
2663
- " viewer",
2664
- wsClientCount !== 1 ? "s" : ""
2665
- ] })
2666
- ] }),
2667
- status === "error" && /* @__PURE__ */ jsxs5(Text7, { color: "red", children: [
2668
- "\u2717 ",
2669
- errorMessage ?? "Server error"
2670
- ] })
2671
- ] }),
2672
- /* @__PURE__ */ jsx7(
2673
- Box6,
2674
- {
2675
- flexDirection: "column",
2676
- height: LOG_DISPLAY + 2,
2677
- borderStyle: "single",
2678
- borderColor: "gray",
2679
- overflow: "hidden",
2680
- children: serverLogHistory.slice(-LOG_DISPLAY).map((log, index) => /* @__PURE__ */ jsx7(Text7, { dimColor: true, wrap: "truncate", children: log }, index))
2681
- }
2682
- )
2683
- ] }),
2684
- /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
2685
- /* @__PURE__ */ jsxs5(Box6, { gap: 2, children: [
2686
- /* @__PURE__ */ jsx7(Text7, { bold: true, dimColor: true, children: "Network Proxy" }),
2687
- /* @__PURE__ */ jsx7(Text7, { color: "green", children: "\u2713 Active" }),
2688
- /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
2689
- "\u25CF ",
2690
- proxyCacheSize,
2691
- " key",
2692
- proxyCacheSize !== 1 ? "s" : "",
2693
- " in the network cache \xB7",
2694
- " ",
2695
- proxyCacheHits,
2696
- " hit \xB7 ",
2697
- proxyCacheMisses,
2698
- " miss"
2699
- ] })
2700
- ] }),
2701
- /* @__PURE__ */ jsx7(
2702
- Box6,
2703
- {
2704
- flexDirection: "column",
2705
- height: LOG_DISPLAY + 2,
2706
- borderStyle: "single",
2707
- borderColor: "gray",
2708
- overflow: "hidden",
2709
- children: proxyLogHistory.length === 0 ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " No requests yet." }) : proxyLogHistory.slice(-LOG_DISPLAY).map((log, i) => /* @__PURE__ */ jsx7(Text7, { dimColor: true, wrap: "truncate", children: log }, i))
2710
- }
2711
- )
2712
- ] }),
2713
- debug && /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
2714
- /* @__PURE__ */ jsxs5(Box6, { gap: 2, children: [
2715
- /* @__PURE__ */ jsx7(Text7, { bold: true, dimColor: true, children: "CDP Debug" }),
2716
- /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
2717
- "\u25CF ",
2718
- cdpLogHistory.length,
2719
- " event(s)"
2720
- ] })
2721
- ] }),
2722
- /* @__PURE__ */ jsx7(
2723
- Box6,
2724
- {
2725
- flexDirection: "column",
2726
- height: LOG_DISPLAY + 2,
2727
- borderStyle: "single",
2728
- borderColor: "gray",
2729
- overflow: "hidden",
2730
- children: cdpLogHistory.length === 0 ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " No CDP events yet." }) : cdpLogHistory.slice(-LOG_DISPLAY).map((log, i) => /* @__PURE__ */ jsx7(Text7, { dimColor: true, wrap: "truncate", children: log }, i))
2731
- }
2732
- )
2733
- ] }),
2734
- /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, gap: 1, children: [
2735
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "[" }),
2736
- /* @__PURE__ */ jsx7(Text7, { children: "q" }),
2737
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "] quit [" }),
2738
- /* @__PURE__ */ jsx7(Text7, { children: "c" }),
2739
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "] credentials [" }),
2740
- /* @__PURE__ */ jsx7(Text7, { children: "v" }),
2741
- /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
2742
- "] ",
2743
- wsClientCount === 0 ? "open viewer" : "relaunch viewer"
2744
- ] })
2745
- ] })
2746
- ] });
2747
- };
2748
-
2749
- // src/commands/dev.ts
2750
- async function runSetupUI(initialMode) {
2751
- return new Promise((resolve2) => {
2752
- let unmountFn = null;
2753
- const handleComplete = (result) => {
2754
- unmountFn?.();
2755
- resolve2(result);
2756
- };
2757
- const { unmount } = render3(
2758
- createElement3(CredentialSetupUI, {
2759
- ...initialMode !== void 0 && { initialMode },
2760
- onComplete: handleComplete
2761
- }),
2762
- { exitOnCtrlC: false }
2763
- );
2764
- unmountFn = unmount;
2765
- });
2766
- }
2767
- function devCommand(program2) {
2768
- program2.command("dev").description("Start the plugin viewer dev server").option("-p, --port <port>", "port to listen on", "3121").option("-c, --credentials <path>", "path to a credentials JSON file").option("-d, --debug", "Enable CDP lifecycle logs in the TUI").option("-v, --verbose", "Log every CDP event and handled error (implies --debug)").action(
2769
- async (options) => {
2770
- const port = parseInt(options.port, 10);
2771
- const pluginDir = process.cwd();
2772
- const outputDir = `${pluginDir}/.kizenapp`;
2773
- ensureGitignore(pluginDir);
2774
- let credentials = null;
2775
- let credentialMode;
2776
- let activeCredentialProfile;
2777
- const { lastPath } = await loadConfig(outputDir);
2778
- if (options.credentials) {
2779
- credentials = await loadCredentialsFromFile(options.credentials);
2780
- } else {
2781
- const config = await loadConfig(outputDir);
2782
- if (config.credentialMode === "local") {
2783
- credentialMode = "local";
2784
- } else if (config.credentialMode === "global") {
2785
- credentialMode = "global";
2786
- activeCredentialProfile = config.activeCredentialProfile;
2787
- if (activeCredentialProfile) {
2788
- credentials = await loadCredentialProfile(activeCredentialProfile);
2789
- if (!credentials) {
2790
- credentials = await loadGlobalCredentials();
2791
- activeCredentialProfile = void 0;
2792
- }
2793
- } else {
2794
- credentials = await loadGlobalCredentials();
2795
- }
2796
- if (!credentials) {
2797
- const result = await runSetupUI("global");
2798
- credentials = result.credentials;
2799
- activeCredentialProfile = result.profileName;
2800
- }
2801
- } else {
2802
- const result = await runSetupUI();
2803
- credentials = result.credentials;
2804
- credentialMode = result.mode;
2805
- activeCredentialProfile = result.profileName;
2806
- const profile = result.profileName;
2807
- await saveConfig(outputDir, {
2808
- credentialMode: result.mode,
2809
- ...profile !== void 0 && { activeCredentialProfile: profile }
2810
- });
2811
- }
2812
- }
2813
- const verbose = options.verbose === true;
2814
- const debug = verbose || options.debug === true;
2815
- const { waitUntilExit } = render3(
2816
- createElement3(DevUI, {
2817
- port,
2818
- pluginDir,
2819
- outputDir,
2820
- credentials,
2821
- ...credentialMode !== void 0 && { credentialMode },
2822
- ...activeCredentialProfile !== void 0 && { activeCredentialProfile },
2823
- ...lastPath !== void 0 && { lastPath },
2824
- debug,
2825
- verbose
2826
- }),
2827
- { exitOnCtrlC: false }
2828
- );
2829
- await waitUntilExit();
2830
- }
2831
- );
2832
- }
2833
-
2834
- // src/index.ts
2835
- program.name("appbuilder").description("Kizen plugin app builder").version("0.1.0");
2836
- createCommand(program);
2837
- buildCommand(program);
2838
- devCommand(program);
2839
- program.parse();
168
+ `.trim(),kn=3e4,ht=Be(process.cwd(),".kizenapp","venv"),In=["awslambdaric","bcrypt","fpdf2","lxml","msgpack","numpy","paramiko","pyjwt","pypdf","pytz","requests","tzdata"],We=null;function Bn(e){let t=/^python-(\d+\.\d+)$/.exec(e);return t?.[1]?`python${t[1]}`:"python3"}function Yt(e,t,i){return new Promise((n,r)=>{let c=Zt(e,t,{stdio:["ignore","pipe","pipe"]}),S=(_,b)=>{let h=[],g="";return _.on("data",f=>{g+=f.toString("utf-8");let a=g.indexOf(`
169
+ `);for(;a>=0;){let v=g.slice(0,a).replace(/\r$/,"");g=g.slice(a+1),h.push(v),i?.(v,b),a=g.indexOf(`
170
+ `)}}),_.on("end",()=>{if(g.length>0){let f=g.replace(/\r$/,"");h.push(f),i?.(f,b)}}),h},y=S(c.stderr,"stderr");S(c.stdout,"stdout"),c.on("error",r),c.on("close",_=>{if(_===0)n();else{let b=y.slice(-10).join(`
171
+ `).trim(),h=b.length>0?`: ${b}`:"";r(new Error(`${e} exited with code ${String(_)}${h}`))}})})}var xt=new Set;function Ke(e){for(let t of xt)t(e)}function Dn(e,t){return t&&xt.add(t),We||(We=(async()=>{let i=Be(ht,"bin","python");try{await Tn(i)}catch{let n=Bn(e);await Yt(n,["-m","venv",ht])}Ke({kind:"start"});try{await Yt(i,["-m","pip","install","--disable-pip-version-check",...In],(n,r)=>{Ke({kind:"log",line:n,stream:r})}),Ke({kind:"complete"})}catch(n){let r=n instanceof Error?n.message:String(n);throw Ke({kind:"error",message:r}),We=null,await Qt(ht,{recursive:!0,force:!0}).catch(()=>{}),n}finally{xt.clear()}return i})()),We}function Rn(e){let t="__KIZEN_RESULT_START__",i="__KIZEN_RESULT_END__",n=e.indexOf(t),r=e.indexOf(i);if(n===-1||r===-1)return{userStdout:e,result:null};let c=e.slice(0,n).trimEnd(),S=e.slice(n+t.length,r).trim();try{let y=JSON.parse(S);return{userStdout:c,result:y}}catch{return{userStdout:e,result:null}}}async function er(e){let t=e.timeout??kn,i=await Dn(e.scriptRuntime,e.onInstallProgress),n=Date.now(),r;try{r=await Pn(Be(_n(),"kizen-step-"));let c=Be(r,"wrapper.py"),S=Be(r,"script.py");await Promise.all([Xt(c,En,"utf-8"),Xt(S,e.script,"utf-8")]);let y={...process.env,__KIZEN_INPUTS__:JSON.stringify(e.inputs),__KIZEN_SECRETS__:JSON.stringify(e.secrets),__KIZEN_SCRIPT_PATH__:S},{exitCode:_,stdout:b,stderr:h}=await new Promise(N=>{let E=[],C=[],m=!1,u=Zt(i,[c],{env:y,cwd:r,stdio:["ignore","pipe","pipe"]}),w=setTimeout(()=>{u.kill("SIGKILL")},t);u.stdout.on("data",o=>{E.push(o)}),u.stderr.on("data",o=>{C.push(o)});let s=o=>{m||(m=!0,clearTimeout(w),N({exitCode:o??1,stdout:Buffer.concat(E).toString("utf-8"),stderr:Buffer.concat(C).toString("utf-8")}))};u.on("close",s),u.on("error",o=>{m||(m=!0,clearTimeout(w),N({exitCode:1,stdout:"",stderr:o.message}))})}),g=Date.now()-n,{userStdout:f,result:a}=Rn(b);if(a){let N={success:a.success,outputValues:a.outputValues,logs:a.logs,stdout:f,stderr:h,exitCode:_,durationMs:g};return a.error!==null&&(N.error=a.error),a.httpRequests&&(N.httpRequests=a.httpRequests),N}let v={success:!1,outputValues:{},logs:[],stdout:f,stderr:h,exitCode:_,durationMs:g};return _!==0&&(v.error=`Process exited with code ${String(_)}`),v}finally{r&&Qt(r,{recursive:!0,force:!0})}}var tr=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".json",".md",".mdx",".yml",".yaml",".toml",".css",".scss",".html",".htm",".xml",".svg",".txt",".sh",".py",".env",".gitignore",".prettierrc",".eslintrc"]);function bt(e){if(e.kind==="info")return e.message;let t=e.fromCache?" [cache]":"";return`${e.method} ${e.url} \u2192 ${String(e.status)}${t}`}var Mn=2*1024*1024,Hn=new Set([".DS_Store",".gitignore"]);function Un(e){if(e===void 0||e==="")return!1;let t=e.startsWith("[")?e.slice(0,e.indexOf("]")+1):e.split(":")[0]??"";return t==="localhost"||t==="127.0.0.1"||t==="[::1]"}var qn=["kizen.com","kizen.dev"];function Vn(e){let t;try{t=new URL(e)}catch{return!1}if(t.protocol!=="https:")return!1;let i=t.hostname.toLowerCase();return qn.some(n=>i===n||i.endsWith("."+n))}function Jn(e){if(e===void 0||e==="")return!1;try{let{hostname:t}=new URL(e);return t==="localhost"||t==="127.0.0.1"||t==="::1"}catch{return!1}}async function ir(e,t){if(t.includes("\0"))return null;let i=t.replace(/^\/+/,""),n=$n(e,i);if(n!==e&&!n.startsWith(e+vt))return null;try{let r=await An(n);return r!==e&&!r.startsWith(e+vt)?null:r}catch{return null}}function sr(e,t){let i=t===e?"":t.slice(e.length+1);return i===""?!1:i.split(vt).some(r=>$e.has(r))}function ar(){let e=zn(import.meta.url);return De(On(e),"viewer")}async function Ct(e){try{return await Ln(e),!0}catch{return!1}}function lr(e,t,i,n,r,c,S,y,_,b){let h=Kt({onChange:y}),g=Nn(b);return(f,a)=>{(async()=>{let v=f.url??"/";try{if(!Un(f.headers.host)){a.writeHead(403,{"Content-Type":"application/json; charset=utf-8"}),a.end(JSON.stringify({error:"Forbidden"}));return}if((f.method==="POST"||f.method==="PATCH"||f.method==="DELETE")&&!Jn(f.headers.origin)){a.writeHead(403,{"Content-Type":"application/json; charset=utf-8"}),a.end(JSON.stringify({error:"Forbidden"}));return}if(t(`Received request: ${v}`),v==="/api/credentials"){a.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),a.end(n.current!==null?JSON.stringify(n.current):"{}");return}if(v==="/api/credential-profiles"&&f.method==="GET"){let s=await Ee();a.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),a.end(JSON.stringify({profiles:s,active:r.current}));return}if(v==="/api/credentials/switch"&&f.method==="POST"){let s=[];for await(let p of f)s.push(p);let l=JSON.parse(Buffer.concat(s).toString("utf-8")||"{}").profile,d=l===void 0||l===""?await Z():await be(l);if(d!==null){n.current=d,r.current=l??void 0;let p=r.current;await ue(c,{credentialMode:"global",...p!==void 0&&{activeCredentialProfile:p}}),S({type:"credentials-updated",credentials:d})}a.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),a.end(JSON.stringify({ok:d!==null}));return}if(v==="/api/bundle"){let s=De(process.cwd(),".kizenapp","bundle.json");try{let o=await Fn(s,"utf-8");a.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),a.end(o)}catch{a.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),a.end("{}")}return}if(v==="/api/last-path"&&f.method==="POST"){let s=[];for await(let l of f)s.push(l);let o=JSON.parse(Buffer.concat(s).toString("utf-8")||"{}");if(typeof o.path=="string"){_.current=o.path;let l=await Ie(c);await ue(c,{...l,lastPath:o.path})}a.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),a.end('{"ok":true}');return}if(v==="/api/execute-step"&&f.method==="POST"){let s=[];for await(let p of f)s.push(p);let o=JSON.parse(Buffer.concat(s).toString("utf-8")||"{}");if(typeof o.script!="string"){a.writeHead(400,{"Content-Type":"application/json; charset=utf-8"}),a.end(JSON.stringify({error:"Missing required field: script"}));return}let l={script:o.script,scriptRuntime:o.scriptRuntime??"python-3.9",inputs:o.inputs??{},secrets:o.secrets??{},onInstallProgress:p=>{p.kind==="start"?S({type:"venv-install-start"}):p.kind==="log"?S({type:"venv-install-log",line:p.line,stream:p.stream}):p.kind==="complete"?S({type:"venv-install-complete"}):S({type:"venv-install-error",message:p.message})}};typeof o.timeout=="number"&&o.timeout>0&&(l.timeout=Math.min(o.timeout,300*1e3));let d=await er(l);a.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),a.end(JSON.stringify(d));return}if(v==="/api/proxy-cache/clear"){h.clear(),i({kind:"info",message:"Proxy cache cleared"}),a.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),a.end('{"ok":true}');return}if(v.startsWith("/api/proxy")){let s=f.headers["x-proxy-target"];if(typeof s!="string"){a.writeHead(400),a.end("Missing x-proxy-target header");return}if(!Vn(s)){a.writeHead(400),a.end("Invalid proxy target");return}let o=v.slice(10)||"/",l=`${s}${o}`,d=f.method??"GET",p=[];for await(let pe of f)p.push(pe);let{host:j,"x-proxy-target":k,...I}=f.headers;if(d==="GET"){let pe=`GET::${l}`,{response:fe,fromCache:Ce}=await h.get(pe,()=>fetch(l,{method:"GET",headers:I}));i({kind:"request",method:"GET",status:fe.status,fromCache:Ce,url:o}),a.writeHead(fe.status,fe.headers),a.end(fe.body);return}let O=p.length>0?Buffer.concat(p):void 0,q=O&&O.length>0?O:void 0,J=await fetch(l,{method:d,headers:I,...q!==void 0&&{body:q}}),X=ft(Object.fromEntries(J.headers)),tt=await gt(J,mt);i({kind:"request",method:d,status:J.status,fromCache:!1,url:o}),a.writeHead(J.status,X),a.end(tt);return}if(v.startsWith("/api/source/tree")&&f.method==="GET"){let o=new URL(v,"http://localhost").searchParams.get("path")??"",l=await ir(g,o);if(l===null){a.writeHead(403,{"Content-Type":"application/json; charset=utf-8"}),a.end(JSON.stringify({error:"Invalid path"}));return}if(sr(g,l)){a.writeHead(403,{"Content-Type":"application/json; charset=utf-8"}),a.end(JSON.stringify({error:"Path is excluded"}));return}try{let d=await jn(l,{withFileTypes:!0}),p=await Promise.all(d.filter(k=>k.isDirectory()?!$e.has(k.name):k.isFile()?!Hn.has(k.name):!1).map(async k=>{let I=k.isDirectory()?"dir":"file",O={name:k.name,kind:I};if(I==="file")try{let q=await nr(De(l,k.name));O.size=q.size}catch{}return O}));p.sort((k,I)=>k.kind!==I.kind?k.kind==="dir"?-1:1:k.name.localeCompare(I.name));let j=l===g?"":l.slice(g.length+1);a.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),a.end(JSON.stringify({path:j,entries:p}))}catch(d){let p=d instanceof Error?d.message:String(d);t(`source/tree failed (${o}): ${p}`),a.writeHead(404,{"Content-Type":"application/json; charset=utf-8"}),a.end(JSON.stringify({error:"Not found"}))}return}if(v.startsWith("/api/source/file")&&f.method==="GET"){let o=new URL(v,"http://localhost").searchParams.get("path")??"";if(o===""){a.writeHead(400,{"Content-Type":"application/json; charset=utf-8"}),a.end(JSON.stringify({error:"Missing path"}));return}let l=await ir(g,o);if(l===null){a.writeHead(403,{"Content-Type":"application/json; charset=utf-8"}),a.end(JSON.stringify({error:"Invalid path"}));return}if(sr(g,l)){a.writeHead(403,{"Content-Type":"application/json; charset=utf-8"}),a.end(JSON.stringify({error:"Path is excluded"}));return}try{let d=await nr(l);if(!d.isFile()){a.writeHead(400,{"Content-Type":"application/json; charset=utf-8"}),a.end(JSON.stringify({error:"Not a file"}));return}if(d.size>Mn){a.writeHead(413,{"Content-Type":"application/json; charset=utf-8"}),a.end(JSON.stringify({error:"File too large",size:d.size}));return}let p=or(l).toLowerCase(),j=yt[p],k=tr.has(p),I=j??(k?"text/plain; charset=utf-8":"application/octet-stream");a.writeHead(200,{"Content-Type":I,"Content-Length":String(d.size)}),rr(l).pipe(a)}catch(d){let p=d instanceof Error?d.message:String(d);t(`source/file failed (${o}): ${p}`),a.writeHead(404,{"Content-Type":"application/json; charset=utf-8"}),a.end(JSON.stringify({error:"Not found"}))}return}let C=De(e,v==="/"?"/index.html":v),m=await Ct(C)?C:De(e,"index.html"),u=or(m),w=yt[u]??"application/octet-stream";a.writeHead(200,{"Content-Type":w}),rr(m).pipe(a)}catch(N){if(v.startsWith("/api/proxy")){let E=v.slice(10)||"/",C=f.method??"GET";N instanceof ke?i({kind:"info",message:`${C} ${E} \u2192 502 (response exceeded ${String(N.limit)} bytes)`}):i({kind:"request",method:C,status:502,fromCache:!1,url:E})}else i({kind:"info",message:`Error handling ${v}: ${N instanceof Error?N.message:String(N)}`});a.headersSent||(a.writeHead(502),a.end("Bad Gateway"))}})()}}import{launch as Wn}from"chrome-launcher";import{createRequire as Kn}from"module";import{mkdirSync as Gn}from"fs";import{readFile as Xn}from"fs/promises";import{dirname as Yn,join as Zn}from"path";import{fileURLToPath as Qn}from"url";var cr=Kn(import.meta.url)("chrome-remote-interface"),eo=new Set(["content-security-policy","content-security-policy-report-only","x-frame-options"]),ur=["kizen.dev","kizen.com"];function dr(e){try{let{hostname:t}=new URL(e);return ur.some(i=>t===i||t.endsWith("."+i))}catch{return!1}}function Ge(e){if(e.type==="number")return Number(e.value);if(e.type==="boolean")return e.value==="true";if(e.type!=="undefined"){if(e.type==="object"&&e.value==="null")return null;if(e.type==="object"&&e.valuePreview){let{valuePreview:t}=e;if(t.subtype==="array")return t.properties.filter(n=>/^\d+$/.test(n.name)).sort((n,r)=>parseInt(n.name)-parseInt(r.name)).map(Ge);let i={};for(let n of t.properties)i[n.name]=Ge(n);return i}return e.value??`[${e.type}]`}}function to(e){if(e.value!==void 0)return e.value;if(e.preview){let{preview:t}=e;if(t.subtype==="array")return t.properties.filter(n=>/^\d+$/.test(n.name)).sort((n,r)=>parseInt(n.name)-parseInt(r.name)).map(Ge);let i={};for(let n of t.properties)i[n.name]=Ge(n);return i}return e.description??`[${e.type}]`}async function ro(e,t){if(t.type==="object"&&t.objectId)try{let n=(await e.Runtime.callFunctionOn({objectId:t.objectId,functionDeclaration:'function() { const seen = new WeakSet(); return JSON.stringify(this, function(k,v) { if (typeof v === "object" && v !== null) { if (seen.has(v)) return "[Circular]"; seen.add(v); } return v; }); }',returnByValue:!0})).result.value;if(typeof n=="string")return JSON.parse(n)}catch{}return to(t)}async function no(e){try{let t=Zn(Yn(Qn(import.meta.url)),"viewer","icon.png"),i=await Xn(t);await e.Browser.setDockTile({image:i.toString("base64")})}catch{}}async function pr(e){Gn(e.userDataDir,{recursive:!0});let t=`http://localhost:${String(e.port)}`,i=await Wn({startingUrl:t,chromeFlags:[`--app=${t}`,"--no-first-run","--no-default-browser-check","--disable-extensions"],userDataDir:e.userDataDir,handleSIGINT:!1,logLevel:"silent"}),n=i.port,r=null,c=null,S=null,y=null,_=0,b=!1,h=u=>{e.debugLog?.(u)},g=u=>{e.verbose&&e.debugLog?.(u)},f=()=>{c!==null&&(clearInterval(c),c=null)},a=u=>u==="chrome://new-tab-page/"||u==="chrome://newtab/",v=async u=>{f(),S=null,await no(u),await u.Fetch.enable({patterns:ur.flatMap(s=>[{urlPattern:`*://${s}/*`,requestStage:"Response"},{urlPattern:`*://*.${s}/*`,requestStage:"Response"}])}),u.on("Fetch.requestPaused",s=>{(async()=>{let o=s,{requestId:l,request:d,responseHeaders:p,responseStatusCode:j}=o,k=d.url;g(`Fetch ${String(j??"?")} ${k}`);let I=[],O=!1;for(let q of p??[])if(!eo.has(q.name.toLowerCase()))if(q.name.toLowerCase()==="set-cookie"&&dr(k)){let J=q.value.split(`
172
+ `).map(X=>/SameSite=/i.test(X)?X.replace(/SameSite=\w+/i,"SameSite=None"):X+"; SameSite=None; Secure").join(`
173
+ `);I.push({name:q.name,value:J}),O=!0}else I.push(q);O&&g(` rewrote Set-Cookie SameSite on ${k}`);try{let{body:q,base64Encoded:J}=await u.Fetch.getResponseBody({requestId:l});await u.Fetch.fulfillRequest({requestId:l,responseCode:j??200,responseHeaders:I,body:q,...J&&{base64Encoded:!0}})}catch(q){g(` fulfillRequest failed (${q.message}), falling through`);try{await u.Fetch.continueRequest({requestId:l})}catch(J){g(` continueRequest also failed (${J.message})`)}}})()}),c=setInterval(()=>{(async()=>{try{let{cookies:s}=await u.Network.getAllCookies(),o=0;for(let l of s){if(!dr(`https://${l.domain??""}`)||l.sameSite==="None")continue;let d=l.domain??"",p=d.startsWith(".")?d.slice(1):d;await u.Network.setCookie({url:`https://${p}`,name:l.name,value:l.value,domain:d,path:l.path??"/",secure:!0,...l.httpOnly!==void 0&&{httpOnly:l.httpOnly},...l.expirationDate!==void 0&&{expires:l.expirationDate},sameSite:"None"}),o++}o>0&&g(`cookie-patch cycle: updated ${String(o)} cookie(s) to SameSite=None`)}catch(s){g(`cookie-patch cycle failed: ${s.message}`)}})()},2e3),await u.send("Runtime.enable",{generatePreview:!0}),u.on("Target.attachedToTarget",s=>{let o=s,{sessionId:l,targetInfo:d}=o;g(`attached session ${l} (${d.type} ${d.url})`),u.send("Runtime.enable",{generatePreview:!0},l).catch(p=>{g(` Runtime.enable on session ${l} failed: ${p.message}`)})}),await u.Target.setAutoAttach({autoAttach:!0,waitForDebuggerOnStart:!1,flatten:!0}),await u.Target.setDiscoverTargets({discover:!0});let w=s=>{if(s.type==="page"){if(a(s.url)){h(`closed stray new-tab-page target ${s.targetId}`),S!==null&&u.Target.activateTarget({targetId:S}).catch(()=>{}),u.Target.closeTarget({targetId:s.targetId}).catch(()=>{});return}S===null&&(S=s.targetId,h(`captured main target ${s.targetId} (${s.url})`))}};u.on("Target.targetCreated",s=>{let{targetInfo:o}=s;g(`target +${o.type} ${o.targetId} ${o.url}`),w(o)}),u.on("Target.targetInfoChanged",s=>{let{targetInfo:o}=s;g(`target ~${o.type} ${o.targetId} ${o.url}`),w(o)}),u.on("Runtime.consoleAPICalled",s=>{(async()=>{let{type:o,args:l}=s;if(l[0]?.type==="string"&&String(l[0].value).includes("Chrome Security"))return;let d=o==="warning"?"warn":["log","warn","error","info"].includes(o)?o:"log";g(`console.${d} (${String(l.length)} arg(s))`);let p=await Promise.all(l.map(j=>ro(u,j)));e.broadcast({type:"console-message",level:d,args:p})})().catch(o=>{g(`console serialization failed: ${o.message}`)})}),u.on("disconnect",()=>{b||r!==u||(h("CDP disconnected"),r=null,f(),N())})},N=()=>{if(b||y!==null)return;let u=Math.min(1e3*2**_,1e4),w=_+1;_++,h(`CDP reconnect scheduled in ${String(u)}ms (attempt #${String(w)})`),y=setTimeout(()=>{y=null,(async()=>{let s;try{s=await cr({port:n})}catch{h(`CDP reconnect attempt #${String(w)} failed`),N();return}if(b){try{await s.close()}catch{}return}r=s;try{await v(s),h(`CDP reconnected after ${String(w)} attempt(s)`),_=0}catch{r===s&&(r=null);try{await s.close()}catch{}h(`CDP reconnect attempt #${String(w)} failed during setup`),N()}})()},u)},E=30,C=300;for(let u=0;u<E;u++){await new Promise(w=>setTimeout(w,C)),h(`CDP connect attempt ${String(u+1)}/${String(E)}`);try{let w=await cr({port:n});r=w,e.path&&w.send("Page.navigate",{url:`${t}${e.path}`});try{await v(w),h("CDP connected")}catch{r===w&&(r=null);try{await w.close()}catch{}h("CDP setup failed after connect")}break}catch{if(u===E-1){let w=`CDP unavailable after ${String(E)} attempts \u2014 viewer running without interception (CSP/cookie rewrites disabled)`;return h(w),e.onWarn?.(w),{kill:()=>{i.kill()}}}}}return r?{kill:()=>{if(h("CDP kill requested \u2014 tearing down"),b=!0,y!==null&&(clearTimeout(y),y=null),f(),r!==null){try{r.close()}catch{}r=null}i.kill()}}:{kill:()=>{i.kill()}}}import{useCallback as oo,useState as io}from"react";function Xe(e){let[t,i]=io([]),n=oo(r=>{i(c=>[...c,`${new Date().toLocaleTimeString()}: ${r}`].slice(-e))},[e]);return[t,n]}import{useCallback as Ye,useEffect as wt,useState as Ze}from"react";import{Box as z,Text as D,useInput as so}from"ink";import{jsx as P,jsxs as H}from"react/jsx-runtime";var ne=["apiKey","userId","businessId"],ao={apiKey:"API Key",userId:"User ID",businessId:"Business ID"},Re=({text:e})=>P(D,{dimColor:!0,children:e}),Qe=({initialMode:e,showProfileManager:t,onComplete:i,onCancel:n})=>{let[r,c]=Ze(e==="global"&&!t?{type:"loading"}:e==="global"&&t?{type:"profile-loading"}:{type:"mode-select",cursor:0}),[S,y]=Ze(""),[_,b]=Ze(null),[h,g]=Ze(void 0),f=Ye(s=>{if(s==="local"){i({mode:"local",credentials:null});return}c({type:"profile-loading"})},[i]),a=Ye(async s=>{if(s===null){c({type:"profile-loading"});let d=await Ee();c({type:"name-entry",nameBuffer:"",existingProfiles:d});return}g(s.isDefault?void 0:s.name),c({type:"loading"});let o=s.isDefault?await Z():await be(s.name),l=o?Math.max(0,G.indexOf(o.environment)):0;c({type:"creds-entry",field:0,values:o??{},envCursor:l})},[]),v=Ye(async(s,o)=>{let l=G[o]??"go",d={apiKey:s.apiKey??"",userId:s.userId??"",businessId:s.businessId??"",environment:l};c({type:"saving"});try{await Jt(h??re,d),i({mode:"global",credentials:d,...h!==void 0&&{profileName:h}})}catch(p){b(p instanceof Error?p.message:String(p)),c({type:"creds-entry",field:0,values:s,envCursor:o})}},[i,h]);so((s,o)=>{if(o.ctrl&&s==="c"&&process.exit(0),o.escape){if((r.type==="creds-entry"||r.type==="name-entry")&&e==="global"&&t){c({type:"profile-loading"}),N();return}n?.();return}if(r.type==="mode-select"){if(o.upArrow)c({type:"mode-select",cursor:0});else if(o.downArrow)c({type:"mode-select",cursor:1});else if(o.return){let l=r.cursor===0?"global":"local";f(l)}return}if(r.type==="profile-select"){let{profiles:l,cursor:d}=r,p=l.length,j=l.length+1;if(o.upArrow)c({...r,cursor:(d-1+j)%j});else if(o.downArrow)c({...r,cursor:(d+1)%j});else if(o.return)if(d===p)c({type:"name-entry",nameBuffer:"",existingProfiles:l});else{let k=l[d]??l[0];k&&a(k)}return}if(r.type==="name-entry"){let{nameBuffer:l,existingProfiles:d}=r;if(o.backspace||o.delete)c({type:"name-entry",nameBuffer:l.slice(0,-1),existingProfiles:d});else if(o.return){let p=l.trim();if(!p){c({...r,nameError:"Name cannot be empty"});return}if(p===re){c({...r,nameError:`"${re}" is reserved`});return}if(d.some(j=>j.name===p)){c({...r,nameError:`Profile "${p}" already exists`});return}g(p),c({type:"creds-entry",field:0,values:{},envCursor:0}),y("")}else s&&!o.ctrl&&!o.meta&&c({type:"name-entry",nameBuffer:l+s,existingProfiles:d});return}if(r.type==="creds-entry"){let{field:l,values:d,envCursor:p}=r;if(l===ne.length){o.leftArrow?c({...r,envCursor:(p-1+G.length)%G.length}):o.rightArrow?c({...r,envCursor:(p+1)%G.length}):o.upArrow?(c({...r,field:l-1}),y(d[ne[l-1]]??"")):o.return&&v(d,p);return}let k=ne[l];if(!k)return;if(o.backspace||o.delete)y(I=>I.slice(0,-1));else if(o.upArrow&&l>0){let I={...d,[k]:S},O=l-1;c({...r,field:O,values:I}),y(I[ne[O]]??"")}else if(o.return||o.tab||o.downArrow){let I={...d,[k]:S},O=l+1;c({...r,field:O,values:I}),O<ne.length?y(I[ne[O]]??""):y("")}else s&&!o.ctrl&&!o.meta&&y(I=>I+s)}});let N=Ye(async()=>{let s=await Ee();c({type:"profile-select",profiles:s,cursor:0})},[]);if(wt(()=>{r.type==="profile-loading"&&N()},[r.type,N]),wt(()=>{e==="global"&&!t&&(async()=>{let s=await Z(),o=s?Math.max(0,G.indexOf(s.environment)):0;c({type:"creds-entry",field:0,values:s??{},envCursor:o})})()},[]),wt(()=>{r.type==="creds-entry"&&r.field===0&&y(r.values.apiKey??"")},[r.type]),r.type==="loading"||r.type==="saving"||r.type==="profile-loading"){let s=r.type==="saving"?"Saving credentials\u2026":r.type==="profile-loading"?"Loading profiles\u2026":"Loading credentials\u2026";return P(z,{paddingY:1,paddingX:2,children:P(D,{dimColor:!0,children:s})})}if(r.type==="done")return null;if(r.type==="mode-select")return H(z,{flexDirection:"column",paddingY:1,paddingX:2,gap:1,children:[P(V,{}),H(z,{flexDirection:"column",gap:0,children:[P(D,{bold:!0,children:"Credential mode"}),[{label:"Global",desc:"~/.kizenappbuilder/credentials.json",mode:"global"},{label:"Local",desc:"Enter credentials in the browser dev tools",mode:"local"}].map((o,l)=>{let d=r.cursor===l;return H(z,{gap:2,children:[P(D,{...d&&{color:"cyan"},children:d?"\u276F":" "}),P(D,{bold:d,...d&&{color:"cyan"},children:o.label}),P(D,{dimColor:!0,children:o.desc})]},o.mode)})]}),P(Re,{text:"\u2191\u2193 to move \xB7 Enter to select \xB7 Ctrl+C to quit"})]});if(r.type==="profile-select"){let{profiles:s,cursor:o}=r,l=s.length;return H(z,{flexDirection:"column",paddingY:1,paddingX:2,gap:1,children:[P(V,{}),H(z,{flexDirection:"column",gap:0,children:[P(D,{bold:!0,children:"Credential profiles"}),s.map((d,p)=>{let j=o===p;return H(z,{gap:2,children:[P(D,{...j&&{color:"cyan"},children:j?"\u276F":" "}),P(D,{bold:j,...j&&{color:"cyan"},children:d.isDefault?"Default":d.name}),P(D,{dimColor:!0,children:d.path})]},d.name)}),H(z,{gap:2,children:[P(D,{...o===l&&{color:"cyan"},children:o===l?"\u276F":" "}),P(D,{bold:o===l,...o===l&&{color:"cyan"},children:"+ Add new profile"})]})]}),P(Re,{text:"\u2191\u2193 to move \xB7 Enter to select \xB7 Esc to cancel"})]})}if(r.type==="name-entry"){let{nameBuffer:s,nameError:o}=r;return H(z,{flexDirection:"column",paddingY:1,paddingX:2,gap:1,children:[P(V,{}),P(z,{flexDirection:"column",gap:0,children:P(D,{bold:!0,children:"New credential profile"})}),o&&H(D,{color:"red",children:["Error: ",o]}),H(z,{gap:2,children:[P(z,{width:12,children:P(D,{bold:!0,color:"cyan",children:"Profile name"})}),P(D,{color:"cyan",children:">"}),P(D,{children:s}),P(D,{color:"cyan",children:"\u2588"})]}),P(Re,{text:"Type a name \xB7 Enter to confirm \xB7 Esc to go back"})]})}let{field:E,values:C,envCursor:m}=r,u=E===ne.length,w=h??re;return H(z,{flexDirection:"column",paddingY:1,paddingX:2,gap:1,children:[P(V,{}),H(z,{flexDirection:"column",gap:0,children:[P(D,{bold:!0,children:h!==void 0?`Profile: ${h}`:"Global credentials"}),H(D,{dimColor:!0,children:["Saved to:"," ",h!==void 0?`~/.kizenappbuilder/${h}.json`:xe]})]}),_&&H(D,{color:"red",children:["Error: ",_]}),H(z,{flexDirection:"column",gap:0,children:[ne.map((s,o)=>{let l=E===o,d=l?S:C[s]??"";return H(z,{gap:2,children:[P(z,{width:12,children:P(D,{bold:l,...l&&{color:"cyan"},children:ao[s]})}),P(D,{...l&&{color:"cyan"},children:">"}),P(D,{children:d}),l&&P(D,{color:"cyan",children:"\u2588"})]},s)}),H(z,{gap:2,children:[P(z,{width:12,children:P(D,{bold:u,...u&&{color:"cyan"},children:"Environment"})}),P(D,{...u&&{color:"cyan"},children:">"}),P(z,{gap:1,children:G.map((s,o)=>{let l=o===m;return u?P(D,{bold:l,...l&&{color:"cyan"},children:l?`[${s}]`:s},s):P(D,{dimColor:!l,bold:l,children:s},s)})})]})]}),u?P(Re,{text:"\u2190\u2192 to select \xB7 \u2191\u2193 to move \xB7 Enter to save \xB7 Esc to cancel"}):P(Re,{text:"\u2191\u2193 to move \xB7 Backspace to delete \xB7 Esc to cancel"}),H(D,{dimColor:!0,children:["Profile: ",w]})]})};import{jsx as L,jsxs as $}from"react/jsx-runtime";var mo=[".kizenapp",".git"],mr=({port:e,pluginDir:t,outputDir:i,credentials:n,credentialMode:r,activeCredentialProfile:c,lastPath:S,debug:y=!1,verbose:_=!1})=>{let b=Q(n),h=Q(c),g=Q(S),[f,a]=W("main"),[v,N]=W(r),[E,C]=W("starting"),[m,u]=W(null),[w,s]=Xe(50),[o,l]=Xe(50),[d,p]=Xe(50),[j,k]=W([]),[I,O]=W("pending"),[q,J]=W(null),[X,tt]=W(null),[pe,fe]=W(null),[Ce,rt]=W(0),[St,br]=W(0),[vr,Cr]=W(0),[wr,Sr]=W(0),nt=Q(!1),Pt=Q(!1),je=Q(null),we=Q(null),ee=Q(new Set),Fe=Q([]),K=ve(x=>{let F=JSON.stringify(x);if(ee.current.size===0){Fe.current=[...Fe.current,F].slice(-50);return}let te=!1;for(let Y of ee.current)if(Y.readyState===1)try{Y.send(F)}catch{ee.current.delete(Y),te=!0}te&&rt(ee.current.size)},[]),Ae=s,ot=ve(()=>{je.current?.kill(),je.current=null,pr({port:e,userDataDir:fr(i,".chrome"),broadcast:K,onWarn:x=>{s(`\u26A0 ${x}`)},...g.current!==void 0&&{path:g.current},...y&&{debugLog:p},..._&&{verbose:!0}}).then(x=>{je.current=x})},[e,i,K,y,_,p,s]);et(()=>{let x=()=>{je.current?.kill()};return process.on("exit",x),()=>{process.off("exit",x)}},[]),co((x,F)=>{(x==="q"||F.ctrl&&x==="c")&&process.exit(0),x==="c"&&a("editing"),x==="v"&&ot()},{isActive:f==="main"}),et(()=>{E!=="running"||Pt.current||(Pt.current=!0,ot())},[E,ot]);let Pr=ve(x=>{b.current=x.credentials,h.current=x.profileName,a("main"),N(x.mode);let F=x.profileName;ue(i,{credentialMode:x.mode,...F!==void 0&&{activeCredentialProfile:F}}),K({type:"credentials-updated",credentials:x.credentials})},[i,K]),Tt=ve(x=>{k(F=>[...F,`${new Date().toLocaleTimeString()}: ${bt(x)}`].slice(-50)),x.kind==="request"&&(x.fromCache?Cr(F=>F+1):Sr(F=>F+1)),K({type:"proxy-log",entry:x})},[K]),ie=ve(x=>{l(x),K({type:"log",message:x})},[l,K]),it=ve(()=>{nt.current||(nt.current=!0,O("building"),J(null),ie("Build started"),ze(t,i).then(({bundleSize:x})=>{ie(`Build finished \u2014 bundle: ${Se(x)}`),O("done"),tt(new Date),fe(x),ie("Notifying viewers to reload"),K({type:"rebuild"})}).catch(x=>{let F=x instanceof Error?x.message:String(x);J(F),O("error")}).finally(()=>{nt.current=!1}))},[t,i,ie,K]);if(et(()=>{it();let x=uo(t,{recursive:!0},(F,te)=>{if(!te)return;let Y=te.replace(/\\/g,"/");mo.some(Oe=>Y.startsWith(Oe))||(ie(`File change detected: ${te}`),we.current!==null&&clearTimeout(we.current),we.current=setTimeout(it,150))});return x.on("error",F=>{ie(`File watcher error \u2014 rebuilds have stopped: ${F.message}`)}),()=>{x.close(),we.current!==null&&clearTimeout(we.current)}},[t,it,ie]),et(()=>{let x=ar();Ct(fr(x,"index.html")).then(F=>{if(!F){u("Viewer not built. Run 'pnpm build:viewer' first."),C("error");return}let te=lr(x,Ae,Tt,b,h,i,K,br,g,t),Y=po(te),Oe=new fo({server:Y});return Oe.on("connection",se=>{for(let _r of Fe.current)se.readyState===1&&se.send(_r);Fe.current=[],Ae("Viewer connected for live reload"),ee.current.add(se),rt(ee.current.size),se.on("close",()=>{ee.current.delete(se),rt(ee.current.size)})}),Y.listen(e,"127.0.0.1",()=>{C("running"),Ae(`Server started on 127.0.0.1:${String(e)}`)}),Y.on("error",se=>{u(se.message),C("error")}),()=>{Oe.close(),Y.close()}})},[e,Ae,Tt,K,i,t]),f==="editing")return L(Qe,{...v!==void 0&&{initialMode:v},showProfileManager:v==="global",onComplete:Pr,onCancel:()=>{a("main")}});let st=X!==null?Math.round((Date.now()-X.getTime())/1e3):null,Tr=st===null?"":st<5?" (just now)":` (${String(st)}s ago)`;return $(U,{flexDirection:"column",paddingY:1,paddingX:2,children:[L(V,{marginBottom:1}),$(U,{flexDirection:"column",children:[$(U,{gap:2,marginBottom:0,children:[L(R,{bold:!0,dimColor:!0,children:"App Build"}),I==="pending"&&L(R,{dimColor:!0,children:"waiting\u2026"}),I==="building"&&$(U,{gap:1,children:[L(Me,{}),L(R,{children:"Building plugin package\u2026"})]}),I==="done"&&$(U,{gap:1,children:[L(R,{color:"green",children:"\u2713 Built"}),X!==null&&$(R,{dimColor:!0,children:["at ",X.toLocaleTimeString(),Tr]}),pe!==null&&$(R,{dimColor:!0,children:["\xB7 bundle: ",Se(pe)]})]}),I==="error"&&$(R,{color:"red",children:["\u2717 ",q??"unknown error"]})]}),L(U,{flexDirection:"column",height:10,borderStyle:"single",borderColor:"gray",overflow:"hidden",children:o.slice(-8).map((x,F)=>L(R,{dimColor:!0,wrap:"truncate",children:x},F))})]}),$(U,{marginTop:1,flexDirection:"column",children:[$(U,{gap:2,children:[L(R,{bold:!0,dimColor:!0,children:"Web Server"}),E==="starting"&&L(R,{dimColor:!0,children:"starting\u2026"}),E==="running"&&$(U,{gap:2,children:[L(R,{color:"green",children:"\u2713 Running"}),$(R,{dimColor:!0,children:["\u25CF ",Ce," viewer",Ce!==1?"s":""]})]}),E==="error"&&$(R,{color:"red",children:["\u2717 ",m??"Server error"]})]}),L(U,{flexDirection:"column",height:10,borderStyle:"single",borderColor:"gray",overflow:"hidden",children:w.slice(-8).map((x,F)=>L(R,{dimColor:!0,wrap:"truncate",children:x},F))})]}),$(U,{marginTop:1,flexDirection:"column",children:[$(U,{gap:2,children:[L(R,{bold:!0,dimColor:!0,children:"Network Proxy"}),L(R,{color:"green",children:"\u2713 Active"}),$(R,{dimColor:!0,children:["\u25CF ",St," key",St!==1?"s":""," in the network cache \xB7"," ",vr," hit \xB7 ",wr," miss"]})]}),L(U,{flexDirection:"column",height:10,borderStyle:"single",borderColor:"gray",overflow:"hidden",children:j.length===0?L(R,{dimColor:!0,children:" No requests yet."}):j.slice(-8).map((x,F)=>L(R,{dimColor:!0,wrap:"truncate",children:x},F))})]}),y&&$(U,{marginTop:1,flexDirection:"column",children:[$(U,{gap:2,children:[L(R,{bold:!0,dimColor:!0,children:"CDP Debug"}),$(R,{dimColor:!0,children:["\u25CF ",d.length," event(s)"]})]}),L(U,{flexDirection:"column",height:10,borderStyle:"single",borderColor:"gray",overflow:"hidden",children:d.length===0?L(R,{dimColor:!0,children:" No CDP events yet."}):d.slice(-8).map((x,F)=>L(R,{dimColor:!0,wrap:"truncate",children:x},F))})]}),$(U,{marginTop:1,gap:1,children:[L(R,{dimColor:!0,children:"["}),L(R,{children:"q"}),L(R,{dimColor:!0,children:"] quit ["}),L(R,{children:"c"}),L(R,{dimColor:!0,children:"] credentials ["}),L(R,{children:"v"}),$(R,{dimColor:!0,children:["] ",Ce===0?"open viewer":"relaunch viewer"]})]})]})};async function gr(e){return new Promise(t=>{let i=null,n=c=>{i?.(),t(c)},{unmount:r}=hr(yr(Qe,{...e!==void 0&&{initialMode:e},onComplete:n}),{exitOnCtrlC:!1});i=r})}function xr(e){e.command("dev").description("Start the plugin viewer dev server").option("-p, --port <port>","port to listen on","3121").option("-c, --credentials <path>","path to a credentials JSON file").option("-d, --debug","Enable CDP lifecycle logs in the TUI").option("-v, --verbose","Log every CDP event and handled error (implies --debug)").action(async t=>{let i=parseInt(t.port,10),n=process.cwd(),r=`${n}/.kizenapp`;ye(n);let c=null,S,y,{lastPath:_}=await Ie(r);if(t.credentials)c=await Je(t.credentials);else{let f=await Ie(r);if(f.credentialMode==="local")S="local";else if(f.credentialMode==="global"){if(S="global",y=f.activeCredentialProfile,y?(c=await be(y),c||(c=await Z(),y=void 0)):c=await Z(),!c){let a=await gr("global");c=a.credentials,y=a.profileName}}else{let a=await gr();c=a.credentials,S=a.mode,y=a.profileName;let v=a.profileName;await ue(r,{credentialMode:a.mode,...v!==void 0&&{activeCredentialProfile:v}})}}let b=t.verbose===!0,h=b||t.debug===!0,{waitUntilExit:g}=hr(yr(mr,{port:i,pluginDir:n,outputDir:r,credentials:c,...S!==void 0&&{credentialMode:S},...y!==void 0&&{activeCredentialProfile:y},..._!==void 0&&{lastPath:_},debug:h,verbose:b}),{exitOnCtrlC:!1});await g()})}var bo=ho(yo(xo(import.meta.url)),"..","package.json"),vo=JSON.parse(go(bo,"utf-8"));Le.name("appbuilder").description("Kizen plugin app builder").version(vo.version);Wt(Le);Ft(Le);xr(Le);Le.parse();
2840
174
  //# sourceMappingURL=index.js.map