@kizenapps/cli 0.7.0 → 0.9.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 (87) hide show
  1. package/dist/electron/main.js +9 -8
  2. package/dist/electron/main.js.map +1 -1
  3. package/dist/index.js +579 -114
  4. package/dist/index.js.map +1 -1
  5. package/dist/nwjs/shell/icon.png +0 -0
  6. package/dist/nwjs/shell/index.html +200 -0
  7. package/dist/nwjs/shell/package.json +16 -0
  8. package/dist/viewer/assets/fa-core-DAYjf9Yn.js +558 -0
  9. package/dist/viewer/assets/fa-core-Ff8SZmm_.js +558 -0
  10. package/dist/viewer/assets/index-47Vh9MH0.js +17 -0
  11. package/dist/viewer/assets/index-6uunWX8M.js +17 -0
  12. package/dist/viewer/assets/index-B48OpZHg.js +17 -0
  13. package/dist/viewer/assets/index-BFfw7eD7.js +17 -0
  14. package/dist/viewer/assets/index-BFyUUb06.js +17 -0
  15. package/dist/viewer/assets/index-BL-Yu7Bf.js +17 -0
  16. package/dist/viewer/assets/index-BUmv8vz7.js +17 -0
  17. package/dist/viewer/assets/index-BZ4HmEz8.js +17 -0
  18. package/dist/viewer/assets/index-BZIXccTd.js +17 -0
  19. package/dist/viewer/assets/index-Bk9hnfnO.js +17 -0
  20. package/dist/viewer/assets/index-C0SIHm9A.js +17 -0
  21. package/dist/viewer/assets/index-CDd-WK6Y.js +17 -0
  22. package/dist/viewer/assets/index-CDxQqP40.js +17 -0
  23. package/dist/viewer/assets/{index-C_6izRRh.js → index-CGJzvpL5.js} +7 -7
  24. package/dist/viewer/assets/index-CIarX3Lt.js +17 -0
  25. package/dist/viewer/assets/index-CIfVP45E.js +17 -0
  26. package/dist/viewer/assets/index-CJeb7uFu.css +2 -0
  27. package/dist/viewer/assets/index-CbwLb2S_.js +17 -0
  28. package/dist/viewer/assets/index-CsfWgoID.js +17 -0
  29. package/dist/viewer/assets/index-CvWmkIY5.js +17 -0
  30. package/dist/viewer/assets/index-Cy-weKfG.css +2 -0
  31. package/dist/viewer/assets/index-Cym12l2M.js +17 -0
  32. package/dist/viewer/assets/index-D0W0oANg.js +17 -0
  33. package/dist/viewer/assets/index-DCPtqd3W.js +17 -0
  34. package/dist/viewer/assets/index-DF_zYdLw.js +17 -0
  35. package/dist/viewer/assets/index-DGqxQcUx.js +17 -0
  36. package/dist/viewer/assets/index-DK0OVb7V.js +17 -0
  37. package/dist/viewer/assets/index-DKjVFdzw.js +17 -0
  38. package/dist/viewer/assets/index-DLhj0I_2.js +17 -0
  39. package/dist/viewer/assets/index-DPS_ZjbM.js +17 -0
  40. package/dist/viewer/assets/index-DSbHkhcS.js +17 -0
  41. package/dist/viewer/assets/index-DTbmNtEU.js +17 -0
  42. package/dist/viewer/assets/index-DUYd6RpI.js +17 -0
  43. package/dist/viewer/assets/index-DZwYe1wZ.css +2 -0
  44. package/dist/viewer/assets/index-DkRokbYc.js +17 -0
  45. package/dist/viewer/assets/index-DtOWzkR5.js +17 -0
  46. package/dist/viewer/assets/index-DuvXujkm.js +17 -0
  47. package/dist/viewer/assets/index-Dv0mX-oM.js +17 -0
  48. package/dist/viewer/assets/index-DyXisBY9.css +2 -0
  49. package/dist/viewer/assets/index-Nm1xKWTO.js +17 -0
  50. package/dist/viewer/assets/index-ZBdAq6dF.js +17 -0
  51. package/dist/viewer/assets/index-ZOxL_JJZ.js +17 -0
  52. package/dist/viewer/assets/index-kMMOwjlY.js +17 -0
  53. package/dist/viewer/assets/{index--m1PKX6N.js → index-ov2oU6RI.js} +7 -7
  54. package/dist/viewer/assets/index-uF6u0kT8.js +17 -0
  55. package/dist/viewer/assets/index-xbDHMSaa.css +2 -0
  56. package/dist/viewer/assets/react-D7PmSb6e.js +9 -0
  57. package/dist/viewer/assets/react-DIjeYlvh.js +9 -0
  58. package/dist/viewer/index.html +4 -4
  59. package/package.json +4 -4
  60. package/dist/viewer/assets/index-2OcBnmZB.js +0 -17
  61. package/dist/viewer/assets/index-7ah1RuPy.js +0 -17
  62. package/dist/viewer/assets/index-B3gNsKTt.js +0 -17
  63. package/dist/viewer/assets/index-B40AtXqk.js +0 -17
  64. package/dist/viewer/assets/index-B4ikC-wc.js +0 -17
  65. package/dist/viewer/assets/index-BKOfPpLS.js +0 -17
  66. package/dist/viewer/assets/index-BM3JrC-1.js +0 -17
  67. package/dist/viewer/assets/index-BUnK11-F.js +0 -17
  68. package/dist/viewer/assets/index-Bc7IZVr6.js +0 -17
  69. package/dist/viewer/assets/index-C1KzcpVj.js +0 -17
  70. package/dist/viewer/assets/index-CBBwr2o_.js +0 -17
  71. package/dist/viewer/assets/index-CHtXWYIY.js +0 -17
  72. package/dist/viewer/assets/index-CN9NvJkQ.js +0 -17
  73. package/dist/viewer/assets/index-CNKPBxhv.js +0 -17
  74. package/dist/viewer/assets/index-CgzK6zig.js +0 -17
  75. package/dist/viewer/assets/index-Cw2cxYy2.js +0 -17
  76. package/dist/viewer/assets/index-D06uhGtQ.js +0 -17
  77. package/dist/viewer/assets/index-DAHWT-g0.js +0 -17
  78. package/dist/viewer/assets/index-DAxJgHat.js +0 -17
  79. package/dist/viewer/assets/index-DF_geUaW.js +0 -17
  80. package/dist/viewer/assets/index-DUgUEouL.css +0 -2
  81. package/dist/viewer/assets/index-DcGGXuA3.js +0 -17
  82. package/dist/viewer/assets/index-DlVmY-nR.js +0 -17
  83. package/dist/viewer/assets/index-FZw9Ev_R.js +0 -17
  84. package/dist/viewer/assets/index-M1Obm_99.js +0 -17
  85. package/dist/viewer/assets/index-_kM1Ri7v.js +0 -17
  86. package/dist/viewer/assets/index-aTt_sTlu.js +0 -17
  87. package/dist/viewer/assets/index-vYFGGFc2.js +0 -17
package/dist/index.js CHANGED
@@ -229,14 +229,231 @@ import { render as render2 } from "ink";
229
229
  // src/ui/DevUI.tsx
230
230
  import { useCallback as useCallback2, useEffect as useEffect3, useRef, useState as useState3 } from "react";
231
231
  import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
232
- import { spawn } from "child_process";
233
232
  import { createReadStream, watch } from "fs";
234
- import { access, readFile as readFile4 } from "fs/promises";
233
+ import { access, readFile as readFile5 } from "fs/promises";
235
234
  import { createServer } from "http";
235
+ import { dirname as dirname3, extname, join as join7 } from "path";
236
+ import { fileURLToPath as fileURLToPath2 } from "url";
237
+ import { WebSocketServer } from "ws";
238
+
239
+ // src/chrome/launcher.ts
240
+ import { launch } from "chrome-launcher";
236
241
  import { createRequire } from "module";
237
- import { dirname as dirname2, extname, join as join6 } from "path";
242
+ import { mkdirSync } from "fs";
243
+ import { readFile as readFile2 } from "fs/promises";
244
+ import { dirname, join as join4 } from "path";
238
245
  import { fileURLToPath } from "url";
239
- import { WebSocketServer } from "ws";
246
+ var CDP = createRequire(import.meta.url)("chrome-remote-interface");
247
+ var CSP_HEADERS = /* @__PURE__ */ new Set([
248
+ "content-security-policy",
249
+ "content-security-policy-report-only",
250
+ "x-frame-options"
251
+ ]);
252
+ var KIZEN_DOMAINS = ["kizen.dev", "kizen.com"];
253
+ function isKizenUrl(url) {
254
+ try {
255
+ const { hostname } = new URL(url);
256
+ return KIZEN_DOMAINS.some((d) => hostname === d || hostname.endsWith("." + d));
257
+ } catch {
258
+ return false;
259
+ }
260
+ }
261
+ function parseProp(prop) {
262
+ if (prop.type === "number") return Number(prop.value);
263
+ if (prop.type === "boolean") return prop.value === "true";
264
+ if (prop.type === "undefined") return void 0;
265
+ if (prop.type === "object" && prop.value === "null") return null;
266
+ if (prop.type === "object" && prop.valuePreview) {
267
+ const { valuePreview } = prop;
268
+ if (valuePreview.subtype === "array") {
269
+ return valuePreview.properties.filter((p) => /^\d+$/.test(p.name)).sort((a, b) => parseInt(a.name) - parseInt(b.name)).map(parseProp);
270
+ }
271
+ const obj = {};
272
+ for (const p of valuePreview.properties) obj[p.name] = parseProp(p);
273
+ return obj;
274
+ }
275
+ return prop.value ?? `[${prop.type}]`;
276
+ }
277
+ function serializeArg(arg) {
278
+ if (arg.value !== void 0) return arg.value;
279
+ if (arg.preview) {
280
+ const { preview } = arg;
281
+ if (preview.subtype === "array") {
282
+ return preview.properties.filter((p) => /^\d+$/.test(p.name)).sort((a, b) => parseInt(a.name) - parseInt(b.name)).map(parseProp);
283
+ }
284
+ const obj = {};
285
+ for (const prop of preview.properties) obj[prop.name] = parseProp(prop);
286
+ return obj;
287
+ }
288
+ return arg.description ?? `[${arg.type}]`;
289
+ }
290
+ async function serializeArgAsync(client, arg) {
291
+ if (arg.type === "object" && arg.objectId) {
292
+ try {
293
+ const result = await client.Runtime.callFunctionOn({
294
+ objectId: arg.objectId,
295
+ 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; }); }',
296
+ returnByValue: true
297
+ });
298
+ const val = result.result.value;
299
+ if (typeof val === "string") return JSON.parse(val);
300
+ } catch {
301
+ }
302
+ }
303
+ return serializeArg(arg);
304
+ }
305
+ async function installDockIcon(client) {
306
+ try {
307
+ const iconPath = join4(dirname(fileURLToPath(import.meta.url)), "viewer", "icon.png");
308
+ const icon = await readFile2(iconPath);
309
+ await client.Browser.setDockTile({ image: icon.toString("base64") });
310
+ } catch {
311
+ }
312
+ }
313
+ async function launchChromeViewer(opts) {
314
+ mkdirSync(opts.userDataDir, { recursive: true });
315
+ const chrome = await launch({
316
+ startingUrl: `http://localhost:${opts.port}`,
317
+ chromeFlags: [
318
+ `--app=http://localhost:${opts.port}`,
319
+ "--no-first-run",
320
+ "--no-default-browser-check",
321
+ "--disable-extensions"
322
+ ],
323
+ userDataDir: opts.userDataDir,
324
+ handleSIGINT: false,
325
+ logLevel: "silent"
326
+ });
327
+ let client = null;
328
+ let cookieTimer = null;
329
+ let cdpPort = chrome.port;
330
+ for (let attempt = 0; attempt < 10; attempt++) {
331
+ await new Promise((r) => setTimeout(r, 300));
332
+ try {
333
+ client = await CDP({ port: cdpPort });
334
+ break;
335
+ } catch {
336
+ if (attempt === 9) {
337
+ return {
338
+ kill: () => chrome.kill()
339
+ };
340
+ }
341
+ }
342
+ }
343
+ if (!client) {
344
+ return {
345
+ kill: () => chrome.kill()
346
+ };
347
+ }
348
+ const c = client;
349
+ await installDockIcon(c);
350
+ await c.Fetch.enable({
351
+ patterns: KIZEN_DOMAINS.flatMap((d) => [
352
+ { urlPattern: `*://${d}/*`, requestStage: "Response" },
353
+ { urlPattern: `*://*.${d}/*`, requestStage: "Response" }
354
+ ])
355
+ });
356
+ c.on("Fetch.requestPaused", (rawParams) => {
357
+ void (async () => {
358
+ const params = rawParams;
359
+ const { requestId, request, responseHeaders, responseStatusCode } = params;
360
+ const url = request?.url ?? "";
361
+ const filtered = [];
362
+ for (const h of responseHeaders ?? []) {
363
+ if (CSP_HEADERS.has(h.name.toLowerCase())) continue;
364
+ if (h.name.toLowerCase() === "set-cookie" && isKizenUrl(url)) {
365
+ const patched = h.value.split("\n").map((cookie) => {
366
+ if (!/SameSite=/i.test(cookie)) return cookie + "; SameSite=None; Secure";
367
+ return cookie.replace(/SameSite=\w+/i, "SameSite=None");
368
+ }).join("\n");
369
+ filtered.push({ name: h.name, value: patched });
370
+ } else {
371
+ filtered.push(h);
372
+ }
373
+ }
374
+ try {
375
+ const { body, base64Encoded } = await c.Fetch.getResponseBody({ requestId });
376
+ await c.Fetch.fulfillRequest({
377
+ requestId,
378
+ responseCode: responseStatusCode ?? 200,
379
+ responseHeaders: filtered,
380
+ body,
381
+ ...base64Encoded && { base64Encoded: true }
382
+ });
383
+ } catch {
384
+ try {
385
+ await c.Fetch.continueRequest({ requestId });
386
+ } catch {
387
+ }
388
+ }
389
+ })();
390
+ });
391
+ cookieTimer = setInterval(() => {
392
+ void (async () => {
393
+ try {
394
+ const { cookies } = await c.Network.getAllCookies();
395
+ for (const cookie of cookies) {
396
+ if (!isKizenUrl(`https://${cookie.domain ?? ""}`)) continue;
397
+ if (cookie.sameSite === "None") continue;
398
+ const raw = cookie.domain ?? "";
399
+ const bare = raw.startsWith(".") ? raw.slice(1) : raw;
400
+ await c.Network.setCookie({
401
+ url: `https://${bare}`,
402
+ name: cookie.name,
403
+ value: cookie.value,
404
+ domain: raw,
405
+ path: cookie.path ?? "/",
406
+ secure: true,
407
+ ...cookie.httpOnly !== void 0 && { httpOnly: cookie.httpOnly },
408
+ ...cookie.expirationDate !== void 0 && {
409
+ expires: cookie.expirationDate
410
+ },
411
+ sameSite: "None"
412
+ });
413
+ }
414
+ } catch {
415
+ }
416
+ })();
417
+ }, 2e3);
418
+ await c.send("Runtime.enable", { generatePreview: true });
419
+ c.on("Target.attachedToTarget", (rawParams) => {
420
+ const { sessionId } = rawParams;
421
+ void c.send("Runtime.enable", { generatePreview: true }, sessionId).catch(() => {
422
+ });
423
+ });
424
+ await c.Target.setAutoAttach({
425
+ autoAttach: true,
426
+ waitForDebuggerOnStart: false,
427
+ flatten: true
428
+ });
429
+ await c.Target.setDiscoverTargets({ discover: true });
430
+ c.on("Target.targetCreated", (rawParams) => {
431
+ const { targetInfo } = rawParams;
432
+ if (targetInfo.type === "page" && targetInfo.url === "chrome://newtab/") {
433
+ void c.Target.closeTarget({ targetId: targetInfo.targetId }).catch(() => {
434
+ });
435
+ }
436
+ });
437
+ c.on("Runtime.consoleAPICalled", (rawParams) => {
438
+ void (async () => {
439
+ const { type, args } = rawParams;
440
+ if (args[0]?.type === "string" && String(args[0].value).includes("Chrome Security")) return;
441
+ const level = type === "warning" ? "warn" : ["log", "warn", "error", "info"].includes(type) ? type : "log";
442
+ const serialized = await Promise.all(args.map((arg) => serializeArgAsync(c, arg)));
443
+ opts.broadcast({ type: "console-message", level, args: serialized });
444
+ })().catch(() => {
445
+ });
446
+ });
447
+ const kill = () => {
448
+ if (cookieTimer !== null) clearInterval(cookieTimer);
449
+ try {
450
+ void c.close();
451
+ } catch {
452
+ }
453
+ chrome.kill();
454
+ };
455
+ return { kill };
456
+ }
240
457
 
241
458
  // src/lib/proxyCache.ts
242
459
  function createProxyCache() {
@@ -269,33 +486,14 @@ function createProxyCache() {
269
486
  };
270
487
  }
271
488
 
272
- // src/lib/config.ts
273
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
274
- import { join as join4 } from "path";
275
- async function loadConfig(outputDir) {
276
- try {
277
- const content = await readFile2(join4(outputDir, "config.json"), "utf-8");
278
- return JSON.parse(content);
279
- } catch {
280
- return {};
281
- }
282
- }
283
- async function saveConfig(outputDir, config) {
284
- await mkdir2(outputDir, { recursive: true });
285
- await writeFile2(join4(outputDir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
286
- }
287
-
288
- // src/ui/CredentialSetupUI.tsx
289
- import { useCallback, useEffect as useEffect2, useState as useState2 } from "react";
290
- import { Box as Box3, Text as Text3, useInput } from "ink";
291
-
292
489
  // src/lib/credentials.ts
293
- import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
490
+ import { mkdir as mkdir2, readFile as readFile3, readdir as readdir2, writeFile as writeFile2 } from "fs/promises";
294
491
  import { homedir } from "os";
295
- import { dirname, join as join5 } from "path";
492
+ import { dirname as dirname2, join as join5 } from "path";
296
493
  var ENVIRONMENTS = ["go", "fmo", "staging", "integration", "test1"];
297
494
  var GLOBAL_CREDENTIALS_DIR = join5(homedir(), ".kizenappbuilder");
298
495
  var GLOBAL_CREDENTIALS_PATH = join5(GLOBAL_CREDENTIALS_DIR, "credentials.json");
496
+ var DEFAULT_PROFILE_NAME = "credentials";
299
497
  function isValidEnvironment(value) {
300
498
  return ENVIRONMENTS.includes(value);
301
499
  }
@@ -324,11 +522,67 @@ async function loadGlobalCredentials() {
324
522
  }
325
523
  }
326
524
  async function saveGlobalCredentials(credentials) {
327
- await mkdir3(dirname(GLOBAL_CREDENTIALS_PATH), { recursive: true });
328
- await writeFile3(GLOBAL_CREDENTIALS_PATH, JSON.stringify(credentials, null, 2), "utf-8");
525
+ await mkdir2(dirname2(GLOBAL_CREDENTIALS_PATH), { recursive: true });
526
+ await writeFile2(GLOBAL_CREDENTIALS_PATH, JSON.stringify(credentials, null, 2), "utf-8");
527
+ }
528
+ function getProfilePath(name) {
529
+ return join5(GLOBAL_CREDENTIALS_DIR, `${name}.json`);
530
+ }
531
+ async function listCredentialProfiles() {
532
+ const profiles = [
533
+ { name: DEFAULT_PROFILE_NAME, path: GLOBAL_CREDENTIALS_PATH, isDefault: true }
534
+ ];
535
+ try {
536
+ const entries = await readdir2(GLOBAL_CREDENTIALS_DIR);
537
+ for (const entry of entries) {
538
+ if (!entry.endsWith(".json")) {
539
+ continue;
540
+ }
541
+ const name = entry.slice(0, -5);
542
+ if (name === DEFAULT_PROFILE_NAME) {
543
+ continue;
544
+ }
545
+ profiles.push({ name, path: join5(GLOBAL_CREDENTIALS_DIR, entry), isDefault: false });
546
+ }
547
+ } catch {
548
+ }
549
+ return profiles;
550
+ }
551
+ async function saveCredentialProfile(name, credentials) {
552
+ if (name === DEFAULT_PROFILE_NAME) {
553
+ await saveGlobalCredentials(credentials);
554
+ return;
555
+ }
556
+ await mkdir2(GLOBAL_CREDENTIALS_DIR, { recursive: true });
557
+ await writeFile2(getProfilePath(name), JSON.stringify(credentials, null, 2), "utf-8");
558
+ }
559
+ async function loadCredentialProfile(name) {
560
+ try {
561
+ return await loadCredentialsFromFile(getProfilePath(name));
562
+ } catch {
563
+ return null;
564
+ }
565
+ }
566
+
567
+ // src/lib/config.ts
568
+ import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
569
+ import { join as join6 } from "path";
570
+ async function loadConfig(outputDir) {
571
+ try {
572
+ const content = await readFile4(join6(outputDir, "config.json"), "utf-8");
573
+ return JSON.parse(content);
574
+ } catch {
575
+ return {};
576
+ }
577
+ }
578
+ async function saveConfig(outputDir, config) {
579
+ await mkdir3(outputDir, { recursive: true });
580
+ await writeFile3(join6(outputDir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
329
581
  }
330
582
 
331
583
  // src/ui/CredentialSetupUI.tsx
584
+ import { useCallback, useEffect as useEffect2, useState as useState2 } from "react";
585
+ import { Box as Box3, Text as Text3, useInput } from "ink";
332
586
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
333
587
  var FIELDS = ["apiKey", "userId", "businessId"];
334
588
  var FIELD_LABELS = {
@@ -339,32 +593,39 @@ var FIELD_LABELS = {
339
593
  var Hint = ({ text }) => /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: text });
340
594
  var CredentialSetupUI = ({
341
595
  initialMode,
596
+ showProfileManager,
342
597
  onComplete,
343
598
  onCancel
344
599
  }) => {
345
600
  const [phase, setPhase] = useState2(
346
- initialMode === "global" ? { type: "loading" } : { type: "mode-select", cursor: 0 }
601
+ initialMode === "global" && !showProfileManager ? { type: "loading" } : initialMode === "global" && showProfileManager ? { type: "profile-loading" } : { type: "mode-select", cursor: 0 }
347
602
  );
348
603
  const [inputBuffer, setInputBuffer] = useState2("");
349
604
  const [error, setError] = useState2(null);
605
+ const [activeProfileName, setActiveProfileName] = useState2(void 0);
350
606
  const handleModeChosen = useCallback(
351
- async (mode) => {
607
+ (mode) => {
352
608
  if (mode === "local") {
353
609
  onComplete({ mode: "local", credentials: null });
354
610
  return;
355
611
  }
356
- setPhase({ type: "loading" });
357
- const existing = await loadGlobalCredentials();
358
- const envCursor2 = existing ? Math.max(0, ENVIRONMENTS.indexOf(existing.environment)) : 0;
359
- setPhase({
360
- type: "creds-entry",
361
- field: 0,
362
- values: existing ?? {},
363
- envCursor: envCursor2
364
- });
612
+ setPhase({ type: "profile-loading" });
365
613
  },
366
614
  [onComplete]
367
615
  );
616
+ const handleProfileChosen = useCallback(async (profile) => {
617
+ if (profile === null) {
618
+ setPhase({ type: "profile-loading" });
619
+ const profiles = await listCredentialProfiles();
620
+ setPhase({ type: "name-entry", nameBuffer: "", existingProfiles: profiles });
621
+ return;
622
+ }
623
+ setActiveProfileName(profile.isDefault ? void 0 : profile.name);
624
+ setPhase({ type: "loading" });
625
+ const existing = profile.isDefault ? await loadGlobalCredentials() : await loadCredentialProfile(profile.name);
626
+ const envCursor2 = existing ? Math.max(0, ENVIRONMENTS.indexOf(existing.environment)) : 0;
627
+ setPhase({ type: "creds-entry", field: 0, values: existing ?? {}, envCursor: envCursor2 });
628
+ }, []);
368
629
  const handleSave = useCallback(
369
630
  async (values2, envCursor2) => {
370
631
  const environment = ENVIRONMENTS[envCursor2] ?? "go";
@@ -376,20 +637,32 @@ var CredentialSetupUI = ({
376
637
  };
377
638
  setPhase({ type: "saving" });
378
639
  try {
379
- await saveGlobalCredentials(credentials);
380
- onComplete({ mode: "global", credentials });
640
+ const profileName = activeProfileName ?? DEFAULT_PROFILE_NAME;
641
+ await saveCredentialProfile(profileName, credentials);
642
+ onComplete({
643
+ mode: "global",
644
+ credentials,
645
+ ...activeProfileName !== void 0 && { profileName: activeProfileName }
646
+ });
381
647
  } catch (err) {
382
648
  setError(err instanceof Error ? err.message : String(err));
383
649
  setPhase({ type: "creds-entry", field: 0, values: values2, envCursor: envCursor2 });
384
650
  }
385
651
  },
386
- [onComplete]
652
+ [onComplete, activeProfileName]
387
653
  );
388
654
  useInput((input, key) => {
389
655
  if (key.ctrl && input === "c") {
390
656
  process.exit(0);
391
657
  }
392
658
  if (key.escape) {
659
+ if (phase.type === "creds-entry" || phase.type === "name-entry") {
660
+ if (initialMode === "global" && showProfileManager) {
661
+ setPhase({ type: "profile-loading" });
662
+ void loadProfileList();
663
+ return;
664
+ }
665
+ }
393
666
  onCancel?.();
394
667
  return;
395
668
  }
@@ -400,7 +673,53 @@ var CredentialSetupUI = ({
400
673
  setPhase({ type: "mode-select", cursor: 1 });
401
674
  } else if (key.return) {
402
675
  const mode = phase.cursor === 0 ? "global" : "local";
403
- void handleModeChosen(mode);
676
+ handleModeChosen(mode);
677
+ }
678
+ return;
679
+ }
680
+ if (phase.type === "profile-select") {
681
+ const { profiles, cursor } = phase;
682
+ const addNewIdx = profiles.length;
683
+ const totalItems = profiles.length + 1;
684
+ if (key.upArrow) {
685
+ setPhase({ ...phase, cursor: (cursor - 1 + totalItems) % totalItems });
686
+ } else if (key.downArrow) {
687
+ setPhase({ ...phase, cursor: (cursor + 1) % totalItems });
688
+ } else if (key.return) {
689
+ if (cursor === addNewIdx) {
690
+ setPhase({ type: "name-entry", nameBuffer: "", existingProfiles: profiles });
691
+ } else {
692
+ const selected = profiles[cursor] ?? profiles[0];
693
+ if (selected) {
694
+ void handleProfileChosen(selected);
695
+ }
696
+ }
697
+ }
698
+ return;
699
+ }
700
+ if (phase.type === "name-entry") {
701
+ const { nameBuffer, existingProfiles } = phase;
702
+ if (key.backspace || key.delete) {
703
+ setPhase({ type: "name-entry", nameBuffer: nameBuffer.slice(0, -1), existingProfiles });
704
+ } else if (key.return) {
705
+ const trimmed = nameBuffer.trim();
706
+ if (!trimmed) {
707
+ setPhase({ ...phase, nameError: "Name cannot be empty" });
708
+ return;
709
+ }
710
+ if (trimmed === DEFAULT_PROFILE_NAME) {
711
+ setPhase({ ...phase, nameError: `"${DEFAULT_PROFILE_NAME}" is reserved` });
712
+ return;
713
+ }
714
+ if (existingProfiles.some((p) => p.name === trimmed)) {
715
+ setPhase({ ...phase, nameError: `Profile "${trimmed}" already exists` });
716
+ return;
717
+ }
718
+ setActiveProfileName(trimmed);
719
+ setPhase({ type: "creds-entry", field: 0, values: {}, envCursor: 0 });
720
+ setInputBuffer("");
721
+ } else if (input && !key.ctrl && !key.meta) {
722
+ setPhase({ type: "name-entry", nameBuffer: nameBuffer + input, existingProfiles });
404
723
  }
405
724
  return;
406
725
  }
@@ -448,9 +767,22 @@ var CredentialSetupUI = ({
448
767
  }
449
768
  }
450
769
  });
770
+ const loadProfileList = useCallback(async () => {
771
+ const profiles = await listCredentialProfiles();
772
+ setPhase({ type: "profile-select", profiles, cursor: 0 });
773
+ }, []);
451
774
  useEffect2(() => {
452
- if (initialMode === "global") {
453
- void handleModeChosen("global");
775
+ if (phase.type === "profile-loading") {
776
+ void loadProfileList();
777
+ }
778
+ }, [phase.type, loadProfileList]);
779
+ useEffect2(() => {
780
+ if (initialMode === "global" && !showProfileManager) {
781
+ void (async () => {
782
+ const existing = await loadGlobalCredentials();
783
+ const envCursor2 = existing ? Math.max(0, ENVIRONMENTS.indexOf(existing.environment)) : 0;
784
+ setPhase({ type: "creds-entry", field: 0, values: existing ?? {}, envCursor: envCursor2 });
785
+ })();
454
786
  }
455
787
  }, []);
456
788
  useEffect2(() => {
@@ -458,8 +790,9 @@ var CredentialSetupUI = ({
458
790
  setInputBuffer(phase.values.apiKey ?? "");
459
791
  }
460
792
  }, [phase.type]);
461
- if (phase.type === "loading" || phase.type === "saving") {
462
- return /* @__PURE__ */ jsx3(Box3, { paddingY: 1, paddingX: 2, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: phase.type === "loading" ? "Loading credentials\u2026" : "Saving credentials\u2026" }) });
793
+ if (phase.type === "loading" || phase.type === "saving" || phase.type === "profile-loading") {
794
+ const label = phase.type === "saving" ? "Saving credentials\u2026" : phase.type === "profile-loading" ? "Loading profiles\u2026" : "Loading credentials\u2026";
795
+ return /* @__PURE__ */ jsx3(Box3, { paddingY: 1, paddingX: 2, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: label }) });
463
796
  }
464
797
  if (phase.type === "done") {
465
798
  return null;
@@ -489,8 +822,65 @@ var CredentialSetupUI = ({
489
822
  /* @__PURE__ */ jsx3(Hint, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Ctrl+C to quit" })
490
823
  ] });
491
824
  }
825
+ if (phase.type === "profile-select") {
826
+ const { profiles, cursor } = phase;
827
+ const addNewIdx = profiles.length;
828
+ return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
829
+ /* @__PURE__ */ jsx3(Logo, {}),
830
+ /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, children: [
831
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "Kizen App Builder" }),
832
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2500".repeat(24) })
833
+ ] }),
834
+ /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", gap: 0, children: [
835
+ /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Credential profiles" }),
836
+ profiles.map((profile, i) => {
837
+ const selected = cursor === i;
838
+ return /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
839
+ /* @__PURE__ */ jsx3(Text3, { ...selected && { color: "cyan" }, children: selected ? "\u276F" : " " }),
840
+ /* @__PURE__ */ jsx3(Text3, { bold: selected, ...selected && { color: "cyan" }, children: profile.isDefault ? "Default" : profile.name }),
841
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: profile.path })
842
+ ] }, profile.name);
843
+ }),
844
+ /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
845
+ /* @__PURE__ */ jsx3(Text3, { ...cursor === addNewIdx && { color: "cyan" }, children: cursor === addNewIdx ? "\u276F" : " " }),
846
+ /* @__PURE__ */ jsx3(
847
+ Text3,
848
+ {
849
+ bold: cursor === addNewIdx,
850
+ ...cursor === addNewIdx && { color: "cyan" },
851
+ children: "+ Add new profile"
852
+ }
853
+ )
854
+ ] })
855
+ ] }),
856
+ /* @__PURE__ */ jsx3(Hint, { text: "\u2191\u2193 to move \xB7 Enter to select \xB7 Esc to cancel" })
857
+ ] });
858
+ }
859
+ if (phase.type === "name-entry") {
860
+ const { nameBuffer, nameError } = phase;
861
+ return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
862
+ /* @__PURE__ */ jsx3(Logo, {}),
863
+ /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, children: [
864
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "Kizen App Builder" }),
865
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2500".repeat(24) })
866
+ ] }),
867
+ /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", gap: 0, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: "New credential profile" }) }),
868
+ nameError && /* @__PURE__ */ jsxs2(Text3, { color: "red", children: [
869
+ "Error: ",
870
+ nameError
871
+ ] }),
872
+ /* @__PURE__ */ jsxs2(Box3, { gap: 2, children: [
873
+ /* @__PURE__ */ jsx3(Box3, { width: 12, children: /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "Profile name" }) }),
874
+ /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: ">" }),
875
+ /* @__PURE__ */ jsx3(Text3, { children: nameBuffer }),
876
+ /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: "\u2588" })
877
+ ] }),
878
+ /* @__PURE__ */ jsx3(Hint, { text: "Type a name \xB7 Enter to confirm \xB7 Esc to go back" })
879
+ ] });
880
+ }
492
881
  const { field: activeField, values, envCursor } = phase;
493
882
  const isEnvField = activeField === FIELDS.length;
883
+ const profileLabel = activeProfileName ?? DEFAULT_PROFILE_NAME;
494
884
  return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", paddingY: 1, paddingX: 2, gap: 1, children: [
495
885
  /* @__PURE__ */ jsx3(Logo, {}),
496
886
  /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, children: [
@@ -498,10 +888,11 @@ var CredentialSetupUI = ({
498
888
  /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2500".repeat(24) })
499
889
  ] }),
500
890
  /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", gap: 0, children: [
501
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Global credentials" }),
891
+ /* @__PURE__ */ jsx3(Text3, { bold: true, children: activeProfileName !== void 0 ? `Profile: ${activeProfileName}` : "Global credentials" }),
502
892
  /* @__PURE__ */ jsxs2(Text3, { dimColor: true, children: [
503
- "Saved to: ",
504
- GLOBAL_CREDENTIALS_PATH
893
+ "Saved to:",
894
+ " ",
895
+ activeProfileName !== void 0 ? `~/.kizenappbuilder/${activeProfileName}.json` : GLOBAL_CREDENTIALS_PATH
505
896
  ] })
506
897
  ] }),
507
898
  error && /* @__PURE__ */ jsxs2(Text3, { color: "red", children: [
@@ -531,7 +922,11 @@ var CredentialSetupUI = ({
531
922
  }) })
532
923
  ] })
533
924
  ] }),
534
- isEnvField ? /* @__PURE__ */ jsx3(Hint, { text: "\u2190\u2192 to select \xB7 \u2191\u2193 to move \xB7 Enter to save \xB7 Esc to cancel" }) : /* @__PURE__ */ jsx3(Hint, { text: "\u2191\u2193 to move \xB7 Backspace to delete \xB7 Esc to cancel" })
925
+ isEnvField ? /* @__PURE__ */ jsx3(Hint, { text: "\u2190\u2192 to select \xB7 \u2191\u2193 to move \xB7 Enter to save \xB7 Esc to cancel" }) : /* @__PURE__ */ jsx3(Hint, { text: "\u2191\u2193 to move \xB7 Backspace to delete \xB7 Esc to cancel" }),
926
+ /* @__PURE__ */ jsxs2(Text3, { dimColor: true, children: [
927
+ "Profile: ",
928
+ profileLabel
929
+ ] })
535
930
  ] });
536
931
  };
537
932
 
@@ -576,12 +971,8 @@ var Spinner2 = () => {
576
971
  return /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: SPINNER_FRAMES2[frame] ?? "\u280B" });
577
972
  };
578
973
  function getViewerPath() {
579
- const filename = fileURLToPath(import.meta.url);
580
- return join6(dirname2(filename), "viewer");
581
- }
582
- function getElectronMainPath() {
583
- const filename = fileURLToPath(import.meta.url);
584
- return join6(dirname2(filename), "electron", "main.js");
974
+ const filename = fileURLToPath2(import.meta.url);
975
+ return join7(dirname3(filename), "viewer");
585
976
  }
586
977
  async function fileExists(filePath) {
587
978
  try {
@@ -591,7 +982,7 @@ async function fileExists(filePath) {
591
982
  return false;
592
983
  }
593
984
  }
594
- function createRequestHandler(viewerPath, createServerLog, createProxyLog, credentialsRef) {
985
+ function createRequestHandler(viewerPath, createServerLog, createProxyLog, credentialsRef, activeProfileRef, outputDir, broadcast) {
595
986
  const proxyCache = createProxyCache();
596
987
  return (req, res) => {
597
988
  void (async () => {
@@ -603,10 +994,38 @@ function createRequestHandler(viewerPath, createServerLog, createProxyLog, crede
603
994
  res.end(credentialsRef.current !== null ? JSON.stringify(credentialsRef.current) : "{}");
604
995
  return;
605
996
  }
997
+ if (url === "/api/credential-profiles" && req.method === "GET") {
998
+ const profiles = await listCredentialProfiles();
999
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1000
+ res.end(JSON.stringify({ profiles, active: activeProfileRef.current }));
1001
+ return;
1002
+ }
1003
+ if (url === "/api/credentials/switch" && req.method === "POST") {
1004
+ const chunks = [];
1005
+ for await (const chunk of req) {
1006
+ chunks.push(chunk);
1007
+ }
1008
+ const body = JSON.parse(Buffer.concat(chunks).toString("utf-8") || "{}");
1009
+ const profileName = body.profile;
1010
+ const creds = profileName === void 0 || profileName === "" ? await loadGlobalCredentials() : await loadCredentialProfile(profileName);
1011
+ if (creds !== null) {
1012
+ credentialsRef.current = creds;
1013
+ activeProfileRef.current = profileName ?? void 0;
1014
+ const active = activeProfileRef.current;
1015
+ await saveConfig(outputDir, {
1016
+ credentialMode: "global",
1017
+ ...active !== void 0 && { activeCredentialProfile: active }
1018
+ });
1019
+ broadcast({ type: "credentials-updated", credentials: creds });
1020
+ }
1021
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
1022
+ res.end(JSON.stringify({ ok: creds !== null }));
1023
+ return;
1024
+ }
606
1025
  if (url === "/api/bundle") {
607
- const bundlePath = join6(process.cwd(), ".kizenapp", "bundle.json");
1026
+ const bundlePath = join7(process.cwd(), ".kizenapp", "bundle.json");
608
1027
  try {
609
- const content = await readFile4(bundlePath, "utf-8");
1028
+ const content = await readFile5(bundlePath, "utf-8");
610
1029
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
611
1030
  res.end(content);
612
1031
  } catch {
@@ -679,17 +1098,27 @@ function createRequestHandler(viewerPath, createServerLog, createProxyLog, crede
679
1098
  return;
680
1099
  }
681
1100
  const rawPath = url === "/" ? "/index.html" : url;
682
- const filePath = join6(viewerPath, rawPath);
683
- const resolvedPath = await fileExists(filePath) ? filePath : join6(viewerPath, "index.html");
1101
+ const filePath = join7(viewerPath, rawPath);
1102
+ const resolvedPath = await fileExists(filePath) ? filePath : join7(viewerPath, "index.html");
684
1103
  const ext = extname(resolvedPath);
685
1104
  const mimeType = MIME_TYPES[ext] ?? "application/octet-stream";
686
1105
  res.writeHead(200, { "Content-Type": mimeType });
687
1106
  createReadStream(resolvedPath).pipe(res);
688
1107
  } catch (err) {
689
- createProxyLog({
690
- kind: "info",
691
- message: `Error handling ${url}: ${err instanceof Error ? err.message : String(err)}`
692
- });
1108
+ if (url.startsWith("/api/proxy")) {
1109
+ createProxyLog({
1110
+ kind: "request",
1111
+ method: req.method ?? "GET",
1112
+ status: 502,
1113
+ fromCache: false,
1114
+ url: url.slice("/api/proxy".length) || "/"
1115
+ });
1116
+ } else {
1117
+ createProxyLog({
1118
+ kind: "info",
1119
+ message: `Error handling ${url}: ${err instanceof Error ? err.message : String(err)}`
1120
+ });
1121
+ }
693
1122
  if (!res.headersSent) {
694
1123
  res.writeHead(502);
695
1124
  res.end("Bad Gateway");
@@ -702,10 +1131,16 @@ var DevUI = ({
702
1131
  port,
703
1132
  pluginDir,
704
1133
  outputDir,
705
- credentials: initialCredentials
1134
+ credentials: initialCredentials,
1135
+ credentialMode: initialCredentialMode,
1136
+ activeCredentialProfile: initialActiveProfile
706
1137
  }) => {
707
1138
  const credentialsRef = useRef(initialCredentials);
1139
+ const activeProfileRef = useRef(initialActiveProfile);
708
1140
  const [credMode, setCredMode] = useState3("main");
1141
+ const [credentialMode, setCredentialMode] = useState3(
1142
+ initialCredentialMode
1143
+ );
709
1144
  const [status, setStatus] = useState3("starting");
710
1145
  const [errorMessage, setErrorMessage] = useState3(null);
711
1146
  const [serverLogHistory, setServerLogHistory] = useState3([]);
@@ -716,10 +1151,40 @@ var DevUI = ({
716
1151
  const [lastBuilt, setLastBuilt] = useState3(null);
717
1152
  const [wsClientCount, setWsClientCount] = useState3(0);
718
1153
  const buildingRef = useRef(false);
719
- const electronLaunchedRef = useRef(false);
1154
+ const viewerLaunchedRef = useRef(false);
1155
+ const chromeHandleRef = useRef(null);
720
1156
  const debounceTimerRef = useRef(null);
721
1157
  const wsClientsRef = useRef(/* @__PURE__ */ new Set());
722
1158
  const pendingMessagesRef = useRef([]);
1159
+ const broadcast = useCallback2((msg) => {
1160
+ const json = JSON.stringify(msg);
1161
+ if (wsClientsRef.current.size === 0) {
1162
+ pendingMessagesRef.current = [...pendingMessagesRef.current, json].slice(-LOG_LIMIT);
1163
+ return;
1164
+ }
1165
+ for (const client of wsClientsRef.current) {
1166
+ if (client.readyState === 1) {
1167
+ client.send(json);
1168
+ }
1169
+ }
1170
+ }, []);
1171
+ const createServerLog = useCallback2((message) => {
1172
+ setServerLogHistory(
1173
+ (h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-LOG_LIMIT)
1174
+ );
1175
+ }, []);
1176
+ const launchChromeViewerCb = useCallback2(() => {
1177
+ chromeHandleRef.current?.kill();
1178
+ chromeHandleRef.current = null;
1179
+ void launchChromeViewer({
1180
+ port,
1181
+ userDataDir: join7(outputDir, ".chrome"),
1182
+ broadcast
1183
+ }).then((handle) => {
1184
+ chromeHandleRef.current = handle;
1185
+ process.on("exit", () => handle.kill());
1186
+ });
1187
+ }, [port, outputDir, broadcast]);
723
1188
  useInput2(
724
1189
  (input, key) => {
725
1190
  if (input === "q" || key.ctrl && input === "c") {
@@ -728,55 +1193,30 @@ var DevUI = ({
728
1193
  if (input === "c") {
729
1194
  setCredMode("editing");
730
1195
  }
731
- if (input === "d") {
732
- broadcast({ type: "open-devtools" });
1196
+ if (input === "v") {
1197
+ launchChromeViewerCb();
733
1198
  }
734
1199
  },
735
1200
  { isActive: credMode === "main" }
736
1201
  );
737
1202
  useEffect3(() => {
738
- if (status !== "running" || electronLaunchedRef.current) {
1203
+ if (status !== "running" || viewerLaunchedRef.current) {
739
1204
  return;
740
1205
  }
741
- electronLaunchedRef.current = true;
742
- const _require = createRequire(import.meta.url);
743
- const electronBin = _require("electron");
744
- const electronMain = getElectronMainPath();
745
- const userDataDir = join6(outputDir, ".electron");
746
- const proc = spawn(
747
- electronBin,
748
- [electronMain, `--port=${String(port)}`, `--user-data-dir=${userDataDir}`],
749
- {
750
- stdio: "ignore"
751
- }
752
- );
753
- proc.unref();
754
- process.on("exit", () => {
755
- proc.kill();
756
- });
757
- }, [status, port, outputDir]);
758
- const createServerLog = useCallback2((message) => {
759
- setServerLogHistory(
760
- (h) => [...h, `${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${message}`].slice(-LOG_LIMIT)
761
- );
762
- }, []);
763
- const broadcast = useCallback2((msg) => {
764
- const json = JSON.stringify(msg);
765
- if (wsClientsRef.current.size === 0) {
766
- pendingMessagesRef.current = [...pendingMessagesRef.current, json].slice(-LOG_LIMIT);
767
- return;
768
- }
769
- for (const client of wsClientsRef.current) {
770
- if (client.readyState === 1) {
771
- client.send(json);
772
- }
773
- }
774
- }, []);
1206
+ viewerLaunchedRef.current = true;
1207
+ launchChromeViewerCb();
1208
+ }, [status, launchChromeViewerCb]);
775
1209
  const handleCredentialsDone = useCallback2(
776
1210
  (result) => {
777
1211
  credentialsRef.current = result.credentials;
1212
+ activeProfileRef.current = result.profileName;
778
1213
  setCredMode("main");
779
- void saveConfig(outputDir, { credentialMode: result.mode });
1214
+ setCredentialMode(result.mode);
1215
+ const profile = result.profileName;
1216
+ void saveConfig(outputDir, {
1217
+ credentialMode: result.mode,
1218
+ ...profile !== void 0 && { activeCredentialProfile: profile }
1219
+ });
780
1220
  broadcast({ type: "credentials-updated", credentials: result.credentials });
781
1221
  },
782
1222
  [outputDir, broadcast]
@@ -852,7 +1292,7 @@ var DevUI = ({
852
1292
  }, [pluginDir, triggerBuild, createBuildLog]);
853
1293
  useEffect3(() => {
854
1294
  const viewerPath = getViewerPath();
855
- void fileExists(join6(viewerPath, "index.html")).then((viewerBuilt) => {
1295
+ void fileExists(join7(viewerPath, "index.html")).then((viewerBuilt) => {
856
1296
  if (!viewerBuilt) {
857
1297
  setErrorMessage("Viewer not built. Run 'pnpm build:viewer' first.");
858
1298
  setStatus("error");
@@ -862,7 +1302,10 @@ var DevUI = ({
862
1302
  viewerPath,
863
1303
  createServerLog,
864
1304
  createProxyLog,
865
- credentialsRef
1305
+ credentialsRef,
1306
+ activeProfileRef,
1307
+ outputDir,
1308
+ broadcast
866
1309
  );
867
1310
  const server = createServer(handler);
868
1311
  const wss = new WebSocketServer({ server });
@@ -894,11 +1337,13 @@ var DevUI = ({
894
1337
  server.close();
895
1338
  };
896
1339
  });
897
- }, [port, createServerLog, createProxyLog]);
1340
+ }, [port, createServerLog, createProxyLog, broadcast, outputDir]);
898
1341
  if (credMode === "editing") {
899
1342
  return /* @__PURE__ */ jsx4(
900
1343
  CredentialSetupUI,
901
1344
  {
1345
+ ...credentialMode !== void 0 && { initialMode: credentialMode },
1346
+ showProfileManager: credentialMode === "global",
902
1347
  onComplete: handleCredentialsDone,
903
1348
  onCancel: () => {
904
1349
  setCredMode("main");
@@ -1000,8 +1445,11 @@ var DevUI = ({
1000
1445
  /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "] quit [" }),
1001
1446
  /* @__PURE__ */ jsx4(Text4, { children: "c" }),
1002
1447
  /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "] credentials [" }),
1003
- /* @__PURE__ */ jsx4(Text4, { children: "d" }),
1004
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "] devtools" })
1448
+ /* @__PURE__ */ jsx4(Text4, { children: "v" }),
1449
+ /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
1450
+ "] ",
1451
+ wsClientCount === 0 ? "open viewer" : "relaunch viewer"
1452
+ ] })
1005
1453
  ] })
1006
1454
  ] });
1007
1455
  };
@@ -1032,6 +1480,7 @@ function devCommand(program2) {
1032
1480
  ensureGitignore(pluginDir);
1033
1481
  let credentials = null;
1034
1482
  let credentialMode;
1483
+ let activeCredentialProfile;
1035
1484
  if (options.credentials) {
1036
1485
  credentials = await loadCredentialsFromFile(options.credentials);
1037
1486
  } else {
@@ -1040,16 +1489,31 @@ function devCommand(program2) {
1040
1489
  credentialMode = "local";
1041
1490
  } else if (config.credentialMode === "global") {
1042
1491
  credentialMode = "global";
1043
- credentials = await loadGlobalCredentials();
1492
+ activeCredentialProfile = config.activeCredentialProfile;
1493
+ if (activeCredentialProfile) {
1494
+ credentials = await loadCredentialProfile(activeCredentialProfile);
1495
+ if (!credentials) {
1496
+ credentials = await loadGlobalCredentials();
1497
+ activeCredentialProfile = void 0;
1498
+ }
1499
+ } else {
1500
+ credentials = await loadGlobalCredentials();
1501
+ }
1044
1502
  if (!credentials) {
1045
1503
  const result = await runSetupUI("global");
1046
1504
  credentials = result.credentials;
1505
+ activeCredentialProfile = result.profileName;
1047
1506
  }
1048
1507
  } else {
1049
1508
  const result = await runSetupUI();
1050
1509
  credentials = result.credentials;
1051
1510
  credentialMode = result.mode;
1052
- await saveConfig(outputDir, { credentialMode: result.mode });
1511
+ activeCredentialProfile = result.profileName;
1512
+ const profile = result.profileName;
1513
+ await saveConfig(outputDir, {
1514
+ credentialMode: result.mode,
1515
+ ...profile !== void 0 && { activeCredentialProfile: profile }
1516
+ });
1053
1517
  }
1054
1518
  }
1055
1519
  const { waitUntilExit } = render2(
@@ -1058,7 +1522,8 @@ function devCommand(program2) {
1058
1522
  pluginDir,
1059
1523
  outputDir,
1060
1524
  credentials,
1061
- ...credentialMode !== void 0 && { credentialMode }
1525
+ ...credentialMode !== void 0 && { credentialMode },
1526
+ ...activeCredentialProfile !== void 0 && { activeCredentialProfile }
1062
1527
  }),
1063
1528
  { exitOnCtrlC: false }
1064
1529
  );